From 7c6d7aa5ce5362671030c9e4d342159cb9d3a31e Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Fri, 5 Jul 2024 12:14:13 +0200 Subject: [PATCH] chore: add parsing validator indexes as ranges --- bolt-sidecar/bin/sidecar.rs | 2 +- bolt-sidecar/src/config/mod.rs | 19 ++++-- bolt-sidecar/src/config/validator_indexes.rs | 72 ++++++++++++++++++++ bolt-sidecar/src/state/consensus.rs | 14 ++-- 4 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 bolt-sidecar/src/config/validator_indexes.rs diff --git a/bolt-sidecar/bin/sidecar.rs b/bolt-sidecar/bin/sidecar.rs index 28ac755e..62676f87 100644 --- a/bolt-sidecar/bin/sidecar.rs +++ b/bolt-sidecar/bin/sidecar.rs @@ -37,7 +37,7 @@ async fn main() -> eyre::Result<()> { let shutdown_tx = start_rpc_server(&config, api_events).await?; let mut consensus_state = ConsensusState::new( beacon_client.clone(), - &config.validator_indexes, + config.validator_indexes.clone(), config.chain.commitment_deadline(), ); diff --git a/bolt-sidecar/src/config/mod.rs b/bolt-sidecar/src/config/mod.rs index 43ee3b21..26ee58a0 100644 --- a/bolt-sidecar/src/config/mod.rs +++ b/bolt-sidecar/src/config/mod.rs @@ -1,4 +1,4 @@ -use std::{fs::read_to_string, path::Path}; +use std::{fs::read_to_string, path::Path, str::FromStr}; use alloy_primitives::Address; use blst::min_pk::SecretKey; @@ -7,6 +7,9 @@ use reqwest::Url; use crate::crypto::bls::random_bls_secret; +pub mod validator_indexes; +pub use validator_indexes::ValidatorIndexes; + pub mod chain; pub use chain::ChainConfig; @@ -43,9 +46,13 @@ pub struct Opts { /// Max number of commitments to accept per block #[clap(short = 'm', long)] pub(super) max_commitments: Option, - /// Validator indexes - #[clap(short = 'v', long, value_parser, num_args = 1.., value_delimiter = ',')] - pub(super) validator_indexes: Vec, + /// Validator indexes of connected validators that the sidecar + /// should accept commitments on behalf of. Accepted values: + /// - a comma-separated list of indexes (e.g. "1,2,3,4") + /// - a contiguous range of indexes (e.g. "1..4") + /// - a mix of the above (e.g. "1,2..4,6..8") + #[clap(short = 'v', long, value_parser = ValidatorIndexes::from_str)] + pub(super) validator_indexes: ValidatorIndexes, /// The JWT secret token to authenticate calls to the engine API. /// /// It can either be a hex-encoded string or a file path to a file @@ -95,7 +102,7 @@ pub struct Config { pub limits: Limits, /// Validator indexes of connected validators that the /// sidecar should accept commitments on behalf of - pub validator_indexes: Vec, + pub validator_indexes: ValidatorIndexes, /// Local bulider private key for signing fallback payloads. /// If not provided, a random key will be used. pub builder_private_key: SecretKey, @@ -118,7 +125,7 @@ impl Default for Config { fee_recipient: Address::ZERO, builder_private_key: random_bls_secret(), limits: Limits::default(), - validator_indexes: Vec::new(), + validator_indexes: ValidatorIndexes::default(), chain: ChainConfig::default(), } } diff --git a/bolt-sidecar/src/config/validator_indexes.rs b/bolt-sidecar/src/config/validator_indexes.rs new file mode 100644 index 00000000..d9c9db5b --- /dev/null +++ b/bolt-sidecar/src/config/validator_indexes.rs @@ -0,0 +1,72 @@ +use std::str::FromStr; + +#[derive(Debug, Clone, Default)] +pub struct ValidatorIndexes(Vec); + +impl ValidatorIndexes { + pub fn contains(&self, index: u64) -> bool { + self.0.contains(&index) + } +} + +impl FromStr for ValidatorIndexes { + type Err = eyre::Report; + + /// Parse an array of validator indexes. Accepted values: + /// - a comma-separated list of indexes (e.g. "1,2,3,4") + /// - a contiguous range of indexes (e.g. "1..4") + /// - a mix of the above (e.g. "1,2..4,6..8") + /// + /// TODO: add parsing from a directory path, using the format of + /// validator definitions + fn from_str(s: &str) -> Result { + let s = s.trim(); + let mut vec = Vec::new(); + + for comma_separated_part in s.split(',') { + if comma_separated_part.contains("..") { + let mut parts = comma_separated_part.split(".."); + + let start = parts.next().ok_or_else(|| eyre::eyre!("Invalid range"))?; + let start = start.parse::()?; + + let end = parts.next().ok_or_else(|| eyre::eyre!("Invalid range"))?; + let end = end.parse::()?; + + vec.extend(start..=end); + } else { + let index = comma_separated_part.parse::()?; + vec.push(index); + } + } + + Ok(Self(vec)) + } +} + +impl From> for ValidatorIndexes { + fn from(vec: Vec) -> Self { + Self(vec) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_parse_validator_indexes() { + use super::ValidatorIndexes; + use std::str::FromStr; + + let indexes = ValidatorIndexes::from_str("1,2,3,4").unwrap(); + assert_eq!(indexes.0, vec![1, 2, 3, 4]); + + let indexes = ValidatorIndexes::from_str("1..4").unwrap(); + assert_eq!(indexes.0, vec![1, 2, 3, 4]); + + let indexes = ValidatorIndexes::from_str("1..4,6..8").unwrap(); + assert_eq!(indexes.0, vec![1, 2, 3, 4, 6, 7, 8]); + + let indexes = ValidatorIndexes::from_str("1,2..4,6..8").unwrap(); + assert_eq!(indexes.0, vec![1, 2, 3, 4, 6, 7, 8]); + } +} diff --git a/bolt-sidecar/src/state/consensus.rs b/bolt-sidecar/src/state/consensus.rs index 67ecd381..eede2fbd 100644 --- a/bolt-sidecar/src/state/consensus.rs +++ b/bolt-sidecar/src/state/consensus.rs @@ -5,6 +5,7 @@ use ethereum_consensus::{deneb::BeaconBlockHeader, phase0::mainnet::SLOTS_PER_EP use super::CommitmentDeadline; use crate::{ + config::ValidatorIndexes, primitives::{CommitmentRequest, Slot}, BeaconClient, }; @@ -39,7 +40,7 @@ pub struct ConsensusState { beacon_api_client: Client, header: BeaconBlockHeader, epoch: Epoch, - validator_indexes: Vec, + validator_indexes: ValidatorIndexes, // Timestamp of when the latest slot was received latest_slot_timestamp: Instant, // The latest slot received @@ -59,15 +60,15 @@ impl ConsensusState { /// Create a new `ConsensusState` with the given configuration. pub fn new( beacon_api_client: BeaconClient, - validator_indexes: &[u64], + validator_indexes: ValidatorIndexes, commitment_deadline_duration: Duration, ) -> Self { ConsensusState { beacon_api_client, + validator_indexes, header: BeaconBlockHeader::default(), epoch: Epoch::default(), latest_slot: Default::default(), - validator_indexes: validator_indexes.to_vec(), latest_slot_timestamp: Instant::now(), commitment_deadline: CommitmentDeadline::new(0, commitment_deadline_duration), commitment_deadline_duration, @@ -147,10 +148,7 @@ impl ConsensusState { .proposer_duties .iter() .find(|&duty| { - duty.slot == slot - && self - .validator_indexes - .contains(&(duty.validator_index as u64)) + duty.slot == slot && self.validator_indexes.contains(duty.validator_index as u64) }) .map(|duty| duty.validator_index as u64) .ok_or(ConsensusError::ValidatorNotFound) @@ -185,7 +183,7 @@ mod tests { ]; // Validator indexes that we are interested in - let validator_indexes = vec![100, 102]; + let validator_indexes = ValidatorIndexes::from(vec![100, 102]); // Create a ConsensusState with the sample proposer duties and validator indexes let state = ConsensusState {