Skip to content

Commit

Permalink
Merge branch 'main' into remove_operatorsinfo_test_logs
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasArrachea committed Dec 19, 2024
2 parents 1f204e5 + 305a7ad commit 6b91795
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 15 deletions.
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ ark-serialize = "0.5.0"
async-trait = "0.1.83"
aws-config = "1.5.9"
aws-sdk-kms = "1.49.0"
backoff = { version = "0.4.0", features = ["futures", "tokio"] }
clap = { version = "4.5.20", features = ["derive"] }
eigen-chainio-txmanager = { path = "crates/chainio/txmanager/" }
eigen-client-avsregistry = { path = "crates/chainio/clients/avsregistry" }
Expand Down
96 changes: 83 additions & 13 deletions crates/chainio/clients/avsregistry/src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,43 @@ impl AvsRegistryChainReader {
Ok(quorum_stakes)
}

/// Query registration detail
///
/// # Arguments
///
/// * `operator_address` - The operator address.
///
/// # Returns
///
/// A vector of booleans, where each boolean represents if the operator
/// is registered for a quorum.
///
/// # Errors
///
/// Returns an error if the operator id cannot be fetched or if the quorum bitmap
pub async fn query_registration_detail(
&self,
operator_address: Address,
) -> Result<[bool; 64], AvsRegistryError> {
let operator_id = self.get_operator_id(operator_address).await?;

let provider = get_provider(&self.provider);
let registry_coordinator =
RegistryCoordinator::new(self.registry_coordinator_addr, &provider);
let quorum_bitmap = registry_coordinator
.getCurrentQuorumBitmap(operator_id)
.call()
.await?;

let inner_value = quorum_bitmap._0.into_limbs()[0];
let mut quorums: [bool; 64] = [false; 64];
for i in 0..64_u64 {
let other = inner_value & (1 << i) != 0;
quorums[i as usize] = other;
}
Ok(quorums)
}

/// Get operator id
///
/// # Arguments
Expand Down Expand Up @@ -597,23 +634,18 @@ impl AvsRegistryChainReader {
mod tests {
use super::*;
use eigen_logging::get_test_logger;
use eigen_testing_utils::m2_holesky_constants::{
HOLESKY_RPC_PROVIDER, OPERATOR_STATE_RETRIEVER, REGISTRY_COORDINATOR,
};
use hex::FromHex;
use std::str::FromStr;
const HOLESKY_REGISTRY_COORDINATOR: &str = "0x53012C69A189cfA2D9d29eb6F19B32e0A2EA3490";
const HOLESKY_OPERATOR_STATE_RETRIEVER: &str = "0xB4baAfee917fb4449f5ec64804217bccE9f46C67";

async fn build_avs_registry_chain_reader() -> AvsRegistryChainReader {
let holesky_registry_coordinator =
Address::from_str(HOLESKY_REGISTRY_COORDINATOR).expect("failed to parse address");
let holesky_operator_state_retriever =
Address::from_str(HOLESKY_OPERATOR_STATE_RETRIEVER).expect("failed to parse address");

let holesky_provider = "https://ethereum-holesky.blockpi.network/v1/rpc/public";
AvsRegistryChainReader::new(
get_test_logger(),
holesky_registry_coordinator,
holesky_operator_state_retriever,
holesky_provider.to_string(),
REGISTRY_COORDINATOR,
OPERATOR_STATE_RETRIEVER,
HOLESKY_RPC_PROVIDER.to_string(),
)
.await
.unwrap()
Expand Down Expand Up @@ -697,9 +729,11 @@ mod tests {
#[tokio::test]
async fn test_is_operator_registered() {
let avs_reader = build_avs_registry_chain_reader().await;
let address = Address::from_str(HOLESKY_REGISTRY_COORDINATOR).unwrap();

let is_registered = avs_reader.is_operator_registered(address).await.unwrap();
let is_registered = avs_reader
.is_operator_registered(REGISTRY_COORDINATOR)
.await
.unwrap();
assert!(!is_registered);
}

Expand Down Expand Up @@ -727,4 +761,40 @@ mod tests {
.await
.unwrap();
}

#[tokio::test]
async fn test_query_registration_detail() {
let avs_reader = build_avs_registry_chain_reader().await;

let operator_id = U256::from_str(
"35344093966194310405039483339636912150346494903629410125452342281826147822033",
)
.unwrap();

let operator_id_b256: B256 = operator_id.into();

let operator_address = avs_reader
.get_operator_from_id(operator_id_b256.into())
.await
.unwrap();

let ret_query_registration_detail = avs_reader
.query_registration_detail(operator_address)
.await
.unwrap();

println!("{:?}", ret_query_registration_detail);

// all the value are false
for ret_value in ret_query_registration_detail.iter() {
assert!(!ret_value);
}

// register an operator
let is_registered = avs_reader
.is_operator_registered(operator_address)
.await
.unwrap();
assert!(!is_registered);
}
}
1 change: 1 addition & 0 deletions crates/chainio/txmanager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ eigen-signer.workspace = true
reqwest.workspace = true
thiserror.workspace = true
tokio.workspace = true
backoff.workspace = true

[dev-dependencies]
eigen-testing-utils.workspace = true
Expand Down
86 changes: 84 additions & 2 deletions crates/chainio/txmanager/src/simple_tx_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ use alloy::primitives::U256;
use alloy::providers::{PendingTransactionBuilder, Provider, ProviderBuilder, RootProvider};
use alloy::rpc::types::eth::{TransactionInput, TransactionReceipt, TransactionRequest};
use alloy::signers::local::PrivateKeySigner;
use backoff::{future::retry, ExponentialBackoffBuilder};
use eigen_logging::logger::SharedLogger;
use eigen_signer::signer::Config;
use reqwest::Url;
use std::time::Duration;
use thiserror::Error;

static FALLBACK_GAS_TIP_CAP: u128 = 5_000_000_000;

pub type Transport = alloy::transports::http::Http<reqwest::Client>;

/// Possible errors raised in Tx Manager
#[derive(Error, Debug)]
#[derive(Error, Debug, PartialEq)]
pub enum TxManagerError {
#[error("signer error")]
SignerError,
Expand Down Expand Up @@ -156,6 +158,48 @@ impl SimpleTxManager {
SimpleTxManager::wait_for_receipt(self, pending_tx).await
}

/// Send a transaction to the Ethereum node. It takes an unsigned/signed transaction,
/// sends it to the Ethereum node and waits for the receipt.
/// If you pass in a signed transaction it will ignore the signature
/// and re-sign the transaction after adding the nonce and gas limit.
/// If the transaction fails, it will retry sending the transaction until it gets a receipt,
/// using an **exponential backoff** strategy.
/// If no receipt is received after `max_elapsed_time`, it will return an error.
///
/// # Arguments
///
/// * `tx`: The transaction to be sent.
/// * `initial_interval`: The initial interval duration for the backoff.
/// * `max_elapsed_time`: The maximum elapsed time for retrying.
/// * `multiplier`: The multiplier used to compute the exponential backoff.
///
/// # Returns
///
/// * `TransactionReceipt` The transaction receipt.
///
/// # Errors
///
/// * `TxManagerError` - If the transaction cannot be sent, or there is an error
/// signing the transaction or estimating gas and nonce.
pub async fn send_tx_with_retries(
&self,
tx: &mut TransactionRequest,
initial_interval: Duration,
max_elapsed_time: Duration,
multiplier: f64,
) -> Result<TransactionReceipt, TxManagerError> {
let backoff_config = ExponentialBackoffBuilder::default()
.with_initial_interval(initial_interval)
.with_max_elapsed_time(Some(max_elapsed_time))
.with_multiplier(multiplier)
.build();
retry(backoff_config, || async {
let mut cloned_tx = tx.clone();
Ok(self.send_tx(&mut cloned_tx).await?)
})
.await
}

/// Estimates the gas and nonce for a transaction.
///
/// # Arguments
Expand Down Expand Up @@ -270,14 +314,16 @@ impl SimpleTxManager {

#[cfg(test)]
mod tests {
use super::SimpleTxManager;
use super::{SimpleTxManager, TxManagerError};
use alloy::consensus::TxLegacy;
use alloy::network::TransactionBuilder;
use alloy::primitives::{address, bytes, TxKind::Call, U256};
use alloy::rpc::types::eth::TransactionRequest;
use eigen_logging::get_test_logger;
use eigen_testing_utils::anvil::start_anvil_container;
use std::time::Duration;
use tokio;
use tokio::time::Instant;

#[tokio::test]
async fn test_send_transaction_from_legacy() {
Expand Down Expand Up @@ -339,4 +385,40 @@ mod tests {
assert!(block_number > 0);
assert_eq!(receipt.to, Some(to));
}

#[tokio::test]
async fn test_send_transaction_with_retries_returns_after_timeout() {
let rpc_url = "http://fake:8545";
let logger = get_test_logger();
let private_key =
"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string();
let simple_tx_manager =
SimpleTxManager::new(logger, 1.0, private_key.as_str(), rpc_url).unwrap();
let to = address!("a0Ee7A142d267C1f36714E4a8F75612F20a79720");

let account_nonce = 0x69;
let mut tx = TransactionRequest::default()
.with_to(to)
.with_nonce(account_nonce)
.with_chain_id(31337)
.with_value(U256::from(100))
.with_gas_limit(21_000)
.with_max_priority_fee_per_gas(1_000_000_000)
.with_max_fee_per_gas(20_000_000_000)
.with_gas_price(21_000_000_000);
let start = Instant::now();

let result = simple_tx_manager
.send_tx_with_retries(
&mut tx,
Duration::from_millis(5),
Duration::from_secs(1),
1.0,
)
.await;
assert_eq!(result, Err(TxManagerError::SendTxError));
// substract one interval for asserting, because if the last try does not fit in the max_elapsed_time, it will not be executed
let elapsed = start.elapsed();
assert!(elapsed >= Duration::from_secs(1) - Duration::from_millis(5));
}
}
3 changes: 3 additions & 0 deletions testing/testing-utils/src/m2_holesky_constants.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use alloy_primitives::{address, Address};

/// Holesky rpc provider
pub const HOLESKY_RPC_PROVIDER: &str = "https://ethereum-holesky-rpc.publicnode.com";

/// https://holesky.etherscan.io/address/0xA44151489861Fe9e3055d95adC98FbD462B948e7
pub const DELEGATION_MANAGER_ADDRESS: Address =
address!("A44151489861Fe9e3055d95adC98FbD462B948e7");
Expand Down

0 comments on commit 6b91795

Please sign in to comment.