From 410b53a585e486991a3f83ef07840831cd859364 Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 30 Aug 2024 14:11:34 -0300 Subject: [PATCH] BLS-aggregation integration tests (#91) Implemented integration tests for `bls_aggregation` crate. Added 5 tests: - 2 quorums 1 operator (ignored) - 2 quorums 2 operators shared (ignored) - 1 quorum 1 operator - 1 quorum 2 operators - 2 quorums 2 operators separated Two cases are ignored as they are failing due to this bug: [bls aggregation service doesn't support >1 quorum](https://github.com/Layr-Labs/eigensdk-go/issues/261) --------- Co-authored-by: supernovahs Co-authored-by: Supernovahs.eth <91280922+supernovahs@users.noreply.github.com> Co-authored-by: Pablo Deymonnaz Co-authored-by: ricomateo --- Cargo.lock | 6 + Makefile | 11 +- .../chainio/clients/avsregistry/src/writer.rs | 2 +- ...rt-anvil-chain-with-el-and-avs-deployed.sh | 1 + crates/crypto/bls/src/lib.rs | 145 ++- .../services/avsregistry/src/chaincaller.rs | 7 +- crates/services/bls_aggregation/Cargo.toml | 6 + .../bls_aggregation/src/bls_agg_test.rs | 976 +++++++++++++++++- 8 files changed, 1085 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb536a5e..e9530337 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2248,6 +2248,7 @@ version = "0.0.1-alpha" dependencies = [ "alloy-node-bindings", "alloy-primitives", + "alloy-provider", "ark-bn254", "ark-ec", "eigen-client-avsregistry", @@ -2255,11 +2256,16 @@ dependencies = [ "eigen-crypto-bn254", "eigen-logging", "eigen-services-avsregistry", + "eigen-services-operatorsinfo", + "eigen-testing-utils", "eigen-types", + "eigen-utils", "parking_lot", + "serial_test", "sha2", "thiserror", "tokio", + "tokio-util", ] [[package]] diff --git a/Makefile b/Makefile index 9739db11..da45dd4a 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ __CONTRACTS__: ## start-anvil-chain-with-contracts-deployed: ## ./crates/contracts/anvil/start-anvil-chain-with-el-and-avs-deployed.sh -deploy-contracts-to-anvil-and-save-state: ## +deploy-contracts-to-anvil-and-save-state: ## ./crates/contracts/anvil/deploy-contracts-save-anvil-state.sh __TESTING__: ## @@ -14,17 +14,16 @@ reset-anvil: -docker stop anvil -docker rm anvil -pr: reset-anvil ## - $(MAKE) start-anvil-chain-with-contracts-deployed - docker start anvil +pr: reset-anvil ## + $(MAKE) start-anvil-chain-with-contracts-deployed > /dev/null & + sleep 4 # needed to wait for anvil setup to finish cargo test --workspace cargo clippy --workspace --lib --examples --tests --benches --all-features cargo +nightly fmt -- --check docker stop anvil fireblocks-tests: - $(MAKE) start-anvil-chain-with-contracts-deployed - docker start anvil + $(MAKE) start-anvil-chain-with-contracts-deployed > /dev/null & cargo test --workspace --features fireblock-tests start-anvil: reset-anvil ## diff --git a/crates/chainio/clients/avsregistry/src/writer.rs b/crates/chainio/clients/avsregistry/src/writer.rs index 0c94551b..f8b8c9e4 100644 --- a/crates/chainio/clients/avsregistry/src/writer.rs +++ b/crates/chainio/clients/avsregistry/src/writer.rs @@ -220,7 +220,7 @@ impl AvsRegistryChainWriter { .await .map_err(AvsRegistryError::AlloyContractError)?; - info!(tx_hash = ?tx,"Succesfully deregistered operator with the AVS's registry coordinator" ); + info!(tx_hash = ?tx,"Sent transaction to deregister operator in the AVS's registry coordinator" ); Ok(*tx.tx_hash()) } diff --git a/crates/contracts/anvil/start-anvil-chain-with-el-and-avs-deployed.sh b/crates/contracts/anvil/start-anvil-chain-with-el-and-avs-deployed.sh index d507feae..23661343 100755 --- a/crates/contracts/anvil/start-anvil-chain-with-el-and-avs-deployed.sh +++ b/crates/contracts/anvil/start-anvil-chain-with-el-and-avs-deployed.sh @@ -38,3 +38,4 @@ cd ../../contracts cast rpc anvil_mine 200 --rpc-url http://localhost:8545 > /dev/null echo "Anvil is ready. Advanced chain to block-number:" $(cast block-number) +docker attach anvil diff --git a/crates/crypto/bls/src/lib.rs b/crates/crypto/bls/src/lib.rs index 1513e5b0..8d99a057 100644 --- a/crates/crypto/bls/src/lib.rs +++ b/crates/crypto/bls/src/lib.rs @@ -14,6 +14,9 @@ use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::{fields::PrimeField, BigInt, BigInteger256, Fp2}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use eigen_crypto_bn254::utils::map_to_curve; +use eigen_utils::binding::IBLSSignatureChecker::{ + G1Point as G1PointChecker, G2Point as G2PointChecker, +}; use eigen_utils::binding::{ BLSApkRegistry, RegistryCoordinator::{self}, @@ -113,17 +116,12 @@ pub struct BlsKeyPair { impl BlsKeyPair { /// Input [`Fr`] as a [`String`] pub fn new(fr: String) -> Result { - let sk_result = Fr::from_str(&fr); - match sk_result { - Ok(sk) => { - let pk = G1Projective::from(G1Affine::generator()) * sk; - Ok(Self { - priv_key: sk, - pub_key: BlsG1Point::new(pk.into_affine()), - }) - } - Err(_) => Err(BlsError::InvalidBlsPrivateKey), - } + let sk = Fr::from_str(&fr).map_err(|_| BlsError::InvalidBlsPrivateKey)?; + let pk = G1Projective::from(G1Affine::generator()) * sk; + Ok(Self { + priv_key: sk, + pub_key: BlsG1Point::new(pk.into_affine()), + }) } /// Get public key on G1 @@ -165,20 +163,69 @@ pub fn convert_to_g1_point(g1: G1Affine) -> Result { let x_point_result = g1.x(); let y_point_result = g1.y(); - if let (Some(x_point), Some(y_point)) = (x_point_result, y_point_result) { - let x = BigInt::new(x_point.into_bigint().0); - let y = BigInt::new(y_point.into_bigint().0); + let (Some(x_point), Some(y_point)) = (x_point_result, y_point_result) else { + return Err(BlsError::InvalidG1Affine); + }; - let x_u256 = U256::from_limbs(x.0); - let y_u256 = U256::from_limbs(y.0); + let x = BigInt::new(x_point.into_bigint().0); + let y = BigInt::new(y_point.into_bigint().0); - Ok(G1Point { - X: x_u256, - Y: y_u256, - }) - } else { - Err(BlsError::InvalidG1Affine) - } + let x_u256 = U256::from_limbs(x.0); + let y_u256 = U256::from_limbs(y.0); + + Ok(G1Point { + X: x_u256, + Y: y_u256, + }) +} + +/// Convert [`G1Affine`] to Alloy [`G1PointChecker`] +pub fn convert_to_bls_checker_g1_point(g1: G1Affine) -> Result { + let x_point_result = g1.x(); + let y_point_result = g1.y(); + + let (Some(x_point), Some(y_point)) = (x_point_result, y_point_result) else { + return Err(BlsError::InvalidG1Affine); + }; + let x = BigInt::new(x_point.into_bigint().0); + let y = BigInt::new(y_point.into_bigint().0); + + let x_u256 = U256::from_limbs(x.0); + let y_u256 = U256::from_limbs(y.0); + + Ok(G1PointChecker { + X: x_u256, + Y: y_u256, + }) +} + +/// Convert [`G2Affine`] to Alloy [`G2PointChecker`] +pub fn convert_to_bls_checker_g2_point(g2: G2Affine) -> Result { + let x_point_result = g2.x(); + let y_point_result = g2.y(); + + let (Some(x_point), Some(y_point)) = (x_point_result, y_point_result) else { + return Err(BlsError::InvalidG2Affine); + }; + let x_point_c0 = x_point.c0; + let x_point_c1 = x_point.c1; + let y_point_c0 = y_point.c0; + let y_point_c1 = y_point.c1; + + let x_0 = BigInt::new(x_point_c0.into_bigint().0); + let x_1 = BigInt::new(x_point_c1.into_bigint().0); + let y_0 = BigInt::new(y_point_c0.into_bigint().0); + let y_1 = BigInt::new(y_point_c1.into_bigint().0); + + let x_u256_0 = U256::from_limbs(x_0.0); + let x_u256_1 = U256::from_limbs(x_1.0); + let y_u256_0 = U256::from_limbs(y_0.0); + let y_u256_1 = U256::from_limbs(y_1.0); + + Ok(G2PointChecker { + X: [x_u256_1, x_u256_0], + Y: [y_u256_1, y_u256_0], + }) } /// Convert [`G2Affine`] to [`G2Point`] @@ -189,32 +236,31 @@ pub fn convert_to_g2_point(g2: G2Affine) -> Result { let y_point_result = g2.y(); // let y_point_c1 = g2.y().unwrap().c1; - if let (Some(x_point), Some(y_point)) = (x_point_result, y_point_result) { - let x_point_c0 = x_point.c0; - let x_point_c1 = x_point.c1; - let y_point_c0 = y_point.c0; - let y_point_c1 = y_point.c1; - - let x_0 = BigInt::new(x_point_c0.into_bigint().0); - let x_1 = BigInt::new(x_point_c1.into_bigint().0); - let y_0 = BigInt::new(y_point_c0.into_bigint().0); - let y_1 = BigInt::new(y_point_c1.into_bigint().0); - - let x_u256_0 = U256::from_limbs(x_0.0); - let x_u256_1 = U256::from_limbs(x_1.0); - let y_u256_0 = U256::from_limbs(y_0.0); - let y_u256_1 = U256::from_limbs(y_1.0); - - Ok(G2Point { - X: [x_u256_1, x_u256_0], - Y: [y_u256_1, y_u256_0], - }) - } else { - Err(BlsError::InvalidG2Affine) - } + let (Some(x_point), Some(y_point)) = (x_point_result, y_point_result) else { + return Err(BlsError::InvalidG2Affine); + }; + let x_point_c0 = x_point.c0; + let x_point_c1 = x_point.c1; + let y_point_c0 = y_point.c0; + let y_point_c1 = y_point.c1; + + let x_0 = BigInt::new(x_point_c0.into_bigint().0); + let x_1 = BigInt::new(x_point_c1.into_bigint().0); + let y_0 = BigInt::new(y_point_c0.into_bigint().0); + let y_1 = BigInt::new(y_point_c1.into_bigint().0); + + let x_u256_0 = U256::from_limbs(x_0.0); + let x_u256_1 = U256::from_limbs(x_1.0); + let y_u256_0 = U256::from_limbs(y_0.0); + let y_u256_1 = U256::from_limbs(y_1.0); + + Ok(G2Point { + X: [x_u256_1, x_u256_0], + Y: [y_u256_1, y_u256_0], + }) } -/// Convert [`G1Point`] to [`G1Affine`] +/// Convert [`G1PointRegistry`] to [`G1Affine`] pub fn alloy_registry_g1_point_to_g1_affine(g1_point: G1PointRegistry) -> G1Affine { let x_point = g1_point.X.into_limbs(); let x = Fq::new(BigInteger256::new(x_point)); @@ -223,7 +269,7 @@ pub fn alloy_registry_g1_point_to_g1_affine(g1_point: G1PointRegistry) -> G1Affi G1Affine::new(x, y) } -/// Convert [`G1Point`] to [`G1Affine`] +/// Convert [`G2PointRegistry`] to [`G2Affine`] pub fn alloy_registry_g2_point_to_g2_affine(g2_point: G2PointRegistry) -> G2Affine { let x_fp2 = Fp2::new( BigInteger256::new(g2_point.X[1].into_limbs()).into(), @@ -236,7 +282,7 @@ pub fn alloy_registry_g2_point_to_g2_affine(g2_point: G2PointRegistry) -> G2Affi G2Affine::new(x_fp2, y_fp2) } -/// Convert [`G2Affine`] to [`G2Point`] +/// Convert [`G2Affine`] to [`G2PointRegistry`] pub fn convert_to_registry_g2_point(g2: G2Affine) -> Result { let x_point_result = g2.x(); let y_point_result = g2.y(); @@ -286,7 +332,6 @@ impl Signature { #[cfg(test)] mod tests { - use super::*; use ark_bn254::Fq2; use eigen_crypto_bn254::utils::verify_message; diff --git a/crates/services/avsregistry/src/chaincaller.rs b/crates/services/avsregistry/src/chaincaller.rs index ae00f137..c7390fdc 100644 --- a/crates/services/avsregistry/src/chaincaller.rs +++ b/crates/services/avsregistry/src/chaincaller.rs @@ -89,7 +89,12 @@ impl AvsRegistryServ let mut pub_key_g1 = G1Projective::from(PublicKey::identity()); let mut total_stake: U256 = U256::from(0); for operator in operators_avs_state.values() { - if !operator.stake_per_quorum[quorum_num].is_zero() { + if !operator + .stake_per_quorum + .get(quorum_num) + .unwrap_or(&U256::ZERO) + .is_zero() + { if let Some(pub_keys) = &operator.operator_info.pub_keys { pub_key_g1 += pub_keys.g1_pub_key.g1(); total_stake += operator.stake_per_quorum[quorum_num]; diff --git a/crates/services/bls_aggregation/Cargo.toml b/crates/services/bls_aggregation/Cargo.toml index 5b6e0683..b7c4159b 100644 --- a/crates/services/bls_aggregation/Cargo.toml +++ b/crates/services/bls_aggregation/Cargo.toml @@ -23,5 +23,11 @@ tokio = { workspace = true, features = ["full"] } [dev-dependencies] alloy-node-bindings.workspace = true +alloy-provider.workspace = true eigen-logging.workspace = true +eigen-testing-utils.workspace = true +eigen-utils.workspace = true +eigen-services-operatorsinfo.workspace = true +serial_test = "3.1" sha2 = "0.10.8" +tokio-util = "0.7.11" diff --git a/crates/services/bls_aggregation/src/bls_agg_test.rs b/crates/services/bls_aggregation/src/bls_agg_test.rs index b5ccb32b..b776c619 100644 --- a/crates/services/bls_aggregation/src/bls_agg_test.rs +++ b/crates/services/bls_aggregation/src/bls_agg_test.rs @@ -1,20 +1,974 @@ #[cfg(test)] pub mod integration_test { - use alloy_node_bindings::Anvil; - use eigen_crypto_bls::BlsKeyPair; - use eigen_types::operator::operator_id_from_g1_pub_key; + use crate::bls_agg::{BlsAggregationServiceResponse, BlsAggregatorService}; + use alloy_primitives::{hex, Bytes, FixedBytes, B256, U256}; + use alloy_provider::Provider; + use eigen_client_avsregistry::{ + reader::AvsRegistryChainReader, writer::AvsRegistryChainWriter, + }; + use eigen_crypto_bls::{ + convert_to_bls_checker_g1_point, convert_to_bls_checker_g2_point, BlsKeyPair, + }; + use eigen_logging::get_test_logger; + use eigen_services_avsregistry::chaincaller::AvsRegistryServiceChainCaller; + use eigen_services_operatorsinfo::operatorsinfo_inmemory::OperatorInfoServiceInMemory; + use eigen_testing_utils::anvil_constants::{ + get_erc20_mock_strategy, get_operator_state_retriever_address, + get_registry_coordinator_address, get_service_manager_address, + }; + use eigen_types::{ + avs::TaskIndex, + operator::{operator_id_from_g1_pub_key, QuorumThresholdPercentages}, + }; + use eigen_utils::{ + binding::{ + IBLSSignatureChecker::{self, G1Point, NonSignerStakesAndSignature}, + RegistryCoordinator::{self, OperatorSetParam, StrategyParams}, + }, + get_provider, get_signer, + }; + use serial_test::serial; + use sha2::{Digest, Sha256}; + use std::{ + process::{Command, Stdio}, + thread::sleep, + time::Duration, + }; + use tokio::task; + use tokio_util::sync::CancellationToken; - #[test] - fn test_1_quorum_1_operator() { - let _anvil = Anvil::new().try_spawn().unwrap(); + const PRIVATE_KEY_1: &str = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; // the owner addr + const PRIVATE_KEY_2: &str = "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; + const BLS_KEY_1: &str = + "1371012690269088913462269866874713266643928125698382731338806296762673180359922"; + const BLS_KEY_2: &str = + "14610126902690889134622698668747132666439281256983827313388062967626731803500"; - let bls_key_pair = BlsKeyPair::new( - "13710126902690889134622698668747132666439281256983827313388062967626731803599".into(), + const HTTP_ENDPOINT: &str = "http://localhost:8545"; + const WS_ENDPOINT: &str = "ws://localhost:8545"; + + fn hash(task_response: u64) -> B256 { + let mut hasher = Sha256::new(); + hasher.update(task_response.to_be_bytes()); + B256::from_slice(hasher.finalize().as_ref()) + } + + fn agg_response_to_non_signer_stakes_and_signature( + agg_response: BlsAggregationServiceResponse, + ) -> NonSignerStakesAndSignature { + let non_signer_pubkeys: Vec = agg_response + .non_signers_pub_keys_g1 + .iter() + .map(|point| convert_to_bls_checker_g1_point(point.g1()).unwrap()) + .collect(); + let quorum_apks = agg_response + .quorum_apks_g1 + .iter() + .map(|point| convert_to_bls_checker_g1_point(point.g1()).unwrap()) + .collect(); + + NonSignerStakesAndSignature { + nonSignerPubkeys: non_signer_pubkeys, + quorumApks: quorum_apks, + apkG2: convert_to_bls_checker_g2_point(agg_response.signers_apk_g2.g2()).unwrap(), + sigma: convert_to_bls_checker_g1_point(agg_response.signers_agg_sig_g1.g1_point().g1()) + .unwrap(), + nonSignerQuorumBitmapIndices: agg_response.non_signer_quorum_bitmap_indices, + quorumApkIndices: agg_response.quorum_apk_indices, + totalStakeIndices: agg_response.total_stake_indices, + nonSignerStakeIndices: agg_response.non_signer_stake_indices, + } + } + + fn mine_anvil_block() { + Command::new("cast") + .args(["rpc", "anvil_mine", "1", "--rpc-url", HTTP_ENDPOINT]) + .stdout(Stdio::null()) + .output() + .expect("Failed to execute command"); + } + + #[tokio::test] + #[serial] + async fn test_1_quorum_1_operator() { + let registry_coordinator_address = get_registry_coordinator_address().await; + let operator_state_retriever_address = get_operator_state_retriever_address().await; + let service_manager_address = get_service_manager_address().await; + let provider = get_provider(HTTP_ENDPOINT); + let salt: FixedBytes<32> = FixedBytes::from([0x02; 32]); + let quorum_nums = Bytes::from([0]); + let quorum_threshold_percentages: QuorumThresholdPercentages = vec![100]; + + let bls_key_pair = BlsKeyPair::new(BLS_KEY_1.to_string()).unwrap(); + let operator_id = + hex!("fd329fe7e54f459b9c104064efe0172db113a50b5f394949b4ef80b3c34ca7f5").into(); + + // Create avs clients to interact with contracts deployed on anvil + let avs_registry_reader = AvsRegistryChainReader::new( + get_test_logger(), + registry_coordinator_address, + operator_state_retriever_address, + HTTP_ENDPOINT.to_string(), + ) + .await + .unwrap(); + let avs_writer = AvsRegistryChainWriter::build_avs_registry_chain_writer( + get_test_logger(), + HTTP_ENDPOINT.to_string(), + PRIVATE_KEY_1.to_string(), + registry_coordinator_address, + operator_state_retriever_address, + ) + .await + .unwrap(); + + // Create quorum + let contract_registry_coordinator = RegistryCoordinator::new( + registry_coordinator_address, + get_signer(PRIVATE_KEY_1.to_string(), HTTP_ENDPOINT), + ); + let operator_set_params = OperatorSetParam { + maxOperatorCount: 10, + kickBIPsOfOperatorStake: 100, + kickBIPsOfTotalStake: 1000, + }; + let strategy_params = StrategyParams { + strategy: get_erc20_mock_strategy().await, + multiplier: 1, + }; + let _ = contract_registry_coordinator + .createQuorum(operator_set_params, 0, vec![strategy_params]) + .send() + .await + .unwrap(); + + // Register operator + avs_writer + .register_operator_in_quorum_with_avs_registry_coordinator( + bls_key_pair.clone(), + salt, + U256::from_be_slice(&[0xff; 32]), + quorum_nums.clone(), + "socket".to_string(), + ) + .await + .unwrap(); + + // Sleep is needed so registered operators are accesible to the OperatorInfoServiceInMemory + sleep(Duration::from_secs(1)); + + // Create aggregation service + let operators_info = OperatorInfoServiceInMemory::new( + get_test_logger(), + avs_registry_reader.clone(), + WS_ENDPOINT.to_string(), + ) + .await; + + let cancellation_token = CancellationToken::new(); + let operators_info_clone = operators_info.clone(); + let token_clone = cancellation_token.clone(); + task::spawn(async move { operators_info_clone.start_service(&token_clone, 0, 0).await }); + + let avs_registry_service = + AvsRegistryServiceChainCaller::new(avs_registry_reader.clone(), operators_info); + + let bls_agg_service = BlsAggregatorService::new(avs_registry_service); + let current_block_num = provider.get_block_number().await.unwrap(); + mine_anvil_block(); + + // Create the task related parameters + let task_index: TaskIndex = 0; + let time_to_expiry = Duration::from_secs(1); + + // Initialize the task + bls_agg_service + .initialize_new_task( + task_index, + current_block_num as u32, + quorum_nums.to_vec(), + quorum_threshold_percentages, + time_to_expiry, + ) + .await + .unwrap(); + + // Compute the signature and send it to the aggregation service + let task_response = 123; + let task_response_digest = hash(task_response); + let bls_signature = bls_key_pair.sign_message(task_response_digest.as_ref()); + bls_agg_service + .process_new_signature(task_index, task_response_digest, bls_signature, operator_id) + .await + .unwrap(); + + // Wait for the response from the aggregation service + let bls_agg_response = bls_agg_service + .aggregated_response_receiver + .lock() + .await + .recv() + .await + .unwrap() + .unwrap(); + + // Send the shutdown signal to the OperatorInfoServiceInMemory + cancellation_token.cancel(); + + // Check the response + let service_manager = IBLSSignatureChecker::new(service_manager_address, provider); + service_manager + .checkSignatures( + task_response_digest, + quorum_nums, + current_block_num as u32, + agg_response_to_non_signer_stakes_and_signature(bls_agg_response), + ) + .call() + .await + .unwrap(); + } + + #[tokio::test] + #[serial] + async fn test_1_quorum_2_operators() { + let registry_coordinator_address = get_registry_coordinator_address().await; + let operator_state_retriever_address = get_operator_state_retriever_address().await; + let service_manager_address = get_service_manager_address().await; + let provider = get_provider(HTTP_ENDPOINT); + let salt: FixedBytes<32> = FixedBytes::from([0x02; 32]); + + let bls_key_pair_1 = BlsKeyPair::new(BLS_KEY_1.to_string()).unwrap(); + let operator_id_1 = + hex!("fd329fe7e54f459b9c104064efe0172db113a50b5f394949b4ef80b3c34ca7f5").into(); + + let bls_key_pair_2 = BlsKeyPair::new(BLS_KEY_2.to_string()).unwrap(); + let operator_id_2 = + hex!("7213614953817d00866957a5f866c67a5fb8d4e392af501701f7ab35294dc4b3").into(); + + let quorum_nums = Bytes::from([1u8]); + let quorum_threshold_percentages: QuorumThresholdPercentages = vec![100]; + + let contract_registry_coordinator = RegistryCoordinator::new( + registry_coordinator_address, + get_signer(PRIVATE_KEY_1.to_string(), HTTP_ENDPOINT), + ); + + // Create quorum + let operator_set_params = OperatorSetParam { + maxOperatorCount: 10, + kickBIPsOfOperatorStake: 100, + kickBIPsOfTotalStake: 1000, + }; + let strategy_params = vec![StrategyParams { + strategy: get_erc20_mock_strategy().await, + multiplier: 1, + }]; + let _ = contract_registry_coordinator + .createQuorum(operator_set_params.clone(), 0, strategy_params.clone()) + .send() + .await + .unwrap(); + + // Create avs clients to interact with contracts deployed on anvil + let avs_registry_reader = AvsRegistryChainReader::new( + get_test_logger(), + registry_coordinator_address, + operator_state_retriever_address, + HTTP_ENDPOINT.to_string(), + ) + .await + .unwrap(); + + let avs_writer = AvsRegistryChainWriter::build_avs_registry_chain_writer( + get_test_logger(), + HTTP_ENDPOINT.to_string(), + PRIVATE_KEY_1.to_string(), + registry_coordinator_address, + operator_state_retriever_address, + ) + .await + .unwrap(); + + // Register operator + avs_writer + .register_operator_in_quorum_with_avs_registry_coordinator( + bls_key_pair_1.clone(), + salt, + U256::from_be_slice(&[0xff; 32]), + quorum_nums.clone(), + "socket".to_string(), + ) + .await + .unwrap(); + + let avs_writer = AvsRegistryChainWriter::build_avs_registry_chain_writer( + get_test_logger(), + HTTP_ENDPOINT.to_string(), + PRIVATE_KEY_2.to_string(), + registry_coordinator_address, + operator_state_retriever_address, + ) + .await + .unwrap(); + avs_writer + .register_operator_in_quorum_with_avs_registry_coordinator( + bls_key_pair_2.clone(), + salt, + U256::from_be_slice(&[0xff; 32]), + quorum_nums.clone(), + "socket".to_string(), + ) + .await + .unwrap(); + + // Sleep is needed so registered operators are accesible to the OperatorInfoServiceInMemory + sleep(Duration::from_secs(1)); + + // Create aggregation service + let operators_info = OperatorInfoServiceInMemory::new( + get_test_logger(), + avs_registry_reader.clone(), + WS_ENDPOINT.to_string(), + ) + .await; + + let cancellation_token = CancellationToken::new(); + let operators_info_clone = operators_info.clone(); + let token_clone = cancellation_token.clone(); + task::spawn(async move { operators_info_clone.start_service(&token_clone, 0, 0).await }); + + let avs_registry_service = + AvsRegistryServiceChainCaller::new(avs_registry_reader.clone(), operators_info); + + let bls_agg_service = BlsAggregatorService::new(avs_registry_service); + + let current_block_num = provider.get_block_number().await.unwrap(); + + mine_anvil_block(); + + // Create the task related parameters + let task_index: TaskIndex = 0; + let time_to_expiry = Duration::from_secs(1); + + // Initialize the task + bls_agg_service + .initialize_new_task( + task_index, + current_block_num as u32, + quorum_nums.to_vec(), + quorum_threshold_percentages, + time_to_expiry, + ) + .await + .unwrap(); + + // Compute the signature and send it to the aggregation service + let task_response = 123; + let task_response_digest = hash(task_response); + + let bls_signature_1 = bls_key_pair_1.sign_message(task_response_digest.as_ref()); + bls_agg_service + .process_new_signature( + task_index, + task_response_digest, + bls_signature_1, + operator_id_1, + ) + .await + .unwrap(); + + let bls_signature_2 = bls_key_pair_2.sign_message(task_response_digest.as_ref()); + bls_agg_service + .process_new_signature( + task_index, + task_response_digest, + bls_signature_2, + operator_id_2, + ) + .await + .unwrap(); + + // Wait for the response from the aggregation service + let bls_agg_response = bls_agg_service + .aggregated_response_receiver + .lock() + .await + .recv() + .await + .unwrap() + .unwrap(); + + // Send the shutdown signal to the OperatorInfoServiceInMemory + cancellation_token.cancel(); + + // Check the response + let service_manager = IBLSSignatureChecker::new(service_manager_address, provider); + service_manager + .checkSignatures( + task_response_digest, + quorum_nums, + current_block_num as u32, + agg_response_to_non_signer_stakes_and_signature(bls_agg_response), + ) + .call() + .await + .unwrap(); + } + + #[tokio::test] + #[serial] + async fn test_2_quorums_2_operators_separated() { + // operator 1 stakes on quorum 2 + // operator 2 stakes on quorum 3 + let registry_coordinator_address = get_registry_coordinator_address().await; + let operator_state_retriever_address = get_operator_state_retriever_address().await; + let service_manager_address = get_service_manager_address().await; + let provider = get_provider(HTTP_ENDPOINT); + let salt: FixedBytes<32> = FixedBytes::from([0x02; 32]); + + let bls_key_pair_1 = BlsKeyPair::new(BLS_KEY_1.to_string()).unwrap(); + let operator_id_1 = + hex!("fd329fe7e54f459b9c104064efe0172db113a50b5f394949b4ef80b3c34ca7f5").into(); + + let bls_key_pair_2 = BlsKeyPair::new(BLS_KEY_2.to_string()).unwrap(); + let operator_id_2 = + hex!("7213614953817d00866957a5f866c67a5fb8d4e392af501701f7ab35294dc4b3").into(); + + let quorum_nums = Bytes::from([2u8, 3u8]); + let quorum_threshold_percentages: QuorumThresholdPercentages = vec![100, 100]; + + let contract_registry_coordinator = RegistryCoordinator::new( + registry_coordinator_address, + get_signer(PRIVATE_KEY_1.to_string(), HTTP_ENDPOINT), + ); + + // Create quorums + let operator_set_params = OperatorSetParam { + maxOperatorCount: 10, + kickBIPsOfOperatorStake: 100, + kickBIPsOfTotalStake: 1000, + }; + let strategy_params = vec![StrategyParams { + strategy: get_erc20_mock_strategy().await, + multiplier: 1, + }]; + let _ = contract_registry_coordinator + .createQuorum(operator_set_params.clone(), 0, strategy_params.clone()) + .send() + .await + .unwrap(); + let _ = contract_registry_coordinator + .createQuorum(operator_set_params.clone(), 0, strategy_params.clone()) + .send() + .await + .unwrap(); + let _ = contract_registry_coordinator + .createQuorum(operator_set_params, 0, strategy_params) + .send() + .await + .unwrap(); + + // Create avs clients to interact with contracts deployed on anvil + let avs_registry_reader = AvsRegistryChainReader::new( + get_test_logger(), + registry_coordinator_address, + operator_state_retriever_address, + HTTP_ENDPOINT.to_string(), + ) + .await + .unwrap(); + + let avs_writer = AvsRegistryChainWriter::build_avs_registry_chain_writer( + get_test_logger(), + HTTP_ENDPOINT.to_string(), + PRIVATE_KEY_1.to_string(), + registry_coordinator_address, + operator_state_retriever_address, + ) + .await + .unwrap(); + + // Register operator + avs_writer + .register_operator_in_quorum_with_avs_registry_coordinator( + bls_key_pair_1.clone(), + salt, + U256::from_be_slice(&[0xff; 32]), + Bytes::from([quorum_nums[0]]), + "socket".to_string(), + ) + .await + .unwrap(); + + let avs_writer = AvsRegistryChainWriter::build_avs_registry_chain_writer( + // TODO: check if needed + get_test_logger(), + HTTP_ENDPOINT.to_string(), + PRIVATE_KEY_2.to_string(), + registry_coordinator_address, + operator_state_retriever_address, ) + .await .unwrap(); - let _operator_id = operator_id_from_g1_pub_key(bls_key_pair.public_key()); + avs_writer + .register_operator_in_quorum_with_avs_registry_coordinator( + bls_key_pair_2.clone(), + salt, + U256::from_be_slice(&[0xff; 32]), + Bytes::from([quorum_nums[1]]), + "socket".to_string(), + ) + .await + .unwrap(); + + // Sleep is needed so registered operators are accesible to the OperatorInfoServiceInMemory + sleep(Duration::from_secs(1)); + + // Create aggregation service + let operators_info = OperatorInfoServiceInMemory::new( + get_test_logger(), + avs_registry_reader.clone(), + WS_ENDPOINT.to_string(), + ) + .await; + + let cancellation_token = CancellationToken::new(); + let operators_info_clone = operators_info.clone(); + let token_clone = cancellation_token.clone(); + task::spawn(async move { operators_info_clone.start_service(&token_clone, 0, 0).await }); + + let avs_registry_service = + AvsRegistryServiceChainCaller::new(avs_registry_reader.clone(), operators_info); + + let bls_agg_service = BlsAggregatorService::new(avs_registry_service); + + let current_block_num = provider.get_block_number().await.unwrap(); + + mine_anvil_block(); + + // Create the task related parameters + let task_index: TaskIndex = 0; + let time_to_expiry = Duration::from_secs(1); + + // Initialize the task + bls_agg_service + .initialize_new_task( + task_index, + current_block_num as u32, + quorum_nums.to_vec(), + quorum_threshold_percentages, + time_to_expiry, + ) + .await + .unwrap(); + + // Compute the signature and send it to the aggregation service + let task_response = 123; + let task_response_digest = hash(task_response); + + let bls_signature_1 = bls_key_pair_1.sign_message(task_response_digest.as_ref()); + bls_agg_service + .process_new_signature( + task_index, + task_response_digest, + bls_signature_1, + operator_id_1, + ) + .await + .unwrap(); + + let bls_signature_2 = bls_key_pair_2.sign_message(task_response_digest.as_ref()); + bls_agg_service + .process_new_signature( + task_index, + task_response_digest, + bls_signature_2, + operator_id_2, + ) + .await + .unwrap(); + + // Wait for the response from the aggregation service + let bls_agg_response = bls_agg_service + .aggregated_response_receiver + .lock() + .await + .recv() + .await + .unwrap() + .unwrap(); + + // Send the shutdown signal to the OperatorInfoServiceInMemory + cancellation_token.cancel(); + + // Check the response + let service_manager = IBLSSignatureChecker::new(service_manager_address, provider); + service_manager + .checkSignatures( + task_response_digest, + quorum_nums, + current_block_num as u32, + agg_response_to_non_signer_stakes_and_signature(bls_agg_response), + ) + .call() + .await + .unwrap(); + } + + #[tokio::test] + #[serial] + #[ignore] + // This tests fails because of https://github.com/Layr-Labs/eigensdk-go/issues/261 + async fn test_2_quorums_2_operators_shared() { + // operator 1 stakes on quorums [0, 1] + // operator 2 stakes on quorums [1] + let registry_coordinator_address = get_registry_coordinator_address().await; + let operator_state_retriever_address = get_operator_state_retriever_address().await; + let service_manager_address = get_service_manager_address().await; + let provider = get_provider(HTTP_ENDPOINT); + let salt: FixedBytes<32> = FixedBytes::from([0x02; 32]); + + let bls_key_pair_1 = BlsKeyPair::new(BLS_KEY_1.to_string()).unwrap(); + let operator_id_1 = + hex!("fd329fe7e54f459b9c104064efe0172db113a50b5f394949b4ef80b3c34ca7f5").into(); + + let bls_key_pair_2 = BlsKeyPair::new(BLS_KEY_2.to_string()).unwrap(); + let operator_id_2 = + hex!("7213614953817d00866957a5f866c67a5fb8d4e392af501701f7ab35294dc4b3").into(); + + let quorum_nums = Bytes::from([0u8, 1u8]); + let quorum_threshold_percentages: QuorumThresholdPercentages = vec![100, 100]; + + let contract_registry_coordinator = RegistryCoordinator::new( + registry_coordinator_address, + get_signer(PRIVATE_KEY_1.to_string(), HTTP_ENDPOINT), + ); + + // Create quorums + let operator_set_params = OperatorSetParam { + maxOperatorCount: 10, + kickBIPsOfOperatorStake: 100, + kickBIPsOfTotalStake: 1000, + }; + let strategy_params = vec![StrategyParams { + strategy: get_erc20_mock_strategy().await, + multiplier: 1, + }]; + let _ = contract_registry_coordinator + .createQuorum(operator_set_params.clone(), 0, strategy_params.clone()) + .send() + .await + .unwrap(); + let _ = contract_registry_coordinator + .createQuorum(operator_set_params, 0, strategy_params) + .send() + .await + .unwrap(); + + // Create avs clients to interact with contracts deployed on anvil + let avs_registry_reader = AvsRegistryChainReader::new( + get_test_logger(), + registry_coordinator_address, + operator_state_retriever_address, + HTTP_ENDPOINT.to_string(), + ) + .await + .unwrap(); + + let avs_writer = AvsRegistryChainWriter::build_avs_registry_chain_writer( + get_test_logger(), + HTTP_ENDPOINT.to_string(), + PRIVATE_KEY_1.to_string(), + registry_coordinator_address, + operator_state_retriever_address, + ) + .await + .unwrap(); + + // Register operator + avs_writer + .register_operator_in_quorum_with_avs_registry_coordinator( + bls_key_pair_1.clone(), + salt, + U256::from_be_slice(&[0xff; 32]), + quorum_nums.clone(), + "socket".to_string(), + ) + .await + .unwrap(); + + let avs_writer = AvsRegistryChainWriter::build_avs_registry_chain_writer( + // TODO: check if needed + get_test_logger(), + HTTP_ENDPOINT.to_string(), + PRIVATE_KEY_2.to_string(), + registry_coordinator_address, + operator_state_retriever_address, + ) + .await + .unwrap(); + avs_writer + .register_operator_in_quorum_with_avs_registry_coordinator( + bls_key_pair_2.clone(), + salt, + U256::from_be_slice(&[0xff; 32]), + Bytes::from([quorum_nums[1]]), + "socket".to_string(), + ) + .await + .unwrap(); + + // Sleep is needed so registered operators are accesible to the OperatorInfoServiceInMemory + sleep(Duration::from_secs(1)); + + // Create aggregation service + let operators_info = OperatorInfoServiceInMemory::new( + get_test_logger(), + avs_registry_reader.clone(), + WS_ENDPOINT.to_string(), + ) + .await; + + let cancellation_token = CancellationToken::new(); + let operators_info_clone = operators_info.clone(); + let token_clone = cancellation_token.clone(); + task::spawn(async move { operators_info_clone.start_service(&token_clone, 0, 0).await }); + + let avs_registry_service = + AvsRegistryServiceChainCaller::new(avs_registry_reader.clone(), operators_info); + + let bls_agg_service = BlsAggregatorService::new(avs_registry_service); + + let current_block_num = provider.get_block_number().await.unwrap(); + + mine_anvil_block(); + + // Create the task related parameters + let task_index: TaskIndex = 0; + let time_to_expiry = Duration::from_secs(1); + + // Initialize the task + bls_agg_service + .initialize_new_task( + task_index, + current_block_num as u32, + quorum_nums.to_vec(), + quorum_threshold_percentages, + time_to_expiry, + ) + .await + .unwrap(); + + // Compute the signature and send it to the aggregation service + let task_response = 123; + let task_response_digest = hash(task_response); + + let bls_signature_1 = bls_key_pair_1.sign_message(task_response_digest.as_ref()); + bls_agg_service + .process_new_signature( + task_index, + task_response_digest, + bls_signature_1, + operator_id_1, + ) + .await + .unwrap(); + + let bls_signature_2 = bls_key_pair_2.sign_message(task_response_digest.as_ref()); + bls_agg_service + .process_new_signature( + task_index, + task_response_digest, + bls_signature_2, + operator_id_2, + ) + .await + .unwrap(); + + // Wait for the response from the aggregation service + let bls_agg_response = bls_agg_service + .aggregated_response_receiver + .lock() + .await + .recv() + .await + .unwrap() + .unwrap(); + + // Send the shutdown signal to the OperatorInfoServiceInMemory + cancellation_token.cancel(); + + // Check the response + let service_manager = IBLSSignatureChecker::new(service_manager_address, provider); + service_manager + .checkSignatures( + task_response_digest, + quorum_nums, + current_block_num as u32, + agg_response_to_non_signer_stakes_and_signature(bls_agg_response), + ) + .call() + .await + .unwrap(); + } + + #[tokio::test] + #[serial] + #[ignore] + // This tests fails because of https://github.com/Layr-Labs/eigensdk-go/issues/261 + async fn test_2_quorums_1_operator() { + let registry_coordinator_address = get_registry_coordinator_address().await; + let operator_state_retriever_address = get_operator_state_retriever_address().await; + let service_manager_address = get_service_manager_address().await; + let provider = get_provider(HTTP_ENDPOINT); + let salt: FixedBytes<32> = FixedBytes::from([0x02; 32]); + + let bls_key_pair_1 = BlsKeyPair::new(BLS_KEY_1.to_string()).unwrap(); + let operator_id_1 = + hex!("fd329fe7e54f459b9c104064efe0172db113a50b5f394949b4ef80b3c34ca7f5").into(); + + let quorum_nums = Bytes::from([0u8, 1u8]); + let quorum_threshold_percentages: QuorumThresholdPercentages = vec![100, 100]; + + let contract_registry_coordinator = RegistryCoordinator::new( + registry_coordinator_address, + get_signer(PRIVATE_KEY_1.to_string(), HTTP_ENDPOINT), + ); + + // Create quorums + let operator_set_params = OperatorSetParam { + maxOperatorCount: 10, + kickBIPsOfOperatorStake: 100, + kickBIPsOfTotalStake: 1000, + }; + let strategy_params = vec![StrategyParams { + strategy: get_erc20_mock_strategy().await, + multiplier: 1, + }]; + let _ = contract_registry_coordinator + .createQuorum(operator_set_params.clone(), 0, strategy_params.clone()) + .send() + .await + .unwrap(); + let _ = contract_registry_coordinator + .createQuorum(operator_set_params, 0, strategy_params) + .send() + .await + .unwrap(); + + // Create avs clients to interact with contracts deployed on anvil + let avs_registry_reader = AvsRegistryChainReader::new( + get_test_logger(), + registry_coordinator_address, + operator_state_retriever_address, + HTTP_ENDPOINT.to_string(), + ) + .await + .unwrap(); + + let avs_writer = AvsRegistryChainWriter::build_avs_registry_chain_writer( + get_test_logger(), + HTTP_ENDPOINT.to_string(), + PRIVATE_KEY_1.to_string(), + registry_coordinator_address, + operator_state_retriever_address, + ) + .await + .unwrap(); + + // Register operator + avs_writer + .register_operator_in_quorum_with_avs_registry_coordinator( + bls_key_pair_1.clone(), + salt, + U256::from_be_slice(&[0xff; 32]), + quorum_nums.clone(), + "socket".to_string(), + ) + .await + .unwrap(); + + // Sleep is needed so registered operators are accesible to the OperatorInfoServiceInMemory + sleep(Duration::from_secs(1)); + + // Create aggregation service + let operators_info = OperatorInfoServiceInMemory::new( + get_test_logger(), + avs_registry_reader.clone(), + WS_ENDPOINT.to_string(), + ) + .await; + + let cancellation_token = CancellationToken::new(); + let operators_info_clone = operators_info.clone(); + let token_clone = cancellation_token.clone(); + task::spawn(async move { operators_info_clone.start_service(&token_clone, 0, 0).await }); + + let avs_registry_service = + AvsRegistryServiceChainCaller::new(avs_registry_reader.clone(), operators_info); + + let bls_agg_service = BlsAggregatorService::new(avs_registry_service); + + // Create the task related parameters + let task_index: TaskIndex = 0; + let time_to_expiry = Duration::from_secs(1); + + // Initialize the task + let current_block_num = provider.get_block_number().await.unwrap(); + + bls_agg_service + .initialize_new_task( + task_index, + current_block_num as u32, + quorum_nums.to_vec(), + quorum_threshold_percentages, + time_to_expiry, + ) + .await + .unwrap(); + + mine_anvil_block(); + + // Compute the signature and send it to the aggregation service + let task_response = 123; + let task_response_digest = hash(task_response); + + let bls_signature_1 = bls_key_pair_1.sign_message(task_response_digest.as_ref()); + bls_agg_service + .process_new_signature( + task_index, + task_response_digest, + bls_signature_1.clone(), + operator_id_1, + ) + .await + .unwrap(); + + // Wait for the response from the aggregation service + let bls_agg_response = bls_agg_service + .aggregated_response_receiver + .lock() + .await + .recv() + .await + .unwrap() + .unwrap(); + + // Send the shutdown signal to the OperatorInfoServiceInMemory + cancellation_token.cancel(); - // TODO: integration tests need a `chainio/client` builder to construct the clients. - // Will implement this builder and integration test in a following PR. + // Check the response + let service_manager = IBLSSignatureChecker::new(service_manager_address, provider); + service_manager + .checkSignatures( + task_response_digest, + quorum_nums, + current_block_num as u32, + agg_response_to_non_signer_stakes_and_signature(bls_agg_response), + ) + .call() + .await + .unwrap(); } }