diff --git a/bolt-sidecar/src/chain_io/manager.rs b/bolt-sidecar/src/chain_io/manager.rs index 30df2e575..ae8f7fbb0 100644 --- a/bolt-sidecar/src/chain_io/manager.rs +++ b/bolt-sidecar/src/chain_io/manager.rs @@ -14,6 +14,7 @@ use eyre::bail; use reqwest::{Client, Url}; use serde::Serialize; +use tracing::debug; use BoltManagerContract::{ BoltManagerContractErrors, BoltManagerContractInstance, ProposerStatus, ValidatorDoesNotExist, }; @@ -22,6 +23,8 @@ use crate::config::chain::Chain; use super::utils::{self, CompressedHash}; +const CHUNK_SIZE: usize = 100; + /// A wrapper over a BoltManagerContract that exposes various utility methods. #[derive(Debug, Clone)] pub struct BoltManager(BoltManagerContractInstance, RootProvider>>); @@ -54,57 +57,82 @@ impl BoltManager { commitment_signer_pubkey: Address, ) -> eyre::Result> { let hashes_with_preimages = utils::pubkey_hashes(keys); - let hashes = hashes_with_preimages.keys().cloned().collect::>(); - - let returndata = self.0.getProposerStatuses(hashes).call().await; - - // TODO: clean this after https://github.com/alloy-rs/alloy/issues/787 is merged - let error = match returndata.map(|data| data.statuses) { - Ok(statuses) => { - for status in &statuses { - if !status.active { - bail!( + let mut hashes = hashes_with_preimages.keys().cloned().collect::>(); + let total_keys = hashes.len(); + + let mut proposers_statuses = Vec::with_capacity(hashes.len()); + + let mut i = 0; + while !hashes.is_empty() { + i += 1; + + // No more than CHUNK_SIZE at a time to avoid EL config limits + // + // TODO: write an unsafe function that splits a vec into owned chunks without + // allocating + let hashes_chunk = hashes.drain(..CHUNK_SIZE.min(hashes.len())).collect::>(); + + debug!( + "fetching {} proposer statuses for chunk {} of {}", + hashes_chunk.len(), + i, + total_keys.div_ceil(CHUNK_SIZE) + ); + + let returndata = self.0.getProposerStatuses(hashes_chunk).call().await; + + // TODO: clean this after https://github.com/alloy-rs/alloy/issues/787 is merged + let error = match returndata.map(|data| data.statuses) { + Ok(statuses) => { + for status in &statuses { + if !status.active { + bail!( "validator with public key {:?} and public key hash {:?} is not active in Bolt", hashes_with_preimages.get(&status.pubkeyHash), status.pubkeyHash ); - } else if status.operator != commitment_signer_pubkey { - bail!(generate_operator_keys_mismatch_error( - status.pubkeyHash, - commitment_signer_pubkey, - status.operator - )); + } else if status.operator != commitment_signer_pubkey { + bail!(generate_operator_keys_mismatch_error( + status.pubkeyHash, + commitment_signer_pubkey, + status.operator + )); + } } - } - return Ok(statuses); - } - Err(error) => match error { - ContractError::TransportError(TransportError::ErrorResp(err)) => { - error!("Error response from BoltManager contract: {:?}", err); - let data = err.data.unwrap_or_default(); - let data = data.get().trim_matches('"'); - let data = Bytes::from_str(data)?; - - BoltManagerContractErrors::abi_decode(&data, true)? + proposers_statuses.extend(statuses); + + continue; + } + Err(error) => match error { + ContractError::TransportError(TransportError::ErrorResp(err)) => { + error!("error response from contract: {:?}", err); + let data = err.data.unwrap_or_default(); + let data = data.get().trim_matches('"'); + let data = Bytes::from_str(data)?; + + BoltManagerContractErrors::abi_decode(&data, true)? + } + e => return Err(e)?, + }, + }; + + match error { + BoltManagerContractErrors::ValidatorDoesNotExist(ValidatorDoesNotExist { + pubkeyHash: pubkey_hash, + }) => { + bail!("ValidatorDoesNotExist -- validator with public key {:?} and public key hash {:?} is not registered in Bolt", hashes_with_preimages.get(&pubkey_hash), pubkey_hash); + } + BoltManagerContractErrors::InvalidQuery(_) => { + bail!("InvalidQuery -- invalid zero public key hash"); + } + BoltManagerContractErrors::KeyNotFound(_) => { + bail!("KeyNotFound -- operator associated with commitment signer public key {:?} is not registered in Bolt", commitment_signer_pubkey); } - e => return Err(e)?, - }, - }; - - match error { - BoltManagerContractErrors::ValidatorDoesNotExist(ValidatorDoesNotExist { - pubkeyHash: pubkey_hash, - }) => { - bail!("ValidatorDoesNotExist -- validator with public key {:?} and public key hash {:?} is not registered in Bolt", hashes_with_preimages.get(&pubkey_hash), pubkey_hash); - } - BoltManagerContractErrors::InvalidQuery(_) => { - bail!("InvalidQuery -- invalid zero public key hash"); - } - BoltManagerContractErrors::KeyNotFound(_) => { - bail!("KeyNotFound -- operator associated with commitment signer public key {:?} is not registered in Bolt", commitment_signer_pubkey); } } + + Ok(proposers_statuses) } } @@ -161,6 +189,7 @@ mod tests { use super::BoltManager; #[tokio::test] + #[ignore = "requires Chainbound tailnet"] async fn test_verify_validator_pubkeys() { let url = Url::parse("http://remotebeast:48545").expect("valid url"); let manager = diff --git a/bolt-sidecar/src/driver.rs b/bolt-sidecar/src/driver.rs index ed33033d2..69ca91b7b 100644 --- a/bolt-sidecar/src/driver.rs +++ b/bolt-sidecar/src/driver.rs @@ -183,13 +183,18 @@ impl SidecarDriver { { let commitment_signer_pubkey = commitment_signer.public_key(); let validator_public_keys_len = validator_public_keys.len(); + info!( + validator_public_keys_len, + commitment_signer_pubkey = ?commitment_signer_pubkey, + "Verifying validators and operator keys with Bolt Manager, this may take a while..." + ); bolt_manager .verify_validator_pubkeys(validator_public_keys, commitment_signer_pubkey) .await?; info!( validator_public_keys_len, commitment_signer_pubkey = ?commitment_signer_pubkey, - "Validators and operator keys verified with Bolt Manager successfully" + "Verified validators and operator keys verified with Bolt Manager successfully" ); } else { warn!(