diff --git a/Cargo.lock b/Cargo.lock index 3258997..9a546c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -177,6 +177,7 @@ version = "0.1.0" dependencies = [ "dashmap", "processor", + "signature_collector", "ssv_types", "tracing", "types", @@ -564,7 +565,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-util", "itoa", "matchit", @@ -684,7 +685,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -884,9 +885,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" +checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" dependencies = [ "jobserver", "libc", @@ -1053,13 +1054,15 @@ dependencies = [ "fdlimit", "http_api", "http_metrics", - "hyper 1.5.1", + "hyper 1.5.2", "network", "parking_lot", "processor", "sensitive_url", "serde", + "signature_collector", "slot_clock", + "ssv_types", "strum", "task_executor", "tokio", @@ -1243,18 +1246,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1271,9 +1274,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" @@ -2143,6 +2146,17 @@ dependencies = [ "bytes", ] +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + [[package]] name = "fdlimit" version = "0.3.0" @@ -2355,7 +2369,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.23.19", + "rustls 0.23.20", "rustls-pki-types", ] @@ -2840,9 +2854,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.31" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", @@ -2864,9 +2878,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" dependencies = [ "bytes", "futures-channel", @@ -2889,7 +2903,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.31", + "hyper 0.14.32", "rustls 0.21.12", "tokio", "tokio-rustls", @@ -2902,7 +2916,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.31", + "hyper 0.14.32", "native-tls", "tokio", "tokio-native-tls", @@ -2918,7 +2932,7 @@ dependencies = [ "futures-util", "http 1.2.0", "http-body 1.0.1", - "hyper 1.5.1", + "hyper 1.5.2", "pin-project-lite", "tokio", "tower-service", @@ -3136,7 +3150,7 @@ dependencies = [ "bytes", "futures", "http 0.2.12", - "hyper 0.14.31", + "hyper 0.14.32", "log", "rand", "tokio", @@ -3266,6 +3280,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -3427,7 +3450,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3749,7 +3772,7 @@ dependencies = [ "quinn", "rand", "ring 0.17.8", - "rustls 0.23.19", + "rustls 0.23.20", "socket2", "thiserror 1.0.69", "tokio", @@ -3821,7 +3844,7 @@ dependencies = [ "libp2p-identity", "rcgen", "ring 0.17.8", - "rustls 0.23.19", + "rustls 0.23.20", "rustls-webpki 0.101.7", "thiserror 1.0.69", "x509-parser", @@ -4210,9 +4233,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", ] @@ -4751,7 +4774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 2.0.6", + "thiserror 2.0.8", "ucd-trie", ] @@ -5008,9 +5031,9 @@ dependencies = [ [[package]] name = "proptest-derive" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" +checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", @@ -5107,9 +5130,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.1.0", - "rustls 0.23.19", + "rustls 0.23.20", "socket2", - "thiserror 2.0.6", + "thiserror 2.0.8", "tokio", "tracing", ] @@ -5125,10 +5148,10 @@ dependencies = [ "rand", "ring 0.17.8", "rustc-hash 2.1.0", - "rustls 0.23.19", + "rustls 0.23.20", "rustls-pki-types", "slab", - "thiserror 2.0.6", + "thiserror 2.0.8", "tinyvec", "tracing", "web-time", @@ -5136,9 +5159,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52cd4b1eff68bf27940dd39811292c49e007f4d0b4c357358dc9b0197be6b527" +checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" dependencies = [ "cfg_aliases", "libc", @@ -5258,9 +5281,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -5334,7 +5357,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.31", + "hyper 0.14.32", "hyper-rustls", "hyper-tls", "ipnet", @@ -5461,17 +5484,19 @@ dependencies = [ [[package]] name = "ruint" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" +checksum = "f5ef8fb1dd8de3870cb8400d51b4c2023854bbafd5431a3ac7e7317243e22d2f" dependencies = [ "alloy-rlp", "arbitrary", "ark-ff 0.3.0", "ark-ff 0.4.2", "bytes", - "fastrlp", + "fastrlp 0.3.1", + "fastrlp 0.4.0", "num-bigint", + "num-integer", "num-traits", "parity-scale-codec", "primitive-types", @@ -5557,7 +5582,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.23", + "semver 1.0.24", ] [[package]] @@ -5610,9 +5635,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.19" +version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ "once_cell", "ring 0.17.8", @@ -5633,9 +5658,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" dependencies = [ "web-time", ] @@ -5785,9 +5810,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" dependencies = [ "core-foundation-sys", "libc", @@ -5804,9 +5829,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" [[package]] name = "semver-parser" @@ -6540,11 +6565,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.6" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" dependencies = [ - "thiserror-impl 2.0.6", + "thiserror-impl 2.0.8", ] [[package]] @@ -6560,9 +6585,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.6" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" dependencies = [ "proc-macro2", "quote", diff --git a/anchor/client/Cargo.toml b/anchor/client/Cargo.toml index 2e35697..d0da391 100644 --- a/anchor/client/Cargo.toml +++ b/anchor/client/Cargo.toml @@ -25,7 +25,9 @@ parking_lot = { workspace = true } processor = { workspace = true } sensitive_url = { workspace = true } serde = { workspace = true } +signature_collector = { workspace = true } slot_clock = { workspace = true } +ssv_types = { workspace = true } strum = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } diff --git a/anchor/client/src/lib.rs b/anchor/client/src/lib.rs index 8cdf5b4..ac65d71 100644 --- a/anchor/client/src/lib.rs +++ b/anchor/client/src/lib.rs @@ -15,7 +15,9 @@ use eth2_config::Eth2Config; use network::Network; use parking_lot::RwLock; use sensitive_url::SensitiveUrl; +use signature_collector::SignatureCollectorManager; use slot_clock::{SlotClock, SystemTimeSlotClock}; +use ssv_types::OperatorId; use std::fs::File; use std::io::Read; use std::net::SocketAddr; @@ -90,6 +92,10 @@ impl Client { // Start the processor let processor_senders = processor::spawn(config.processor, executor.clone()); + // Create the processor-adjacent managers + let signature_collector = + Arc::new(SignatureCollectorManager::new(processor_senders.clone())); + // Optionally start the metrics server. let http_metrics_shared_state = if config.http_metrics.enabled { let shared_state = Arc::new(RwLock::new(http_metrics::Shared { genesis_time: None })); @@ -243,7 +249,7 @@ impl Client { ); // Perform some potentially long-running initialization tasks. - let (genesis_time, _genesis_validators_root) = tokio::select! { + let (genesis_time, genesis_validators_root) = tokio::select! { tuple = init_from_beacon_node(&beacon_nodes, &proposer_nodes) => tuple?, () = executor.exit() => return Err("Shutting down".to_string()) }; @@ -263,7 +269,13 @@ impl Client { let proposer_nodes = Arc::new(proposer_nodes); start_fallback_updater_service(executor.clone(), proposer_nodes.clone())?; - let validator_store = Arc::new(AnchorValidatorStore::new(processor_senders)); + let validator_store = Arc::new(AnchorValidatorStore::new( + processor_senders, + signature_collector, + spec.clone(), + genesis_validators_root, + OperatorId(123), + )); let duties_service = Arc::new( DutiesServiceBuilder::new() diff --git a/anchor/validator_store/Cargo.toml b/anchor/validator_store/Cargo.toml index ee14ed8..b9c929d 100644 --- a/anchor/validator_store/Cargo.toml +++ b/anchor/validator_store/Cargo.toml @@ -8,6 +8,7 @@ rust-version = "1.81.0" [dependencies] dashmap = "6.1.0" processor = { workspace = true } +signature_collector = { workspace = true } ssv_types = { workspace = true } tracing = { workspace = true } types = { workspace = true } diff --git a/anchor/validator_store/src/lib.rs b/anchor/validator_store/src/lib.rs index 9cd70b2..e1c4a45 100644 --- a/anchor/validator_store/src/lib.rs +++ b/anchor/validator_store/src/lib.rs @@ -1,5 +1,8 @@ use dashmap::DashMap; +use signature_collector::{CollectionError, SignatureCollectorManager, SignatureRequest}; +use ssv_types::{Cluster, OperatorId}; use std::marker::PhantomData; +use std::sync::Arc; use tracing::{error, warn}; use types::attestation::Attestation; use types::beacon_block::BeaconBlock; @@ -19,31 +22,80 @@ use types::validator_registration_data::{ SignedValidatorRegistrationData, ValidatorRegistrationData, }; use types::voluntary_exit::VoluntaryExit; -use types::{Address, EthSpec, PublicKeyBytes, Signature}; +use types::{ + Address, ChainSpec, Domain, EthSpec, Hash256, PublicKeyBytes, SecretKey, Signature, SignedRoot, +}; use validator_store::{ DoppelgangerStatus, Error as ValidatorStoreError, ProposalData, ValidatorStore, }; +struct InitializedCluster { + cluster: Cluster, + decrypted_key_share: SecretKey, +} + pub struct AnchorValidatorStore { - validators: DashMap, - _processor: processor::Senders, + clusters: DashMap, + signature_collector: Arc, + spec: Arc, + genesis_validators_root: Hash256, + operator_id: OperatorId, _phantom: PhantomData, } impl AnchorValidatorStore { - pub fn new(processor: processor::Senders) -> AnchorValidatorStore { + pub fn new( + _processor: processor::Senders, + signature_collector: Arc, + spec: Arc, + genesis_validators_root: Hash256, + operator_id: OperatorId, + ) -> AnchorValidatorStore { Self { - validators: DashMap::new(), - _processor: processor, + clusters: DashMap::new(), + signature_collector, + spec, + genesis_validators_root, + operator_id, _phantom: PhantomData, } } + + async fn collect_signature( + &self, + validator_pubkey: PublicKeyBytes, + signing_root: Hash256, + ) -> Result { + let Some(cluster) = self.clusters.get(&validator_pubkey) else { + return Err(Error::UnknownPubkey(validator_pubkey)); + }; + + let collector = self.signature_collector.sign_and_collect( + SignatureRequest { + cluster_id: cluster.cluster.cluster_id, + signing_root, + threshold: cluster.cluster.cluster_members.len() as u64 - cluster.cluster.faulty, + }, + self.operator_id, + cluster.decrypted_key_share.clone(), + ); + + // free lock before invoking future + drop(cluster); + Ok((*collector.await.map_err(SpecificError::from)?).clone()) + } } #[derive(Debug)] pub enum SpecificError { ExitsUnsupported, - SigningTimeout, + SignatureCollectionFailed(CollectionError), +} + +impl From for SpecificError { + fn from(err: CollectionError) -> SpecificError { + SpecificError::SignatureCollectionFailed(err) + } } pub type Error = ValidatorStoreError; @@ -52,9 +104,9 @@ impl ValidatorStore for AnchorValidatorStore { type Error = SpecificError; fn validator_index(&self, pubkey: &PublicKeyBytes) -> Option { - self.validators + self.clusters .get(pubkey) - .map(|v| v.validator_metadata.validator_index.0 as u64) + .map(|v| v.cluster.validator_metadata.validator_index.0 as u64) } fn voting_pubkeys(&self, _filter_func: F) -> I @@ -63,27 +115,28 @@ impl ValidatorStore for AnchorValidatorStore { F: Fn(DoppelgangerStatus) -> Option, { // we don't care about doppelgangers - self.validators.iter().map(|v| *v.key()).collect() + self.clusters.iter().map(|v| *v.key()).collect() } fn doppelganger_protection_allows_signing(&self, _validator_pubkey: PublicKeyBytes) -> bool { + // we don't care about doppelgangers true } fn num_voting_validators(&self) -> usize { - self.validators.len() + self.clusters.len() } fn graffiti(&self, validator_pubkey: &PublicKeyBytes) -> Option { - self.validators + self.clusters .get(validator_pubkey) - .map(|v| v.validator_metadata.graffiti) + .map(|v| v.cluster.validator_metadata.graffiti) } fn get_fee_recipient(&self, validator_pubkey: &PublicKeyBytes) -> Option
{ - self.validators + self.clusters .get(validator_pubkey) - .map(|v| v.validator_metadata.fee_recipient) + .map(|v| v.cluster.validator_metadata.fee_recipient) } fn determine_builder_boost_factor(&self, _validator_pubkey: &PublicKeyBytes) -> Option { @@ -92,24 +145,31 @@ impl ValidatorStore for AnchorValidatorStore { async fn randao_reveal( &self, - _validator_pubkey: PublicKeyBytes, - _signing_epoch: Epoch, + validator_pubkey: PublicKeyBytes, + signing_epoch: Epoch, ) -> Result { - todo!() + let domain = self.spec.get_domain( + signing_epoch, + Domain::Randao, + &self.spec.fork_at_epoch(signing_epoch), + self.genesis_validators_root, + ); + let signing_root = signing_epoch.signing_root(domain); + self.collect_signature(validator_pubkey, signing_root).await } fn set_validator_index(&self, validator_pubkey: &PublicKeyBytes, index: u64) { // we actually have the index already. we use the opportunity to do a sanity check - match self.validators.get(validator_pubkey) { + match self.clusters.get(validator_pubkey) { None => warn!( validator = validator_pubkey.as_hex_string(), "Trying to set index for unknown validator" ), Some(v) => { - if v.validator_metadata.validator_index.0 as u64 != index { + if v.cluster.validator_metadata.validator_index.0 as u64 != index { error!( validator = validator_pubkey.as_hex_string(), - expected = v.validator_metadata.validator_index.0, + expected = v.cluster.validator_metadata.validator_index.0, actual = index, "Mismatched validator index", ) @@ -205,9 +265,9 @@ impl ValidatorStore for AnchorValidatorStore { } fn proposal_data(&self, pubkey: &PublicKeyBytes) -> Option { - self.validators.get(pubkey).map(|v| ProposalData { - validator_index: Some(v.validator_metadata.validator_index.0 as u64), - fee_recipient: Some(v.validator_metadata.fee_recipient), + self.clusters.get(pubkey).map(|v| ProposalData { + validator_index: Some(v.cluster.validator_metadata.validator_index.0 as u64), + fee_recipient: Some(v.cluster.validator_metadata.fee_recipient), gas_limit: 30_000_000, // TODO support scalooors builder_proposals: false, // TODO support MEVooors })