From b6e35bc94d7d3c2936915b8fa24ad3af2c573c51 Mon Sep 17 00:00:00 2001 From: frisitano Date: Tue, 26 May 2026 23:41:57 +0100 Subject: [PATCH 1/4] Add optional execution proofs Implements EIP-8025 proof types, proof engine integration, proof gossip/RPC, and proof sync over the updated unstable branch. --- Cargo.lock | 5 + beacon_node/beacon_chain/src/beacon_chain.rs | 6 + beacon_node/beacon_chain/src/builder.rs | 5 + beacon_node/beacon_chain/src/chain_config.rs | 32 +- beacon_node/beacon_chain/src/eip8025/mod.rs | 18 + .../beacon_chain/src/eip8025/proof_status.rs | 614 ++++++++++++++++++ .../src/eip8025/proof_verification.rs | 466 +++++++++++++ beacon_node/beacon_chain/src/errors.rs | 3 + beacon_node/beacon_chain/src/lib.rs | 2 + .../src/observed_execution_proofs.rs | 284 ++++++++ .../payload_envelope_verification/import.rs | 8 + .../payload_notifier.rs | 43 +- beacon_node/beacon_chain/src/test_utils.rs | 4 +- beacon_node/execution_layer/Cargo.toml | 5 + .../execution_layer/src/eip8025/errors.rs | 92 +++ .../execution_layer/src/eip8025/mod.rs | 17 + .../src/eip8025/proof_engine.rs | 72 ++ .../src/eip8025/proof_node_client.rs | 197 ++++++ .../execution_layer/src/eip8025/types.rs | 202 ++++++ .../src/engine_api/new_payload_request.rs | 26 +- beacon_node/execution_layer/src/lib.rs | 29 + beacon_node/http_api/src/beacon/pool.rs | 111 +++- beacon_node/http_api/src/lib.rs | 5 + beacon_node/lighthouse_network/src/config.rs | 4 + .../lighthouse_network/src/discovery/enr.rs | 16 + .../src/peer_manager/mod.rs | 9 + .../lighthouse_network/src/rpc/codec.rs | 41 +- .../lighthouse_network/src/rpc/config.rs | 42 ++ .../lighthouse_network/src/rpc/methods.rs | 168 +++++ beacon_node/lighthouse_network/src/rpc/mod.rs | 5 + .../lighthouse_network/src/rpc/protocol.rs | 106 ++- .../src/rpc/rate_limiter.rs | 51 ++ .../src/service/api_types.rs | 54 +- .../src/service/gossip_cache.rs | 1 + .../lighthouse_network/src/service/mod.rs | 53 ++ .../lighthouse_network/src/types/globals.rs | 1 + .../lighthouse_network/src/types/pubsub.rs | 21 +- .../lighthouse_network/src/types/topics.rs | 13 + .../gossip_methods.rs | 119 +++- .../src/network_beacon_processor/mod.rs | 126 +++- .../network_beacon_processor/rpc_methods.rs | 260 +++++++- beacon_node/network/src/router.rs | 112 +++- beacon_node/network/src/sync/manager.rs | 103 ++- beacon_node/network/src/sync/mod.rs | 1 + .../network/src/sync/network_context.rs | 192 +++++- beacon_node/network/src/sync/proof_sync.rs | 452 +++++++++++++ beacon_node/src/cli.rs | 27 + beacon_node/src/config.rs | 31 + consensus/types/src/execution/eip8025.rs | 354 ++++++++++ consensus/types/src/execution/mod.rs | 6 + 50 files changed, 4582 insertions(+), 32 deletions(-) create mode 100644 beacon_node/beacon_chain/src/eip8025/mod.rs create mode 100644 beacon_node/beacon_chain/src/eip8025/proof_status.rs create mode 100644 beacon_node/beacon_chain/src/eip8025/proof_verification.rs create mode 100644 beacon_node/beacon_chain/src/observed_execution_proofs.rs create mode 100644 beacon_node/execution_layer/src/eip8025/errors.rs create mode 100644 beacon_node/execution_layer/src/eip8025/mod.rs create mode 100644 beacon_node/execution_layer/src/eip8025/proof_engine.rs create mode 100644 beacon_node/execution_layer/src/eip8025/proof_node_client.rs create mode 100644 beacon_node/execution_layer/src/eip8025/types.rs create mode 100644 beacon_node/network/src/sync/proof_sync.rs create mode 100644 consensus/types/src/execution/eip8025.rs diff --git a/Cargo.lock b/Cargo.lock index 129be32fcdd..c2e384e7d7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3386,14 +3386,18 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types-eth", "arc-swap", + "async-stream", + "async-trait", "bls", "builder_client", "bytes", "eth2", "ethereum_serde_utils", "ethereum_ssz", + "ethereum_ssz_derive", "fixed_bytes", "fork_choice", + "futures", "hash-db", "hash256-std-hasher", "hex", @@ -3408,6 +3412,7 @@ dependencies = [ "pretty_reqwest_error", "rand 0.9.2", "reqwest", + "reqwest-eventsource", "sensitive_url", "serde", "serde_json", diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index b3d258a2fb0..9cd5cdc4029 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -31,6 +31,7 @@ use crate::data_column_verification::{ validate_partial_data_column_sidecar_for_gossip, }; use crate::early_attester_cache::EarlyAttesterCache; +use crate::eip8025::ExecutionProofStatusCache; use crate::envelope_times_cache::EnvelopeTimesCache; use crate::errors::{BeaconChainError as Error, BlockProductionError}; use crate::events::ServerSentEventHandler; @@ -59,6 +60,7 @@ use crate::observed_attesters::{ }; use crate::observed_block_producers::ObservedBlockProducers; use crate::observed_data_sidecars::ObservedDataSidecars; +use crate::observed_execution_proofs::ObservedExecutionProofs; use crate::observed_operations::{ObservationOutcome, ObservedOperations}; use crate::observed_slashable::ObservedSlashable; use crate::partial_data_column_assembler::PartialMergeResult; @@ -436,6 +438,10 @@ pub struct BeaconChain { /// Maintains a record of column sidecars seen over the gossip network. pub observed_column_sidecars: RwLock, T::EthSpec>>, + /// Maintains proof-gossip deduplication state without storing proof bytes. + pub observed_execution_proofs: RwLock, + /// Maintains EIP-8025 proof-status metadata and bounded request-root mappings. + pub execution_proof_statuses: RwLock, /// Maintains a record of slashable message seen over the gossip network or RPC. pub observed_slashable: RwLock>, /// Cache of pending execution payload envelopes for local block building. diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index b8da2bcdedc..d09efb36e5b 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -21,6 +21,9 @@ use crate::validator_pubkey_cache::ValidatorPubkeyCache; use crate::{ BeaconChain, BeaconChainTypes, BeaconForkChoiceStore, BeaconSnapshot, ServerSentEventHandler, }; +use crate::{ + eip8025::ExecutionProofStatusCache, observed_execution_proofs::ObservedExecutionProofs, +}; use bls::Signature; use execution_layer::ExecutionLayer; use fixed_bytes::FixedBytesExtended; @@ -1008,6 +1011,8 @@ where observed_block_producers: <_>::default(), observed_column_sidecars: RwLock::new(ObservedDataSidecars::new(self.spec.clone())), observed_blob_sidecars: RwLock::new(ObservedDataSidecars::new(self.spec.clone())), + observed_execution_proofs: RwLock::new(ObservedExecutionProofs::default()), + execution_proof_statuses: RwLock::new(ExecutionProofStatusCache::default()), observed_slashable: <_>::default(), pending_payload_envelopes: <_>::default(), observed_voluntary_exits: <_>::default(), diff --git a/beacon_node/beacon_chain/src/chain_config.rs b/beacon_node/beacon_chain/src/chain_config.rs index dde09bf1057..81f8fc0a83f 100644 --- a/beacon_node/beacon_chain/src/chain_config.rs +++ b/beacon_node/beacon_chain/src/chain_config.rs @@ -3,7 +3,7 @@ pub use proto_array::DisallowedReOrgOffsets; use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::{collections::HashSet, sync::LazyLock, time::Duration}; -use types::{Checkpoint, Hash256}; +use types::{Checkpoint, Hash256, MIN_REQUIRED_EXECUTION_PROOFS}; pub const DEFAULT_FORK_CHOICE_BEFORE_PROPOSAL_TIMEOUT: u64 = 250; @@ -114,6 +114,35 @@ pub struct ChainConfig { pub node_custody_type: NodeCustodyType, /// Disable proposer re-org pub disable_proposer_reorg: bool, + /// Non-default EIP-8025 proof quorum configuration. + /// + /// When disabled, valid execution proofs are tracked as proof metadata only and never change + /// payload/fork-choice validity. + #[serde(default)] + pub execution_proof_quorum: ExecutionProofQuorumConfig, +} + +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +pub struct ExecutionProofQuorumConfig { + /// Allow proof validity to mark a Gloas payload envelope as received in fork choice. + pub enabled: bool, + /// Required number of distinct valid proof types for the same new-payload request root. + pub min_valid_proof_types: usize, +} + +impl Default for ExecutionProofQuorumConfig { + fn default() -> Self { + Self { + enabled: false, + min_valid_proof_types: MIN_REQUIRED_EXECUTION_PROOFS, + } + } +} + +impl ExecutionProofQuorumConfig { + pub fn threshold(&self) -> Option { + (self.enabled && self.min_valid_proof_types > 0).then_some(self.min_valid_proof_types) + } } impl Default for ChainConfig { @@ -154,6 +183,7 @@ impl Default for ChainConfig { enable_partial_columns: false, node_custody_type: NodeCustodyType::Fullnode, disable_proposer_reorg: false, + execution_proof_quorum: ExecutionProofQuorumConfig::default(), } } } diff --git a/beacon_node/beacon_chain/src/eip8025/mod.rs b/beacon_node/beacon_chain/src/eip8025/mod.rs new file mode 100644 index 00000000000..23ed5c16691 --- /dev/null +++ b/beacon_node/beacon_chain/src/eip8025/mod.rs @@ -0,0 +1,18 @@ +//! EIP-8025: Optional Execution Proofs +//! +//! This module provides beacon chain integration for EIP-8025 optional execution proofs. +//! It includes: +//! - Proof verification logic using validator signatures +//! - TODO: integrate into proof engine + +pub mod proof_status; +pub mod proof_verification; + +pub use proof_status::{ + ExecutionProofBlockStatus, ExecutionProofObservation, ExecutionProofStatusCache, + ExecutionProofStatusSummary, MissingExecutionProofInfo, +}; +pub use proof_verification::{ + ExecutionProofError, compute_execution_proof_domain, compute_signing_root, + verify_signed_execution_proof_signature, +}; diff --git a/beacon_node/beacon_chain/src/eip8025/proof_status.rs b/beacon_node/beacon_chain/src/eip8025/proof_status.rs new file mode 100644 index 00000000000..2f7af8e9a35 --- /dev/null +++ b/beacon_node/beacon_chain/src/eip8025/proof_status.rs @@ -0,0 +1,614 @@ +use super::proof_verification::{ExecutionProofError, verify_signed_execution_proof_signature}; +use crate::observed_execution_proofs::ProofObservation; +use crate::{BeaconChain, BeaconChainError, BeaconChainTypes, ForkChoiceError}; +use execution_layer::{NewPayloadRequest, NewPayloadRequestGloas}; +use lru::LruCache; +use state_processing::per_block_processing::deneb::kzg_commitment_to_versioned_hash; +use std::collections::{HashMap, HashSet}; +use std::num::NonZeroUsize; +use std::sync::Arc; +use types::{ + Hash256, ProofStatus, ProofType, SignedBlindedBeaconBlock, SignedExecutionPayloadEnvelope, + SignedExecutionProof, Slot, +}; + +const DEFAULT_REQUEST_ROOT_CACHE_SIZE: usize = 8192; +const DEFAULT_PROOF_CACHE_SIZE: usize = 8192; + +/// Proof metadata for one beacon block / `engine_newPayload` request. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ExecutionProofBlockStatus { + pub block_root: Hash256, + pub request_root: Hash256, + pub slot: Slot, + valid_proof_types: HashSet, +} + +impl ExecutionProofBlockStatus { + fn new(block_root: Hash256, request_root: Hash256, slot: Slot) -> Self { + Self { + block_root, + request_root, + slot, + valid_proof_types: HashSet::new(), + } + } + + pub fn valid_proof_type_count(&self) -> usize { + self.valid_proof_types.len() + } + + pub fn valid_proof_types(&self) -> impl Iterator + '_ { + self.valid_proof_types.iter().copied() + } +} + +/// Bounded request-root ingress cache plus proof-status metadata. +/// +/// This deliberately stores proof status only. Unfinalized proof bytes remain hot/prunable and are +/// not durably tracked here. +#[derive(Debug)] +pub struct ExecutionProofStatusCache { + request_root_to_block_root: LruCache, + block_root_to_request_root: LruCache, + proofs_by_block_and_type: LruCache<(Hash256, ProofType), Arc>, + statuses_by_block_root: HashMap, +} + +impl Default for ExecutionProofStatusCache { + fn default() -> Self { + let request_root_capacity = NonZeroUsize::new(DEFAULT_REQUEST_ROOT_CACHE_SIZE) + .expect("default request-root cache size is non-zero"); + let proof_capacity = NonZeroUsize::new(DEFAULT_PROOF_CACHE_SIZE) + .expect("default proof cache size is non-zero"); + Self { + request_root_to_block_root: LruCache::new(request_root_capacity), + block_root_to_request_root: LruCache::new(request_root_capacity), + proofs_by_block_and_type: LruCache::new(proof_capacity), + statuses_by_block_root: HashMap::new(), + } + } +} + +impl ExecutionProofStatusCache { + pub fn register_request_root( + &mut self, + block_root: Hash256, + request_root: Hash256, + slot: Slot, + ) { + self.request_root_to_block_root + .put(request_root, block_root); + self.block_root_to_request_root + .put(block_root, request_root); + self.statuses_by_block_root + .entry(block_root) + .or_insert_with(|| ExecutionProofBlockStatus::new(block_root, request_root, slot)); + } + + pub fn block_root_for_request_root(&self, request_root: &Hash256) -> Option { + self.request_root_to_block_root.peek(request_root).copied() + } + + pub fn request_root_for_block_root(&self, block_root: &Hash256) -> Option { + self.block_root_to_request_root.peek(block_root).copied() + } + + pub fn observe_valid_proof( + &mut self, + block_root: Hash256, + request_root: Hash256, + slot: Slot, + proof: Arc, + ) -> ExecutionProofStatusSummary { + let proof_type = proof.proof_type(); + self.register_request_root(block_root, request_root, slot); + let status = self + .statuses_by_block_root + .entry(block_root) + .or_insert_with(|| ExecutionProofBlockStatus::new(block_root, request_root, slot)); + let newly_observed = status.valid_proof_types.insert(proof_type); + self.proofs_by_block_and_type + .put((block_root, proof_type), proof); + + ExecutionProofStatusSummary { + block_root, + request_root, + slot, + newly_observed, + valid_proof_type_count: status.valid_proof_type_count(), + } + } + + pub fn status_by_block_root(&self, block_root: &Hash256) -> Option<&ExecutionProofBlockStatus> { + self.statuses_by_block_root.get(block_root) + } + + pub fn latest_status_with_valid_proofs( + &self, + configured_proof_types: &[ProofType], + ) -> Option { + self.statuses_by_block_root + .values() + .filter(|status| { + configured_proof_types + .iter() + .any(|proof_type| status.valid_proof_types.contains(proof_type)) + }) + .max_by_key(|status| status.slot) + .cloned() + } + + pub fn proof_by_block_root_and_type( + &mut self, + block_root: Hash256, + proof_type: ProofType, + ) -> Option> { + self.proofs_by_block_and_type + .get(&(block_root, proof_type)) + .cloned() + } + + pub fn missing_execution_proofs( + &self, + configured_proof_types: &[ProofType], + ) -> Vec { + self.statuses_by_block_root + .values() + .filter_map(|status| { + let missing_any = configured_proof_types + .iter() + .any(|proof_type| !status.valid_proof_types.contains(proof_type)); + missing_any.then(|| MissingExecutionProofInfo { + root: status.block_root, + slot: status.slot, + existing_proof_types: status.valid_proof_types.clone(), + }) + }) + .collect() + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ExecutionProofStatusSummary { + pub block_root: Hash256, + pub request_root: Hash256, + pub slot: Slot, + pub newly_observed: bool, + pub valid_proof_type_count: usize, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MissingExecutionProofInfo { + pub root: Hash256, + pub slot: Slot, + pub existing_proof_types: HashSet, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ExecutionProofObservation { + pub status: ProofStatus, + pub block_root: Option, + pub request_root: Hash256, + pub valid_proof_type_count: usize, + pub quorum_threshold: Option, + pub proof_backed_payload_promotion: bool, +} + +impl ExecutionProofObservation { + fn syncing(request_root: Hash256, quorum_threshold: Option) -> Self { + Self { + status: ProofStatus::Syncing, + block_root: None, + request_root, + valid_proof_type_count: 0, + quorum_threshold, + proof_backed_payload_promotion: false, + } + } +} + +impl BeaconChain { + /// Compute and cache the EIP-8025 new-payload request root for a known Gloas block root. + pub fn register_execution_payload_request_root( + &self, + block_root: Hash256, + ) -> Result { + let (request_root, slot) = self.execution_payload_request_context(block_root)?; + self.execution_proof_statuses + .write() + .register_request_root(block_root, request_root, slot); + Ok(request_root) + } + + /// Return the cached block root for an EIP-8025 new-payload request root. + pub fn block_root_for_execution_proof_request( + &self, + request_root: &Hash256, + ) -> Option { + self.execution_proof_statuses + .read() + .block_root_for_request_root(request_root) + } + + /// Record one externally-validated proof and optionally apply the non-default proof quorum. + /// + /// This function assumes BLS signature checks and proof-engine verification have already + /// succeeded. Invalid proofs must not call this path. + pub fn observe_valid_execution_proof( + &self, + proof: &SignedExecutionProof, + block_root_hint: Option, + ) -> Result { + let request_root = proof.request_root(); + let quorum_threshold = self.config.execution_proof_quorum.threshold(); + + let Some((block_root, slot)) = + self.resolve_execution_proof_block_root(request_root, block_root_hint)? + else { + return Ok(ExecutionProofObservation::syncing( + request_root, + quorum_threshold, + )); + }; + + let summary = self.execution_proof_statuses.write().observe_valid_proof( + block_root, + request_root, + slot, + Arc::new(proof.clone()), + ); + + self.observed_execution_proofs.write().observe_valid_proof( + request_root, + proof.proof_type(), + slot, + ); + + let proof_backed_payload_promotion = if quorum_threshold + .is_some_and(|threshold| summary.valid_proof_type_count >= threshold) + { + self.try_mark_payload_envelope_proof_valid(block_root)? + } else { + false + }; + + Ok(ExecutionProofObservation { + status: if proof_backed_payload_promotion { + ProofStatus::Valid + } else { + ProofStatus::Accepted + }, + block_root: Some(block_root), + request_root, + valid_proof_type_count: summary.valid_proof_type_count, + quorum_threshold, + proof_backed_payload_promotion, + }) + } + + /// Verify a signed execution proof and record proof metadata if it is valid. + /// + /// This path keeps proof validity optional: invalid proofs never invalidate the payload and + /// valid proofs only affect fork choice when `execution_proof_quorum` is explicitly enabled. + pub async fn verify_and_observe_execution_proof( + &self, + proof: &SignedExecutionProof, + block_root_hint: Option, + ) -> Result { + let request_root = proof.request_root(); + let proof_type = proof.proof_type(); + let quorum_threshold = self.config.execution_proof_quorum.threshold(); + + match self.observed_execution_proofs.read().check( + request_root, + proof_type, + proof.proof_data(), + proof.validator_index(), + ) { + ProofObservation::AlreadyRejectedProof => { + return Ok(ExecutionProofObservation { + status: ProofStatus::Invalid, + block_root: None, + request_root, + valid_proof_type_count: 0, + quorum_threshold, + proof_backed_payload_promotion: false, + }); + } + ProofObservation::AlreadyHaveValidProof | ProofObservation::DuplicateFromValidator => { + return Ok(ExecutionProofObservation { + status: ProofStatus::Accepted, + block_root: self.block_root_for_execution_proof_request(&request_root), + request_root, + valid_proof_type_count: 0, + quorum_threshold, + proof_backed_payload_promotion: false, + }); + } + ProofObservation::New => {} + } + + let Some((_, slot)) = + self.resolve_execution_proof_block_root(request_root, block_root_hint)? + else { + return Ok(ExecutionProofObservation::syncing( + request_root, + quorum_threshold, + )); + }; + + self.observed_execution_proofs + .write() + .observe_verification_attempt(request_root, proof_type, proof.validator_index()); + + let validator_index = usize::try_from(proof.validator_index()) + .map_err(|_| ExecutionProofError::InvalidValidatorIndex)?; + let validator_pubkey = self + .validator_pubkey_bytes(validator_index)? + .ok_or(ExecutionProofError::InvalidValidatorIndex)?; + let fork_name = self.spec.fork_name_at_slot::(slot); + + verify_signed_execution_proof_signature::( + proof, + &validator_pubkey, + fork_name, + self.genesis_validators_root, + &self.spec, + )?; + + let proof_engine = self + .execution_layer + .as_ref() + .and_then(|execution_layer| execution_layer.proof_engine()) + .ok_or(ExecutionProofError::NoExecutionLayer)?; + + match proof_engine.verify_execution_proof(proof).await? { + ProofStatus::Valid => self.observe_valid_execution_proof(proof, block_root_hint), + ProofStatus::Invalid => { + self.observed_execution_proofs + .write() + .observe_invalid_proof(proof_type, proof.proof_data()); + Ok(ExecutionProofObservation { + status: ProofStatus::Invalid, + block_root: self.block_root_for_execution_proof_request(&request_root), + request_root, + valid_proof_type_count: 0, + quorum_threshold, + proof_backed_payload_promotion: false, + }) + } + status => Ok(ExecutionProofObservation { + status, + block_root: self.block_root_for_execution_proof_request(&request_root), + request_root, + valid_proof_type_count: 0, + quorum_threshold, + proof_backed_payload_promotion: false, + }), + } + } + + fn resolve_execution_proof_block_root( + &self, + request_root: Hash256, + block_root_hint: Option, + ) -> Result, BeaconChainError> { + if let Some(block_root) = block_root_hint { + let (computed_request_root, slot) = + self.execution_payload_request_context(block_root)?; + if computed_request_root != request_root { + return Err(BeaconChainError::ExecutionProofError( + super::proof_verification::ExecutionProofError::UnknownRequestRoot( + request_root, + ), + )); + } + self.execution_proof_statuses.write().register_request_root( + block_root, + request_root, + slot, + ); + return Ok(Some((block_root, slot))); + } + + let Some(block_root) = self + .execution_proof_statuses + .read() + .block_root_for_request_root(&request_root) + else { + return Ok(None); + }; + + let (_, slot) = self.execution_payload_request_context(block_root)?; + Ok(Some((block_root, slot))) + } + + fn execution_payload_request_context( + &self, + block_root: Hash256, + ) -> Result<(Hash256, Slot), BeaconChainError> { + let block = self + .get_blinded_block(&block_root)? + .ok_or(BeaconChainError::MissingBeaconBlock(block_root))?; + let envelope = self.get_payload_envelope(&block_root)?.ok_or( + BeaconChainError::MissingExecutionPayloadEnvelope(block_root), + )?; + let slot = block.slot(); + let request = build_gloas_new_payload_request(&block, &envelope)?; + + Ok((request.request_root(), slot)) + } + + fn try_mark_payload_envelope_proof_valid( + &self, + block_root: Hash256, + ) -> Result { + if self.get_payload_envelope(&block_root)?.is_none() { + return Ok(false); + } + + let mut fork_choice = self.canonical_head.fork_choice_write_lock(); + fork_choice + .on_valid_payload_envelope_received(block_root) + .map_err(map_fork_choice_error)?; + + Ok(true) + } + + pub fn execution_proof_by_block_root_and_type( + &self, + block_root: Hash256, + proof_type: ProofType, + ) -> Option> { + self.execution_proof_statuses + .write() + .proof_by_block_root_and_type(block_root, proof_type) + } + + pub fn execution_proofs_by_block_root( + &self, + block_root: Hash256, + proof_types: &[ProofType], + ) -> Vec> { + proof_types + .iter() + .filter_map(|proof_type| { + self.execution_proof_by_block_root_and_type(block_root, *proof_type) + }) + .collect() + } + + pub fn execution_proofs_by_range( + &self, + start_slot: Slot, + count: u64, + proof_types: &[ProofType], + ) -> Result>, BeaconChainError> { + let mut proofs = vec![]; + for offset in 0..count { + let Some(slot) = start_slot.as_u64().checked_add(offset).map(Slot::new) else { + break; + }; + let Some(block_root) = self.block_root_at_slot(slot, crate::WhenSlotSkipped::None)? + else { + continue; + }; + proofs.extend(self.execution_proofs_by_block_root(block_root, proof_types)); + } + Ok(proofs) + } + + pub fn missing_execution_proofs( + &self, + proof_types: &[ProofType], + ) -> Vec { + self.execution_proof_statuses + .read() + .missing_execution_proofs(proof_types) + } + + pub fn latest_execution_proof_status( + &self, + proof_types: &[ProofType], + ) -> Option { + self.execution_proof_statuses + .read() + .latest_status_with_valid_proofs(proof_types) + } +} + +fn build_gloas_new_payload_request<'a, E: types::EthSpec>( + block: &'a SignedBlindedBeaconBlock, + envelope: &'a SignedExecutionPayloadEnvelope, +) -> Result, BeaconChainError> { + let bid = &block + .message() + .body() + .signed_execution_payload_bid() + .map_err(BeaconChainError::BeaconStateError)? + .message; + + let versioned_hashes = bid + .blob_kzg_commitments + .iter() + .map(kzg_commitment_to_versioned_hash) + .collect::>() + .try_into() + .map_err(BeaconChainError::SszTypesError)?; + + Ok(NewPayloadRequest::Gloas(NewPayloadRequestGloas { + execution_payload: &envelope.message.payload, + versioned_hashes, + parent_beacon_block_root: envelope.message.parent_beacon_block_root, + execution_requests: &envelope.message.execution_requests, + })) +} + +fn map_fork_choice_error(error: ForkChoiceError) -> BeaconChainError { + BeaconChainError::ForkChoiceError(error) +} + +#[cfg(test)] +mod tests { + use super::*; + use bls::SignatureBytes; + use ssz_types::VariableList; + use types::{ExecutionProof, PublicInput}; + + fn signed_proof(request_root: Hash256, proof_type: ProofType) -> Arc { + Arc::new(SignedExecutionProof { + message: ExecutionProof { + proof_data: VariableList::new(vec![proof_type]).unwrap(), + proof_type, + public_input: PublicInput { + new_payload_request_root: request_root, + }, + }, + validator_index: 0, + signature: SignatureBytes::empty(), + }) + } + + #[test] + fn latest_status_with_valid_proofs_ignores_empty_and_unconfigured_statuses() { + let mut cache = ExecutionProofStatusCache::default(); + let block_root_a = Hash256::repeat_byte(0xaa); + let block_root_b = Hash256::repeat_byte(0xbb); + let block_root_c = Hash256::repeat_byte(0xcc); + let request_root_a = Hash256::repeat_byte(0x0a); + let request_root_b = Hash256::repeat_byte(0x0b); + let request_root_c = Hash256::repeat_byte(0x0c); + + cache.register_request_root(block_root_c, request_root_c, Slot::new(30)); + assert!( + cache.latest_status_with_valid_proofs(&[1]).is_none(), + "request-root-only statuses must not advertise proof availability" + ); + + cache.observe_valid_proof( + block_root_a, + request_root_a, + Slot::new(10), + signed_proof(request_root_a, 1), + ); + cache.observe_valid_proof( + block_root_b, + request_root_b, + Slot::new(20), + signed_proof(request_root_b, 2), + ); + + let status = cache + .latest_status_with_valid_proofs(&[1]) + .expect("configured proof type should be advertised"); + assert_eq!(status.block_root, block_root_a); + assert_eq!(status.slot, Slot::new(10)); + assert_eq!(status.valid_proof_types().collect::>(), vec![1]); + + assert!( + cache.latest_status_with_valid_proofs(&[3]).is_none(), + "unconfigured proof types must not make a peer look useful" + ); + } +} diff --git a/beacon_node/beacon_chain/src/eip8025/proof_verification.rs b/beacon_node/beacon_chain/src/eip8025/proof_verification.rs new file mode 100644 index 00000000000..7dd21bcb121 --- /dev/null +++ b/beacon_node/beacon_chain/src/eip8025/proof_verification.rs @@ -0,0 +1,466 @@ +//! EIP-8025 Proof Verification +//! +//! This module implements the proof verification logic for EIP-8025 optional execution proofs. +//! It provides: +//! - BLS signature verification for validator signatures +//! - Validator index validation against the BeaconState +//! - TODO: integration into proof engine for end-to-end verification + +use crate::BeaconChainError; +use execution_layer::eip8025::ProofEngineError; +use std::fmt; +use tree_hash::TreeHash; +use types::{ + ChainSpec, DOMAIN_EXECUTION_PROOF, EthSpec, ForkName, Hash256, SignedExecutionProof, + SigningData, +}; + +/// Errors that can occur during execution proof verification. +#[derive(Debug)] +pub enum ExecutionProofError { + /// The BLS signature is invalid. + InvalidSignature, + /// The proof data is empty. + EmptyProofData, + /// The validator index is out of range. + InvalidValidatorIndex, + /// Failed to decompress the validator's public key. + InvalidValidatorPubkey, + /// Failed to decompress the signature. + InvalidSignatureFormat, + /// Failed to retrieve beacon state. + StateError(String), + /// No execution layer configured. + NoExecutionLayer, + /// The request root referenced by the proof is not known. + UnknownRequestRoot(Hash256), + /// There was an error in the proof engine during verification. + ProofEngineError(ProofEngineError), +} + +impl fmt::Display for ExecutionProofError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ExecutionProofError::InvalidSignature => { + write!(f, "Invalid BLS signature") + } + ExecutionProofError::EmptyProofData => { + write!(f, "Proof data is empty") + } + ExecutionProofError::InvalidValidatorIndex => { + write!(f, "Validator index out of range") + } + ExecutionProofError::InvalidValidatorPubkey => { + write!(f, "Invalid validator public key format") + } + ExecutionProofError::InvalidSignatureFormat => { + write!(f, "Invalid signature format") + } + ExecutionProofError::StateError(msg) => { + write!(f, "Beacon state error: {}", msg) + } + ExecutionProofError::NoExecutionLayer => { + write!(f, "No execution layer configured") + } + ExecutionProofError::UnknownRequestRoot(root) => { + write!( + f, + "Unknown request root {:?}. Block may not be imported yet or was already finalized.", + root + ) + } + ExecutionProofError::ProofEngineError(engine_error) => { + write!(f, "Proof engine error: {:?}", engine_error) + } + } + } +} + +impl std::error::Error for ExecutionProofError {} + +/// Compute the signing root for an execution proof message. +/// +/// This function is public for use by the validator client when signing proofs. +pub fn compute_signing_root(message: &types::ExecutionProof, domain: Hash256) -> Hash256 { + SigningData { + object_root: message.tree_hash_root(), + domain, + } + .tree_hash_root() +} + +/// Compute the domain for execution proof signing. +/// +/// This function is public for use by the validator client when signing proofs. +pub fn compute_execution_proof_domain( + fork_name: ForkName, + genesis_validators_root: Hash256, + spec: &ChainSpec, +) -> Hash256 { + let fork_version = spec.fork_version_for_name(fork_name); + let fork_data_root = ChainSpec::compute_fork_data_root(fork_version, genesis_validators_root); + + let mut domain = [0; 32]; + domain[0..4].copy_from_slice(&DOMAIN_EXECUTION_PROOF); + domain[4..].copy_from_slice( + fork_data_root + .as_slice() + .get(..28) + .expect("fork data root is 32 bytes so first 28 bytes should exist"), + ); + + Hash256::from(domain) +} + +// TODO: migrate into an impl on BeaconChain +/// Verify a validator's BLS signature over an execution proof. +/// +/// This function: +/// 1. Checks that the fork supports EIP-8025 +/// 2. Checks that proof data is not empty (max proof size should be enforced by ssz deserialization) +/// 3. Verifies the BLS signature over the proof message using the validator's pubkey +/// +/// # Arguments +/// +/// * `signed_proof` - The signed execution proof to verify +/// * `validator_pubkey` - The public key of the validator at the specified index +/// * `fork_name` - The current fork name +/// * `genesis_validators_root` - The genesis validators root for domain computation +/// * `spec` - The chain specification +/// +/// # Returns +/// +/// `Ok(())` if the proof is valid, otherwise an `ExecutionProofError`. +pub fn verify_signed_execution_proof_signature( + signed_proof: &SignedExecutionProof, + validator_pubkey: &bls::PublicKeyBytes, + fork_name: ForkName, + genesis_validators_root: Hash256, + spec: &ChainSpec, +) -> Result<(), BeaconChainError> { + // Check proof data is not empty + if signed_proof.message.proof_data.is_empty() { + Err(ExecutionProofError::EmptyProofData)?; + } + + // Decompress the validator's public key + let pubkey = validator_pubkey + .decompress() + .map_err(|_| ExecutionProofError::InvalidValidatorPubkey)?; + + // Decompress the signature using bls::SignatureBytes::decompress() + let signature = signed_proof + .signature + .decompress() + .map_err(|_| ExecutionProofError::InvalidSignatureFormat)?; + + // Get the domain for execution proof signing + let domain = compute_execution_proof_domain(fork_name, genesis_validators_root, spec); + + // Compute the signing root + let signing_root = compute_signing_root(&signed_proof.message, domain); + + // Verify the signature + if !signature.verify(&pubkey, signing_root) { + Err(ExecutionProofError::InvalidSignature)?; + } + + Ok(()) +} + +impl From for BeaconChainError { + fn from(engine_error: ProofEngineError) -> Self { + BeaconChainError::ExecutionProofError(ExecutionProofError::ProofEngineError(engine_error)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::BeaconChainError; + use bls::{Keypair, SignatureBytes}; + use ssz_types::VariableList; + use types::{ExecutionProof, MainnetEthSpec, PublicInput}; + + fn get_fulu_spec() -> ChainSpec { + ForkName::Fulu.make_genesis_spec(MainnetEthSpec::default_spec()) + } + + fn create_test_proof(proof_data: Vec) -> ExecutionProof { + ExecutionProof { + proof_data: VariableList::new(proof_data).unwrap(), + proof_type: 1, + public_input: PublicInput { + new_payload_request_root: Hash256::repeat_byte(0xab), + }, + } + } + + fn sign_proof( + proof: &ExecutionProof, + keypair: &Keypair, + fork_name: ForkName, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> SignedExecutionProof { + let domain = compute_execution_proof_domain(fork_name, genesis_validators_root, spec); + let signing_root = compute_signing_root(proof, domain); + let signature = keypair.sk.sign(signing_root); + + // Convert signature to bls::SignatureBytes + let sig_bytes = signature.serialize(); + let signature_vec: SignatureBytes = SignatureBytes::deserialize(&sig_bytes).unwrap(); + + SignedExecutionProof { + message: proof.clone(), + validator_index: 0, + signature: signature_vec, + } + } + + #[test] + fn test_verify_valid_signature() { + let keypair = Keypair::random(); + let spec = get_fulu_spec(); + let genesis_validators_root = Hash256::repeat_byte(0xcd); + let proof = create_test_proof(vec![1, 2, 3, 4]); + + let signed = sign_proof( + &proof, + &keypair, + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + let result = verify_signed_execution_proof_signature::( + &signed, + &keypair.pk.compress(), + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + assert!(result.is_ok()); + } + + #[test] + fn test_verify_invalid_signature() { + let keypair = Keypair::random(); + let wrong_keypair = Keypair::random(); + let spec = get_fulu_spec(); + let genesis_validators_root = Hash256::repeat_byte(0xcd); + let proof = create_test_proof(vec![1, 2, 3, 4]); + + // Sign with one keypair, verify with another + let signed = sign_proof( + &proof, + &keypair, + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + let result = verify_signed_execution_proof_signature::( + &signed, + &wrong_keypair.pk.compress(), + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + assert!(matches!( + result, + Err(BeaconChainError::ExecutionProofError( + ExecutionProofError::InvalidSignature + )) + )); + } + + #[test] + fn test_verify_empty_proof_data() { + let keypair = Keypair::random(); + let spec = get_fulu_spec(); + let genesis_validators_root = Hash256::repeat_byte(0xcd); + let proof = create_test_proof(vec![]); // Empty proof data + + let signed = sign_proof( + &proof, + &keypair, + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + let result = verify_signed_execution_proof_signature::( + &signed, + &keypair.pk.compress(), + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + assert!(matches!( + result, + Err(BeaconChainError::ExecutionProofError( + ExecutionProofError::EmptyProofData + )) + )); + } + + #[test] + fn test_verify_invalid_pubkey_format() { + let keypair = Keypair::random(); + let spec = get_fulu_spec(); + let genesis_validators_root = Hash256::repeat_byte(0xcd); + let proof = create_test_proof(vec![1, 2, 3, 4]); + + let signed = sign_proof( + &proof, + &keypair, + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + // Create invalid pubkey bytes (all zeros is not a valid point on the curve) + let invalid_pubkey = bls::PublicKeyBytes::empty(); + + let result = verify_signed_execution_proof_signature::( + &signed, + &invalid_pubkey, + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + assert!(matches!( + result, + Err(BeaconChainError::ExecutionProofError( + ExecutionProofError::InvalidValidatorPubkey + )) + )); + } + + #[test] + fn test_verify_invalid_signature_format() { + let keypair = Keypair::random(); + let spec = get_fulu_spec(); + let genesis_validators_root = Hash256::repeat_byte(0xcd); + let proof = create_test_proof(vec![1, 2, 3, 4]); + + // Create a signed proof with invalid signature bytes. + // BLS signatures are G2 points. Bytes 0xff repeated are not a valid + // compressed G2 point representation because they fail deserialization. + let invalid_signature = SignatureBytes::deserialize(&[0xff; 96]).unwrap(); + let signed = SignedExecutionProof { + message: proof, + validator_index: 0, + signature: invalid_signature, + }; + + let result = verify_signed_execution_proof_signature::( + &signed, + &keypair.pk.compress(), + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + assert!(matches!( + result, + Err(BeaconChainError::ExecutionProofError( + ExecutionProofError::InvalidSignatureFormat + )) + )); + } + + #[test] + fn test_compute_signing_root_deterministic() { + let proof = create_test_proof(vec![1, 2, 3, 4]); + let domain = Hash256::repeat_byte(0xaa); + + let root1 = compute_signing_root(&proof, domain); + let root2 = compute_signing_root(&proof, domain); + + assert_eq!(root1, root2); + } + + #[test] + fn test_compute_signing_root_different_inputs() { + let proof1 = create_test_proof(vec![1, 2, 3, 4]); + let proof2 = create_test_proof(vec![5, 6, 7, 8]); + let domain = Hash256::repeat_byte(0xaa); + + let root1 = compute_signing_root(&proof1, domain); + let root2 = compute_signing_root(&proof2, domain); + + assert_ne!(root1, root2); + } + + #[test] + fn test_compute_signing_root_different_domains() { + let proof = create_test_proof(vec![1, 2, 3, 4]); + let domain1 = Hash256::repeat_byte(0xaa); + let domain2 = Hash256::repeat_byte(0xbb); + + let root1 = compute_signing_root(&proof, domain1); + let root2 = compute_signing_root(&proof, domain2); + + assert_ne!(root1, root2); + } + + #[test] + fn test_compute_execution_proof_domain() { + let spec = get_fulu_spec(); + let genesis_validators_root = Hash256::repeat_byte(0xcd); + + let domain1 = + compute_execution_proof_domain(ForkName::Fulu, genesis_validators_root, &spec); + + // Domain should be deterministic + let domain2 = + compute_execution_proof_domain(ForkName::Fulu, genesis_validators_root, &spec); + assert_eq!(domain1, domain2); + + // Different genesis_validators_root should produce different domain + let different_root = Hash256::repeat_byte(0xef); + let domain3 = compute_execution_proof_domain(ForkName::Fulu, different_root, &spec); + assert_ne!(domain1, domain3); + } + + #[test] + fn test_verify_with_different_genesis_validators_root() { + let keypair = Keypair::random(); + let spec = get_fulu_spec(); + let genesis_validators_root = Hash256::repeat_byte(0xcd); + let different_root = Hash256::repeat_byte(0xef); + let proof = create_test_proof(vec![1, 2, 3, 4]); + + // Sign with one genesis_validators_root + let signed = sign_proof( + &proof, + &keypair, + ForkName::Fulu, + genesis_validators_root, + &spec, + ); + + // Verify with different genesis_validators_root + let result = verify_signed_execution_proof_signature::( + &signed, + &keypair.pk.compress(), + ForkName::Fulu, + different_root, + &spec, + ); + + // Should fail because domain computation uses genesis_validators_root + assert!(matches!( + result, + Err(BeaconChainError::ExecutionProofError( + ExecutionProofError::InvalidSignature + )) + )); + } +} diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 5efe9a3c232..32bb152a700 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -2,6 +2,7 @@ use crate::beacon_block_streamer::Error as BlockStreamerError; use crate::beacon_chain::ForkChoiceError; use crate::beacon_fork_choice_store::Error as ForkChoiceStoreError; use crate::data_availability_checker::AvailabilityCheckError; +use crate::eip8025::proof_verification::ExecutionProofError; use crate::migrate::PruningError; use crate::naive_aggregation_pool::Error as NaiveAggregationError; use crate::observed_aggregates::Error as ObservedAttestationsError; @@ -147,6 +148,7 @@ pub enum BeaconChainError { AltairForkDisabled, BuilderMissing, ExecutionLayerMissing, + ExecutionProofError(ExecutionProofError), BlockVariantLacksExecutionPayload(Hash256), ExecutionLayerErrorPayloadReconstruction(ExecutionBlockHash, Box), EngineGetCapabilititesFailed(Box), @@ -281,6 +283,7 @@ easy_from_to!(BlockSignatureVerifierError, BeaconChainError); easy_from_to!(PruningError, BeaconChainError); easy_from_to!(ArithError, BeaconChainError); easy_from_to!(ForkChoiceStoreError, BeaconChainError); +easy_from_to!(ExecutionProofError, BeaconChainError); easy_from_to!(StateAdvanceError, BeaconChainError); easy_from_to!(BlockReplayError, BeaconChainError); easy_from_to!(InconsistentFork, BeaconChainError); diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 804268a6139..5366b68529e 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -20,6 +20,7 @@ pub mod custody_context; pub mod data_availability_checker; pub mod data_column_verification; mod early_attester_cache; +pub mod eip8025; pub mod envelope_times_cache; mod errors; pub mod events; @@ -41,6 +42,7 @@ pub mod observed_aggregates; mod observed_attesters; pub mod observed_block_producers; pub mod observed_data_sidecars; +pub mod observed_execution_proofs; pub mod observed_operations; mod observed_slashable; pub mod partial_data_column_assembler; diff --git a/beacon_node/beacon_chain/src/observed_execution_proofs.rs b/beacon_node/beacon_chain/src/observed_execution_proofs.rs new file mode 100644 index 00000000000..1484cd15db7 --- /dev/null +++ b/beacon_node/beacon_chain/src/observed_execution_proofs.rs @@ -0,0 +1,284 @@ +//! Deduplication cache for execution proofs received via gossip. +//! +//! Implements gossip IGNORE rules from the EIP-8025 p2p-interface spec: +//! - IGNORE-2: No valid proof already received for `(request_root, proof_type)` +//! - IGNORE-3: First proof from validator for `(request_root, proof_type, validator_index)` +//! +//! Request-root scoped entries are evicted at finalization: proofs for finalized blocks are +//! irrelevant. Invalid proof-data entries are process-local and retained until restart. + +use std::collections::{HashMap, HashSet}; +use tree_hash::TreeHash; +use types::execution::eip8025::ProofData; +use types::{Hash256, ProofType, Slot}; + +/// Gossip deduplication cache for execution proofs. +/// +/// Checked *before* BLS/proof-engine verification to avoid redundant work. +#[derive(Debug, Default)] +pub struct ObservedExecutionProofs { + /// Tracks `(request_root, proof_type)` pairs for which we already have a *valid* proof. + /// Used to implement IGNORE-2. + valid_proofs: HashMap<(Hash256, ProofType), ()>, + + /// Tracks `(request_root, proof_type, validator_index)` triples we have already attempted + /// to verify (regardless of outcome). Used to implement IGNORE-3. + seen_from_validator: HashSet<(Hash256, ProofType, u64)>, + + /// Tracks `(proof_type, hash_tree_root(proof_data))` pairs for proofs already rejected by the + /// proof engine. + invalid_proofs: HashSet<(ProofType, Hash256)>, + + /// Maps slot → set of request roots observed at that slot. Populated when a valid/accepted + /// proof is observed. Used to prune `valid_proofs` and `seen_from_validator` at finalization. + slot_to_request_roots: HashMap>, +} + +/// Result of checking the dedup cache. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ProofObservation { + /// We already rejected this `(proof_type, proof_data)` pair. + AlreadyRejectedProof, + /// We already have a valid proof for this `(request_root, proof_type)` — IGNORE-2. + AlreadyHaveValidProof, + /// We already saw a proof from this validator for this `(request_root, proof_type)` — IGNORE-3. + DuplicateFromValidator, + /// First time seeing this proof — proceed with verification. + New, +} + +impl ObservedExecutionProofs { + /// Check whether a proof should be processed or ignored based on the dedup rules. + /// + /// This does *not* insert the proof into the cache; call [`observe_verification_attempt`] + /// and then [`observe_invalid_proof`] or [`observe_valid_proof`] after verification completes. + pub fn check( + &self, + request_root: Hash256, + proof_type: ProofType, + proof_data: &ProofData, + validator_index: u64, + ) -> ProofObservation { + // IGNORE-2: already have a valid proof for this (root, type) + if self.valid_proofs.contains_key(&(request_root, proof_type)) { + return ProofObservation::AlreadyHaveValidProof; + } + + // IGNORE-3: already saw a proof from this validator for this (root, type) + if self + .seen_from_validator + .contains(&(request_root, proof_type, validator_index)) + { + return ProofObservation::DuplicateFromValidator; + } + + if self + .invalid_proofs + .contains(&(proof_type, proof_data.tree_hash_root())) + { + return ProofObservation::AlreadyRejectedProof; + } + + ProofObservation::New + } + + /// Record that we attempted to verify a proof from this validator. + /// Must be called for every verification attempt, regardless of outcome. + pub fn observe_verification_attempt( + &mut self, + request_root: Hash256, + proof_type: ProofType, + validator_index: u64, + ) { + self.seen_from_validator + .insert((request_root, proof_type, validator_index)); + } + + /// Record that the proof engine rejected this `(proof_type, proof_data)` pair. + /// Returns `true` if this is the first rejection recorded for the pair. + pub fn observe_invalid_proof(&mut self, proof_type: ProofType, proof_data: &ProofData) -> bool { + self.invalid_proofs + .insert((proof_type, proof_data.tree_hash_root())) + } + + /// Record that a valid proof was received for `(request_root, proof_type)` at `slot`. + pub fn observe_valid_proof( + &mut self, + request_root: Hash256, + proof_type: ProofType, + slot: Slot, + ) { + self.valid_proofs.insert((request_root, proof_type), ()); + self.slot_to_request_roots + .entry(slot) + .or_default() + .insert(request_root); + } + + /// Prune entries for request roots whose slot is at or below `finalized_slot`. + /// + /// Call at finalization. Any proof for a finalized block will never need dedup again. + /// Entries in `seen_from_validator` without a known slot (e.g. for proofs that failed + /// BLS or engine verification) are retained until restart. + pub fn prune(&mut self, finalized_slot: Slot) { + let pruned_roots: HashSet = self + .slot_to_request_roots + .extract_if(|&slot, _| slot <= finalized_slot) + .flat_map(|(_, roots)| roots) + .collect(); + self.valid_proofs + .retain(|(root, _), _| !pruned_roots.contains(root)); + self.seen_from_validator + .retain(|(root, _, _)| !pruned_roots.contains(root)); + } + + /// Number of valid-proof entries (for metrics / tests). + pub fn valid_proof_count(&self) -> usize { + self.valid_proofs.len() + } + + /// Number of seen-from-validator entries (for metrics / tests). + pub fn seen_from_validator_count(&self) -> usize { + self.seen_from_validator.len() + } + + /// Number of invalid proof-data entries (for metrics / tests). + pub fn invalid_proof_count(&self) -> usize { + self.invalid_proofs.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn make_proof_data(bytes: &[u8]) -> ProofData { + ProofData::new(bytes.to_vec()).expect("proof data should fit") + } + + #[test] + fn new_proof_is_observed() { + let cache = ObservedExecutionProofs::default(); + let root = Hash256::repeat_byte(0x01); + let proof_data = make_proof_data(&[1, 2, 3]); + assert_eq!(cache.check(root, 1, &proof_data, 42), ProofObservation::New); + } + + #[test] + fn ignore_2_valid_proof_dedup() { + let mut cache = ObservedExecutionProofs::default(); + let root = Hash256::repeat_byte(0x01); + let proof_data = make_proof_data(&[1, 2, 3]); + + cache.observe_valid_proof(root, 1, Slot::new(1)); + + // Same (root, type) from a different validator → still IGNORE + assert_eq!( + cache.check(root, 1, &proof_data, 99), + ProofObservation::AlreadyHaveValidProof + ); + + // Different type → New + assert_eq!(cache.check(root, 2, &proof_data, 99), ProofObservation::New); + } + + #[test] + fn invalid_proof_data_dedup_uses_type_and_data_root() { + let mut cache = ObservedExecutionProofs::default(); + let root = Hash256::repeat_byte(0x01); + let other_root = Hash256::repeat_byte(0x02); + let proof_data = make_proof_data(&[1, 2, 3]); + let other_proof_data = make_proof_data(&[4, 5, 6]); + + assert!(cache.observe_invalid_proof(1, &proof_data)); + assert!(!cache.observe_invalid_proof(1, &proof_data)); + + assert_eq!( + cache.check(other_root, 1, &proof_data, 99), + ProofObservation::AlreadyRejectedProof + ); + + // Same proof data with a different type is a distinct cache key. + assert_eq!(cache.check(root, 2, &proof_data, 42), ProofObservation::New); + + // Same type with different proof data is a distinct cache key. + assert_eq!( + cache.check(root, 1, &other_proof_data, 42), + ProofObservation::New + ); + + assert_eq!(cache.invalid_proof_count(), 1); + } + + #[test] + fn cheap_dedup_checks_precede_invalid_proof_data_rooting() { + let mut cache = ObservedExecutionProofs::default(); + let root = Hash256::repeat_byte(0x01); + let proof_data = make_proof_data(&[1, 2, 3]); + + cache.observe_valid_proof(root, 1, Slot::new(1)); + cache.observe_invalid_proof(1, &proof_data); + + assert_eq!( + cache.check(root, 1, &proof_data, 42), + ProofObservation::AlreadyHaveValidProof + ); + + let other_root = Hash256::repeat_byte(0x02); + cache.observe_verification_attempt(other_root, 1, 42); + + assert_eq!( + cache.check(other_root, 1, &proof_data, 42), + ProofObservation::DuplicateFromValidator + ); + } + + #[test] + fn ignore_3_validator_dedup() { + let mut cache = ObservedExecutionProofs::default(); + let root = Hash256::repeat_byte(0x01); + let proof_data = make_proof_data(&[1, 2, 3]); + + cache.observe_verification_attempt(root, 1, 42); + + assert_eq!( + cache.check(root, 1, &proof_data, 42), + ProofObservation::DuplicateFromValidator + ); + + // Same validator, different type → New + assert_eq!(cache.check(root, 2, &proof_data, 42), ProofObservation::New); + + // Different validator, same type → New + assert_eq!(cache.check(root, 1, &proof_data, 43), ProofObservation::New); + } + + #[test] + fn prune_removes_finalized_roots() { + let mut cache = ObservedExecutionProofs::default(); + let root_a = Hash256::repeat_byte(0x01); + let root_b = Hash256::repeat_byte(0x02); + let proof_data = make_proof_data(&[1, 2, 3]); + + // root_a at slot 10 (will be finalized), root_b at slot 20 (will be retained). + cache.observe_valid_proof(root_a, 1, Slot::new(10)); + cache.observe_valid_proof(root_b, 1, Slot::new(20)); + cache.observe_verification_attempt(root_a, 1, 42); + cache.observe_verification_attempt(root_b, 1, 43); + + cache.prune(Slot::new(15)); + + assert_eq!(cache.valid_proof_count(), 1); + assert_eq!(cache.seen_from_validator_count(), 1); + // root_b still tracked + assert_eq!( + cache.check(root_b, 1, &proof_data, 99), + ProofObservation::AlreadyHaveValidProof + ); + // root_a gone → New + assert_eq!( + cache.check(root_a, 1, &proof_data, 42), + ProofObservation::New + ); + } +} diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/import.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/import.rs index 73ddb43273f..8a1b6cadf56 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/import.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/import.rs @@ -318,6 +318,14 @@ impl BeaconChain { // This prevents inconsistency between the two at the expense of concurrency. drop(fork_choice); + if let Err(error) = self.register_execution_payload_request_root(block_root) { + warn!( + ?error, + ?block_root, + "Unable to cache EIP-8025 execution proof request root" + ); + } + // We're declaring the envelope "imported" at this point, since fork choice and the DB know // about it. let envelope_time_imported = self.slot_clock.now_duration().unwrap_or(Duration::MAX); diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/payload_notifier.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/payload_notifier.rs index 0bbe32525aa..6f08e0c4503 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/payload_notifier.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/payload_notifier.rs @@ -2,8 +2,10 @@ use std::sync::Arc; use execution_layer::{NewPayloadRequest, NewPayloadRequestGloas}; use fork_choice::PayloadVerificationStatus; +use ssz::Encode; use state_processing::per_block_processing::deneb::kzg_commitment_to_versioned_hash; use tracing::warn; +use types::ProofAttributes; use types::{SignedBeaconBlock, SignedExecutionPayloadEnvelope}; use crate::{ @@ -64,10 +66,45 @@ impl PayloadNotifier { } else { let parent_root = self.block.message().parent_root(); let request = Self::build_new_payload_request(&self.envelope, &self.block)?; + self.request_execution_proofs(&request); notify_new_payload(&self.chain, self.envelope.slot(), parent_root, request).await } } + fn request_execution_proofs(&self, request: &NewPayloadRequest<'_, T::EthSpec>) { + let Some(execution_layer) = self.chain.execution_layer.as_ref() else { + return; + }; + let Some(proof_engine) = execution_layer.proof_engine() else { + return; + }; + + let proof_types = execution_layer + .proof_types() + .iter() + .map(|proof_type| proof_type.to_u8()) + .collect(); + let proof_attributes = ProofAttributes { proof_types }; + let request_body = request.as_ssz_bytes(); + let request_root = request.request_root(); + + self.chain.task_executor.spawn( + async move { + if let Err(error) = proof_engine + .request_proofs_ssz(request_body, proof_attributes) + .await + { + warn!( + ?error, + ?request_root, + "Failed to request EIP-8025 execution proofs" + ); + } + }, + "eip8025_proof_request", + ); + } + fn build_new_payload_request<'a>( envelope: &'a SignedExecutionPayloadEnvelope, block: &'a SignedBeaconBlock, @@ -83,7 +120,11 @@ impl PayloadNotifier { .blob_kzg_commitments .iter() .map(kzg_commitment_to_versioned_hash) - .collect(); + .collect::>() + .try_into() + .map_err(|e| { + BlockError::BeaconChainError(Box::new(crate::BeaconChainError::SszTypesError(e))) + })?; Ok(NewPayloadRequest::Gloas(NewPayloadRequestGloas { execution_payload: &envelope.message.payload, diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index c2ccad7d8c5..a4777141fe5 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -2834,7 +2834,9 @@ where .blob_kzg_commitments .iter() .map(kzg_commitment_to_versioned_hash) - .collect(); + .collect::>() + .try_into() + .expect("versioned hashes should fit"); let request = NewPayloadRequest::Gloas(NewPayloadRequestGloas { execution_payload: &signed_envelope.message.payload, diff --git a/beacon_node/execution_layer/Cargo.toml b/beacon_node/execution_layer/Cargo.toml index a23ea948e4e..e37a85f1444 100644 --- a/beacon_node/execution_layer/Cargo.toml +++ b/beacon_node/execution_layer/Cargo.toml @@ -10,14 +10,18 @@ alloy-primitives = { workspace = true } alloy-rlp = { workspace = true } alloy-rpc-types-eth = { workspace = true } arc-swap = "1.6.0" +async-stream = "0.3" +async-trait = "0.1" bls = { workspace = true } builder_client = { path = "../builder_client" } bytes = { workspace = true } eth2 = { workspace = true, features = ["events", "lighthouse", "network"] } ethereum_serde_utils = { workspace = true } ethereum_ssz = { workspace = true } +ethereum_ssz_derive = { workspace = true } fixed_bytes = { workspace = true } fork_choice = { workspace = true } +futures = { workspace = true } hash-db = "0.15.2" hash256-std-hasher = "0.15.2" hex = { workspace = true } @@ -32,6 +36,7 @@ parking_lot = { workspace = true } pretty_reqwest_error = { workspace = true } rand = { workspace = true } reqwest = { workspace = true } +reqwest-eventsource = "0.6" sensitive_url = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/beacon_node/execution_layer/src/eip8025/errors.rs b/beacon_node/execution_layer/src/eip8025/errors.rs new file mode 100644 index 00000000000..f2fca595820 --- /dev/null +++ b/beacon_node/execution_layer/src/eip8025/errors.rs @@ -0,0 +1,92 @@ +use pretty_reqwest_error::PrettyReqwestError; +use std::fmt; + +#[derive(Debug)] +pub enum ProofEngineError { + InvalidProofType(String), + InvalidHeaderFormat(String), + InvalidPayload(String), + ProofGenerationUnavailable(String), + HttpClientError(PrettyReqwestError), + JsonRpcError { code: i64, message: String }, + SerdeError(serde_json::Error), + SszError(ssz_types::Error), + SseError(String), + ForkNotSupported(String), + ProofTypeNotSupported(u8), + Timeout, + EngineUnavailable, +} + +impl ProofEngineError { + pub fn rpc_error_code(&self) -> Option { + match self { + ProofEngineError::JsonRpcError { code, .. } => Some(*code), + _ => None, + } + } + + pub fn is_not_supported(&self) -> bool { + matches!(self, ProofEngineError::ProofTypeNotSupported(_)) + } +} + +pub mod error_codes { + pub const INVALID_HEADER_FORMAT: i64 = -39002; + pub const INVALID_PAYLOAD: i64 = -39003; + pub const PROOF_GENERATION_UNAVAILABLE: i64 = -39004; +} + +impl fmt::Display for ProofEngineError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ProofEngineError::InvalidProofType(msg) => write!(f, "invalid proof type: {msg}"), + ProofEngineError::InvalidHeaderFormat(msg) => { + write!(f, "invalid header format: {msg}") + } + ProofEngineError::InvalidPayload(msg) => write!(f, "invalid payload: {msg}"), + ProofEngineError::ProofGenerationUnavailable(msg) => { + write!(f, "proof generation unavailable: {msg}") + } + ProofEngineError::HttpClientError(err) => write!(f, "HTTP request failed: {err}"), + ProofEngineError::JsonRpcError { code, message } => { + write!(f, "JSON-RPC error ({code}): {message}") + } + ProofEngineError::SerdeError(err) => write!(f, "serialization error: {err}"), + ProofEngineError::SszError(err) => write!(f, "SSZ error: {err}"), + ProofEngineError::SseError(msg) => write!(f, "SSE error: {msg}"), + ProofEngineError::ForkNotSupported(fork) => write!(f, "fork not supported: {fork}"), + ProofEngineError::ProofTypeNotSupported(proof_type) => { + write!(f, "proof type {proof_type} not supported") + } + ProofEngineError::Timeout => write!(f, "proof engine request timed out"), + ProofEngineError::EngineUnavailable => write!(f, "proof engine is unavailable"), + } + } +} + +impl std::error::Error for ProofEngineError {} + +impl From for ProofEngineError { + fn from(e: serde_json::Error) -> Self { + ProofEngineError::SerdeError(e) + } +} + +impl From for ProofEngineError { + fn from(e: ssz_types::Error) -> Self { + ProofEngineError::SszError(e) + } +} + +impl From for ProofEngineError { + fn from(e: reqwest::Error) -> Self { + ProofEngineError::HttpClientError(e.into()) + } +} + +impl From for ProofEngineError { + fn from(e: PrettyReqwestError) -> Self { + ProofEngineError::HttpClientError(e) + } +} diff --git a/beacon_node/execution_layer/src/eip8025/mod.rs b/beacon_node/execution_layer/src/eip8025/mod.rs new file mode 100644 index 00000000000..44857440b72 --- /dev/null +++ b/beacon_node/execution_layer/src/eip8025/mod.rs @@ -0,0 +1,17 @@ +//! EIP-8025 optional execution proof-engine transport. +//! +//! This module intentionally does not act as an execution engine and does not gate fork choice. +//! It provides HTTP helpers for requesting and verifying proofs. Beacon-chain code records proof +//! status separately and only applies proof-backed payload promotion when explicitly configured. + +pub mod errors; +pub mod proof_engine; +pub mod proof_node_client; +pub mod types; + +pub use errors::ProofEngineError; +pub use proof_engine::HttpProofEngine; +pub use proof_node_client::{ + HttpProofNodeClient, PROOF_ENGINE_TIMEOUT, ProofNodeClient, ProofRequestResponse, +}; +pub use types::{FailureReason, ProofComplete, ProofEvent, ProofFailure, ProofType, SseEventParts}; diff --git a/beacon_node/execution_layer/src/eip8025/proof_engine.rs b/beacon_node/execution_layer/src/eip8025/proof_engine.rs new file mode 100644 index 00000000000..1332957839a --- /dev/null +++ b/beacon_node/execution_layer/src/eip8025/proof_engine.rs @@ -0,0 +1,72 @@ +use super::errors::ProofEngineError; +use super::proof_node_client::{HttpProofNodeClient, ProofNodeClient}; +use super::types::ProofEvent; +use bytes::Bytes; +use futures::stream::Stream; +use sensitive_url::SensitiveUrl; +use ssz::Encode; +use std::pin::Pin; +use std::time::Duration; +use types::execution::eip8025::{ProofAttributes, ProofStatus, SignedExecutionProof}; +use types::{EthSpec, Hash256}; + +pub struct HttpProofEngine { + proof_node: Box, +} + +impl HttpProofEngine { + pub fn new(url: SensitiveUrl, timeout: Option) -> Self { + Self::with_proof_node(HttpProofNodeClient::new(url, timeout)) + } + + pub fn with_proof_node(proof_node: impl ProofNodeClient + 'static) -> Self { + Self { + proof_node: Box::new(proof_node), + } + } + + pub async fn verify_execution_proof( + &self, + proof: &SignedExecutionProof, + ) -> Result { + self.proof_node + .verify_proof(proof.request_root(), proof.proof_type(), proof.proof_data()) + .await + } + + pub async fn get_proof( + &self, + new_payload_request_root: Hash256, + proof_type: u8, + ) -> Result { + self.proof_node + .get_proof(new_payload_request_root, proof_type) + .await + } + + pub async fn request_proofs( + &self, + new_payload_request: crate::NewPayloadRequest<'_, E>, + proof_attributes: ProofAttributes, + ) -> Result { + self.request_proofs_ssz(new_payload_request.as_ssz_bytes(), proof_attributes) + .await + } + + pub async fn request_proofs_ssz( + &self, + ssz_body: Vec, + proof_attributes: ProofAttributes, + ) -> Result { + self.proof_node + .request_proofs(ssz_body, proof_attributes) + .await + } + + pub fn subscribe_proof_events( + &self, + filter_root: Option, + ) -> Pin> + Send + '_>> { + self.proof_node.subscribe_proof_events(filter_root) + } +} diff --git a/beacon_node/execution_layer/src/eip8025/proof_node_client.rs b/beacon_node/execution_layer/src/eip8025/proof_node_client.rs new file mode 100644 index 00000000000..c4e88490f24 --- /dev/null +++ b/beacon_node/execution_layer/src/eip8025/proof_node_client.rs @@ -0,0 +1,197 @@ +use super::errors::ProofEngineError; +use super::types::{ProofEvent, ProofType, SseEventParts}; +use bytes::Bytes; +use futures::stream::Stream; +use reqwest::Client; +use reqwest_eventsource::{Event, EventSource}; +use sensitive_url::SensitiveUrl; +use std::pin::Pin; +use std::time::Duration; +use tokio_stream::StreamExt; +use types::Hash256; +use types::execution::eip8025::{ProofAttributes, ProofStatus}; + +pub const PROOF_ENGINE_TIMEOUT: Duration = Duration::from_secs(1); + +const PATH_PROOF_REQUESTS: &str = "/v1/execution_proof_requests"; +const PATH_PROOF_VERIFICATIONS: &str = "/v1/execution_proof_verifications"; +const PATH_PROOFS: &str = "/v1/execution_proofs"; + +const QUERY_PROOF_TYPES: &str = "proof_types"; +const QUERY_NEW_PAYLOAD_REQUEST_ROOT: &str = "new_payload_request_root"; +const QUERY_PROOF_TYPE: &str = "proof_type"; + +const HEADER_CONTENT_TYPE: &str = "content-type"; +const HEADER_VALUE_SSZ: &str = "application/octet-stream"; + +#[async_trait::async_trait] +pub trait ProofNodeClient: Send + Sync { + async fn request_proofs( + &self, + ssz_body: Vec, + proof_attributes: ProofAttributes, + ) -> Result; + + async fn verify_proof( + &self, + root: Hash256, + proof_type: u8, + proof_data: &[u8], + ) -> Result; + + async fn get_proof(&self, root: Hash256, proof_type: u8) -> Result; + + fn subscribe_proof_events( + &self, + filter_root: Option, + ) -> Pin> + Send + '_>>; +} + +#[derive(Debug, Clone, serde::Deserialize)] +pub struct ProofRequestResponse { + pub new_payload_request_root: Hash256, +} + +#[derive(Debug, Clone, serde::Deserialize)] +struct ProofVerificationResponse { + status: ProofVerificationStatus, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +enum ProofVerificationStatus { + Valid, + Invalid, +} + +pub struct HttpProofNodeClient { + client: Client, + url: SensitiveUrl, + timeout: Duration, +} + +impl HttpProofNodeClient { + pub fn new(url: SensitiveUrl, timeout: Option) -> Self { + let client = Client::builder() + .build() + .expect("failed to build proof-engine HTTP client"); + + Self { + client, + url, + timeout: timeout.unwrap_or(PROOF_ENGINE_TIMEOUT), + } + } + + fn url(&self, path: &str) -> reqwest::Url { + let mut url = self.url.expose_full().clone(); + url.set_path(path); + url + } +} + +#[async_trait::async_trait] +impl ProofNodeClient for HttpProofNodeClient { + async fn request_proofs( + &self, + ssz_body: Vec, + proof_attributes: ProofAttributes, + ) -> Result { + let proof_types_csv = proof_attributes + .proof_types + .iter() + .map(|proof_type| ProofType::from_u8(*proof_type).map(|pt| pt.as_str().to_string())) + .collect::, _>>()? + .join(","); + + let response: ProofRequestResponse = self + .client + .post(self.url(PATH_PROOF_REQUESTS)) + .query(&[(QUERY_PROOF_TYPES, &proof_types_csv)]) + .header(HEADER_CONTENT_TYPE, HEADER_VALUE_SSZ) + .body(ssz_body) + .timeout(self.timeout) + .send() + .await? + .error_for_status()? + .json() + .await?; + + Ok(response.new_payload_request_root) + } + + async fn verify_proof( + &self, + root: Hash256, + proof_type: u8, + proof_data: &[u8], + ) -> Result { + let proof_type = ProofType::from_u8(proof_type)?; + let response: ProofVerificationResponse = self + .client + .post(self.url(PATH_PROOF_VERIFICATIONS)) + .query(&[ + (QUERY_NEW_PAYLOAD_REQUEST_ROOT, &root.to_string()), + (QUERY_PROOF_TYPE, &proof_type.to_string()), + ]) + .header(HEADER_CONTENT_TYPE, HEADER_VALUE_SSZ) + .body(proof_data.to_vec()) + .timeout(self.timeout) + .send() + .await? + .error_for_status()? + .json() + .await?; + + match response.status { + ProofVerificationStatus::Valid => Ok(ProofStatus::Valid), + ProofVerificationStatus::Invalid => Ok(ProofStatus::Invalid), + } + } + + async fn get_proof(&self, root: Hash256, proof_type: u8) -> Result { + let proof_type = ProofType::from_u8(proof_type)?; + Ok(self + .client + .get(self.url(&format!("{PATH_PROOFS}/{root}/{proof_type}"))) + .timeout(self.timeout) + .send() + .await? + .error_for_status()? + .bytes() + .await?) + } + + fn subscribe_proof_events( + &self, + filter_root: Option, + ) -> Pin> + Send + '_>> { + let client = self.client.clone(); + let url = self.url(PATH_PROOF_REQUESTS); + + Box::pin(async_stream::try_stream! { + let builder = if let Some(root) = filter_root { + client.get(url).query(&[(QUERY_NEW_PAYLOAD_REQUEST_ROOT, &root.to_string())]) + } else { + client.get(url) + }; + let mut events = EventSource::new(builder) + .map_err(|e| ProofEngineError::SseError( + format!("failed to create proof-engine event source: {e}") + ))?; + + while let Some(event) = events.next().await { + match event { + Ok(Event::Open) => {} + Ok(Event::Message(message)) => { + yield ProofEvent::try_from(SseEventParts(&message.event, &message.data))?; + } + Err(error) => { + events.close(); + Err(ProofEngineError::SseError(error.to_string()))?; + } + } + } + }) + } +} diff --git a/beacon_node/execution_layer/src/eip8025/types.rs b/beacon_node/execution_layer/src/eip8025/types.rs new file mode 100644 index 00000000000..c9985eb8afd --- /dev/null +++ b/beacon_node/execution_layer/src/eip8025/types.rs @@ -0,0 +1,202 @@ +use super::errors::ProofEngineError; +use serde::{Deserialize, Deserializer, Serialize}; +use std::fmt; +use std::str::FromStr; +use types::Hash256; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(into = "String", try_from = "String")] +#[repr(u8)] +pub enum ProofType { + EthrexRisc0 = 0, + EthrexSP1 = 1, + EthrexZisk = 2, + RethOpenVM = 3, + RethRisc0 = 4, + RethSP1 = 5, + RethZisk = 6, +} + +impl ProofType { + pub fn as_str(&self) -> &'static str { + match self { + Self::EthrexRisc0 => "ethrex-risc0", + Self::EthrexSP1 => "ethrex-sp1", + Self::EthrexZisk => "ethrex-zisk", + Self::RethOpenVM => "reth-openvm", + Self::RethRisc0 => "reth-risc0", + Self::RethSP1 => "reth-sp1", + Self::RethZisk => "reth-zisk", + } + } + + pub fn from_u8(value: u8) -> Result { + match value { + 0 => Ok(Self::EthrexRisc0), + 1 => Ok(Self::EthrexSP1), + 2 => Ok(Self::EthrexZisk), + 3 => Ok(Self::RethOpenVM), + 4 => Ok(Self::RethRisc0), + 5 => Ok(Self::RethSP1), + 6 => Ok(Self::RethZisk), + _ => Err(ProofEngineError::InvalidProofType(format!( + "no mapping for proof type {value}" + ))), + } + } + + pub fn to_u8(self) -> u8 { + self as u8 + } + + pub fn all() -> &'static [ProofType] { + &[ + Self::EthrexRisc0, + Self::EthrexSP1, + Self::EthrexZisk, + Self::RethOpenVM, + Self::RethRisc0, + Self::RethSP1, + Self::RethZisk, + ] + } +} + +impl FromStr for ProofType { + type Err = ProofEngineError; + + fn from_str(s: &str) -> Result { + match s { + "ethrex-risc0" => Ok(Self::EthrexRisc0), + "ethrex-sp1" => Ok(Self::EthrexSP1), + "ethrex-zisk" => Ok(Self::EthrexZisk), + "reth-openvm" => Ok(Self::RethOpenVM), + "reth-risc0" => Ok(Self::RethRisc0), + "reth-sp1" => Ok(Self::RethSP1), + "reth-zisk" => Ok(Self::RethZisk), + _ => Err(ProofEngineError::InvalidProofType(format!( + "unknown proof type: {s}" + ))), + } + } +} + +impl fmt::Display for ProofType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl From for String { + fn from(proof_type: ProofType) -> Self { + proof_type.as_str().to_string() + } +} + +impl TryFrom for ProofType { + type Error = ProofEngineError; + + fn try_from(s: String) -> Result { + s.parse() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ProofTypes(pub Vec); + +impl Default for ProofTypes { + fn default() -> Self { + Self(vec![ + ProofType::EthrexRisc0, + ProofType::EthrexSP1, + ProofType::EthrexZisk, + ProofType::RethOpenVM, + ]) + } +} + +impl std::ops::Deref for ProofTypes { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for ProofTypes { + fn from(proof_types: Vec) -> Self { + Self(proof_types) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ProofEvent { + ProofComplete(ProofComplete), + ProofFailure(ProofFailure), +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct ProofComplete { + pub new_payload_request_root: Hash256, + #[serde(deserialize_with = "deserialize_proof_type")] + pub proof_type: u8, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct ProofFailure { + pub new_payload_request_root: Hash256, + #[serde(deserialize_with = "deserialize_proof_type")] + pub proof_type: u8, + pub reason: FailureReason, + pub error: String, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum FailureReason { + WitnessTimeout, + ProvingTimeout, + ProvingError, + InternalError, + #[serde(other)] + Unknown, +} + +fn deserialize_proof_type<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum ProofTypeValue { + Number(u8), + String(String), + } + + match ProofTypeValue::deserialize(deserializer)? { + ProofTypeValue::Number(n) => Ok(n), + ProofTypeValue::String(s) => { + if let Ok(proof_type) = s.parse::() { + return Ok(proof_type.to_u8()); + } + s.parse::().map_err(serde::de::Error::custom) + } + } +} + +pub struct SseEventParts<'a>(pub &'a str, pub &'a str); + +impl<'a> TryFrom> for ProofEvent { + type Error = ProofEngineError; + + fn try_from(parts: SseEventParts<'a>) -> Result { + let SseEventParts(name, data) = parts; + match name { + "proof_complete" => Ok(Self::ProofComplete(serde_json::from_str(data)?)), + "proof_failure" => Ok(Self::ProofFailure(serde_json::from_str(data)?)), + other => Err(ProofEngineError::SseError(format!( + "unknown SSE event type: {other}" + ))), + } + } +} diff --git a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs index ba94296b859..b6596fbfc60 100644 --- a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs +++ b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs @@ -1,8 +1,12 @@ use crate::{Error, block_hash::calculate_execution_block_hash, metrics}; use crate::versioned_hashes::verify_versioned_hashes; +use ssz_derive::Encode as SszEncode; +use ssz_types::VariableList; use state_processing::per_block_processing::deneb::kzg_commitment_to_versioned_hash; use superstruct::superstruct; +use tree_hash::TreeHash as _; +use tree_hash_derive::TreeHash; use types::{ BeaconBlockRef, BeaconStateError, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadRef, Hash256, VersionedHash, @@ -14,7 +18,7 @@ use types::{ #[superstruct( variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), - variant_attributes(derive(Clone, Debug, PartialEq),), + variant_attributes(derive(Clone, Debug, PartialEq, SszEncode, TreeHash),), map_into(ExecutionPayload), map_ref_into(ExecutionPayloadRef), cast_error( @@ -26,7 +30,9 @@ use types::{ expr = "BeaconStateError::IncorrectStateVariant" ) )] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, SszEncode, TreeHash)] +#[tree_hash(enum_behaviour = "transparent")] +#[ssz(enum_behaviour = "transparent")] pub struct NewPayloadRequest<'block, E: EthSpec> { #[superstruct( only(Bellatrix), @@ -44,7 +50,7 @@ pub struct NewPayloadRequest<'block, E: EthSpec> { #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_gloas"))] pub execution_payload: &'block ExecutionPayloadGloas, #[superstruct(only(Deneb, Electra, Fulu, Gloas))] - pub versioned_hashes: Vec, + pub versioned_hashes: VariableList, #[superstruct(only(Deneb, Electra, Fulu, Gloas))] pub parent_beacon_block_root: Hash256, #[superstruct(only(Electra, Fulu, Gloas))] @@ -52,6 +58,11 @@ pub struct NewPayloadRequest<'block, E: EthSpec> { } impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { + /// Root used by EIP-8025 proofs to bind a proof to an `engine_newPayload` request. + pub fn request_root(&self) -> Hash256 { + self.tree_hash_root() + } + pub fn parent_hash(&self) -> ExecutionBlockHash { match self { Self::Bellatrix(payload) => payload.execution_payload.parent_hash, @@ -196,7 +207,8 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> .blob_kzg_commitments .iter() .map(kzg_commitment_to_versioned_hash) - .collect(), + .collect::>() + .try_into()?, parent_beacon_block_root: block_ref.parent_root, })), BeaconBlockRef::Electra(block_ref) => Ok(Self::Electra(NewPayloadRequestElectra { @@ -206,7 +218,8 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> .blob_kzg_commitments .iter() .map(kzg_commitment_to_versioned_hash) - .collect(), + .collect::>() + .try_into()?, parent_beacon_block_root: block_ref.parent_root, execution_requests: &block_ref.body.execution_requests, })), @@ -217,7 +230,8 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> .blob_kzg_commitments .iter() .map(kzg_commitment_to_versioned_hash) - .collect(), + .collect::>() + .try_into()?, parent_beacon_block_root: block_ref.parent_root, execution_requests: &block_ref.body.execution_requests, })), diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index b1b8b0deaaa..8b1619863a2 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -58,6 +58,7 @@ use types::{ }; mod block_hash; +pub mod eip8025; mod engine_api; pub mod engines; mod keccak; @@ -139,6 +140,7 @@ pub enum Error { NoEngine, NoPayloadBuilder, ApiError(ApiError), + ProofEngineError(eip8025::ProofEngineError), Builder(builder_client::Error), NoHeaderFromBuilder, CannotProduceHeader, @@ -196,6 +198,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: eip8025::ProofEngineError) -> Self { + Error::ProofEngineError(e) + } +} + pub enum BlockProposalContentsType { Full(BlockProposalContents>), Blinded(BlockProposalContents>), @@ -457,6 +465,8 @@ struct Inner { proposers: RwLock>, executor: TaskExecutor, payload_cache: PayloadCache, + proof_engine: Option>, + proof_types: eip8025::types::ProofTypes, /// Track whether the last `newPayload` call errored. /// /// This is used *only* in the informational sync status endpoint, so that a VC using this @@ -468,6 +478,11 @@ struct Inner { pub struct Config { /// Endpoint url for EL nodes that are running the engine api. pub execution_endpoint: Option, + /// Endpoint url for the optional EIP-8025 proof engine. + pub proof_engine_endpoint: Option, + /// Proof types to request from the proof engine when generating proofs. + #[serde(default)] + pub proof_types: eip8025::types::ProofTypes, /// Endpoint urls for services providing the builder api. pub builder_url: Option, /// The timeout value used when making a request to fetch a block header @@ -503,6 +518,8 @@ impl ExecutionLayer { pub fn from_config(config: Config, executor: TaskExecutor) -> Result { let Config { execution_endpoint: url, + proof_engine_endpoint, + proof_types, builder_url, builder_user_agent, builder_header_timeout, @@ -556,6 +573,8 @@ impl ExecutionLayer { .map_err(Error::ApiError)?; Engine::new(api, executor.clone()) }; + let proof_engine = + proof_engine_endpoint.map(|url| Arc::new(eip8025::HttpProofEngine::new(url, None))); let inner = Inner { engine: Arc::new(engine), @@ -566,6 +585,8 @@ impl ExecutionLayer { proposers: RwLock::new(HashMap::new()), executor, payload_cache: PayloadCache::default(), + proof_engine, + proof_types, last_new_payload_errored: RwLock::new(false), }; @@ -589,6 +610,14 @@ impl ExecutionLayer { &self.inner.engine } + pub fn proof_engine(&self) -> Option> { + self.inner.proof_engine.clone() + } + + pub fn proof_types(&self) -> &eip8025::types::ProofTypes { + &self.inner.proof_types + } + pub fn builder(&self) -> Option> { self.inner.builder.load_full() } diff --git a/beacon_node/http_api/src/beacon/pool.rs b/beacon_node/http_api/src/beacon/pool.rs index 3525567eb42..220137a64bf 100644 --- a/beacon_node/http_api/src/beacon/pool.rs +++ b/beacon_node/http_api/src/beacon/pool.rs @@ -16,6 +16,7 @@ use eth2::types::{AttestationPoolQuery, EndpointVersion, Failure, GenericRespons use lighthouse_network::PubsubMessage; use network::NetworkMessage; use operation_pool::ReceivedPreCapella; +use serde::{Deserialize, Serialize}; use slot_clock::SlotClock; use ssz::{Decode, Encode}; use std::collections::HashSet; @@ -24,8 +25,8 @@ use tokio::sync::mpsc::UnboundedSender; use tracing::{debug, error, info, warn}; use types::{ Attestation, AttestationData, AttesterSlashing, ForkName, PayloadAttestationMessage, - ProposerSlashing, SignedBlsToExecutionChange, SignedVoluntaryExit, SingleAttestation, - SyncCommitteeMessage, + ProofStatus, ProposerSlashing, SignedBlsToExecutionChange, SignedExecutionProof, + SignedVoluntaryExit, SingleAttestation, SyncCommitteeMessage, }; use warp::filters::BoxedFilter; use warp::{Filter, Reply}; @@ -45,6 +46,112 @@ pub type BeaconPoolPathAnyFilter = BoxedFilter<( Arc>, )>; +#[derive(Debug, Deserialize)] +pub struct SubmitExecutionProofsRequest { + pub proofs: Vec, +} + +#[derive(Debug, Serialize)] +pub struct SubmitExecutionProofsResponse { + pub statuses: Vec, +} + +#[derive(Debug, Serialize)] +pub struct SubmitExecutionProofStatus { + pub status: ProofStatus, + pub request_root: types::Hash256, + pub block_root: Option, + pub valid_proof_type_count: usize, + pub proof_backed_payload_promotion: bool, +} + +/// POST beacon/pool/execution_proofs +pub fn post_beacon_pool_execution_proofs( + beacon_pool_path: &BeaconPoolPathFilter, +) -> ResponseFilter { + beacon_pool_path + .clone() + .and(warp::path("execution_proofs")) + .and(warp::path::end()) + .and(warp_utils::json::json()) + .then( + |_task_spawner: TaskSpawner, + chain: Arc>, + request: SubmitExecutionProofsRequest| async move { + convert_rejection( + async move { + if chain + .execution_layer + .as_ref() + .and_then(|execution_layer| execution_layer.proof_engine()) + .is_none() + { + return Err(warp_utils::reject::custom_server_error( + "proof engine not configured".to_string(), + )); + } + + if request.proofs.is_empty() { + return Err(warp_utils::reject::custom_bad_request( + "no execution proofs provided".to_string(), + )); + } + + let mut statuses = Vec::with_capacity(request.proofs.len()); + let mut failures = vec![]; + + for (index, proof) in request.proofs.iter().enumerate() { + match chain.verify_and_observe_execution_proof(proof, None).await { + Ok(observation) => { + if observation.status == ProofStatus::Invalid { + failures.push(Failure::new( + index, + "execution proof is invalid".to_string(), + )); + } + statuses.push(SubmitExecutionProofStatus { + status: observation.status, + request_root: observation.request_root, + block_root: observation.block_root, + valid_proof_type_count: observation.valid_proof_type_count, + proof_backed_payload_promotion: observation + .proof_backed_payload_promotion, + }); + } + Err(error) => { + warn!( + ?error, + request_root = ?proof.request_root(), + proof_type = proof.proof_type(), + validator_index = proof.validator_index(), + "Execution proof validation failed" + ); + failures + .push(Failure::new(index, format!("invalid: {error:?}"))); + } + } + } + + if failures.is_empty() { + Ok( + warp::reply::json(&SubmitExecutionProofsResponse { statuses }) + .into_response(), + ) + } else { + Err(warp_utils::reject::indexed_bad_request( + "some execution proofs failed to verify".into(), + failures, + )) + } + } + .await, + ) + .await + }, + ) + .boxed() +} + /// POST beacon/pool/bls_to_execution_changes pub fn post_beacon_pool_bls_to_execution_changes( network_tx_filter: &NetworkTxFilter, diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 74bf1ccd764..fc8bbce94a6 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -229,6 +229,7 @@ pub fn prometheus_metrics() -> warp::filters::log::Log( let post_beacon_pool_bls_to_execution_changes = post_beacon_pool_bls_to_execution_changes(&network_tx_filter, &beacon_pool_path); + // POST beacon/pool/execution_proofs + let post_beacon_pool_execution_proofs = post_beacon_pool_execution_proofs(&beacon_pool_path); + // POST validator/proposer_preferences (JSON) let post_validator_proposer_preferences = post_validator_proposer_preferences( eth_v1.clone(), @@ -3459,6 +3463,7 @@ pub fn serve( .uor(post_beacon_pool_sync_committees) .uor(post_beacon_pool_payload_attestations) .uor(post_beacon_pool_bls_to_execution_changes) + .uor(post_beacon_pool_execution_proofs) .uor(post_validator_proposer_preferences) .uor(post_beacon_execution_payload_envelope) .uor(post_beacon_state_validators) diff --git a/beacon_node/lighthouse_network/src/config.rs b/beacon_node/lighthouse_network/src/config.rs index db42d0cfa8f..3876f7de9ab 100644 --- a/beacon_node/lighthouse_network/src/config.rs +++ b/beacon_node/lighthouse_network/src/config.rs @@ -125,6 +125,9 @@ pub struct Config { /// Whether light client protocols should be enabled. pub enable_light_client_server: bool, + /// Whether optional EIP-8025 execution proof gossip/RPC protocols should be enabled. + pub enable_execution_proof: bool, + /// Configuration for the outbound rate limiter (requests made by this node). pub outbound_rate_limiter_config: Option, @@ -362,6 +365,7 @@ impl Default for Config { proposer_only: false, metrics_enabled: false, enable_light_client_server: true, + enable_execution_proof: false, outbound_rate_limiter_config: None, invalid_block_storage: None, inbound_rate_limiter_config: None, diff --git a/beacon_node/lighthouse_network/src/discovery/enr.rs b/beacon_node/lighthouse_network/src/discovery/enr.rs index 01a01d55ab5..1faf7320945 100644 --- a/beacon_node/lighthouse_network/src/discovery/enr.rs +++ b/beacon_node/lighthouse_network/src/discovery/enr.rs @@ -29,6 +29,8 @@ pub const ATTESTATION_BITFIELD_ENR_KEY: &str = "attnets"; pub const SYNC_COMMITTEE_BITFIELD_ENR_KEY: &str = "syncnets"; /// The ENR field specifying the peerdas custody group count. pub const PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY: &str = "cgc"; +/// The ENR field indicating execution proof support. +pub const EXECUTION_PROOF_ENR_KEY: &str = "eproof"; /// Extension trait for ENR's within Eth2. pub trait Eth2Enr { @@ -47,6 +49,9 @@ pub trait Eth2Enr { fn next_fork_digest(&self) -> Result<[u8; 4], &'static str>; fn eth2(&self) -> Result; + + /// Whether this node advertises optional execution proof support. + fn execution_proof_enabled(&self) -> bool; } impl Eth2Enr for Enr { @@ -99,6 +104,12 @@ impl Eth2Enr for Enr { EnrForkId::from_ssz_bytes(ð2_bytes).map_err(|_| "Could not decode EnrForkId") } + + fn execution_proof_enabled(&self) -> bool { + self.get_decodable::(EXECUTION_PROOF_ENR_KEY) + .and_then(|r| r.ok()) + .unwrap_or(false) + } } /// Either use the given ENR or load an ENR from file if it exists and matches the current NodeId @@ -296,6 +307,10 @@ pub fn build_enr( builder.add_value(NEXT_FORK_DIGEST_ENR_KEY, &next_fork_digest); } + if config.enable_execution_proof { + builder.add_value(EXECUTION_PROOF_ENR_KEY, &true); + } + builder .build(enr_key) .map_err(|e| format!("Could not build Local ENR: {:?}", e)) @@ -325,6 +340,7 @@ fn compare_enr(local_enr: &Enr, disk_enr: &Enr) -> bool { && local_enr.get_decodable::(ATTESTATION_BITFIELD_ENR_KEY) == disk_enr.get_decodable(ATTESTATION_BITFIELD_ENR_KEY) && local_enr.get_decodable::(SYNC_COMMITTEE_BITFIELD_ENR_KEY) == disk_enr.get_decodable(SYNC_COMMITTEE_BITFIELD_ENR_KEY) && local_enr.get_decodable::(PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY) == disk_enr.get_decodable(PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY) + && local_enr.get_decodable::(EXECUTION_PROOF_ENR_KEY) == disk_enr.get_decodable(EXECUTION_PROOF_ENR_KEY) } /// Loads enr from the given directory diff --git a/beacon_node/lighthouse_network/src/peer_manager/mod.rs b/beacon_node/lighthouse_network/src/peer_manager/mod.rs index 6b5144fa6fd..16125797915 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/mod.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/mod.rs @@ -593,6 +593,9 @@ impl PeerManager { Protocol::BlobsByRange => PeerAction::MidToleranceError, Protocol::PayloadEnvelopesByRange => PeerAction::MidToleranceError, Protocol::PayloadEnvelopesByRoot => PeerAction::MidToleranceError, + Protocol::ExecutionProofsByRange => PeerAction::MidToleranceError, + Protocol::ExecutionProofsByRoot => PeerAction::MidToleranceError, + Protocol::ExecutionProofStatus => PeerAction::MidToleranceError, // Lighthouse does not currently make light client requests; therefore, this // is an unexpected scenario. We do not ban the peer for rate limiting. Protocol::LightClientBootstrap => return, @@ -621,6 +624,9 @@ impl PeerManager { Protocol::BlocksByHead => return, Protocol::PayloadEnvelopesByRange => return, Protocol::PayloadEnvelopesByRoot => return, + Protocol::ExecutionProofsByRange => return, + Protocol::ExecutionProofsByRoot => return, + Protocol::ExecutionProofStatus => return, Protocol::BlobsByRange => return, Protocol::BlobsByRoot => return, Protocol::DataColumnsByRoot => return, @@ -647,6 +653,9 @@ impl PeerManager { Protocol::BlocksByHead => PeerAction::MidToleranceError, Protocol::PayloadEnvelopesByRange => PeerAction::MidToleranceError, Protocol::PayloadEnvelopesByRoot => PeerAction::MidToleranceError, + Protocol::ExecutionProofsByRange => PeerAction::MidToleranceError, + Protocol::ExecutionProofsByRoot => PeerAction::MidToleranceError, + Protocol::ExecutionProofStatus => PeerAction::MidToleranceError, Protocol::BlobsByRange => PeerAction::MidToleranceError, Protocol::BlobsByRoot => PeerAction::MidToleranceError, Protocol::DataColumnsByRoot => PeerAction::MidToleranceError, diff --git a/beacon_node/lighthouse_network/src/rpc/codec.rs b/beacon_node/lighthouse_network/src/rpc/codec.rs index ba95fff5e8e..0f73a941386 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec.rs @@ -15,14 +15,14 @@ use std::io::{Read, Write}; use std::marker::PhantomData; use std::sync::Arc; use tokio_util::codec::{Decoder, Encoder}; -use types::SignedExecutionPayloadEnvelope; use types::{ BlobSidecar, ChainSpec, DataColumnSidecar, DataColumnsByRootIdentifier, EthSpec, ForkContext, ForkName, ForkVersionDecode, Hash256, LightClientBootstrap, LightClientFinalityUpdate, LightClientOptimisticUpdate, LightClientUpdate, SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockBellatrix, SignedBeaconBlockCapella, SignedBeaconBlockDeneb, SignedBeaconBlockElectra, SignedBeaconBlockFulu, - SignedBeaconBlockGloas, + SignedBeaconBlockGloas, SignedExecutionPayloadEnvelope, + execution::eip8025::SignedExecutionProof, }; use unsigned_varint::codec::Uvi; @@ -88,6 +88,9 @@ impl SSZSnappyInboundCodec { RpcSuccessResponse::LightClientOptimisticUpdate(res) => res.as_ssz_bytes(), RpcSuccessResponse::LightClientFinalityUpdate(res) => res.as_ssz_bytes(), RpcSuccessResponse::LightClientUpdatesByRange(res) => res.as_ssz_bytes(), + RpcSuccessResponse::ExecutionProofsByRange(res) => res.as_ssz_bytes(), + RpcSuccessResponse::ExecutionProofsByRoot(res) => res.as_ssz_bytes(), + RpcSuccessResponse::ExecutionProofStatus(res) => res.as_ssz_bytes(), RpcSuccessResponse::Pong(res) => res.data.as_ssz_bytes(), RpcSuccessResponse::MetaData(res) => // Encode the correct version of the MetaData response based on the negotiated version. @@ -370,6 +373,9 @@ impl Encoder> for SSZSnappyOutboundCodec { RequestType::Ping(req) => req.as_ssz_bytes(), RequestType::LightClientBootstrap(req) => req.as_ssz_bytes(), RequestType::LightClientUpdatesByRange(req) => req.as_ssz_bytes(), + RequestType::ExecutionProofsByRange(req) => req.as_ssz_bytes(), + RequestType::ExecutionProofsByRoot(req) => req.identifiers.as_ssz_bytes(), + RequestType::ExecutionProofStatus(req) => req.as_ssz_bytes(), // no metadata to encode RequestType::MetaData(_) | RequestType::LightClientOptimisticUpdate @@ -613,6 +619,22 @@ fn handle_rpc_request( LightClientUpdatesByRangeRequest::from_ssz_bytes(decoded_buffer)?, ))) } + SupportedProtocol::ExecutionProofsByRangeV1 => { + Ok(Some(RequestType::ExecutionProofsByRange( + ExecutionProofsByRangeRequest::from_ssz_bytes(decoded_buffer)?, + ))) + } + SupportedProtocol::ExecutionProofsByRootV1 => Ok(Some(RequestType::ExecutionProofsByRoot( + ExecutionProofsByRootRequest { + identifiers: RuntimeVariableList::from_ssz_bytes( + decoded_buffer, + spec.max_request_blocks(current_fork), + )?, + }, + ))), + SupportedProtocol::ExecutionProofStatusV1 => Ok(Some(RequestType::ExecutionProofStatus( + ExecutionProofStatus::from_ssz_bytes(decoded_buffer)?, + ))), // MetaData requests return early from InboundUpgrade and do not reach the decoder. // Handle this case just for completeness. SupportedProtocol::MetaDataV3 => { @@ -862,6 +884,21 @@ fn handle_rpc_response( ), )), }, + SupportedProtocol::ExecutionProofsByRangeV1 => { + Ok(Some(RpcSuccessResponse::ExecutionProofsByRange(Arc::new( + SignedExecutionProof::from_ssz_bytes(decoded_buffer)?, + )))) + } + SupportedProtocol::ExecutionProofsByRootV1 => { + Ok(Some(RpcSuccessResponse::ExecutionProofsByRoot(Arc::new( + SignedExecutionProof::from_ssz_bytes(decoded_buffer)?, + )))) + } + SupportedProtocol::ExecutionProofStatusV1 => { + Ok(Some(RpcSuccessResponse::ExecutionProofStatus( + ExecutionProofStatus::from_ssz_bytes(decoded_buffer)?, + ))) + } // MetaData V2/V3 responses have no context bytes, so behave similarly to V1 responses SupportedProtocol::MetaDataV3 => Ok(Some(RpcSuccessResponse::MetaData(Arc::new( MetaData::V3(MetaDataV3::from_ssz_bytes(decoded_buffer)?), diff --git a/beacon_node/lighthouse_network/src/rpc/config.rs b/beacon_node/lighthouse_network/src/rpc/config.rs index 59f0b8e9a2f..80628548058 100644 --- a/beacon_node/lighthouse_network/src/rpc/config.rs +++ b/beacon_node/lighthouse_network/src/rpc/config.rs @@ -92,6 +92,9 @@ pub struct RateLimiterConfig { pub(super) blocks_by_head_quota: Quota, pub(super) payload_envelopes_by_range_quota: Quota, pub(super) payload_envelopes_by_root_quota: Quota, + pub(super) execution_proofs_by_range_quota: Quota, + pub(super) execution_proofs_by_root_quota: Quota, + pub(super) execution_proof_status_quota: Quota, pub(super) blobs_by_range_quota: Quota, pub(super) blobs_by_root_quota: Quota, pub(super) data_columns_by_root_quota: Quota, @@ -120,6 +123,12 @@ impl RateLimiterConfig { Quota::n_every(NonZeroU64::new(128).unwrap(), 10); pub const DEFAULT_PAYLOAD_ENVELOPES_BY_ROOT_QUOTA: Quota = Quota::n_every(NonZeroU64::new(128).unwrap(), 10); + pub const DEFAULT_EXECUTION_PROOFS_BY_RANGE_QUOTA: Quota = + Quota::n_every(NonZeroU64::new(128).unwrap(), 10); + pub const DEFAULT_EXECUTION_PROOFS_BY_ROOT_QUOTA: Quota = + Quota::n_every(NonZeroU64::new(128).unwrap(), 10); + pub const DEFAULT_EXECUTION_PROOF_STATUS_QUOTA: Quota = + Quota::n_every(NonZeroU64::new(128).unwrap(), 10); // `DEFAULT_BLOCKS_BY_RANGE_QUOTA` * (target + 1) to account for high usage pub const DEFAULT_BLOBS_BY_RANGE_QUOTA: Quota = Quota::n_every(NonZeroU64::new(896).unwrap(), 10); @@ -149,6 +158,9 @@ impl Default for RateLimiterConfig { blocks_by_head_quota: Self::DEFAULT_BLOCKS_BY_HEAD_QUOTA, payload_envelopes_by_range_quota: Self::DEFAULT_PAYLOAD_ENVELOPES_BY_RANGE_QUOTA, payload_envelopes_by_root_quota: Self::DEFAULT_PAYLOAD_ENVELOPES_BY_ROOT_QUOTA, + execution_proofs_by_range_quota: Self::DEFAULT_EXECUTION_PROOFS_BY_RANGE_QUOTA, + execution_proofs_by_root_quota: Self::DEFAULT_EXECUTION_PROOFS_BY_ROOT_QUOTA, + execution_proof_status_quota: Self::DEFAULT_EXECUTION_PROOF_STATUS_QUOTA, blobs_by_range_quota: Self::DEFAULT_BLOBS_BY_RANGE_QUOTA, blobs_by_root_quota: Self::DEFAULT_BLOBS_BY_ROOT_QUOTA, data_columns_by_root_quota: Self::DEFAULT_DATA_COLUMNS_BY_ROOT_QUOTA, @@ -190,6 +202,18 @@ impl Debug for RateLimiterConfig { "payload_envelopes_by_root", fmt_q!(&self.payload_envelopes_by_root_quota), ) + .field( + "execution_proofs_by_range", + fmt_q!(&self.execution_proofs_by_range_quota), + ) + .field( + "execution_proofs_by_root", + fmt_q!(&self.execution_proofs_by_root_quota), + ) + .field( + "execution_proof_status", + fmt_q!(&self.execution_proof_status_quota), + ) .field("blobs_by_range", fmt_q!(&self.blobs_by_range_quota)) .field("blobs_by_root", fmt_q!(&self.blobs_by_root_quota)) .field( @@ -221,6 +245,9 @@ impl FromStr for RateLimiterConfig { let mut blocks_by_head_quota = None; let mut payload_envelopes_by_range_quota = None; let mut payload_envelopes_by_root_quota = None; + let mut execution_proofs_by_range_quota = None; + let mut execution_proofs_by_root_quota = None; + let mut execution_proof_status_quota = None; let mut blobs_by_range_quota = None; let mut blobs_by_root_quota = None; let mut data_columns_by_root_quota = None; @@ -245,6 +272,15 @@ impl FromStr for RateLimiterConfig { Protocol::PayloadEnvelopesByRoot => { payload_envelopes_by_root_quota = payload_envelopes_by_root_quota.or(quota) } + Protocol::ExecutionProofsByRange => { + execution_proofs_by_range_quota = execution_proofs_by_range_quota.or(quota) + } + Protocol::ExecutionProofsByRoot => { + execution_proofs_by_root_quota = execution_proofs_by_root_quota.or(quota) + } + Protocol::ExecutionProofStatus => { + execution_proof_status_quota = execution_proof_status_quota.or(quota) + } Protocol::BlobsByRange => blobs_by_range_quota = blobs_by_range_quota.or(quota), Protocol::BlobsByRoot => blobs_by_root_quota = blobs_by_root_quota.or(quota), Protocol::DataColumnsByRoot => { @@ -287,6 +323,12 @@ impl FromStr for RateLimiterConfig { .unwrap_or(Self::DEFAULT_PAYLOAD_ENVELOPES_BY_RANGE_QUOTA), payload_envelopes_by_root_quota: payload_envelopes_by_root_quota .unwrap_or(Self::DEFAULT_PAYLOAD_ENVELOPES_BY_ROOT_QUOTA), + execution_proofs_by_range_quota: execution_proofs_by_range_quota + .unwrap_or(Self::DEFAULT_EXECUTION_PROOFS_BY_RANGE_QUOTA), + execution_proofs_by_root_quota: execution_proofs_by_root_quota + .unwrap_or(Self::DEFAULT_EXECUTION_PROOFS_BY_ROOT_QUOTA), + execution_proof_status_quota: execution_proof_status_quota + .unwrap_or(Self::DEFAULT_EXECUTION_PROOF_STATUS_QUOTA), blobs_by_range_quota: blobs_by_range_quota .unwrap_or(Self::DEFAULT_BLOBS_BY_RANGE_QUOTA), blobs_by_root_quota: blobs_by_root_quota.unwrap_or(Self::DEFAULT_BLOBS_BY_ROOT_QUOTA), diff --git a/beacon_node/lighthouse_network/src/rpc/methods.rs b/beacon_node/lighthouse_network/src/rpc/methods.rs index f3f294d9135..1e4a1b77ff8 100644 --- a/beacon_node/lighthouse_network/src/rpc/methods.rs +++ b/beacon_node/lighthouse_network/src/rpc/methods.rs @@ -12,7 +12,11 @@ use std::ops::Deref; use std::sync::Arc; use strum::IntoStaticStr; use superstruct::superstruct; +use typenum::Unsigned; use types::data::BlobIdentifier; +use types::execution::eip8025::{ + MaxExecutionProofsPerPayload, ProofByRootIdentifier, ProofType, SignedExecutionProof, +}; use types::light_client::consts::MAX_REQUEST_LIGHT_CLIENT_UPDATES; use types::{ BlobSidecar, ChainSpec, ColumnIndex, DataColumnSidecar, DataColumnsByRootIdentifier, Epoch, @@ -619,6 +623,108 @@ impl LightClientUpdatesByRangeRequest { } } +/// The peer's current execution proof verification status, exchanged via the +/// `ExecutionProofStatus` RPC protocol. +#[derive(Clone, Debug, Default, PartialEq, Encode, Decode)] +pub struct ExecutionProofStatus { + /// The block root of the latest block this peer can use as a proof-sync anchor. + pub block_root: Hash256, + /// The slot of the latest block this peer can use as a proof-sync anchor. + pub slot: u64, + /// Proof types supported by this peer. + pub proof_types: VariableList, +} + +impl ExecutionProofStatus { + pub fn ssz_min_len() -> usize { + 32 + 8 + ssz::BYTES_PER_LENGTH_OFFSET + } + + pub fn ssz_max_len() -> usize { + use typenum::Unsigned; + Self::ssz_min_len() + MaxExecutionProofsPerPayload::USIZE + } +} + +/// Request execution proofs for a slot range from a peer. +#[derive(Clone, Debug, PartialEq)] +pub struct ExecutionProofsByRangeRequest { + /// The starting slot to request execution proofs. + pub start_slot: u64, + /// The number of slots from the start slot. + pub count: u64, + /// Proof types to return across the requested range. Empty list means all known proof types. + pub proof_types: RuntimeVariableList, +} + +impl ExecutionProofsByRangeRequest { + pub fn max_requested(&self) -> u64 { + use typenum::Unsigned; + self.count + .saturating_mul(MaxExecutionProofsPerPayload::to_u64()) + } + + pub fn ssz_min_len() -> usize { + 20 + } + + pub fn ssz_max_len() -> usize { + use typenum::Unsigned; + Self::ssz_min_len() + MaxExecutionProofsPerPayload::USIZE + } + + pub fn from_ssz_bytes(bytes: &[u8]) -> Result { + let mut builder = ssz::SszDecoderBuilder::new(bytes); + builder.register_type::()?; + builder.register_type::()?; + builder.register_type::>()?; + let mut decoder = builder.build()?; + Ok(Self { + start_slot: decoder.decode_next::()?, + count: decoder.decode_next::()?, + proof_types: decoder.decode_next_with(|slice| { + RuntimeVariableList::from_ssz_bytes(slice, MaxExecutionProofsPerPayload::to_usize()) + })?, + }) + } +} + +impl ssz::Encode for ExecutionProofsByRangeRequest { + fn is_ssz_fixed_len() -> bool { + false + } + + fn ssz_append(&self, buf: &mut Vec) { + let mut encoder = ssz::SszEncoder::container(buf, 3); + encoder.append(&self.start_slot); + encoder.append(&self.count); + encoder.append(&self.proof_types); + encoder.finalize(); + } + + fn ssz_bytes_len(&self) -> usize { + Self::ssz_min_len() + self.proof_types.ssz_bytes_len() + } +} + +/// Request execution proofs for specific blocks by root from a peer. +#[derive(Clone, Debug, PartialEq)] +pub struct ExecutionProofsByRootRequest { + /// Each entry identifies a block root and the proof types requested for it. + pub identifiers: RuntimeVariableList, +} + +impl ExecutionProofsByRootRequest { + pub fn new( + identifiers: Vec, + max_request_blocks: usize, + ) -> Result { + let identifiers = RuntimeVariableList::new(identifiers, max_request_blocks) + .map_err(|e| format!("ExecutionProofsByRootRequest too many identifiers: {e:?}"))?; + Ok(Self { identifiers }) + } +} + /* RPC Handling and Grouping */ // Collection of enums and structs used by the Codecs to encode/decode RPC messages @@ -668,6 +774,15 @@ pub enum RpcSuccessResponse { /// A response to a get DATA_COLUMN_SIDECARS_BY_RANGE request. DataColumnsByRange(Arc>), + /// A response to a get EXECUTION_PROOFS_BY_RANGE request. + ExecutionProofsByRange(Arc), + + /// A response to a get EXECUTION_PROOFS_BY_ROOT request. + ExecutionProofsByRoot(Arc), + + /// A response to an EXECUTION_PROOF_STATUS request. + ExecutionProofStatus(ExecutionProofStatus), + /// A PONG response to a PING request. Pong(Ping), @@ -707,6 +822,12 @@ pub enum ResponseTermination { /// Light client updates by range stream termination. LightClientUpdatesByRange, + + /// Execution proofs by range stream termination. + ExecutionProofsByRange, + + /// Execution proofs by root stream termination. + ExecutionProofsByRoot, } impl ResponseTermination { @@ -722,6 +843,8 @@ impl ResponseTermination { ResponseTermination::DataColumnsByRoot => Protocol::DataColumnsByRoot, ResponseTermination::DataColumnsByRange => Protocol::DataColumnsByRange, ResponseTermination::LightClientUpdatesByRange => Protocol::LightClientUpdatesByRange, + ResponseTermination::ExecutionProofsByRange => Protocol::ExecutionProofsByRange, + ResponseTermination::ExecutionProofsByRoot => Protocol::ExecutionProofsByRoot, } } } @@ -827,6 +950,9 @@ impl RpcSuccessResponse { } RpcSuccessResponse::LightClientFinalityUpdate(_) => Protocol::LightClientFinalityUpdate, RpcSuccessResponse::LightClientUpdatesByRange(_) => Protocol::LightClientUpdatesByRange, + RpcSuccessResponse::ExecutionProofsByRange(_) => Protocol::ExecutionProofsByRange, + RpcSuccessResponse::ExecutionProofsByRoot(_) => Protocol::ExecutionProofsByRoot, + RpcSuccessResponse::ExecutionProofStatus(_) => Protocol::ExecutionProofStatus, } } @@ -843,6 +969,9 @@ impl RpcSuccessResponse { Self::LightClientOptimisticUpdate(r) => Some(r.get_slot()), Self::LightClientUpdatesByRange(r) => Some(r.attested_header_slot()), Self::MetaData(_) | Self::Status(_) | Self::Pong(_) => None, + Self::ExecutionProofsByRange(_) + | Self::ExecutionProofsByRoot(_) + | Self::ExecutionProofStatus(_) => None, } } } @@ -947,6 +1076,23 @@ impl std::fmt::Display for RpcSuccessResponse { update.signature_slot(), ) } + RpcSuccessResponse::ExecutionProofsByRange(proof) => { + write!( + f, + "ExecutionProofsByRange: validator_index: {}", + proof.validator_index + ) + } + RpcSuccessResponse::ExecutionProofsByRoot(proof) => { + write!( + f, + "ExecutionProofsByRoot: validator_index: {}", + proof.validator_index + ) + } + RpcSuccessResponse::ExecutionProofStatus(status) => { + write!(f, "ExecutionProofStatus: slot={}", status.slot) + } } } } @@ -1039,3 +1185,25 @@ impl std::fmt::Display for DataColumnsByRootRequest { ) } } + +impl std::fmt::Display for ExecutionProofsByRangeRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Request: ExecutionProofsByRange: Start Slot: {}, Count: {}, Proof Types: {}", + self.start_slot, + self.count, + self.proof_types.len() + ) + } +} + +impl std::fmt::Display for ExecutionProofsByRootRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Request: ExecutionProofsByRoot: Number of Requested Identifiers: {}", + self.identifiers.len() + ) + } +} diff --git a/beacon_node/lighthouse_network/src/rpc/mod.rs b/beacon_node/lighthouse_network/src/rpc/mod.rs index 7c43018af83..6c7b1e2d781 100644 --- a/beacon_node/lighthouse_network/src/rpc/mod.rs +++ b/beacon_node/lighthouse_network/src/rpc/mod.rs @@ -155,6 +155,7 @@ pub struct RPC { events: Vec>, fork_context: Arc, enable_light_client_server: bool, + enable_execution_proof: bool, /// A sequential counter indicating when data gets modified. seq_number: u64, } @@ -163,6 +164,7 @@ impl RPC { pub fn new( fork_context: Arc, enable_light_client_server: bool, + enable_execution_proof: bool, inbound_rate_limiter_config: Option, outbound_rate_limiter_config: Option, seq_number: u64, @@ -184,6 +186,7 @@ impl RPC { events: Vec::new(), fork_context, enable_light_client_server, + enable_execution_proof, seq_number, } } @@ -319,6 +322,7 @@ where fork_context: self.fork_context.clone(), max_rpc_size: self.fork_context.spec.max_payload_size as usize, enable_light_client_server: self.enable_light_client_server, + enable_execution_proof: self.enable_execution_proof, phantom: PhantomData, }, (), @@ -342,6 +346,7 @@ where fork_context: self.fork_context.clone(), max_rpc_size: self.fork_context.spec.max_payload_size as usize, enable_light_client_server: self.enable_light_client_server, + enable_execution_proof: self.enable_execution_proof, phantom: PhantomData, }, (), diff --git a/beacon_node/lighthouse_network/src/rpc/protocol.rs b/beacon_node/lighthouse_network/src/rpc/protocol.rs index 056ffc03b85..d1c6a8cde3c 100644 --- a/beacon_node/lighthouse_network/src/rpc/protocol.rs +++ b/beacon_node/lighthouse_network/src/rpc/protocol.rs @@ -16,6 +16,8 @@ use tokio_util::{ codec::Framed, compat::{Compat, FuturesAsyncReadCompatExt}, }; +use typenum::Unsigned; +use types::execution::eip8025::{MaxExecutionProofsPerPayload, MaxProofSize}; use types::{ BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BlobSidecar, ChainSpec, DataColumnSidecarFulu, DataColumnSidecarGloas, EmptyBlock, Epoch, EthSpec, EthSpecId, ForkContext, ForkName, @@ -125,6 +127,13 @@ pub static LIGHT_CLIENT_UPDATES_BY_RANGE_DENEB_MAX: LazyLock = pub static LIGHT_CLIENT_UPDATES_BY_RANGE_ELECTRA_MAX: LazyLock = LazyLock::new(|| LightClientUpdate::::ssz_max_len_for_fork(ForkName::Electra)); +/// Minimum SSZ size of a `SignedExecutionProof` with empty proof data. +pub const SIGNED_EXECUTION_PROOF_MIN_SIZE: usize = 4 + 8 + 96 + 37; + +/// Maximum SSZ size of a `SignedExecutionProof`. +pub const SIGNED_EXECUTION_PROOF_MAX_SIZE: usize = + SIGNED_EXECUTION_PROOF_MIN_SIZE + MaxProofSize::USIZE; + /// The protocol prefix the RPC protocol id. const PROTOCOL_PREFIX: &str = "/eth2/beacon_chain/req"; /// The number of seconds to wait for the first bytes of a request once a protocol has been @@ -300,6 +309,15 @@ pub enum Protocol { /// The `LightClientUpdatesByRange` protocol name #[strum(serialize = "light_client_updates_by_range")] LightClientUpdatesByRange, + /// The `ExecutionProofsByRange` protocol name. + #[strum(serialize = "execution_proofs_by_range")] + ExecutionProofsByRange, + /// The `ExecutionProofsByRoot` protocol name. + #[strum(serialize = "execution_proofs_by_root")] + ExecutionProofsByRoot, + /// The `ExecutionProofStatus` protocol name. + #[strum(serialize = "execution_proof_status")] + ExecutionProofStatus, } impl Protocol { @@ -322,6 +340,9 @@ impl Protocol { Protocol::LightClientOptimisticUpdate => None, Protocol::LightClientFinalityUpdate => None, Protocol::LightClientUpdatesByRange => None, + Protocol::ExecutionProofsByRange => Some(ResponseTermination::ExecutionProofsByRange), + Protocol::ExecutionProofsByRoot => Some(ResponseTermination::ExecutionProofsByRoot), + Protocol::ExecutionProofStatus => None, } } } @@ -357,6 +378,9 @@ pub enum SupportedProtocol { LightClientOptimisticUpdateV1, LightClientFinalityUpdateV1, LightClientUpdatesByRangeV1, + ExecutionProofsByRangeV1, + ExecutionProofsByRootV1, + ExecutionProofStatusV1, } impl SupportedProtocol { @@ -384,6 +408,9 @@ impl SupportedProtocol { SupportedProtocol::LightClientOptimisticUpdateV1 => "1", SupportedProtocol::LightClientFinalityUpdateV1 => "1", SupportedProtocol::LightClientUpdatesByRangeV1 => "1", + SupportedProtocol::ExecutionProofsByRangeV1 => "1", + SupportedProtocol::ExecutionProofsByRootV1 => "1", + SupportedProtocol::ExecutionProofStatusV1 => "1", } } @@ -413,6 +440,9 @@ impl SupportedProtocol { } SupportedProtocol::LightClientFinalityUpdateV1 => Protocol::LightClientFinalityUpdate, SupportedProtocol::LightClientUpdatesByRangeV1 => Protocol::LightClientUpdatesByRange, + SupportedProtocol::ExecutionProofsByRangeV1 => Protocol::ExecutionProofsByRange, + SupportedProtocol::ExecutionProofsByRootV1 => Protocol::ExecutionProofsByRoot, + SupportedProtocol::ExecutionProofStatusV1 => Protocol::ExecutionProofStatus, } } @@ -490,6 +520,7 @@ pub struct RPCProtocol { pub fork_context: Arc, pub max_rpc_size: usize, pub enable_light_client_server: bool, + pub enable_execution_proof: bool, pub phantom: PhantomData, } @@ -518,6 +549,20 @@ impl UpgradeInfo for RPCProtocol { Encoding::SSZSnappy, )); } + if self.enable_execution_proof { + supported_protocols.push(ProtocolId::new( + SupportedProtocol::ExecutionProofsByRangeV1, + Encoding::SSZSnappy, + )); + supported_protocols.push(ProtocolId::new( + SupportedProtocol::ExecutionProofsByRootV1, + Encoding::SSZSnappy, + )); + supported_protocols.push(ProtocolId::new( + SupportedProtocol::ExecutionProofStatusV1, + Encoding::SSZSnappy, + )); + } supported_protocols } } @@ -614,6 +659,20 @@ impl ProtocolId { LightClientUpdatesByRangeRequest::ssz_max_len(), ), Protocol::MetaData => RpcLimits::new(0, 0), // Metadata requests are empty + Protocol::ExecutionProofsByRange => RpcLimits::new( + ExecutionProofsByRangeRequest::ssz_min_len(), + ExecutionProofsByRangeRequest::ssz_max_len(), + ), + Protocol::ExecutionProofsByRoot => { + let max = spec + .max_blocks_by_root_request + .saturating_mul(4 + 32 + 4 + MaxExecutionProofsPerPayload::USIZE); + RpcLimits::new(0, max) + } + Protocol::ExecutionProofStatus => RpcLimits::new( + ExecutionProofStatus::ssz_min_len(), + ExecutionProofStatus::ssz_max_len(), + ), } } @@ -658,6 +717,14 @@ impl ProtocolId { Protocol::LightClientUpdatesByRange => { rpc_light_client_updates_by_range_limits_by_fork(fork_context.current_fork_name()) } + Protocol::ExecutionProofsByRange | Protocol::ExecutionProofsByRoot => RpcLimits::new( + SIGNED_EXECUTION_PROOF_MIN_SIZE, + SIGNED_EXECUTION_PROOF_MAX_SIZE, + ), + Protocol::ExecutionProofStatus => RpcLimits::new( + ExecutionProofStatus::ssz_min_len(), + ExecutionProofStatus::ssz_max_len(), + ), } } @@ -686,7 +753,10 @@ impl ProtocolId { | SupportedProtocol::MetaDataV1 | SupportedProtocol::MetaDataV2 | SupportedProtocol::MetaDataV3 - | SupportedProtocol::GoodbyeV1 => false, + | SupportedProtocol::GoodbyeV1 + | SupportedProtocol::ExecutionProofsByRangeV1 + | SupportedProtocol::ExecutionProofsByRootV1 + | SupportedProtocol::ExecutionProofStatusV1 => false, } } } @@ -832,6 +902,9 @@ pub enum RequestType { LightClientOptimisticUpdate, LightClientFinalityUpdate, LightClientUpdatesByRange(LightClientUpdatesByRangeRequest), + ExecutionProofsByRange(ExecutionProofsByRangeRequest), + ExecutionProofsByRoot(ExecutionProofsByRootRequest), + ExecutionProofStatus(ExecutionProofStatus), Ping(Ping), MetaData(MetadataRequest), } @@ -860,6 +933,10 @@ impl RequestType { RequestType::LightClientOptimisticUpdate => 1, RequestType::LightClientFinalityUpdate => 1, RequestType::LightClientUpdatesByRange(req) => req.count, + RequestType::ExecutionProofsByRange(req) => req.max_requested(), + RequestType::ExecutionProofsByRoot(req) => (req.identifiers.len() as u64) + .saturating_mul(MaxExecutionProofsPerPayload::to_u64()), + RequestType::ExecutionProofStatus(_) => 1, } } @@ -902,6 +979,9 @@ impl RequestType { RequestType::LightClientUpdatesByRange(_) => { SupportedProtocol::LightClientUpdatesByRangeV1 } + RequestType::ExecutionProofsByRange(_) => SupportedProtocol::ExecutionProofsByRangeV1, + RequestType::ExecutionProofsByRoot(_) => SupportedProtocol::ExecutionProofsByRootV1, + RequestType::ExecutionProofStatus(_) => SupportedProtocol::ExecutionProofStatusV1, } } @@ -920,6 +1000,8 @@ impl RequestType { RequestType::BlobsByRoot(_) => ResponseTermination::BlobsByRoot, RequestType::DataColumnsByRoot(_) => ResponseTermination::DataColumnsByRoot, RequestType::DataColumnsByRange(_) => ResponseTermination::DataColumnsByRange, + RequestType::ExecutionProofsByRange(_) => ResponseTermination::ExecutionProofsByRange, + RequestType::ExecutionProofsByRoot(_) => ResponseTermination::ExecutionProofsByRoot, RequestType::Status(_) => unreachable!(), RequestType::Goodbye(_) => unreachable!(), RequestType::Ping(_) => unreachable!(), @@ -928,6 +1010,7 @@ impl RequestType { RequestType::LightClientFinalityUpdate => unreachable!(), RequestType::LightClientOptimisticUpdate => unreachable!(), RequestType::LightClientUpdatesByRange(_) => unreachable!(), + RequestType::ExecutionProofStatus(_) => unreachable!(), } } @@ -1003,6 +1086,18 @@ impl RequestType { SupportedProtocol::LightClientUpdatesByRangeV1, Encoding::SSZSnappy, )], + RequestType::ExecutionProofsByRange(_) => vec![ProtocolId::new( + SupportedProtocol::ExecutionProofsByRangeV1, + Encoding::SSZSnappy, + )], + RequestType::ExecutionProofsByRoot(_) => vec![ProtocolId::new( + SupportedProtocol::ExecutionProofsByRootV1, + Encoding::SSZSnappy, + )], + RequestType::ExecutionProofStatus(_) => vec![ProtocolId::new( + SupportedProtocol::ExecutionProofStatusV1, + Encoding::SSZSnappy, + )], } } @@ -1025,6 +1120,9 @@ impl RequestType { RequestType::LightClientOptimisticUpdate => true, RequestType::LightClientFinalityUpdate => true, RequestType::LightClientUpdatesByRange(_) => true, + RequestType::ExecutionProofsByRange(_) => false, + RequestType::ExecutionProofsByRoot(_) => false, + RequestType::ExecutionProofStatus(_) => true, } } } @@ -1153,6 +1251,11 @@ impl std::fmt::Display for RequestType { RequestType::LightClientUpdatesByRange(_) => { write!(f, "Light client updates by range request") } + RequestType::ExecutionProofsByRange(req) => write!(f, "{}", req), + RequestType::ExecutionProofsByRoot(req) => write!(f, "{}", req), + RequestType::ExecutionProofStatus(status) => { + write!(f, "ExecutionProofStatus(slot={})", status.slot) + } } } } @@ -1244,6 +1347,7 @@ mod tests { fork_context: fork_context.clone(), max_rpc_size: spec.max_payload_size as usize, enable_light_client_server: true, + enable_execution_proof: false, phantom: PhantomData, }; let protocol_info: HashSet = rpc_protocol diff --git a/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs b/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs index a5c98a4d309..0e6c14115d3 100644 --- a/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs +++ b/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs @@ -115,6 +115,12 @@ pub struct RPCRateLimiter { envrange_rl: Limiter, /// PayloadEnvelopesByRoot rate limiter. envroots_rl: Limiter, + /// ExecutionProofsByRange rate limiter. + proofrange_rl: Limiter, + /// ExecutionProofsByRoot rate limiter. + proofroots_rl: Limiter, + /// ExecutionProofStatus rate limiter. + proofstatus_rl: Limiter, /// DataColumnsByRoot rate limiter. dcbroot_rl: Limiter, /// DataColumnsByRange rate limiter. @@ -160,6 +166,12 @@ pub struct RPCRateLimiterBuilder { perange_quota: Option, /// Quota for the ExecutionPayloadEnvelopesByRoot protocol. peroots_quota: Option, + /// Quota for the ExecutionProofsByRange protocol. + proofrange_quota: Option, + /// Quota for the ExecutionProofsByRoot protocol. + proofroots_quota: Option, + /// Quota for the ExecutionProofStatus protocol. + proofstatus_quota: Option, /// Quota for the BlobsByRange protocol. blbrange_quota: Option, /// Quota for the BlobsByRoot protocol. @@ -192,6 +204,9 @@ impl RPCRateLimiterBuilder { Protocol::BlocksByHead => self.bbhead_quota = q, Protocol::PayloadEnvelopesByRange => self.perange_quota = q, Protocol::PayloadEnvelopesByRoot => self.peroots_quota = q, + Protocol::ExecutionProofsByRange => self.proofrange_quota = q, + Protocol::ExecutionProofsByRoot => self.proofroots_quota = q, + Protocol::ExecutionProofStatus => self.proofstatus_quota = q, Protocol::BlobsByRange => self.blbrange_quota = q, Protocol::BlobsByRoot => self.blbroot_quota = q, Protocol::DataColumnsByRoot => self.dcbroot_quota = q, @@ -225,6 +240,15 @@ impl RPCRateLimiterBuilder { let peroots_quota = self .peroots_quota .ok_or("PayloadEnvelopesByRoot quota not specified")?; + let proofrange_quota = self + .proofrange_quota + .ok_or("ExecutionProofsByRange quota not specified")?; + let proofroots_quota = self + .proofroots_quota + .ok_or("ExecutionProofsByRoot quota not specified")?; + let proofstatus_quota = self + .proofstatus_quota + .ok_or("ExecutionProofStatus quota not specified")?; let lc_bootstrap_quota = self .lcbootstrap_quota .ok_or("LightClientBootstrap quota not specified")?; @@ -263,6 +287,9 @@ impl RPCRateLimiterBuilder { let bbhead_rl = Limiter::from_quota(bbhead_quota)?; let envrange_rl = Limiter::from_quota(perange_quota)?; let envroots_rl = Limiter::from_quota(peroots_quota)?; + let proofrange_rl = Limiter::from_quota(proofrange_quota)?; + let proofroots_rl = Limiter::from_quota(proofroots_quota)?; + let proofstatus_rl = Limiter::from_quota(proofstatus_quota)?; let blbrange_rl = Limiter::from_quota(blbrange_quota)?; let blbroot_rl = Limiter::from_quota(blbroots_quota)?; let dcbroot_rl = Limiter::from_quota(dcbroot_quota)?; @@ -289,6 +316,9 @@ impl RPCRateLimiterBuilder { bbhead_rl, envrange_rl, envroots_rl, + proofrange_rl, + proofroots_rl, + proofstatus_rl, blbrange_rl, blbroot_rl, dcbroot_rl, @@ -345,6 +375,9 @@ impl RPCRateLimiter { blocks_by_head_quota, payload_envelopes_by_range_quota, payload_envelopes_by_root_quota, + execution_proofs_by_range_quota, + execution_proofs_by_root_quota, + execution_proof_status_quota, blobs_by_range_quota, blobs_by_root_quota, data_columns_by_root_quota, @@ -371,6 +404,15 @@ impl RPCRateLimiter { Protocol::PayloadEnvelopesByRoot, payload_envelopes_by_root_quota, ) + .set_quota( + Protocol::ExecutionProofsByRange, + execution_proofs_by_range_quota, + ) + .set_quota( + Protocol::ExecutionProofsByRoot, + execution_proofs_by_root_quota, + ) + .set_quota(Protocol::ExecutionProofStatus, execution_proof_status_quota) .set_quota(Protocol::BlobsByRange, blobs_by_range_quota) .set_quota(Protocol::BlobsByRoot, blobs_by_root_quota) .set_quota(Protocol::DataColumnsByRoot, data_columns_by_root_quota) @@ -421,6 +463,9 @@ impl RPCRateLimiter { Protocol::BlocksByHead => &mut self.bbhead_rl, Protocol::PayloadEnvelopesByRange => &mut self.envrange_rl, Protocol::PayloadEnvelopesByRoot => &mut self.envroots_rl, + Protocol::ExecutionProofsByRange => &mut self.proofrange_rl, + Protocol::ExecutionProofsByRoot => &mut self.proofroots_rl, + Protocol::ExecutionProofStatus => &mut self.proofstatus_rl, Protocol::BlobsByRange => &mut self.blbrange_rl, Protocol::BlobsByRoot => &mut self.blbroot_rl, Protocol::DataColumnsByRoot => &mut self.dcbroot_rl, @@ -448,6 +493,9 @@ impl RPCRateLimiter { bbhead_rl, envrange_rl, envroots_rl, + proofrange_rl, + proofroots_rl, + proofstatus_rl, blbrange_rl, blbroot_rl, dcbroot_rl, @@ -468,6 +516,9 @@ impl RPCRateLimiter { bbhead_rl.prune(time_since_start); envrange_rl.prune(time_since_start); envroots_rl.prune(time_since_start); + proofrange_rl.prune(time_since_start); + proofroots_rl.prune(time_since_start); + proofstatus_rl.prune(time_since_start); blbrange_rl.prune(time_since_start); blbroot_rl.prune(time_since_start); dcbrange_rl.prune(time_since_start); diff --git a/beacon_node/lighthouse_network/src/service/api_types.rs b/beacon_node/lighthouse_network/src/service/api_types.rs index 2429b813e91..1959c0bdfe6 100644 --- a/beacon_node/lighthouse_network/src/service/api_types.rs +++ b/beacon_node/lighthouse_network/src/service/api_types.rs @@ -1,11 +1,13 @@ -use crate::rpc::methods::{ResponseTermination, RpcResponse, RpcSuccessResponse, StatusMessage}; +use crate::rpc::methods::{ + ExecutionProofStatus, ResponseTermination, RpcResponse, RpcSuccessResponse, StatusMessage, +}; use libp2p::PeerId; use std::fmt::{Display, Formatter}; use std::sync::Arc; use types::{ BlobSidecar, DataColumnSidecar, Epoch, EthSpec, LightClientBootstrap, LightClientFinalityUpdate, LightClientOptimisticUpdate, LightClientUpdate, SignedBeaconBlock, - SignedExecutionPayloadEnvelope, + SignedExecutionPayloadEnvelope, execution::eip8025::SignedExecutionProof, }; pub type Id = u32; @@ -33,6 +35,12 @@ pub enum SyncRequestId { BlobsByRange(BlobsByRangeRequestId), /// Data columns by range request DataColumnsByRange(DataColumnsByRangeRequestId), + /// Execution proofs by range request + ExecutionProofsByRange(ExecutionProofsByRangeRequestId), + /// Execution proofs by root request + ExecutionProofsByRoot(ExecutionProofsByRootRequestId), + /// Execution proof status request + ExecutionProofStatus(ExecutionProofStatusRequestId), } /// Request ID for data_columns_by_root requests. Block lookups do not issue this request directly. @@ -72,6 +80,24 @@ pub struct DataColumnsByRangeRequestId { pub peer: PeerId, } +/// Request ID for execution_proofs_by_range requests. +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +pub struct ExecutionProofsByRangeRequestId { + pub id: Id, +} + +/// Request ID for execution_proofs_by_root requests. +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +pub struct ExecutionProofsByRootRequestId { + pub id: Id, +} + +/// Request ID for execution_proof_status requests. +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +pub struct ExecutionProofStatusRequestId { + pub id: Id, +} + #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] pub enum DataColumnsByRangeRequester { ComponentsByRange(ComponentsByRangeRequestId), @@ -182,6 +208,12 @@ pub enum Response { LightClientFinalityUpdate(Arc>), /// A response to a LightClientUpdatesByRange request. LightClientUpdatesByRange(Option>>), + /// A response to a get EXECUTION_PROOFS_BY_RANGE request. + ExecutionProofsByRange(Option>), + /// A response to a get EXECUTION_PROOFS_BY_ROOT request. + ExecutionProofsByRoot(Option>), + /// A response to an EXECUTION_PROOF_STATUS request. + ExecutionProofStatus(ExecutionProofStatus), } impl std::convert::From> for RpcResponse { @@ -241,6 +273,21 @@ impl std::convert::From> for RpcResponse { RpcResponse::StreamTermination(ResponseTermination::LightClientUpdatesByRange) } }, + Response::ExecutionProofsByRange(r) => match r { + Some(proof) => { + RpcResponse::Success(RpcSuccessResponse::ExecutionProofsByRange(proof)) + } + None => RpcResponse::StreamTermination(ResponseTermination::ExecutionProofsByRange), + }, + Response::ExecutionProofsByRoot(r) => match r { + Some(proof) => { + RpcResponse::Success(RpcSuccessResponse::ExecutionProofsByRoot(proof)) + } + None => RpcResponse::StreamTermination(ResponseTermination::ExecutionProofsByRoot), + }, + Response::ExecutionProofStatus(status) => { + RpcResponse::Success(RpcSuccessResponse::ExecutionProofStatus(status)) + } } } } @@ -261,6 +308,9 @@ macro_rules! impl_display { impl_display!(BlocksByRangeRequestId, "{}/{}", id, parent_request_id); impl_display!(BlobsByRangeRequestId, "{}/{}", id, parent_request_id); impl_display!(DataColumnsByRangeRequestId, "{}/{}", id, parent_request_id); +impl_display!(ExecutionProofsByRangeRequestId, "ExecProofsByRange/{}", id); +impl_display!(ExecutionProofsByRootRequestId, "ExecProofsByRoot/{}", id); +impl_display!(ExecutionProofStatusRequestId, "ExecProofStatus/{}", id); impl_display!(ComponentsByRangeRequestId, "{}/{}", id, requester); impl_display!(DataColumnsByRootRequestId, "{}/{}", id, requester); impl_display!(SingleLookupReqId, "{}/Lookup/{}", req_id, lookup_id); diff --git a/beacon_node/lighthouse_network/src/service/gossip_cache.rs b/beacon_node/lighthouse_network/src/service/gossip_cache.rs index e9862e3f74e..09adbfbde79 100644 --- a/beacon_node/lighthouse_network/src/service/gossip_cache.rs +++ b/beacon_node/lighthouse_network/src/service/gossip_cache.rs @@ -261,6 +261,7 @@ impl GossipCache { GossipKind::ExecutionPayloadBid => self.execution_payload_bid, GossipKind::PayloadAttestation => self.payload_attestation, GossipKind::ProposerPreferences => self.proposer_preferences, + GossipKind::ExecutionProof => None, GossipKind::LightClientFinalityUpdate => self.light_client_finality_update, GossipKind::LightClientOptimisticUpdate => self.light_client_optimistic_update, }; diff --git a/beacon_node/lighthouse_network/src/service/mod.rs b/beacon_node/lighthouse_network/src/service/mod.rs index 41d937e3245..c1d3ff5b766 100644 --- a/beacon_node/lighthouse_network/src/service/mod.rs +++ b/beacon_node/lighthouse_network/src/service/mod.rs @@ -382,6 +382,7 @@ impl Network { let eth2_rpc = RPC::new( ctx.fork_context.clone(), config.enable_light_client_server, + config.enable_execution_proof, config.inbound_rate_limiter_config.clone(), config.outbound_rate_limiter_config.clone(), seq_number, @@ -1721,6 +1722,39 @@ impl Network { request_type, }) } + RequestType::ExecutionProofsByRange(_) => { + metrics::inc_counter_vec( + &metrics::TOTAL_RPC_REQUESTS, + &["execution_proofs_by_range"], + ); + Some(NetworkEvent::RequestReceived { + peer_id, + inbound_request_id, + request_type, + }) + } + RequestType::ExecutionProofsByRoot(_) => { + metrics::inc_counter_vec( + &metrics::TOTAL_RPC_REQUESTS, + &["execution_proofs_by_root"], + ); + Some(NetworkEvent::RequestReceived { + peer_id, + inbound_request_id, + request_type, + }) + } + RequestType::ExecutionProofStatus(_) => { + metrics::inc_counter_vec( + &metrics::TOTAL_RPC_REQUESTS, + &["execution_proof_status"], + ); + Some(NetworkEvent::RequestReceived { + peer_id, + inbound_request_id, + request_type, + }) + } RequestType::BlobsByRange(_) => { metrics::inc_counter_vec(&metrics::TOTAL_RPC_REQUESTS, &["blobs_by_range"]); Some(NetworkEvent::RequestReceived { @@ -1848,6 +1882,19 @@ impl Network { peer_id, Response::PayloadEnvelopesByRoot(Some(resp)), ), + RpcSuccessResponse::ExecutionProofsByRange(resp) => self.build_response( + id, + peer_id, + Response::ExecutionProofsByRange(Some(resp)), + ), + RpcSuccessResponse::ExecutionProofsByRoot(resp) => self.build_response( + id, + peer_id, + Response::ExecutionProofsByRoot(Some(resp)), + ), + RpcSuccessResponse::ExecutionProofStatus(status) => { + self.build_response(id, peer_id, Response::ExecutionProofStatus(status)) + } RpcSuccessResponse::BlobsByRoot(resp) => { self.build_response(id, peer_id, Response::BlobsByRoot(Some(resp))) } @@ -1889,6 +1936,12 @@ impl Network { ResponseTermination::PayloadEnvelopesByRoot => { Response::PayloadEnvelopesByRoot(None) } + ResponseTermination::ExecutionProofsByRange => { + Response::ExecutionProofsByRange(None) + } + ResponseTermination::ExecutionProofsByRoot => { + Response::ExecutionProofsByRoot(None) + } ResponseTermination::BlobsByRange => Response::BlobsByRange(None), ResponseTermination::BlobsByRoot => Response::BlobsByRoot(None), ResponseTermination::DataColumnsByRoot => Response::DataColumnsByRoot(None), diff --git a/beacon_node/lighthouse_network/src/types/globals.rs b/beacon_node/lighthouse_network/src/types/globals.rs index df8dbdc559e..0a9642bd826 100644 --- a/beacon_node/lighthouse_network/src/types/globals.rs +++ b/beacon_node/lighthouse_network/src/types/globals.rs @@ -226,6 +226,7 @@ impl NetworkGlobals { pub fn as_topic_config(&self) -> TopicConfig { TopicConfig { enable_light_client_server: self.config.enable_light_client_server, + enable_execution_proof: self.config.enable_execution_proof, subscribe_all_subnets: self.config.subscribe_all_subnets, sampling_subnets: self.sampling_subnets.read().clone(), } diff --git a/beacon_node/lighthouse_network/src/types/pubsub.rs b/beacon_node/lighthouse_network/src/types/pubsub.rs index e5a703ff1e5..b9af8f9c740 100644 --- a/beacon_node/lighthouse_network/src/types/pubsub.rs +++ b/beacon_node/lighthouse_network/src/types/pubsub.rs @@ -16,8 +16,8 @@ use types::{ SignedBeaconBlockCapella, SignedBeaconBlockDeneb, SignedBeaconBlockElectra, SignedBeaconBlockFulu, SignedBeaconBlockGloas, SignedBlsToExecutionChange, SignedContributionAndProof, SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope, - SignedProposerPreferences, SignedVoluntaryExit, SingleAttestation, SubnetId, - SyncCommitteeMessage, SyncSubnetId, + SignedExecutionProof, SignedProposerPreferences, SignedVoluntaryExit, SingleAttestation, + SubnetId, SyncCommitteeMessage, SyncSubnetId, }; #[derive(Debug, Clone, PartialEq)] @@ -52,6 +52,8 @@ pub enum PubsubMessage { ExecutionPayloadBid(Box>), /// Gossipsub message providing notification of signed proposer preferences. ProposerPreferences(Arc), + /// EIP-8025 signed execution proof. + ExecutionProof(Arc), /// Gossipsub message providing notification of a light client finality update. LightClientFinalityUpdate(Box>), /// Gossipsub message providing notification of a light client optimistic update. @@ -159,6 +161,7 @@ impl PubsubMessage { PubsubMessage::PayloadAttestation(_) => GossipKind::PayloadAttestation, PubsubMessage::ExecutionPayloadBid(_) => GossipKind::ExecutionPayloadBid, PubsubMessage::ProposerPreferences(_) => GossipKind::ProposerPreferences, + PubsubMessage::ExecutionProof(_) => GossipKind::ExecutionProof, PubsubMessage::LightClientFinalityUpdate(_) => GossipKind::LightClientFinalityUpdate, PubsubMessage::LightClientOptimisticUpdate(_) => { GossipKind::LightClientOptimisticUpdate @@ -392,6 +395,11 @@ impl PubsubMessage { proposer_preferences, ))) } + GossipKind::ExecutionProof => { + let execution_proof = SignedExecutionProof::from_ssz_bytes(data) + .map_err(|e| format!("{:?}", e))?; + Ok(PubsubMessage::ExecutionProof(Arc::new(execution_proof))) + } GossipKind::LightClientFinalityUpdate => { let light_client_finality_update = match fork_context .get_fork_from_context_bytes(gossip_topic.fork_digest) @@ -458,6 +466,7 @@ impl PubsubMessage { PubsubMessage::PayloadAttestation(data) => data.as_ssz_bytes(), PubsubMessage::ExecutionPayloadBid(data) => data.as_ssz_bytes(), PubsubMessage::ProposerPreferences(data) => data.as_ssz_bytes(), + PubsubMessage::ExecutionProof(data) => data.as_ssz_bytes(), PubsubMessage::LightClientFinalityUpdate(data) => data.as_ssz_bytes(), PubsubMessage::LightClientOptimisticUpdate(data) => data.as_ssz_bytes(), } @@ -574,6 +583,14 @@ impl std::fmt::Display for PubsubMessage { data.message.proposal_slot, data.message.validator_index ) } + PubsubMessage::ExecutionProof(data) => { + write!( + f, + "Execution Proof: request_root: {:?}, proof_type: {}", + data.request_root(), + data.proof_type() + ) + } PubsubMessage::LightClientFinalityUpdate(_data) => { write!(f, "Light CLient Finality Update") } diff --git a/beacon_node/lighthouse_network/src/types/topics.rs b/beacon_node/lighthouse_network/src/types/topics.rs index b51c459a809..b087c7727c7 100644 --- a/beacon_node/lighthouse_network/src/types/topics.rs +++ b/beacon_node/lighthouse_network/src/types/topics.rs @@ -33,12 +33,14 @@ pub const EXECUTION_PAYLOAD: &str = "execution_payload"; pub const EXECUTION_PAYLOAD_BID: &str = "execution_payload_bid"; pub const PAYLOAD_ATTESTATION: &str = "payload_attestation_message"; pub const PROPOSER_PREFERENCES: &str = "proposer_preferences"; +pub const EXECUTION_PROOF_TOPIC: &str = "execution_proof"; pub const LIGHT_CLIENT_FINALITY_UPDATE: &str = "light_client_finality_update"; pub const LIGHT_CLIENT_OPTIMISTIC_UPDATE: &str = "light_client_optimistic_update"; #[derive(Debug)] pub struct TopicConfig { pub enable_light_client_server: bool, + pub enable_execution_proof: bool, pub subscribe_all_subnets: bool, pub sampling_subnets: HashSet, } @@ -102,6 +104,10 @@ pub fn core_topics_to_subscribe( topics.push(GossipKind::ProposerPreferences); } + if opts.enable_execution_proof { + topics.push(GossipKind::ExecutionProof); + } + topics } @@ -129,6 +135,7 @@ pub fn is_fork_non_core_topic(topic: &GossipTopic, _fork_name: ForkName) -> bool | GossipKind::ExecutionPayloadBid | GossipKind::PayloadAttestation | GossipKind::ProposerPreferences + | GossipKind::ExecutionProof | GossipKind::LightClientFinalityUpdate | GossipKind::LightClientOptimisticUpdate => false, } @@ -139,6 +146,7 @@ pub fn all_topics_at_fork(fork: ForkName, spec: &ChainSpec) -> Vec GossipKind::ExecutionPayloadBid, PAYLOAD_ATTESTATION => GossipKind::PayloadAttestation, PROPOSER_PREFERENCES => GossipKind::ProposerPreferences, + EXECUTION_PROOF_TOPIC => GossipKind::ExecutionProof, LIGHT_CLIENT_FINALITY_UPDATE => GossipKind::LightClientFinalityUpdate, LIGHT_CLIENT_OPTIMISTIC_UPDATE => GossipKind::LightClientOptimisticUpdate, topic => match subnet_topic_index(topic) { @@ -360,6 +371,7 @@ impl std::fmt::Display for GossipTopic { GossipKind::PayloadAttestation => PAYLOAD_ATTESTATION.into(), GossipKind::ExecutionPayloadBid => EXECUTION_PAYLOAD_BID.into(), GossipKind::ProposerPreferences => PROPOSER_PREFERENCES.into(), + GossipKind::ExecutionProof => EXECUTION_PROOF_TOPIC.into(), GossipKind::LightClientFinalityUpdate => LIGHT_CLIENT_FINALITY_UPDATE.into(), GossipKind::LightClientOptimisticUpdate => LIGHT_CLIENT_OPTIMISTIC_UPDATE.into(), }; @@ -558,6 +570,7 @@ mod tests { fn get_topic_config(sampling_subnets: &HashSet) -> TopicConfig { TopicConfig { enable_light_client_server: false, + enable_execution_proof: false, subscribe_all_subnets: false, sampling_subnets: sampling_subnets.clone(), } diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index 14cda1b4836..571c1a697ce 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -18,6 +18,7 @@ use beacon_chain::{ GossipVerifiedBlock, NotifyExecutionLayer, attestation_verification::{self, Error as AttnError, VerifiedAttestation}, data_availability_checker::AvailabilityCheckErrorCategory, + eip8025::ExecutionProofError, light_client_finality_update_verification::Error as LightClientFinalityUpdateError, light_client_optimistic_update_verification::Error as LightClientOptimisticUpdateError, observed_operations::ObservationOutcome, @@ -53,11 +54,11 @@ use types::{ Attestation, AttestationData, AttestationRef, AttesterSlashing, BlobSidecar, ColumnIndex, DataColumnSidecar, DataColumnSubnetId, EthSpec, Hash256, IndexedAttestation, LightClientFinalityUpdate, LightClientOptimisticUpdate, PartialDataColumn, - PartialDataColumnHeader, PayloadAttestationMessage, ProposerSlashing, SignedAggregateAndProof, - SignedBeaconBlock, SignedBlsToExecutionChange, SignedContributionAndProof, - SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope, SignedProposerPreferences, - SignedVoluntaryExit, SingleAttestation, Slot, SubnetId, SyncCommitteeMessage, SyncSubnetId, - block::BlockImportSource, + PartialDataColumnHeader, PayloadAttestationMessage, ProofStatus, ProposerSlashing, + SignedAggregateAndProof, SignedBeaconBlock, SignedBlsToExecutionChange, + SignedContributionAndProof, SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope, + SignedExecutionProof, SignedProposerPreferences, SignedVoluntaryExit, SingleAttestation, Slot, + SubnetId, SyncCommitteeMessage, SyncSubnetId, block::BlockImportSource, }; use beacon_processor::work_reprocessing_queue::QueuedColumnReconstruction; @@ -174,6 +175,19 @@ impl FailedAtt { } } +fn is_invalid_execution_proof_error(error: &BeaconChainError) -> bool { + matches!( + error, + BeaconChainError::ExecutionProofError( + ExecutionProofError::InvalidSignature + | ExecutionProofError::EmptyProofData + | ExecutionProofError::InvalidValidatorIndex + | ExecutionProofError::InvalidValidatorPubkey + | ExecutionProofError::InvalidSignatureFormat + ) + ) +} + impl NetworkBeaconProcessor { /* Auxiliary functions */ @@ -206,6 +220,101 @@ impl NetworkBeaconProcessor { }) } + pub async fn process_gossip_execution_proof( + self: Arc, + message_id: MessageId, + peer_id: PeerId, + execution_proof: Arc, + ) { + match self + .chain + .verify_and_observe_execution_proof(&execution_proof, None) + .await + { + Ok(observation) => match observation.status { + ProofStatus::Valid | ProofStatus::Accepted => { + self.propagate_validation_result( + message_id, + peer_id, + MessageAcceptance::Accept, + ); + } + ProofStatus::Syncing | ProofStatus::NotSupported => { + self.propagate_validation_result( + message_id, + peer_id, + MessageAcceptance::Ignore, + ); + } + ProofStatus::Invalid => { + self.gossip_penalize_peer( + peer_id, + PeerAction::LowToleranceError, + "invalid execution proof", + ); + self.propagate_validation_result( + message_id, + peer_id, + MessageAcceptance::Reject, + ); + } + }, + Err(error) if is_invalid_execution_proof_error(&error) => { + self.gossip_penalize_peer( + peer_id, + PeerAction::LowToleranceError, + "invalid execution proof", + ); + self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); + } + Err(error) => { + debug!(?error, %peer_id, "Could not verify gossip execution proof"); + self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); + } + } + } + + pub async fn process_rpc_execution_proof( + self: Arc, + peer_id: PeerId, + execution_proof: Arc, + ) { + match self + .chain + .verify_and_observe_execution_proof(&execution_proof, None) + .await + { + Ok(observation) if observation.status == ProofStatus::Invalid => { + self.send_network_message(NetworkMessage::ReportPeer { + peer_id, + action: PeerAction::LowToleranceError, + source: ReportSource::SyncService, + msg: "invalid execution proof", + }); + } + Ok(observation) => { + debug!( + %peer_id, + status = %observation.status, + request_root = %observation.request_root, + block_root = ?observation.block_root, + "Observed RPC execution proof" + ); + } + Err(error) if is_invalid_execution_proof_error(&error) => { + self.send_network_message(NetworkMessage::ReportPeer { + peer_id, + action: PeerAction::LowToleranceError, + source: ReportSource::SyncService, + msg: "invalid execution proof", + }); + } + Err(error) => { + debug!(?error, %peer_id, "Could not verify RPC execution proof"); + } + } + } + /// Send a message on `message_tx` that `peer_id` has sent an invalid partial message and should /// be penalized. pub(crate) fn propagate_partial_validation_failure( diff --git a/beacon_node/network/src/network_beacon_processor/mod.rs b/beacon_node/network/src/network_beacon_processor/mod.rs index 434f7ecc8b3..84d441c2bb3 100644 --- a/beacon_node/network/src/network_beacon_processor/mod.rs +++ b/beacon_node/network/src/network_beacon_processor/mod.rs @@ -15,7 +15,8 @@ use beacon_processor::{ use lighthouse_network::rpc::InboundRequestId; use lighthouse_network::rpc::methods::{ BlobsByRangeRequest, BlobsByRootRequest, BlocksByHeadRequest, DataColumnsByRangeRequest, - DataColumnsByRootRequest, LightClientUpdatesByRangeRequest, PayloadEnvelopesByRangeRequest, + DataColumnsByRootRequest, ExecutionProofsByRangeRequest, ExecutionProofsByRootRequest, + LightClientUpdatesByRangeRequest, PayloadEnvelopesByRangeRequest, PayloadEnvelopesByRootRequest, }; use lighthouse_network::service::api_types::CustodyBackfillBatchId; @@ -451,6 +452,43 @@ impl NetworkBeaconProcessor { }) } + /// Process an execution proof received over gossip. + pub fn send_gossip_execution_proof( + self: &Arc, + message_id: MessageId, + peer_id: PeerId, + execution_proof: Arc, + ) -> Result<(), Error> { + let processor = self.clone(); + self.executor.spawn( + async move { + processor + .process_gossip_execution_proof(message_id, peer_id, execution_proof) + .await; + }, + "gossip_execution_proof", + ); + Ok(()) + } + + /// Verify an execution proof received over RPC. + pub fn send_rpc_execution_proof( + self: &Arc, + peer_id: PeerId, + execution_proof: Arc, + ) -> Result<(), Error> { + let processor = self.clone(); + self.executor.spawn( + async move { + processor + .process_rpc_execution_proof(peer_id, execution_proof) + .await; + }, + "rpc_execution_proof", + ); + Ok(()) + } + /// Create a new `Work` event for some execution payload envelope. pub fn send_gossip_execution_payload( self: &Arc, @@ -868,6 +906,48 @@ impl NetworkBeaconProcessor { }) } + /// Serve an `ExecutionProofsByRange` RPC request. + pub fn send_execution_proofs_by_range_request( + self: &Arc, + peer_id: PeerId, + inbound_request_id: InboundRequestId, + request: ExecutionProofsByRangeRequest, + ) -> Result<(), Error> { + let processor = self.clone(); + self.executor.spawn_blocking( + move || { + processor.handle_execution_proofs_by_range_request( + peer_id, + inbound_request_id, + request, + ); + }, + "execution_proofs_by_range_request", + ); + Ok(()) + } + + /// Serve an `ExecutionProofsByRoot` RPC request. + pub fn send_execution_proofs_by_root_request( + self: &Arc, + peer_id: PeerId, + inbound_request_id: InboundRequestId, + request: ExecutionProofsByRootRequest, + ) -> Result<(), Error> { + let processor = self.clone(); + self.executor.spawn_blocking( + move || { + processor.handle_execution_proofs_by_root_request( + peer_id, + inbound_request_id, + request, + ); + }, + "execution_proofs_by_root_request", + ); + Ok(()) + } + /// Create a new work event to process `LightClientBootstrap`s from the RPC network. pub fn send_light_client_bootstrap_request( self: &Arc, @@ -953,6 +1033,50 @@ impl NetworkBeaconProcessor { }); } + pub fn local_execution_proof_status( + &self, + ) -> lighthouse_network::rpc::methods::ExecutionProofStatus { + let head = self.chain.canonical_head.cached_head(); + let configured_proof_types = self + .chain + .execution_layer + .as_ref() + .map(|execution_layer| { + execution_layer + .proof_types() + .iter() + .map(|proof_type| proof_type.to_u8()) + .collect::>() + }) + .unwrap_or_default(); + let (block_root, slot, mut available_proof_types) = self + .chain + .latest_execution_proof_status(&configured_proof_types) + .map(|status| { + let proof_types = status + .valid_proof_types() + .filter(|proof_type| configured_proof_types.contains(proof_type)) + .collect::>(); + (status.block_root, status.slot.as_u64(), proof_types) + }) + .unwrap_or_else(|| (head.head_block_root(), head.head_slot().as_u64(), vec![])); + available_proof_types.sort_unstable(); + + let proof_types = ssz_types::VariableList::::new( + available_proof_types, + ) + .unwrap_or_else(|error| { + debug!(?error, "Local execution proof types exceed status limit"); + ssz_types::VariableList::default() + }); + + lighthouse_network::rpc::methods::ExecutionProofStatus { + block_root, + slot, + proof_types, + } + } + pub async fn fetch_engine_blobs_and_publish( self: &Arc, header: Arc>, diff --git a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs index 37a6f3779ae..f48de45b12a 100644 --- a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs @@ -8,7 +8,8 @@ use beacon_chain::{BeaconChainError, BeaconChainTypes, BlockProcessStatus, WhenS use itertools::{Itertools, process_results}; use lighthouse_network::rpc::methods::{ BlobsByRangeRequest, BlobsByRootRequest, BlocksByHeadRequest, DataColumnsByRangeRequest, - DataColumnsByRootRequest, PayloadEnvelopesByRangeRequest, PayloadEnvelopesByRootRequest, + DataColumnsByRootRequest, ExecutionProofsByRangeRequest, ExecutionProofsByRootRequest, + PayloadEnvelopesByRangeRequest, PayloadEnvelopesByRootRequest, }; use lighthouse_network::rpc::*; use lighthouse_network::{PeerId, ReportSource, Response, SyncInfo}; @@ -19,7 +20,7 @@ use std::sync::Arc; use tokio_stream::StreamExt; use tracing::{Span, debug, error, field, instrument, trace, warn}; use types::data::BlobIdentifier; -use types::{ColumnIndex, Epoch, EthSpec, Hash256, Slot}; +use types::{ColumnIndex, Epoch, EthSpec, Hash256, ProofType, Slot}; impl NetworkBeaconProcessor { /* Auxiliary functions */ @@ -1862,6 +1863,261 @@ impl NetworkBeaconProcessor { Ok(()) } + /// Handle an `ExecutionProofsByRange` request from the peer. + pub fn handle_execution_proofs_by_range_request( + &self, + peer_id: PeerId, + inbound_request_id: InboundRequestId, + req: ExecutionProofsByRangeRequest, + ) { + self.terminate_response_stream( + peer_id, + inbound_request_id, + self.handle_execution_proofs_by_range_request_inner(peer_id, inbound_request_id, req), + Response::ExecutionProofsByRange, + ); + } + + fn handle_execution_proofs_by_range_request_inner( + &self, + peer_id: PeerId, + inbound_request_id: InboundRequestId, + req: ExecutionProofsByRangeRequest, + ) -> Result<(), (RpcErrorResponse, &'static str)> { + debug!( + %peer_id, + start_slot = req.start_slot, + count = req.count, + num_proof_types = req.proof_types.len(), + "Received ExecutionProofsByRange Request" + ); + + self.check_execution_proofs_by_range_window(req.start_slot, req.count)?; + let proof_types = self.requested_or_configured_proof_types(req.proof_types.iter().copied()); + let proofs = self + .chain + .execution_proofs_by_range(Slot::new(req.start_slot), req.count, &proof_types) + .map_err(|error| { + debug!(?error, %peer_id, "Error getting execution proofs by range"); + ( + RpcErrorResponse::ServerError, + "Error getting execution proofs by range", + ) + })?; + + let proofs_sent = proofs.len(); + for proof in proofs { + self.send_response( + peer_id, + inbound_request_id, + Response::ExecutionProofsByRange(Some(proof)), + ); + } + + debug!( + %peer_id, + start_slot = req.start_slot, + count = req.count, + returned = proofs_sent, + "ExecutionProofsByRange Response processed" + ); + + Ok(()) + } + + fn proof_serve_range(&self) -> (Slot, Slot) { + let finalized_slot = self + .chain + .canonical_head + .cached_head() + .finalized_checkpoint() + .epoch + .start_slot(T::EthSpec::slots_per_epoch()); + let current_slot = self + .chain + .slot() + .unwrap_or_else(|_| self.chain.slot_clock.genesis_slot()); + (finalized_slot, current_slot) + } + + fn check_execution_proofs_by_range_window( + &self, + start_slot: u64, + count: u64, + ) -> Result<(), (RpcErrorResponse, &'static str)> { + if count == 0 { + return Ok(()); + } + + let Some(end_slot) = start_slot.checked_add(count.saturating_sub(1)) else { + return Err(( + RpcErrorResponse::InvalidRequest, + "ExecutionProofsByRange range overflows", + )); + }; + + let (serve_start_slot, current_slot) = self.proof_serve_range(); + if Slot::new(start_slot) < serve_start_slot || Slot::new(end_slot) > current_slot { + debug!( + start_slot, + end_slot, + %serve_start_slot, + %current_slot, + "ExecutionProofsByRange outside proof serve range" + ); + return Err(( + RpcErrorResponse::ResourceUnavailable, + "ExecutionProofsByRange outside proof serve range", + )); + } + + Ok(()) + } + + fn canonical_block_slot( + &self, + block_root: Hash256, + ) -> Result, (RpcErrorResponse, &'static str)> { + let Some(block) = self.chain.get_blinded_block(&block_root).map_err(|error| { + error!( + ?block_root, + ?error, + "Error loading block for ExecutionProofsByRoot proof range check" + ); + ( + RpcErrorResponse::ServerError, + "Failed loading block for ExecutionProofsByRoot", + ) + })? + else { + return Ok(None); + }; + + let slot = block.slot(); + let canonical_root = self + .chain + .block_root_at_slot(slot, WhenSlotSkipped::None) + .map_err(|error| { + error!( + ?block_root, + %slot, + ?error, + "Error checking canonical block root for ExecutionProofsByRoot" + ); + ( + RpcErrorResponse::ServerError, + "Failed checking canonical block root for ExecutionProofsByRoot", + ) + })?; + + Ok((canonical_root == Some(block_root)).then_some(slot)) + } + + /// Handle an `ExecutionProofsByRoot` request from the peer. + pub fn handle_execution_proofs_by_root_request( + &self, + peer_id: PeerId, + inbound_request_id: InboundRequestId, + req: ExecutionProofsByRootRequest, + ) { + self.terminate_response_stream( + peer_id, + inbound_request_id, + self.handle_execution_proofs_by_root_request_inner(peer_id, inbound_request_id, req), + Response::ExecutionProofsByRoot, + ); + } + + fn handle_execution_proofs_by_root_request_inner( + &self, + peer_id: PeerId, + inbound_request_id: InboundRequestId, + req: ExecutionProofsByRootRequest, + ) -> Result<(), (RpcErrorResponse, &'static str)> { + debug!( + %peer_id, + num_identifiers = req.identifiers.len(), + "Received ExecutionProofsByRoot Request" + ); + + let (serve_start_slot, current_slot) = self.proof_serve_range(); + let mut has_canonical_requested_root = false; + let mut in_range_roots = HashSet::new(); + for identifier in req.identifiers.iter() { + let Some(slot) = self.canonical_block_slot(identifier.block_root)? else { + continue; + }; + has_canonical_requested_root = true; + if slot >= serve_start_slot && slot <= current_slot { + in_range_roots.insert(identifier.block_root); + } + } + + if has_canonical_requested_root && in_range_roots.is_empty() { + debug!( + %serve_start_slot, + %current_slot, + num_identifiers = req.identifiers.len(), + "ExecutionProofsByRoot outside proof serve range" + ); + return Err(( + RpcErrorResponse::ResourceUnavailable, + "ExecutionProofsByRoot outside proof serve range", + )); + } + + let mut proofs_sent = 0usize; + for identifier in req.identifiers.iter() { + if has_canonical_requested_root && !in_range_roots.contains(&identifier.block_root) { + continue; + } + let proof_types = + self.requested_or_configured_proof_types(identifier.proof_types.iter().copied()); + let proofs = self + .chain + .execution_proofs_by_block_root(identifier.block_root, &proof_types); + proofs_sent += proofs.len(); + for proof in proofs { + self.send_response( + peer_id, + inbound_request_id, + Response::ExecutionProofsByRoot(Some(proof)), + ); + } + } + + debug!( + %peer_id, + num_identifiers = req.identifiers.len(), + returned = proofs_sent, + "ExecutionProofsByRoot Response processed" + ); + + Ok(()) + } + + fn requested_or_configured_proof_types(&self, requested: I) -> Vec + where + I: IntoIterator, + { + let requested = requested.into_iter().collect::>(); + if !requested.is_empty() { + return requested; + } + + self.chain + .execution_layer + .as_ref() + .map(|execution_layer| { + execution_layer + .proof_types() + .iter() + .map(|proof_type| proof_type.to_u8()) + .collect() + }) + .unwrap_or_default() + } + /// Helper function to ensure single item protocol always end with either a single chunk or an /// error fn terminate_response_single_item Response>( diff --git a/beacon_node/network/src/router.rs b/beacon_node/network/src/router.rs index 35939c6f396..4888ec6c423 100644 --- a/beacon_node/network/src/router.rs +++ b/beacon_node/network/src/router.rs @@ -12,6 +12,7 @@ use crate::sync::SyncMessage; use beacon_chain::{BeaconChain, BeaconChainTypes}; use beacon_processor::{BeaconProcessorSend, DuplicateCache}; use futures::prelude::*; +use lighthouse_network::rpc::methods::ExecutionProofStatus; use lighthouse_network::rpc::*; use lighthouse_network::{ GossipTopic, MessageId, NetworkGlobals, PeerId, PubsubMessage, Response, @@ -26,7 +27,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream; use tracing::{debug, error, trace, warn}; use types::{ BlobSidecar, DataColumnSidecar, EthSpec, ForkContext, PartialDataColumn, SignedBeaconBlock, - SignedExecutionPayloadEnvelope, + SignedExecutionPayloadEnvelope, SignedExecutionProof, }; /// Handles messages from the network and routes them to the appropriate service to be handled. @@ -312,6 +313,38 @@ impl Router { request, ), ), + RequestType::ExecutionProofsByRange(request) => self + .handle_beacon_processor_send_result( + self.network_beacon_processor + .send_execution_proofs_by_range_request( + peer_id, + inbound_request_id, + request, + ), + ), + RequestType::ExecutionProofsByRoot(request) => self + .handle_beacon_processor_send_result( + self.network_beacon_processor + .send_execution_proofs_by_root_request( + peer_id, + inbound_request_id, + request, + ), + ), + RequestType::ExecutionProofStatus(status) => { + self.network.send_response( + peer_id, + inbound_request_id, + Response::ExecutionProofStatus( + self.network_beacon_processor.local_execution_proof_status(), + ), + ); + self.send_to_sync(SyncMessage::RpcExecutionProofStatus { + peer_id, + request_id: None, + status, + }); + } _ => {} } } @@ -357,6 +390,19 @@ impl Router { Response::PayloadEnvelopesByRange(_) => { debug!("Requesting envelopes by range not supported yet"); } + Response::ExecutionProofsByRange(execution_proof) => { + self.on_execution_proofs_by_range_response( + peer_id, + app_request_id, + execution_proof, + ); + } + Response::ExecutionProofsByRoot(execution_proof) => { + self.on_execution_proofs_by_root_response(peer_id, app_request_id, execution_proof); + } + Response::ExecutionProofStatus(status) => { + self.on_execution_proof_status_response(peer_id, app_request_id, status); + } // Lighthouse currently only serves BlocksByHead and does not issue it as a client, // so receiving a response is unexpected. Drop it without crashing. Response::BlocksByHead(_) => { @@ -584,6 +630,16 @@ impl Router { ), ) } + PubsubMessage::ExecutionProof(execution_proof) => { + trace!(%peer_id, "Received execution proof"); + self.handle_beacon_processor_send_result( + self.network_beacon_processor.send_gossip_execution_proof( + message_id, + peer_id, + execution_proof, + ), + ) + } } } @@ -848,6 +904,60 @@ impl Router { }); } + pub fn on_execution_proofs_by_range_response( + &mut self, + peer_id: PeerId, + app_request_id: AppRequestId, + execution_proof: Option>, + ) { + trace!(%peer_id, "Received ExecutionProofsByRange Response"); + if let AppRequestId::Sync(sync_request_id) = app_request_id { + self.send_to_sync(SyncMessage::RpcExecutionProof { + peer_id, + sync_request_id, + execution_proof, + }); + } else { + crit!("All execution proofs by range responses should belong to sync"); + } + } + + pub fn on_execution_proofs_by_root_response( + &mut self, + peer_id: PeerId, + app_request_id: AppRequestId, + execution_proof: Option>, + ) { + trace!(%peer_id, "Received ExecutionProofsByRoot Response"); + if let AppRequestId::Sync(sync_request_id) = app_request_id { + self.send_to_sync(SyncMessage::RpcExecutionProof { + peer_id, + sync_request_id, + execution_proof, + }); + } else { + crit!("All execution proofs by root responses should belong to sync"); + } + } + + fn on_execution_proof_status_response( + &mut self, + peer_id: PeerId, + app_request_id: AppRequestId, + status: ExecutionProofStatus, + ) { + if let AppRequestId::Sync(SyncRequestId::ExecutionProofStatus(request_id)) = app_request_id + { + self.send_to_sync(SyncMessage::RpcExecutionProofStatus { + peer_id, + request_id: Some(request_id), + status, + }); + } else { + debug!(%peer_id, "ExecutionProofStatus response with unexpected request id"); + } + } + fn handle_beacon_processor_send_result( &mut self, result: Result<(), crate::network_beacon_processor::Error>, diff --git a/beacon_node/network/src/sync/manager.rs b/beacon_node/network/src/sync/manager.rs index 14a38f0e72d..f6c13bd8fe0 100644 --- a/beacon_node/network/src/sync/manager.rs +++ b/beacon_node/network/src/sync/manager.rs @@ -39,6 +39,7 @@ use super::network_context::{ CustodyByRootResult, RangeBlockComponent, RangeRequestId, RpcEvent, SyncNetworkContext, }; use super::peer_sync_info::{PeerSyncType, remote_sync_type}; +use super::proof_sync::ProofSync; use super::range_sync::{EPOCHS_PER_BATCH, RangeSync, RangeSyncType}; use crate::network_beacon_processor::{ChainSegmentProcessId, NetworkBeaconProcessor}; use crate::service::NetworkMessage; @@ -52,14 +53,17 @@ use beacon_chain::block_verification_types::AsBlock; use beacon_chain::{ AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes, BlockError, EngineState, }; +use execution_layer::eip8025::types::ProofTypes; use futures::StreamExt; use lighthouse_network::SyncInfo; use lighthouse_network::rpc::RPCError; +use lighthouse_network::rpc::methods::ExecutionProofStatus; use lighthouse_network::service::api_types::{ BlobsByRangeRequestId, BlocksByRangeRequestId, ComponentsByRangeRequestId, CustodyBackFillBatchRequestId, CustodyBackfillBatchId, CustodyRequester, DataColumnsByRangeRequestId, DataColumnsByRangeRequester, DataColumnsByRootRequestId, - DataColumnsByRootRequester, Id, SingleLookupReqId, SyncRequestId, + DataColumnsByRootRequester, ExecutionProofStatusRequestId, Id, SingleLookupReqId, + SyncRequestId, }; use lighthouse_network::types::{NetworkGlobals, SyncState}; use lighthouse_network::{PeerAction, PeerId}; @@ -74,7 +78,7 @@ use tokio::sync::mpsc; use tracing::{debug, error, info, trace}; use types::{ BlobSidecar, DataColumnSidecar, EthSpec, ForkContext, Hash256, SignedBeaconBlock, - SignedExecutionPayloadEnvelope, Slot, + SignedExecutionPayloadEnvelope, SignedExecutionProof, Slot, }; /// The number of slots ahead of us that is allowed before requesting a long-range (batch) Sync @@ -141,6 +145,21 @@ pub enum SyncMessage { seen_timestamp: Duration, }, + /// An execution proof has been received from the RPC. + RpcExecutionProof { + sync_request_id: SyncRequestId, + peer_id: PeerId, + execution_proof: Option>, + }, + + /// An ExecutionProofStatus response has been received from the RPC, or a peer sent us its + /// status as an inbound request body. + RpcExecutionProofStatus { + peer_id: PeerId, + request_id: Option, + status: ExecutionProofStatus, + }, + /// A block with an unknown parent has been received. UnknownParentBlock(PeerId, Arc>, Hash256), @@ -269,6 +288,9 @@ pub struct SyncManager { /// The object handling long-range batch load-balanced syncing. range_sync: RangeSync, + /// Catch-up mechanism for missing optional execution proofs. + proof_sync: ProofSync, + /// Backfill syncing. backfill_sync: BackFillSync, @@ -324,6 +346,11 @@ impl SyncManager { fork_context: Arc, ) -> Self { let network_globals = beacon_processor.network_globals.clone(); + let proof_types = beacon_chain + .execution_layer + .as_ref() + .map(|execution_layer| execution_layer.proof_types().clone()) + .unwrap_or_else(ProofTypes::default); Self { chain: beacon_chain.clone(), input_channel: sync_recv, @@ -332,8 +359,10 @@ impl SyncManager { beacon_processor.clone(), beacon_chain.clone(), fork_context.clone(), + proof_types, ), range_sync: RangeSync::new(beacon_chain.clone()), + proof_sync: ProofSync::new(beacon_chain.clone()), backfill_sync: BackFillSync::new(beacon_chain.clone(), network_globals.clone()), custody_backfill_sync: CustodyBackFillSync::new(beacon_chain.clone(), network_globals), block_lookups: BlockLookups::new(), @@ -445,6 +474,12 @@ impl SyncManager { } } + if self.network_globals().config.enable_execution_proof + && self.network.is_proof_capable_peer(&peer_id) + { + self.proof_sync.add_peer(peer_id, &mut self.network); + } + self.update_sync_state(); // Try to make progress on custody requests that are waiting for peers @@ -528,6 +563,18 @@ impl SyncManager { SyncRequestId::DataColumnsByRange(req_id) => { self.on_data_columns_by_range_response(req_id, peer_id, RpcEvent::RPCError(error)) } + SyncRequestId::ExecutionProofsByRange(req_id) => { + debug!(%peer_id, ?req_id, "Execution proofs by range request failed"); + self.proof_sync.on_range_request_error(&req_id); + } + SyncRequestId::ExecutionProofsByRoot(req_id) => { + debug!(%peer_id, ?req_id, "Execution proofs by root request failed"); + self.proof_sync.on_root_request_error(&req_id); + } + SyncRequestId::ExecutionProofStatus(req_id) => { + self.proof_sync + .on_peer_execution_proof_status_error(peer_id, req_id); + } } } @@ -541,6 +588,7 @@ impl SyncManager { self.range_sync.peer_disconnect(&mut self.network, peer_id); let _ = self.backfill_sync.peer_disconnected(peer_id); self.block_lookups.peer_disconnected(peer_id); + self.proof_sync.on_proof_capable_peer_disconnected(peer_id); // Inject a Disconnected error on all requests associated with the disconnected peer // to retry all batches/lookups. Only after removing the peer from the data structures to @@ -723,6 +771,7 @@ impl SyncManager { self.backfill_sync.pause(); self.custody_backfill_sync .pause("Range sync in progress".to_string()); + self.proof_sync.pause(); SyncState::SyncingFinalized { start_slot, @@ -736,6 +785,7 @@ impl SyncManager { self.backfill_sync.pause(); self.custody_backfill_sync .pause("Range sync in progress".to_string()); + self.proof_sync.pause(); SyncState::SyncingHead { start_slot, @@ -761,6 +811,9 @@ impl SyncManager { ) { self.network.subscribe_core_topics(); + if self.network_globals().config.enable_execution_proof { + self.proof_sync.start(&mut self.network); + } } } } @@ -792,6 +845,7 @@ impl SyncManager { let epoch_duration = self.chain.slot_clock.slot_duration().as_secs() * T::EthSpec::slots_per_epoch(); let mut epoch_interval = tokio::time::interval(Duration::from_secs(epoch_duration)); + let mut proof_sync_interval = tokio::time::interval(self.chain.slot_clock.slot_duration()); // process any inbound messages loop { @@ -817,6 +871,9 @@ impl SyncManager { _ = epoch_interval.tick() => { self.update_sync_state(); } + _ = proof_sync_interval.tick(), if self.network_globals().config.enable_execution_proof => { + self.proof_sync.poll(&mut self.network); + } } } } @@ -873,6 +930,18 @@ impl SyncManager { envelope, seen_timestamp, ), + SyncMessage::RpcExecutionProof { + sync_request_id, + peer_id, + execution_proof, + } => self.rpc_execution_proof_received(sync_request_id, peer_id, execution_proof), + SyncMessage::RpcExecutionProofStatus { + peer_id, + request_id, + status, + } => self + .proof_sync + .on_peer_execution_proof_status(peer_id, request_id, status), SyncMessage::UnknownParentBlock(peer_id, block, block_root) => { let block_slot = block.slot(); let parent_root = block.parent_root(); @@ -1283,6 +1352,36 @@ impl SyncManager { } } + fn rpc_execution_proof_received( + &mut self, + sync_request_id: SyncRequestId, + peer_id: PeerId, + execution_proof: Option>, + ) { + let Some(proof) = execution_proof else { + match &sync_request_id { + SyncRequestId::ExecutionProofsByRange(id) => { + self.proof_sync.on_range_request_terminated(id); + } + SyncRequestId::ExecutionProofsByRoot(id) => { + self.proof_sync.on_root_request_terminated(id); + } + other => { + debug!(%peer_id, ?other, "Unexpected execution proof stream termination"); + } + } + return; + }; + + if let Err(error) = self + .network + .beacon_processor() + .send_rpc_execution_proof(peer_id, proof) + { + debug!(%peer_id, ?error, "Failed to send RPC execution proof to beacon processor"); + } + } + fn on_single_payload_envelope_response( &mut self, id: SingleLookupReqId, diff --git a/beacon_node/network/src/sync/mod.rs b/beacon_node/network/src/sync/mod.rs index 054bab654c2..025bd9e792d 100644 --- a/beacon_node/network/src/sync/mod.rs +++ b/beacon_node/network/src/sync/mod.rs @@ -9,6 +9,7 @@ mod custody_backfill_sync; pub mod manager; mod network_context; mod peer_sync_info; +mod proof_sync; mod range_data_column_batch_request; mod range_sync; #[cfg(test)] diff --git a/beacon_node/network/src/sync/network_context.rs b/beacon_node/network/src/sync/network_context.rs index 9d5ac40c0a3..34e66c3369c 100644 --- a/beacon_node/network/src/sync/network_context.rs +++ b/beacon_node/network/src/sync/network_context.rs @@ -22,19 +22,25 @@ use crate::sync::network_context::requests::BlobsByRootSingleBlockRequest; use crate::sync::range_data_column_batch_request::RangeDataColumnBatchRequest; use beacon_chain::block_verification_types::LookupBlock; use beacon_chain::block_verification_types::{AsBlock, RangeSyncBlock}; +use beacon_chain::eip8025::MissingExecutionProofInfo; use beacon_chain::{BeaconChain, BeaconChainTypes, BlockProcessStatus, EngineState}; use custody::CustodyRequestResult; +use execution_layer::eip8025::types::ProofTypes; use fnv::FnvHashMap; -use lighthouse_network::rpc::methods::{BlobsByRangeRequest, DataColumnsByRangeRequest}; +use lighthouse_network::rpc::methods::{ + BlobsByRangeRequest, DataColumnsByRangeRequest, ExecutionProofStatus, + ExecutionProofsByRangeRequest, ExecutionProofsByRootRequest, +}; use lighthouse_network::rpc::{BlocksByRangeRequest, GoodbyeReason, RPCError, RequestType}; pub use lighthouse_network::service::api_types::RangeRequestId; use lighthouse_network::service::api_types::{ AppRequestId, BlobsByRangeRequestId, BlocksByRangeRequestId, ComponentsByRangeRequestId, CustodyBackFillBatchRequestId, CustodyBackfillBatchId, CustodyId, CustodyRequester, DataColumnsByRangeRequestId, DataColumnsByRangeRequester, DataColumnsByRootRequestId, - DataColumnsByRootRequester, Id, SingleLookupReqId, SyncRequestId, + DataColumnsByRootRequester, ExecutionProofStatusRequestId, ExecutionProofsByRangeRequestId, + ExecutionProofsByRootRequestId, Id, SingleLookupReqId, SyncRequestId, }; -use lighthouse_network::{Client, NetworkGlobals, PeerAction, PeerId, ReportSource}; +use lighthouse_network::{Client, Eth2Enr, NetworkGlobals, PeerAction, PeerId, ReportSource}; use parking_lot::RwLock; pub use requests::LookupVerifyError; use requests::{ @@ -44,6 +50,7 @@ use requests::{ }; #[cfg(test)] use slot_clock::SlotClock; +use ssz_types::{RuntimeVariableList, VariableList, typenum::Unsigned}; use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; @@ -57,6 +64,7 @@ use types::data::FixedBlobSidecarList; use types::{ BlobSidecar, BlockImportSource, ColumnIndex, DataColumnSidecar, DataColumnSidecarList, EthSpec, ForkContext, Hash256, SignedBeaconBlock, SignedExecutionPayloadEnvelope, Slot, + execution::eip8025::{MaxExecutionProofsPerPayload, ProofByRootIdentifier, ProofType}, }; pub mod custody; @@ -121,6 +129,25 @@ pub enum RpcRequestSendError { pub enum NoPeerError { BlockPeer, CustodyPeer(ColumnIndex), + /// No connected peer with execution proof support advertised in its ENR. + ProofPeer, +} + +/// Age threshold for considering a cached `ExecutionProofStatus` stale enough to re-query. +pub const EXECUTION_PROOF_STATUS_REFRESH_THRESHOLD: std::time::Duration = + std::time::Duration::from_secs(300); + +/// A peer's `ExecutionProofStatus`, plus freshness and whether its anchor was verified locally. +pub struct CachedExecutionProofStatus { + pub status: ExecutionProofStatus, + pub timestamp: std::time::Instant, + pub verified: bool, +} + +impl CachedExecutionProofStatus { + pub fn needs_refresh(&self) -> bool { + !self.verified || self.timestamp.elapsed() > EXECUTION_PROOF_STATUS_REFRESH_THRESHOLD + } } #[derive(Debug, PartialEq, Eq)] @@ -241,6 +268,9 @@ pub struct SyncNetworkContext { pub chain: Arc>, fork_context: Arc, + + /// Proof types to request from peers. + proof_types: ProofTypes, } /// Small enumeration to make dealing with block and blob requests easier. @@ -284,6 +314,7 @@ impl SyncNetworkContext> { Arc::new(beacon_processor), beacon_chain, fork_context, + ProofTypes::default(), ) } } @@ -294,6 +325,7 @@ impl SyncNetworkContext { network_beacon_processor: Arc>, chain: Arc>, fork_context: Arc, + proof_types: ProofTypes, ) -> Self { SyncNetworkContext { network_send, @@ -312,6 +344,7 @@ impl SyncNetworkContext { network_beacon_processor, chain, fork_context, + proof_types, } } @@ -344,6 +377,7 @@ impl SyncNetworkContext { network_beacon_processor: _, chain: _, fork_context: _, + proof_types: _, } = self; let blocks_by_root_ids = blocks_by_root_requests @@ -389,6 +423,157 @@ impl SyncNetworkContext { .custody_peers_for_column(column_index) } + /// Send an `ExecutionProofsByRange` request to the given proof-capable peer. + pub fn request_execution_proofs_by_range( + &mut self, + peer_id: PeerId, + start_slot: Slot, + count: u64, + ) -> Result { + let id = ExecutionProofsByRangeRequestId { id: self.next_id() }; + let proof_types = RuntimeVariableList::new( + self.configured_proof_types().collect(), + MaxExecutionProofsPerPayload::to_usize(), + ) + .map_err(|e| RpcRequestSendError::InternalError(format!("proof_types: {e:?}")))?; + + self.send_network_msg(NetworkMessage::SendRequest { + peer_id, + request: RequestType::ExecutionProofsByRange(ExecutionProofsByRangeRequest { + start_slot: start_slot.as_u64(), + count, + proof_types, + }), + app_request_id: AppRequestId::Sync(SyncRequestId::ExecutionProofsByRange(id)), + }) + .map_err(|e| RpcRequestSendError::InternalError(e.to_owned()))?; + + debug!( + method = "ExecutionProofsByRange", + %start_slot, + count, + peer = %peer_id, + %id, + "Sync RPC request sent" + ); + Ok(id) + } + + /// Send an `ExecutionProofsByRoot` request for all missing proofs to `peer_id`. + pub fn request_execution_proofs_by_root( + &mut self, + peer_id: PeerId, + missing: &[MissingExecutionProofInfo], + ) -> Result { + let mut identifiers = Vec::with_capacity(missing.len()); + for info in missing { + let needed = self + .configured_proof_types() + .filter(|proof_type| !info.existing_proof_types.contains(proof_type)) + .collect::>(); + let proof_types = VariableList::new(needed) + .map_err(|e| RpcRequestSendError::InternalError(format!("proof_types: {e:?}")))?; + identifiers.push(ProofByRootIdentifier { + block_root: info.root, + proof_types, + }); + } + + let max_request_blocks = self + .chain + .spec + .max_request_blocks(self.fork_context.current_fork_name()); + let request = ExecutionProofsByRootRequest::new(identifiers, max_request_blocks) + .map_err(RpcRequestSendError::InternalError)?; + let id = ExecutionProofsByRootRequestId { id: self.next_id() }; + + self.send_network_msg(NetworkMessage::SendRequest { + peer_id, + request: RequestType::ExecutionProofsByRoot(request), + app_request_id: AppRequestId::Sync(SyncRequestId::ExecutionProofsByRoot(id)), + }) + .map_err(|e| RpcRequestSendError::InternalError(e.to_owned()))?; + + debug!( + method = "ExecutionProofsByRoot", + num_roots = missing.len(), + peer = %peer_id, + %id, + "Sync RPC request sent" + ); + Ok(id) + } + + /// Send an `ExecutionProofStatus` request to `peer_id`. + pub fn request_execution_proof_status( + &mut self, + peer_id: PeerId, + ) -> Result { + let id = ExecutionProofStatusRequestId { id: self.next_id() }; + self.send_network_msg(NetworkMessage::SendRequest { + peer_id, + request: RequestType::ExecutionProofStatus(self.local_execution_proof_status()), + app_request_id: AppRequestId::Sync(SyncRequestId::ExecutionProofStatus(id)), + }) + .map_err(|e| RpcRequestSendError::InternalError(e.to_owned()))?; + + debug!( + method = "ExecutionProofStatus", + peer = %peer_id, + %id, + "Sync RPC request sent" + ); + Ok(id) + } + + pub fn local_execution_proof_status(&self) -> ExecutionProofStatus { + let head = self.chain.canonical_head.cached_head(); + let configured_proof_types = self.configured_proof_types_vec(); + let (block_root, slot, mut available_proof_types) = self + .chain + .latest_execution_proof_status(&configured_proof_types) + .map(|status| { + let proof_types = status + .valid_proof_types() + .filter(|proof_type| configured_proof_types.contains(proof_type)) + .collect::>(); + (status.block_root, status.slot.as_u64(), proof_types) + }) + .unwrap_or_else(|| (head.head_block_root(), head.head_slot().as_u64(), vec![])); + available_proof_types.sort_unstable(); + + let proof_types = VariableList::new(available_proof_types).unwrap_or_else(|error| { + debug!(?error, "Local execution proof types exceed status limit"); + VariableList::default() + }); + ExecutionProofStatus { + block_root, + slot, + proof_types, + } + } + + pub fn configured_proof_types(&self) -> impl Iterator + '_ { + self.proof_types.iter().map(|proof_type| proof_type.to_u8()) + } + + pub fn configured_proof_types_vec(&self) -> Vec { + self.configured_proof_types().collect() + } + + /// Returns `true` if the peer has execution proof support in its ENR. + pub fn is_proof_capable_peer(&self, peer_id: &PeerId) -> bool { + self.network_globals() + .peers + .read() + .peer_info(peer_id) + .is_some_and(|info| { + info.enr() + .map(|enr| enr.execution_proof_enabled()) + .unwrap_or(false) + }) + } + pub fn network_globals(&self) -> &NetworkGlobals { &self.network_beacon_processor.network_globals } @@ -447,6 +632,7 @@ impl SyncNetworkContext { network_beacon_processor: _, chain: _, fork_context: _, + proof_types: _, // Don't use a fallback match. We want to be sure that all requests are considered when // adding new ones } = self; diff --git a/beacon_node/network/src/sync/proof_sync.rs b/beacon_node/network/src/sync/proof_sync.rs new file mode 100644 index 00000000000..a0cbc676440 --- /dev/null +++ b/beacon_node/network/src/sync/proof_sync.rs @@ -0,0 +1,452 @@ +//! Catch-up mechanism for optional EIP-8025 execution proofs. + +use super::network_context::{CachedExecutionProofStatus, SyncNetworkContext}; +use beacon_chain::{BeaconChain, BeaconChainTypes, WhenSlotSkipped}; +use lighthouse_network::PeerId; +use lighthouse_network::rpc::methods::ExecutionProofStatus; +use lighthouse_network::service::api_types::{ + ExecutionProofStatusRequestId, ExecutionProofsByRangeRequestId, ExecutionProofsByRootRequestId, +}; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use std::time::Instant; +use tracing::{debug, info}; +use types::{EthSpec, ProofType, Slot}; + +use beacon_chain::eip8025::MissingExecutionProofInfo; + +pub(crate) struct ByRangeRequest { + pub(crate) id: ExecutionProofsByRangeRequestId, + pub(crate) peer_id: PeerId, +} + +pub(crate) struct ByRootRequest { + pub(crate) id: ExecutionProofsByRootRequestId, + pub(crate) peer_id: PeerId, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ProofSyncState { + Idle, + Syncing, +} + +const POST_REQUEST_COOLDOWN_SLOTS: u64 = 1; + +pub struct ProofSync { + chain: Arc>, + state: ProofSyncState, + range_request: Option, + root_request: Option, + post_request_cooldown: u64, + peer_statuses: HashMap, + status_in_flight: HashMap, + logged_no_peer: bool, +} + +impl ProofSync { + pub fn new(chain: Arc>) -> Self { + Self { + chain, + state: ProofSyncState::Idle, + range_request: None, + root_request: None, + post_request_cooldown: 0, + peer_statuses: HashMap::default(), + status_in_flight: HashMap::default(), + logged_no_peer: false, + } + } + + pub fn start(&mut self, cx: &mut SyncNetworkContext) { + if self.state == ProofSyncState::Syncing { + return; + } + info!("Proof sync starting"); + self.post_request_cooldown = 0; + self.refresh_peer_statuses(cx); + self.state = ProofSyncState::Syncing; + } + + pub fn pause(&mut self) { + if self.state == ProofSyncState::Idle { + return; + } + debug!("Proof sync pausing"); + self.state = ProofSyncState::Idle; + } + + pub fn poll(&mut self, cx: &mut SyncNetworkContext) { + if self.state == ProofSyncState::Idle { + return; + } + + if self.post_request_cooldown > 0 { + self.post_request_cooldown = self.post_request_cooldown.saturating_sub(1); + return; + } + + if self.range_request.is_some() || self.root_request.is_some() { + return; + } + + let configured_proof_types = cx.configured_proof_types_vec(); + let missing = self.chain.missing_execution_proofs(&configured_proof_types); + if missing.is_empty() { + return; + } + + let needed_types: HashSet = missing + .iter() + .flat_map(|info| { + configured_proof_types + .iter() + .copied() + .filter(|proof_type| !info.existing_proof_types.contains(proof_type)) + }) + .collect(); + if needed_types.is_empty() { + return; + } + + let Some((peer_id, peer_slot)) = self.best_peer(cx, &needed_types) else { + return; + }; + + let finalized_slot = finalized_request_start_slot(&self.chain); + let missing = + servable_missing_proofs(missing, peer_slot, finalized_slot, &configured_proof_types); + + if missing.is_empty() { + return; + } + + let range_bytes = by_range_request_size(configured_proof_types.len()); + let root_bytes = by_root_request_size(&missing, configured_proof_types.len()); + let start_slot = missing[0].slot; + let Some(count) = missing + .last() + .and_then(|last| last.slot.as_u64().checked_sub(start_slot.as_u64())) + .and_then(|delta| delta.checked_add(1)) + else { + return; + }; + let dense_enough = (count as usize) <= missing.len().saturating_mul(2); + + if dense_enough && range_bytes < root_bytes { + match cx.request_execution_proofs_by_range(peer_id, start_slot, count) { + Ok(id) => { + debug!( + %start_slot, + count, + range_bytes, + root_bytes, + "Proof sync range request sent" + ); + self.range_request = Some(ByRangeRequest { id, peer_id }); + } + Err(error) => { + debug!(?error, "Proof sync range request failed"); + } + } + return; + } + + match cx.request_execution_proofs_by_root(peer_id, &missing) { + Ok(id) => { + debug!( + num_roots = missing.len(), + root_bytes, range_bytes, "Proof sync by-root request sent" + ); + self.root_request = Some(ByRootRequest { id, peer_id }); + } + Err(error) => { + debug!(?error, "Proof sync by-root request failed"); + } + } + } + + pub fn on_range_request_terminated(&mut self, id: &ExecutionProofsByRangeRequestId) { + if self.range_request.as_ref().map(|request| &request.id) == Some(id) { + self.range_request = None; + self.post_request_cooldown = POST_REQUEST_COOLDOWN_SLOTS; + } + } + + pub fn on_root_request_terminated(&mut self, id: &ExecutionProofsByRootRequestId) { + if self.root_request.as_ref().map(|request| &request.id) == Some(id) { + self.root_request = None; + self.post_request_cooldown = POST_REQUEST_COOLDOWN_SLOTS; + } + } + + pub fn on_range_request_error(&mut self, id: &ExecutionProofsByRangeRequestId) { + if self.range_request.as_ref().map(|request| &request.id) == Some(id) { + self.range_request = None; + } + } + + pub fn on_root_request_error(&mut self, id: &ExecutionProofsByRootRequestId) { + if self.root_request.as_ref().map(|request| &request.id) == Some(id) { + self.root_request = None; + } + } + + pub fn add_peer(&mut self, peer_id: PeerId, cx: &mut SyncNetworkContext) { + match cx.request_execution_proof_status(peer_id) { + Ok(id) => { + self.status_in_flight.insert(peer_id, id); + } + Err(error) => { + debug!(?error, %peer_id, "Proof sync status request failed"); + } + } + } + + pub fn on_proof_capable_peer_disconnected(&mut self, peer_id: &PeerId) { + self.peer_statuses.remove(peer_id); + self.status_in_flight.remove(peer_id); + if self + .range_request + .as_ref() + .is_some_and(|request| &request.peer_id == peer_id) + { + self.range_request = None; + } + if self + .root_request + .as_ref() + .is_some_and(|request| &request.peer_id == peer_id) + { + self.root_request = None; + } + } + + pub fn on_peer_execution_proof_status( + &mut self, + peer_id: PeerId, + _request_id: Option, + status: ExecutionProofStatus, + ) { + let best_slot = self.chain.best_slot(); + let verified = if status.slot <= best_slot.as_u64() { + match self + .chain + .block_root_at_slot(Slot::new(status.slot), WhenSlotSkipped::None) + { + Ok(Some(root)) if root == status.block_root => true, + _ => { + debug!( + %peer_id, + slot = status.slot, + claimed_root = %status.block_root, + "Ignoring mismatched execution proof status" + ); + self.on_peer_status_failed(peer_id); + return; + } + } + } else { + false + }; + + self.status_in_flight.remove(&peer_id); + self.peer_statuses.insert( + peer_id, + CachedExecutionProofStatus { + status, + timestamp: Instant::now(), + verified, + }, + ); + } + + pub fn on_peer_execution_proof_status_error( + &mut self, + peer_id: PeerId, + _request_id: ExecutionProofStatusRequestId, + ) { + self.on_peer_status_failed(peer_id); + } + + fn on_peer_status_failed(&mut self, peer_id: PeerId) { + self.status_in_flight.remove(&peer_id); + self.peer_statuses + .entry(peer_id) + .and_modify(|entry| entry.timestamp = Instant::now()) + .or_insert_with(|| CachedExecutionProofStatus { + status: ExecutionProofStatus::default(), + timestamp: Instant::now(), + verified: false, + }); + } + + fn refresh_peer_statuses(&mut self, cx: &mut SyncNetworkContext) { + for (peer_id, status) in self.peer_statuses.iter() { + if status.needs_refresh() && !self.status_in_flight.contains_key(peer_id) { + match cx.request_execution_proof_status(*peer_id) { + Ok(id) => { + self.status_in_flight.insert(*peer_id, id); + } + Err(error) => { + debug!(?error, %peer_id, "Proof sync status refresh failed"); + } + } + } + } + } + + fn best_peer( + &mut self, + cx: &mut SyncNetworkContext, + needed_types: &HashSet, + ) -> Option<(PeerId, Slot)> { + self.refresh_peer_statuses(cx); + + let result = self + .peer_statuses + .iter() + .filter(|(_, cached)| { + cached + .status + .proof_types + .iter() + .any(|proof_type| needed_types.contains(proof_type)) + }) + .max_by_key(|(_, cached)| { + let supported_needed_types = cached + .status + .proof_types + .iter() + .filter(|proof_type| needed_types.contains(proof_type)) + .count(); + (cached.verified, supported_needed_types, cached.status.slot) + }) + .map(|(peer_id, cached)| (*peer_id, Slot::new(cached.status.slot))); + + match result { + None if !self.logged_no_peer => { + debug!("Proof sync has no proof-capable peer"); + self.logged_no_peer = true; + } + Some(_) => { + self.logged_no_peer = false; + } + _ => {} + } + + result + } +} + +fn finalized_request_start_slot(chain: &BeaconChain) -> Slot { + chain + .canonical_head + .cached_head() + .finalized_checkpoint() + .epoch + .start_slot(T::EthSpec::slots_per_epoch()) +} + +fn servable_missing_proofs( + missing: Vec, + peer_slot: Slot, + finalized_slot: Slot, + configured_proof_types: &[ProofType], +) -> Vec { + let mut missing = missing + .into_iter() + .filter(|info| { + if info.slot < finalized_slot { + debug!( + block_root = %info.root, + slot = %info.slot, + %finalized_slot, + "Proof sync skipping missing proof before finalized request window" + ); + false + } else if peer_slot < info.slot { + debug!( + block_root = %info.root, + slot = %info.slot, + %peer_slot, + "Proof sync peer is behind missing proof block" + ); + false + } else { + configured_proof_types + .iter() + .any(|proof_type| !info.existing_proof_types.contains(proof_type)) + } + }) + .collect::>(); + missing.sort_unstable_by_key(|info| info.slot); + missing +} + +fn per_identifier_ssz_bytes( + info: &MissingExecutionProofInfo, + num_configured_types: usize, +) -> usize { + let needed = num_configured_types.saturating_sub(info.existing_proof_types.len()); + 4 + 32 + 4 + needed +} + +fn by_root_request_size( + missing: &[MissingExecutionProofInfo], + num_configured_types: usize, +) -> usize { + missing + .iter() + .map(|info| per_identifier_ssz_bytes(info, num_configured_types)) + .sum() +} + +fn by_range_request_size(num_configured_types: usize) -> usize { + 20 + num_configured_types +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + use types::Hash256; + + fn missing_at( + slot: u64, + existing_proof_types: impl IntoIterator, + ) -> MissingExecutionProofInfo { + MissingExecutionProofInfo { + root: Hash256::with_last_byte((slot & 0xff) as u8), + slot: Slot::new(slot), + existing_proof_types: existing_proof_types.into_iter().collect::>(), + } + } + + #[test] + fn servable_missing_proofs_starts_at_finalized_slot() { + let configured_proof_types = vec![0, 1]; + let missing = vec![ + missing_at(7, []), + missing_at(8, []), + missing_at(9, [0, 1]), + missing_at(10, [0]), + missing_at(11, []), + ]; + + let servable = servable_missing_proofs( + missing, + Slot::new(10), + Slot::new(8), + &configured_proof_types, + ); + + assert_eq!( + servable + .iter() + .map(|info| info.slot.as_u64()) + .collect::>(), + vec![8, 10], + ); + } +} diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 647b5858cb1..b7cccc54422 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -919,6 +919,24 @@ pub fn cli_app() -> Command { .action(ArgAction::Set) .display_order(0) ) + .arg( + Arg::new("proof-engine-endpoint") + .long("proof-engine-endpoint") + .value_name("PROOF-ENGINE-ENDPOINT") + .help("Server endpoint for the optional EIP-8025 proof engine HTTP API.") + .requires("execution-endpoint") + .action(ArgAction::Set) + .display_order(0) + ) + .arg( + Arg::new("proof-types") + .long("proof-types") + .value_name("PROOF-TYPES") + .help("Comma-separated EIP-8025 proof types to request from the proof engine.") + .requires("proof-engine-endpoint") + .action(ArgAction::Set) + .display_order(0) + ) .arg( Arg::new("disable-get-blobs") .long("disable-get-blobs") @@ -1515,6 +1533,15 @@ pub fn cli_app() -> Command { Lighthouse and only passed to the EL if initial verification fails.") .display_order(0) ) + .arg( + Arg::new("execution-proof-quorum") + .long("execution-proof-quorum") + .value_name("K") + .help("Non-default: mark a Gloas payload envelope as proof-valid after K distinct valid EIP-8025 proof types for the same new-payload request root.") + .requires("proof-engine-endpoint") + .action(ArgAction::Set) + .display_order(0) + ) .arg( Arg::new("disable-light-client-server") .long("disable-light-client-server") diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 045b432dc97..f02c61bf91a 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -359,6 +359,29 @@ pub fn get_config( clap_utils::parse_required(cli_args, "execution-timeout-multiplier")?; el_config.execution_timeout_multiplier = Some(execution_timeout_multiplier); + if let Some(endpoint) = cli_args.get_one::("proof-engine-endpoint") { + el_config.proof_engine_endpoint = Some(parse_only_one_value( + endpoint, + SensitiveUrl::parse, + "--proof-engine-endpoint", + )?); + client_config.network.enable_execution_proof = true; + } + + if let Some(proof_types) = cli_args.get_one::("proof-types") { + let proof_types = proof_types + .split(',') + .filter(|proof_type| !proof_type.trim().is_empty()) + .map(|proof_type| { + proof_type + .trim() + .parse::() + .map_err(|e| format!("Invalid --proof-types value: {e}")) + }) + .collect::, _>>()?; + el_config.proof_types = execution_layer::eip8025::types::ProofTypes::from(proof_types); + } + // Store the EL config in the client config. client_config.execution_layer = Some(el_config); @@ -829,6 +852,14 @@ pub fn get_config( client_config.chain.optimistic_finalized_sync = !cli_args.get_flag("disable-optimistic-finalized-sync"); + if let Some(quorum) = clap_utils::parse_optional::(cli_args, "execution-proof-quorum")? { + client_config.chain.execution_proof_quorum.enabled = true; + client_config + .chain + .execution_proof_quorum + .min_valid_proof_types = quorum; + } + if cli_args.get_flag("genesis-backfill") { client_config.chain.genesis_backfill = true; } diff --git a/consensus/types/src/execution/eip8025.rs b/consensus/types/src/execution/eip8025.rs new file mode 100644 index 00000000000..2ac729426c8 --- /dev/null +++ b/consensus/types/src/execution/eip8025.rs @@ -0,0 +1,354 @@ +//! EIP-8025: Optional Execution Proofs +//! +//! This module contains types for the EIP-8025 optional execution proofs feature. +//! See: https://eips.ethereum.org/EIPS/eip-8025 + +use crate::core::{Hash256, SignedRoot}; +use bls::SignatureBytes; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use ssz_types::VariableList; +use tree_hash_derive::TreeHash; + +#[cfg(feature = "arbitrary")] +use arbitrary::Arbitrary; + +/// Maximum proof size: 1344 KiB (1,376,256 bytes). +/// +/// Product of (U21 * U64) * U1024. +pub type MaxProofSize = typenum::Prod; + +/// Maximum proof size in KiB. +pub type MaxProofSizeKiB = typenum::Prod; + +/// Proof data type +/// +/// VariableList of bytes with max length [`MaxProofSize`]. +pub type ProofData = VariableList; + +/// Maximum execution proofs per payload +pub type MaxExecutionProofsPerPayload = typenum::U4; + +/// Proof type identifier +pub type ProofType = u8; + +/// List of execution proofs per payload +pub type ExecutionProofList = VariableList; + +/// Domain type for execution proof signatures (0x0D000000) +pub const DOMAIN_EXECUTION_PROOF: [u8; 4] = [0x0D, 0x00, 0x00, 0x00]; + +/// Minimum required execution proofs for payload verification +pub const MIN_REQUIRED_EXECUTION_PROOFS: usize = 1; + +/// Public input of an [`ExecutionProof`]. +/// +/// Contains the tree hash root of the new payload request that the proof is associated with. +#[derive( + Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode, TreeHash, +)] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct PublicInput { + /// The tree hash root of the NewPayloadRequest associated with the proof. + pub new_payload_request_root: Hash256, +} + +/// The type of an execution proof. +/// +/// Contains the proof data, type, and public input that links it to a specific new payload request. +#[derive( + Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode, TreeHash, +)] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct ExecutionProof { + /// The proof data. + #[serde(with = "ssz_types::serde_utils::hex_var_list")] + pub proof_data: ProofData, + /// The type of proof. + pub proof_type: ProofType, + /// Public input linking the proof to a specific new payload request. + pub public_input: PublicInput, +} + +impl SignedRoot for ExecutionProof {} + +/// A signed execution proof from a validator. +/// +/// Contains the execution proof, the validator's index, and their BLS signature. +#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize, Encode, Decode, TreeHash)] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct SignedExecutionProof { + /// The execution proof message + pub message: ExecutionProof, + /// Index of the validator who signed this proof + #[serde(with = "serde_utils::quoted_u64")] + pub validator_index: u64, + /// BLS signature over the execution proof + pub signature: SignatureBytes, +} + +/// Identifies a block root and the proof types being requested for it. +/// +/// Matches the `ProofByRootIdentifier` container in the EIP-8025 p2p spec. +#[derive( + Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode, TreeHash, +)] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct ProofByRootIdentifier { + /// The beacon block root whose execution proofs are being requested. + pub block_root: Hash256, + /// Proof types the requester still needs for this block root. + pub proof_types: VariableList, +} + +/// Proof attributes for requesting proof generation. +/// +/// Specifies which types of proofs should be generated for a payload. +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct ProofAttributes { + /// List of proof types to generate + pub proof_types: Vec, +} + +// ============================================================================= +// Status Types +// ============================================================================= + +/// Status returned from proof verification operations. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum ProofStatus { + /// The proof is valid. + Valid, + /// The proof/header verification failed. + Invalid, + /// The proof is valid but does not change the canonical head. + Accepted, + /// The proof type is not supported by this client. + NotSupported, + /// The request root that the proof is associated with is not yet known. + Syncing, +} + +impl ProofStatus { + /// Returns true if the status indicates successful verification. + pub fn is_valid(&self) -> bool { + matches!(self, ProofStatus::Valid) + } + + /// Returns true if the status indicates the node is still syncing proofs. + pub fn is_syncing(&self) -> bool { + matches!(self, ProofStatus::Syncing) + } + + /// Returns true if the status indicates the node has accepted the proof. + pub fn is_accepted(&self) -> bool { + matches!(self, ProofStatus::Accepted) + } +} + +impl std::fmt::Display for ProofStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProofStatus::Valid => { + write!(f, "VALID") + } + ProofStatus::Invalid => write!(f, "INVALID"), + ProofStatus::Accepted => write!(f, "ACCEPTED"), + ProofStatus::NotSupported => write!(f, "NOT_SUPPORTED"), + ProofStatus::Syncing => write!(f, "SYNCING"), + } + } +} + +// ============================================================================= +// Utility Implementations +// ============================================================================= + +impl ExecutionProof { + /// Returns true if the proof data is empty. + pub fn is_empty(&self) -> bool { + self.proof_data.is_empty() + } + + /// Returns the size of the proof data in bytes. + pub fn proof_size(&self) -> usize { + self.proof_data.len() + } + + /// Returns the hash tree root of this execution proof. + pub fn hash_tree_root(&self) -> Hash256 { + tree_hash::TreeHash::tree_hash_root(self) + } +} + +impl SignedExecutionProof { + /// Returns a reference to the underlying execution proof. + pub fn proof(&self) -> &ExecutionProof { + &self.message + } + + /// Returns the proof data of the underlying execution proof. + pub fn proof_data(&self) -> &ProofData { + &self.message.proof_data + } + + /// Returns the new payload request root this proof validates. + pub fn request_root(&self) -> Hash256 { + self.message.public_input.new_payload_request_root + } + + /// Returns the proof type. + pub fn proof_type(&self) -> ProofType { + self.message.proof_type + } + + /// Returns the validator index that signed this proof. + pub fn validator_index(&self) -> u64 { + self.validator_index + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ssz::{Decode, Encode}; + + #[test] + fn public_input_round_trip() { + let input = PublicInput { + new_payload_request_root: Hash256::repeat_byte(0xab), + }; + let encoded = input.as_ssz_bytes(); + let decoded = PublicInput::from_ssz_bytes(&encoded).unwrap(); + assert_eq!(input, decoded); + } + + #[test] + fn execution_proof_round_trip() { + let proof = ExecutionProof { + proof_data: VariableList::new(vec![1u8, 2, 3, 4]).unwrap(), + proof_type: 1, + public_input: PublicInput { + new_payload_request_root: Hash256::repeat_byte(0xcd), + }, + }; + let encoded = proof.as_ssz_bytes(); + let decoded = ExecutionProof::from_ssz_bytes(&encoded).unwrap(); + assert_eq!(proof, decoded); + } + + #[test] + fn signed_execution_proof_round_trip() { + let signed_proof = SignedExecutionProof { + message: ExecutionProof { + proof_data: VariableList::new(vec![5u8, 6, 7, 8]).unwrap(), + proof_type: 2, + public_input: PublicInput { + new_payload_request_root: Hash256::repeat_byte(0xef), + }, + }, + validator_index: 42, + signature: SignatureBytes::empty(), + }; + let encoded = signed_proof.as_ssz_bytes(); + let decoded = SignedExecutionProof::from_ssz_bytes(&encoded).unwrap(); + assert_eq!(signed_proof, decoded); + } + + #[test] + fn execution_proof_is_empty() { + let empty_proof = ExecutionProof { + proof_data: VariableList::new(vec![]).unwrap(), + proof_type: 1, + public_input: PublicInput { + new_payload_request_root: Hash256::ZERO, + }, + }; + assert!(empty_proof.is_empty()); + + let non_empty_proof = ExecutionProof { + proof_data: VariableList::new(vec![1u8, 2, 3]).unwrap(), + proof_type: 1, + public_input: PublicInput { + new_payload_request_root: Hash256::ZERO, + }, + }; + assert!(!non_empty_proof.is_empty()); + } + + #[test] + fn execution_proof_size() { + let proof = ExecutionProof { + proof_data: VariableList::new(vec![1u8, 2, 3, 4, 5]).unwrap(), + proof_type: 1, + public_input: PublicInput { + new_payload_request_root: Hash256::ZERO, + }, + }; + assert_eq!(proof.proof_size(), 5); + + let empty_proof = ExecutionProof::default(); + assert_eq!(empty_proof.proof_size(), 0); + } + + #[test] + fn signed_execution_proof_accessors() { + let request_root = Hash256::repeat_byte(0xab); + let proof_type = 42u8; + let validator_index = 123u64; + + let signed_proof = SignedExecutionProof { + message: ExecutionProof { + proof_data: VariableList::new(vec![1u8, 2, 3]).unwrap(), + proof_type, + public_input: PublicInput { + new_payload_request_root: request_root, + }, + }, + validator_index, + signature: SignatureBytes::empty(), + }; + + assert_eq!(signed_proof.request_root(), request_root); + assert_eq!(signed_proof.proof_type(), proof_type); + assert_eq!(signed_proof.validator_index(), validator_index); + assert_eq!(signed_proof.proof().proof_type, proof_type); + } + + #[test] + fn proof_status_is_valid() { + assert!(ProofStatus::Valid.is_valid()); + assert!(!ProofStatus::Invalid.is_valid()); + assert!(!ProofStatus::Accepted.is_valid()); + assert!(!ProofStatus::NotSupported.is_valid()); + } + + #[test] + fn proof_status_is_syncing() { + assert!(ProofStatus::Syncing.is_syncing()); + assert!(!ProofStatus::Accepted.is_syncing()); + assert!(!ProofStatus::Valid.is_syncing()); + assert!(!ProofStatus::Invalid.is_syncing()); + assert!(!ProofStatus::NotSupported.is_syncing()); + } + + #[test] + fn proof_attributes_default() { + let attrs = ProofAttributes::default(); + assert!(attrs.proof_types.is_empty()); + + let attrs_with_types = ProofAttributes { + proof_types: vec![1, 2, 3], + }; + assert_eq!(attrs_with_types.proof_types.len(), 3); + } + + #[test] + fn max_proof_size_is_1344_kib() { + use typenum::Unsigned; + + assert_eq!(MaxProofSizeKiB::USIZE, 1344); + assert_eq!(MaxProofSize::USIZE, 1_376_256); + } +} diff --git a/consensus/types/src/execution/mod.rs b/consensus/types/src/execution/mod.rs index a3d4ed87301..2c2729bd083 100644 --- a/consensus/types/src/execution/mod.rs +++ b/consensus/types/src/execution/mod.rs @@ -1,3 +1,4 @@ +pub mod eip8025; mod eth1_data; mod execution_block_header; #[macro_use] @@ -14,6 +15,11 @@ mod signed_execution_payload_bid; mod signed_execution_payload_envelope; pub use bls_to_execution_change::BlsToExecutionChange; +pub use eip8025::{ + DOMAIN_EXECUTION_PROOF, ExecutionProof, ExecutionProofList, MIN_REQUIRED_EXECUTION_PROOFS, + MaxExecutionProofsPerPayload, ProofAttributes, ProofByRootIdentifier, ProofData, ProofStatus, + ProofType, PublicInput, SignedExecutionProof, +}; pub use eth1_data::Eth1Data; pub use execution_block_header::{EncodableExecutionBlockHeader, ExecutionBlockHeader}; pub use execution_payload::{ From 6d9217742b40741e1baf08a91614fc783c1e4cd7 Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 27 May 2026 01:07:13 +0100 Subject: [PATCH 2/4] Fix optional proof CI failures --- .../lighthouse_network/src/rpc/codec.rs | 18 ++++++++++++++++++ .../lighthouse_network/src/rpc/protocol.rs | 5 ++++- beacon_node/network/src/sync/manager.rs | 3 +-- book/src/help_bn.md | 8 ++++++++ 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/beacon_node/lighthouse_network/src/rpc/codec.rs b/beacon_node/lighthouse_network/src/rpc/codec.rs index 0f73a941386..77b918e956b 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec.rs @@ -1413,6 +1413,24 @@ mod tests { RequestType::LightClientUpdatesByRange(light_client_updates_by_range) ) } + RequestType::ExecutionProofsByRange(execution_proofs_by_range) => { + assert_eq!( + decoded, + RequestType::ExecutionProofsByRange(execution_proofs_by_range) + ) + } + RequestType::ExecutionProofsByRoot(execution_proofs_by_root) => { + assert_eq!( + decoded, + RequestType::ExecutionProofsByRoot(execution_proofs_by_root) + ) + } + RequestType::ExecutionProofStatus(execution_proof_status) => { + assert_eq!( + decoded, + RequestType::ExecutionProofStatus(execution_proof_status) + ) + } } } diff --git a/beacon_node/lighthouse_network/src/rpc/protocol.rs b/beacon_node/lighthouse_network/src/rpc/protocol.rs index d1c6a8cde3c..fd524bd9ac9 100644 --- a/beacon_node/lighthouse_network/src/rpc/protocol.rs +++ b/beacon_node/lighthouse_network/src/rpc/protocol.rs @@ -1310,7 +1310,10 @@ mod tests { LightClientBootstrapV1 | LightClientOptimisticUpdateV1 | LightClientFinalityUpdateV1 - | LightClientUpdatesByRangeV1 => false, + | LightClientUpdatesByRangeV1 + | ExecutionProofsByRangeV1 + | ExecutionProofsByRootV1 + | ExecutionProofStatusV1 => false, } } diff --git a/beacon_node/network/src/sync/manager.rs b/beacon_node/network/src/sync/manager.rs index f6c13bd8fe0..c8158998621 100644 --- a/beacon_node/network/src/sync/manager.rs +++ b/beacon_node/network/src/sync/manager.rs @@ -53,7 +53,6 @@ use beacon_chain::block_verification_types::AsBlock; use beacon_chain::{ AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes, BlockError, EngineState, }; -use execution_layer::eip8025::types::ProofTypes; use futures::StreamExt; use lighthouse_network::SyncInfo; use lighthouse_network::rpc::RPCError; @@ -350,7 +349,7 @@ impl SyncManager { .execution_layer .as_ref() .map(|execution_layer| execution_layer.proof_types().clone()) - .unwrap_or_else(ProofTypes::default); + .unwrap_or_default(); Self { chain: beacon_chain.clone(), input_channel: sync_recv, diff --git a/book/src/help_bn.md b/book/src/help_bn.md index 30163f1f0cd..3355101b8d6 100644 --- a/book/src/help_bn.md +++ b/book/src/help_bn.md @@ -139,6 +139,10 @@ Options: Used by the beacon node to communicate a client version to execution nodes during JWT authentication. It corresponds to the 'clv' field in the JWT claims object.Set to empty by default + --execution-proof-quorum + Non-default: mark a Gloas payload envelope as proof-valid after K + distinct valid EIP-8025 proof types for the same new-payload request + root. --execution-timeout-multiplier Unsigned integer to multiply the default execution timeouts by. [default: 1] @@ -305,6 +309,10 @@ Options: which don't improve their payload after the first call, and high values are useful for ensuring the EL is given ample notice. Default: 1/3 of a slot. + --proof-engine-endpoint + Server endpoint for the optional EIP-8025 proof engine HTTP API. + --proof-types + Comma-separated EIP-8025 proof types to request from the proof engine. --proposer-reorg-cutoff DEPRECATED. This flag has no effect. --proposer-reorg-disallowed-offsets From fcb9fdad5fe3cba47819dfd26b41596433da9d7c Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 27 May 2026 01:37:44 +0100 Subject: [PATCH 3/4] Isolate optional proof CI caches --- .github/workflows/nightly-tests.yml | 5 +++++ .github/workflows/test-suite.yml | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/.github/workflows/nightly-tests.yml b/.github/workflows/nightly-tests.yml index 636d0ea0dd9..2f9506326a8 100644 --- a/.github/workflows/nightly-tests.yml +++ b/.github/workflows/nightly-tests.yml @@ -24,6 +24,8 @@ env: LIGHTHOUSE_GITHUB_TOKEN: ${{ secrets.LIGHTHOUSE_GITHUB_TOKEN }} # Disable incremental compilation CARGO_INCREMENTAL: 0 + # Keep optional-proofs branch-family caches separate while their dependency graphs diverge. + RUST_CACHE_EXTRA_IDENTIFIER: ${{ contains(github.head_ref || github.ref_name, 'optional-proofs-unstable') && 'optional-proofs-unstable' || contains(github.head_ref || github.ref_name, 'optional-proofs') && 'optional-proofs' || '' }} # Enable portable to prevent issues with caching `blst` for the wrong CPU type TEST_FEATURES: portable @@ -58,6 +60,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release bins: cargo-nextest - name: Run beacon_chain tests for ${{ matrix.fork }} @@ -82,6 +85,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release bins: cargo-nextest - name: Run operation_pool tests for ${{ matrix.fork }} @@ -106,6 +110,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release bins: cargo-nextest - name: Create CI logger dir diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 1d66bd30e78..01c93842f4e 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -26,6 +26,8 @@ env: CARGO_INCREMENTAL: 0 # Enable portable to prevent issues with caching `blst` for the wrong CPU type TEST_FEATURES: portable + # Keep optional-proofs branch-family caches separate while their dependency graphs diverge. + RUST_CACHE_EXTRA_IDENTIFIER: ${{ contains(github.head_ref || github.ref_name, 'optional-proofs-unstable') && 'optional-proofs-unstable' || contains(github.head_ref || github.ref_name, 'optional-proofs') && 'optional-proofs' || '' }} jobs: check-labels: runs-on: ubuntu-latest @@ -112,6 +114,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release bins: cargo-nextest env: @@ -120,6 +123,7 @@ jobs: uses: Swatinem/rust-cache@v2 with: cache-provider: warpbuild + key: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} - name: Run tests in release run: make test-release - name: Show cache stats @@ -140,12 +144,14 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release bins: cargo-nextest - if: github.repository == 'sigp/lighthouse' uses: Swatinem/rust-cache@v2 with: cache-provider: warpbuild + key: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} - name: Run beacon_chain tests for all known forks run: make test-beacon-chain http-api-tests: @@ -162,12 +168,14 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release bins: cargo-nextest - if: github.repository == 'sigp/lighthouse' uses: Swatinem/rust-cache@v2 with: cache-provider: warpbuild + key: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} - name: Run http_api tests for all recent forks run: make test-http-api op-pool-tests: @@ -183,6 +191,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release bins: cargo-nextest - name: Run operation_pool tests for all known forks @@ -200,6 +209,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release bins: cargo-nextest - name: Create CI logger dir @@ -229,6 +239,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release bins: cargo-nextest - name: Run slasher tests for all supported backends @@ -247,11 +258,13 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} bins: cargo-nextest - if: github.repository == 'sigp/lighthouse' uses: Swatinem/rust-cache@v2 with: cache-provider: warpbuild + key: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} - name: Run tests in debug run: make test-debug state-transition-vectors-ubuntu: @@ -265,6 +278,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release - name: Run state_transition_vectors in release. run: make run-state-transition-tests @@ -282,12 +296,14 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release bins: cargo-nextest - if: github.repository == 'sigp/lighthouse' uses: Swatinem/rust-cache@v2 with: cache-provider: warpbuild + key: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} - name: Run consensus-spec-tests with blst and fake_crypto run: make test-ef basic-simulator-ubuntu: @@ -301,6 +317,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release - name: Create log dir run: mkdir ${{ runner.temp }}/basic_simulator_logs @@ -323,6 +340,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release - name: Create log dir run: mkdir ${{ runner.temp }}/fallback_simulator_logs @@ -346,6 +364,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -362,6 +381,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release components: rustfmt,clippy bins: cargo-audit,cargo-deny @@ -410,6 +430,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: nightly + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} bins: cargo-udeps cache: false env: @@ -447,6 +468,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release - name: Run Makefile to trigger the bash script run: make cli-local @@ -461,6 +483,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} - uses: taiki-e/install-action@cargo-hack - name: Check types feature powerset run: cargo hack check -p types --feature-powerset --no-dev-deps --exclude-features arbitrary-fuzz,portable @@ -477,6 +500,7 @@ jobs: uses: moonrepo/setup-rust@v1 with: channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} cache-target: release bins: cargo-sort - name: Run cargo sort to check if Cargo.toml files are sorted From 5ea53584a820da6277d7775f2434f151ebecc62b Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 27 May 2026 23:29:52 +0100 Subject: [PATCH 4/4] feat: restore optional proof test coverage --- .github/workflows/kurtosis-eip8025.yml | 139 + .github/workflows/test-suite.yml | 24 + .github/workflows/zkboost-tests.yml | 76 + Cargo.lock | 26 + Cargo.toml | 2 +- Makefile | 11 +- beacon_node/beacon_chain/src/beacon_chain.rs | 40 + beacon_node/beacon_chain/src/builder.rs | 1 + .../beacon_chain/src/eip8025/proof_status.rs | 90 +- .../beacon_chain/src/execution_payload.rs | 40 +- .../beacon_chain/src/internal_events.rs | 29 + beacon_node/beacon_chain/src/lib.rs | 1 + .../execution_layer/src/eip8025/types.rs | 73 +- beacon_node/execution_layer/src/lib.rs | 304 +- .../src/test_utils/mock_event_stream.rs | 116 + .../src/test_utils/mock_proof_node_client.rs | 320 + .../execution_layer/src/test_utils/mod.rs | 7 + beacon_node/http_api/src/beacon/pool.rs | 13 +- beacon_node/http_api/src/lib.rs | 3 +- .../lighthouse_network/src/service/utils.rs | 1 + .../gossip_methods.rs | 59 +- .../network/src/sync/backfill_sync/mod.rs | 3 + .../src/sync/custody_backfill_sync/mod.rs | 1 + beacon_node/network/src/sync/manager.rs | 32 +- .../network/src/sync/network_context.rs | 14 +- common/eth2/src/lib.rs | 29 +- consensus/types/src/core/chain_spec.rs | 6 + consensus/types/src/core/config_and_preset.rs | 1 + lighthouse/environment/Cargo.toml | 3 + lighthouse/environment/src/lib.rs | 22 + lighthouse/environment/src/test_utils.rs | 24 + .../local_testnet/network_params_eip8025.yaml | 40 + .../network_params_eip8025_zkboost.yaml | 73 + .../network_params_eip8025_zkboost_gpu.yaml | 95 + .../local_testnet/start_eip8025_testnet.sh | 87 + testing/proof_engine/Cargo.toml | 16 + testing/proof_engine/src/lib.rs | 298 + testing/proof_engine/src/rig.rs | 384 + testing/proof_engine_zkboost/Cargo.lock | 9908 +++++++++++++++++ testing/proof_engine_zkboost/Cargo.toml | 38 + testing/proof_engine_zkboost/src/lib.rs | 306 + .../src/zkboost_harness.rs | 173 + .../tests/fixture/chain_config.json | 45 + .../tests/fixture/execution_witness.json | 50 + .../tests/fixture/new_payload_request.ssz | Bin 0 -> 602 bytes testing/simulator/Cargo.toml | 12 +- testing/simulator/src/basic_sim.rs | 20 +- testing/simulator/src/fallback_sim.rs | 11 +- testing/simulator/src/lib.rs | 26 + testing/simulator/src/local_network.rs | 322 +- testing/simulator/src/test_utils/builder.rs | 341 + .../simulator/src/test_utils/event_stream.rs | 56 + testing/simulator/src/test_utils/mod.rs | 98 + validator_client/Cargo.toml | 2 + validator_client/http_api/src/lib.rs | 4 +- .../lighthouse_validator_store/src/lib.rs | 55 +- validator_client/signing_method/src/lib.rs | 3 + .../signing_method/src/web3signer.rs | 3 + validator_client/src/cli.rs | 22 + validator_client/src/config.rs | 40 + validator_client/src/lib.rs | 40 + validator_client/validator_metrics/src/lib.rs | 7 + .../validator_services/Cargo.toml | 3 +- .../validator_services/src/lib.rs | 1 + .../validator_services/src/proof_service.rs | 387 + validator_client/validator_store/src/lib.rs | 19 +- 66 files changed, 14254 insertions(+), 241 deletions(-) create mode 100644 .github/workflows/kurtosis-eip8025.yml create mode 100644 .github/workflows/zkboost-tests.yml create mode 100644 beacon_node/beacon_chain/src/internal_events.rs create mode 100644 beacon_node/execution_layer/src/test_utils/mock_event_stream.rs create mode 100644 beacon_node/execution_layer/src/test_utils/mock_proof_node_client.rs create mode 100644 lighthouse/environment/src/test_utils.rs create mode 100644 scripts/local_testnet/network_params_eip8025.yaml create mode 100644 scripts/local_testnet/network_params_eip8025_zkboost.yaml create mode 100644 scripts/local_testnet/network_params_eip8025_zkboost_gpu.yaml create mode 100755 scripts/local_testnet/start_eip8025_testnet.sh create mode 100644 testing/proof_engine/Cargo.toml create mode 100644 testing/proof_engine/src/lib.rs create mode 100644 testing/proof_engine/src/rig.rs create mode 100644 testing/proof_engine_zkboost/Cargo.lock create mode 100644 testing/proof_engine_zkboost/Cargo.toml create mode 100644 testing/proof_engine_zkboost/src/lib.rs create mode 100644 testing/proof_engine_zkboost/src/zkboost_harness.rs create mode 100644 testing/proof_engine_zkboost/tests/fixture/chain_config.json create mode 100644 testing/proof_engine_zkboost/tests/fixture/execution_witness.json create mode 100644 testing/proof_engine_zkboost/tests/fixture/new_payload_request.ssz create mode 100644 testing/simulator/src/lib.rs create mode 100644 testing/simulator/src/test_utils/builder.rs create mode 100644 testing/simulator/src/test_utils/event_stream.rs create mode 100644 testing/simulator/src/test_utils/mod.rs create mode 100644 validator_client/validator_services/src/proof_service.rs diff --git a/.github/workflows/kurtosis-eip8025.yml b/.github/workflows/kurtosis-eip8025.yml new file mode 100644 index 00000000000..87c0e949236 --- /dev/null +++ b/.github/workflows/kurtosis-eip8025.yml @@ -0,0 +1,139 @@ +# Test that the EIP-8025 Kurtosis testnet starts and the proof engine integrates +# correctly with real zkboost-server backends. +name: kurtosis eip8025 + +on: + push: + branches: + - unstable + pull_request: + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + run-eip8025-testnet: + runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-8x' || 'ubuntu-latest' }} + steps: + - uses: actions/checkout@v5 + + - name: Install Kurtosis + run: | + echo "deb [trusted=yes] https://sdk.kurtosis.com/kurtosis-cli-release-artifacts/ /" | sudo tee /etc/apt/sources.list.d/kurtosis.list + sudo apt update + sudo apt install -y kurtosis-cli + kurtosis analytics disable + + - name: Install yq + run: | + sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + + - name: Start EIP-8025 zkboost testnet + run: ./start_eip8025_testnet.sh -e eip8025-zkboost -n network_params_eip8025_zkboost.yaml + working-directory: scripts/local_testnet + + - name: Wait for chain liveness + run: | + BEACON_URL=$(kurtosis port print eip8025-zkboost cl-1-lighthouse-reth http) + echo "Polling $BEACON_URL for head slot > 10..." + for i in $(seq 1 20); do + SLOT=$(curl -sf "$BEACON_URL/eth/v1/beacon/headers/head" \ + | jq -r '.data.header.message.slot' 2>/dev/null || echo 0) + echo " attempt $i: head slot = $SLOT" + if [ "$SLOT" -gt 10 ]; then + echo "Chain is live at slot $SLOT" + break + fi + if [ "$i" -eq 20 ]; then + echo "Timed out waiting for head slot > 10" + exit 1 + fi + sleep 30 + done + + - name: Inspect beacon node state + run: | + set -euo pipefail + ENCLAVE=eip8025-zkboost + SERVICES=(cl-1-lighthouse-reth cl-2-lighthouse-reth cl-3-lighthouse-reth cl-4-lighthouse-reth) + FAILED=0 + + for SVC in "${SERVICES[@]}"; do + URL=$(kurtosis port print "$ENCLAVE" "$SVC" http) + echo "=== $SVC ($URL) ===" + + # Syncing status — must not be syncing + SYNCING=$(curl -sf "$URL/eth/v1/node/syncing" | jq -r '.data.is_syncing') + echo " is_syncing: $SYNCING" + if [ "$SYNCING" != "false" ]; then + echo " FAIL: $SVC is still syncing" + FAILED=1 + fi + + # Peer count — must have at least one connected peer + PEERS=$(curl -sf "$URL/eth/v1/node/peer_count" | jq -r '.data.connected') + echo " connected peers: $PEERS" + if [ "${PEERS:-0}" -lt 1 ]; then + echo " FAIL: $SVC has no connected peers" + FAILED=1 + fi + + # Head slot — must be non-zero + SLOT=$(curl -sf "$URL/eth/v1/beacon/headers/head" | jq -r '.data.header.message.slot') + echo " head slot: $SLOT" + if [ "${SLOT:-0}" -lt 1 ]; then + echo " FAIL: $SVC head slot is 0" + FAILED=1 + fi + + # Finality checkpoints — informational, log the finalized epoch + FINALIZED=$(curl -sf "$URL/eth/v1/beacon/states/head/finality_checkpoints" \ + | jq -r '.data.finalized.epoch') + echo " finalized epoch: $FINALIZED" + done + + exit "$FAILED" + + - name: Check zkboost sidecars are running and generating proofs + run: | + ENCLAVE=eip8025-zkboost + + # Both zkboost services must be in RUNNING state + kurtosis enclave inspect "$ENCLAVE" | grep -E "zkboost-[12].*RUNNING" \ + || { echo "FAIL: one or more zkboost services not in RUNNING state"; exit 1; } + + # Each zkboost sidecar must have generated at least one proof. + # Check via the Prometheus metrics endpoint (zkboost_prove_total) rather than + # log scraping — kurtosis service logs may not be available in all CI environments. + for SVC in zkboost-1 zkboost-2; do + URL=$(kurtosis port print "$ENCLAVE" "$SVC" http) + COUNT=$(curl -sf "$URL/metrics" \ + | awk '/^zkboost_prove_total\{/ {sum += $2} END {print int(sum)}') + echo "$SVC: $COUNT proofs generated" + if [ "${COUNT:-0}" -lt 1 ]; then + echo "FAIL: $SVC has not generated any proofs" + exit 1 + fi + done + + - name: Stop testnet and collect logs + if: always() + run: | + mkdir -p scripts/local_testnet/logs + ENCLAVE=eip8025-zkboost + for SVC in cl-1-lighthouse-reth cl-2-lighthouse-reth cl-3-lighthouse-reth cl-4-lighthouse-reth \ + zkboost-1 zkboost-2; do + kurtosis service logs "$ENCLAVE" "$SVC" > "scripts/local_testnet/logs/${SVC}.log" 2>&1 || true + done + kurtosis enclave rm -f "$ENCLAVE" || true + + - name: Upload logs artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: logs-kurtosis-eip8025 + path: scripts/local_testnet/logs + retention-days: 3 diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 01c93842f4e..f972be78515 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -130,6 +130,30 @@ jobs: if: env.SELF_HOSTED_RUNNERS == 'true' continue-on-error: true run: sccache --show-stats + proof-engine-tests: + name: proof-engine-tests + needs: [check-labels] + if: needs.check-labels.outputs.skip_ci != 'true' + runs-on: ${{ github.repository == 'sigp/lighthouse' && 'warp-ubuntu-latest-x64-4x;snapshot.key=lighthouse-ubuntu-latest-v1' || 'ubuntu-latest' }} + steps: + - uses: actions/checkout@v5 + - if: github.repository != 'sigp/lighthouse' + name: Get latest version of stable Rust + uses: moonrepo/setup-rust@v1 + with: + channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} + cache-target: release + bins: cargo-nextest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: github.repository == 'sigp/lighthouse' + uses: Swatinem/rust-cache@v2 + with: + cache-provider: warpbuild + key: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} + - name: Run proof engine tests sequentially + run: make test-proof-engine beacon-chain-tests: name: beacon-chain-tests needs: [check-labels] diff --git a/.github/workflows/zkboost-tests.yml b/.github/workflows/zkboost-tests.yml new file mode 100644 index 00000000000..bad0f497bbf --- /dev/null +++ b/.github/workflows/zkboost-tests.yml @@ -0,0 +1,76 @@ +name: zkboost-tests + +on: + push: + branches: + - stable + - staging + - trying + - 'pr/*' + pull_request: + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + RUSTFLAGS: "-D warnings -C debuginfo=0" + CARGO_INCREMENTAL: 0 + TEST_FEATURES: portable + # Keep optional-proofs branch-family caches separate while their dependency graphs diverge. + RUST_CACHE_EXTRA_IDENTIFIER: ${{ contains(github.head_ref || github.ref_name, 'optional-proofs-unstable') && 'optional-proofs-unstable' || contains(github.head_ref || github.ref_name, 'optional-proofs') && 'optional-proofs' || '' }} + # Use Clang for C/C++ compilation. Required because leveldb-sys uses + # -Wthread-safety which is a Clang-only flag unsupported by GCC. + CC: clang + CXX: clang++ + # leveldb-1.22's doc/bench/db_bench_sqlite3.cc uses time()/ctime() without + # including ; newer libc++ no longer pulls it in transitively. + CXXFLAGS: "-include ctime" + +jobs: + check-labels: + runs-on: ubuntu-latest + name: Check for 'skip-ci' label + outputs: + skip_ci: ${{ steps.set-output.outputs.SKIP_CI }} + steps: + - name: check for skip-ci label + id: set-output + env: + LABELS: ${{ toJson(github.event.pull_request.labels) }} + run: | + SKIP_CI="false" + if [ -z "${LABELS}" ] || [ "${LABELS}" = "null" ]; then + LABELS="none"; + else + LABELS=$(echo ${LABELS} | jq -r '.[].name') + fi + for label in ${LABELS}; do + if [ "$label" = "skip-ci" ]; then + SKIP_CI="true" + break + fi + done + echo "skip_ci=$SKIP_CI" >> $GITHUB_OUTPUT + + zkboost-tests: + name: zkboost-tests + needs: [check-labels] + if: needs.check-labels.outputs.skip_ci != 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Install dependencies + run: sudo apt update && sudo apt install -y git gcc g++ make cmake pkg-config llvm-dev libclang-dev clang + - name: Get latest version of stable Rust + uses: moonrepo/setup-rust@v1 + with: + channel: stable + cache-extra-identifier: ${{ env.RUST_CACHE_EXTRA_IDENTIFIER }} + cache-target: release + bins: cargo-nextest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Run proof_engine_zkboost integration tests + run: make test-zkboost diff --git a/Cargo.lock b/Cargo.lock index c2e384e7d7b..e9dcaacfdff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6957,6 +6957,22 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "proof_engine_test" +version = "8.1.3" +dependencies = [ + "anyhow", + "beacon_chain", + "execution_layer", + "futures", + "network", + "simulator", + "task_executor", + "tokio", + "tracing", + "types", +] + [[package]] name = "proptest" version = "1.9.0" @@ -8205,22 +8221,29 @@ dependencies = [ name = "simulator" version = "0.2.0" dependencies = [ + "anyhow", + "beacon_chain", "clap", "environment", + "eth2", "execution_layer", "futures", "kzg", + "lighthouse_network", "logging", + "network_utils", "node_test_rig", "parking_lot", "rayon", "sensitive_url", "serde_json", + "task_executor", "tokio", "tracing", "tracing-subscriber", "typenum", "types", + "validator_http_api", ] [[package]] @@ -9526,6 +9549,7 @@ dependencies = [ "doppelganger_service", "environment", "eth2", + "execution_layer", "fdlimit", "graffiti_file", "hyper 1.8.1", @@ -9541,6 +9565,7 @@ dependencies = [ "slot_clock", "tokio", "tracing", + "typenum", "types", "validator_http_api", "validator_http_metrics", @@ -9682,6 +9707,7 @@ dependencies = [ "bls", "either", "eth2", + "execution_layer", "futures", "graffiti_file", "logging", diff --git a/Cargo.toml b/Cargo.toml index 71398530fe4..205ade72ca7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,6 +68,7 @@ members = [ "testing/ef_tests", "testing/execution_engine_integration", "testing/node_test_rig", + "testing/proof_engine", "testing/simulator", "testing/state_transition_vectors", "testing/validator_test_rig", @@ -275,4 +276,3 @@ debug = true [patch.crates-io] quick-protobuf = { git = "https://github.com/sigp/quick-protobuf.git", rev = "87c4ccb9bb2af494de375f5f6c62850badd26304" } - diff --git a/Makefile b/Makefile index dd57bb038e8..039d9095bf5 100644 --- a/Makefile +++ b/Makefile @@ -181,7 +181,12 @@ build-release-tarballs: test-release: cargo nextest run --workspace --release --features "$(TEST_FEATURES)" \ --exclude ef_tests --exclude beacon_chain --exclude slasher --exclude network \ - --exclude http_api + --exclude http_api --exclude proof_engine_test + +# Runs the proof engine integration tests sequentially. Each test spawns multiple +# beacon nodes and is sensitive to slot timing, so dedicated execution is required. +test-proof-engine: + cargo nextest run -p proof_engine_test --release --test-threads 1 # Runs the full workspace tests in **debug**, without downloading any additional test @@ -190,6 +195,10 @@ test-debug: cargo nextest run --workspace --features "$(TEST_FEATURES)" \ --exclude ef_tests --exclude beacon_chain --exclude network --exclude http_api +# Runs the proof_engine_zkboost integration tests against a real mock-backend zkBoost server. +test-zkboost: + cargo nextest run --manifest-path testing/proof_engine_zkboost/Cargo.toml --release + # Runs cargo-fmt (linter). cargo-fmt: cargo fmt --all -- --check diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 9cd5cdc4029..315a6fa6eb1 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -442,6 +442,10 @@ pub struct BeaconChain { pub observed_execution_proofs: RwLock, /// Maintains EIP-8025 proof-status metadata and bounded request-root mappings. pub execution_proof_statuses: RwLock, + /// Lazily initialized event bus used by execution-proof integration tests. + pub internal_event_tx: std::sync::OnceLock< + tokio::sync::broadcast::Sender, + >, /// Maintains a record of slashable message seen over the gossip network or RPC. pub observed_slashable: RwLock>, /// Cache of pending execution payload envelopes for local block building. @@ -4517,6 +4521,16 @@ impl BeaconChain { // This prevents inconsistency between the two at the expense of concurrency. drop(fork_choice); + if let Ok(new_payload_request) = + TryInto::>::try_into(block) + { + self.execution_proof_statuses.write().register_request_root( + block_root, + new_payload_request.request_root(), + block.slot(), + ); + } + // We're declaring the block "imported" at this point, since fork choice and the DB know // about it. let block_time_imported = self.slot_clock.now_duration().unwrap_or(Duration::MAX); @@ -7531,6 +7545,32 @@ impl BeaconChain { self.dump_as_dot(&mut file); } + pub fn subscribe_internal_events( + &self, + ) -> tokio::sync::broadcast::Receiver { + self.internal_event_tx + .get_or_init(|| { + let (tx, _rx) = tokio::sync::broadcast::channel( + crate::internal_events::INTERNAL_EVENT_CHANNEL_CAPACITY, + ); + tx + }) + .subscribe() + } + + pub fn internal_event_sender( + &self, + ) -> Option<&tokio::sync::broadcast::Sender> + { + self.internal_event_tx.get() + } + + pub fn emit_internal_event(&self, event: crate::internal_events::InternalBeaconNodeEvent) { + if let Some(tx) = self.internal_event_tx.get() { + let _ = tx.send(event); + } + } + /// Checks if attestations have been seen from the given `validator_index` at the /// given `epoch`. pub fn validator_seen_at_epoch(&self, validator_index: usize, epoch: Epoch) -> bool { diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index d09efb36e5b..791da4e4be6 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -1013,6 +1013,7 @@ where observed_blob_sidecars: RwLock::new(ObservedDataSidecars::new(self.spec.clone())), observed_execution_proofs: RwLock::new(ObservedExecutionProofs::default()), execution_proof_statuses: RwLock::new(ExecutionProofStatusCache::default()), + internal_event_tx: std::sync::OnceLock::new(), observed_slashable: <_>::default(), pending_payload_envelopes: <_>::default(), observed_voluntary_exits: <_>::default(), diff --git a/beacon_node/beacon_chain/src/eip8025/proof_status.rs b/beacon_node/beacon_chain/src/eip8025/proof_status.rs index 2f7af8e9a35..789071a9c85 100644 --- a/beacon_node/beacon_chain/src/eip8025/proof_status.rs +++ b/beacon_node/beacon_chain/src/eip8025/proof_status.rs @@ -7,9 +7,10 @@ use state_processing::per_block_processing::deneb::kzg_commitment_to_versioned_h use std::collections::{HashMap, HashSet}; use std::num::NonZeroUsize; use std::sync::Arc; +use store::DatabaseBlock; use types::{ - Hash256, ProofStatus, ProofType, SignedBlindedBeaconBlock, SignedExecutionPayloadEnvelope, - SignedExecutionProof, Slot, + EthSpec, Hash256, ProofStatus, ProofType, SignedBlindedBeaconBlock, + SignedExecutionPayloadEnvelope, SignedExecutionProof, Slot, }; const DEFAULT_REQUEST_ROOT_CACHE_SIZE: usize = 8192; @@ -94,6 +95,16 @@ impl ExecutionProofStatusCache { self.block_root_to_request_root.peek(block_root).copied() } + pub fn block_context_for_request_root( + &self, + request_root: &Hash256, + ) -> Option<(Hash256, Slot)> { + let block_root = self.request_root_to_block_root.peek(request_root)?; + self.statuses_by_block_root + .get(block_root) + .map(|status| (status.block_root, status.slot)) + } + pub fn observe_valid_proof( &mut self, block_root: Hash256, @@ -268,7 +279,7 @@ impl BeaconChain { let proof_backed_payload_promotion = if quorum_threshold .is_some_and(|threshold| summary.valid_proof_type_count >= threshold) { - self.try_mark_payload_envelope_proof_valid(block_root)? + self.try_mark_proof_backed_payload_valid(block_root)? } else { false }; @@ -412,15 +423,14 @@ impl BeaconChain { return Ok(Some((block_root, slot))); } - let Some(block_root) = self + let Some((block_root, slot)) = self .execution_proof_statuses .read() - .block_root_for_request_root(&request_root) + .block_context_for_request_root(&request_root) else { return Ok(None); }; - let (_, slot) = self.execution_payload_request_context(block_root)?; Ok(Some((block_root, slot))) } @@ -428,6 +438,15 @@ impl BeaconChain { &self, block_root: Hash256, ) -> Result<(Hash256, Slot), BeaconChainError> { + if let Some(DatabaseBlock::Full(block)) = self.store.try_get_full_block(&block_root)? + && !block.fork_name_unchecked().gloas_enabled() + { + let slot = block.slot(); + let request = NewPayloadRequest::try_from(block.message()) + .map_err(BeaconChainError::BeaconStateError)?; + return Ok((request.request_root(), slot)); + } + let block = self .get_blinded_block(&block_root)? .ok_or(BeaconChainError::MissingBeaconBlock(block_root))?; @@ -440,18 +459,28 @@ impl BeaconChain { Ok((request.request_root(), slot)) } - fn try_mark_payload_envelope_proof_valid( + fn try_mark_proof_backed_payload_valid( &self, block_root: Hash256, ) -> Result { - if self.get_payload_envelope(&block_root)?.is_none() { + let block = self + .get_blinded_block(&block_root)? + .ok_or(BeaconChainError::MissingBeaconBlock(block_root))?; + let is_gloas = block.fork_name_unchecked().gloas_enabled(); + if is_gloas && self.get_payload_envelope(&block_root)?.is_none() { return Ok(false); } let mut fork_choice = self.canonical_head.fork_choice_write_lock(); - fork_choice - .on_valid_payload_envelope_received(block_root) - .map_err(map_fork_choice_error)?; + if is_gloas { + fork_choice + .on_valid_payload_envelope_received(block_root) + .map_err(map_fork_choice_error)?; + } else { + fork_choice + .on_valid_execution_payload(block_root) + .map_err(map_fork_choice_error)?; + } Ok(true) } @@ -503,6 +532,7 @@ impl BeaconChain { &self, proof_types: &[ProofType], ) -> Vec { + self.register_execution_proof_request_window(); self.execution_proof_statuses .read() .missing_execution_proofs(proof_types) @@ -516,6 +546,44 @@ impl BeaconChain { .read() .latest_status_with_valid_proofs(proof_types) } + + fn register_execution_proof_request_window(&self) { + let head = self.canonical_head.cached_head(); + let start_slot = head + .finalized_checkpoint() + .epoch + .start_slot(T::EthSpec::slots_per_epoch()); + let end_slot = head.head_slot(); + + for slot in start_slot.as_u64()..=end_slot.as_u64() { + let slot = Slot::new(slot); + let Ok(Some(block_root)) = self.block_root_at_slot(slot, crate::WhenSlotSkipped::None) + else { + continue; + }; + + if self + .execution_proof_statuses + .read() + .request_root_for_block_root(&block_root) + .is_some() + { + continue; + } + + let Ok((request_root, request_slot)) = + self.execution_payload_request_context(block_root) + else { + continue; + }; + + self.execution_proof_statuses.write().register_request_root( + block_root, + request_root, + request_slot, + ); + } + } } fn build_gloas_new_payload_request<'a, E: types::EthSpec>( diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index c8976fc6a83..c6486bc959b 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -12,12 +12,13 @@ use crate::{ ExecutionPayloadError, }; use execution_layer::{ - BlockProposalContentsType, BuilderParams, NewPayloadRequest, PayloadAttributes, + BlockProposalContentsType, BuilderParams, ExecutionLayer, NewPayloadRequest, PayloadAttributes, PayloadParameters, PayloadStatus, }; use fork_choice::{InvalidationOperation, PayloadVerificationStatus}; use proto_array::{Block as ProtoBlock, ExecutionStatus}; use slot_clock::SlotClock; +use ssz::Encode; use state_processing::per_block_processing::{ compute_timestamp_at_slot, get_expected_withdrawals, is_execution_enabled, partially_verify_execution_payload, @@ -139,6 +140,8 @@ pub async fn notify_new_payload( .as_ref() .ok_or(ExecutionPayloadError::NoExecutionConnection)?; + request_execution_proofs(chain, execution_layer, &new_payload_request); + let execution_block_hash = new_payload_request.execution_payload_ref().block_hash(); let new_payload_response = execution_layer .notify_new_payload(new_payload_request.clone()) @@ -213,6 +216,41 @@ pub async fn notify_new_payload( } } +fn request_execution_proofs( + chain: &Arc>, + execution_layer: &ExecutionLayer, + new_payload_request: &NewPayloadRequest<'_, T::EthSpec>, +) { + let Some(proof_engine) = execution_layer.proof_engine() else { + return; + }; + + let proof_types = execution_layer + .proof_types() + .iter() + .map(|proof_type| proof_type.to_u8()) + .collect(); + let proof_attributes = ProofAttributes { proof_types }; + let request_body = new_payload_request.as_ssz_bytes(); + let request_root = new_payload_request.request_root(); + + chain.task_executor.spawn( + async move { + if let Err(error) = proof_engine + .request_proofs_ssz(request_body, proof_attributes) + .await + { + warn!( + ?error, + ?request_root, + "Failed to request EIP-8025 execution proofs" + ); + } + }, + "eip8025_proof_request", + ); +} + /// Validate the gossip block's execution_payload according to the checks described here: /// https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/p2p-interface.md#beacon_block pub fn validate_execution_payload_for_gossip( diff --git a/beacon_node/beacon_chain/src/internal_events.rs b/beacon_node/beacon_chain/src/internal_events.rs new file mode 100644 index 00000000000..359eff845bd --- /dev/null +++ b/beacon_node/beacon_chain/src/internal_events.rs @@ -0,0 +1,29 @@ +//! Internal event bus for execution-proof integration tests. + +use std::sync::Arc; +use types::execution::eip8025::{ProofByRootIdentifier, ProofStatus, SignedExecutionProof}; +use types::{Hash256, Slot}; + +pub const INTERNAL_EVENT_CHANNEL_CAPACITY: usize = 16_384; + +#[derive(Debug, Clone)] +pub enum InternalBeaconNodeEvent { + GossipExecutionProof(Arc), + RpcExecutionProof(Arc), + OutboundExecutionProofsByRange { + start_slot: Slot, + count: u64, + }, + OutboundExecutionProofsByRoot { + identifiers: Vec, + }, + ExecutionProofVerified { + request_root: Hash256, + status: ProofStatus, + block: Option<(Hash256, Slot)>, + }, + ExecutionProofVerificationFailed { + request_root: Hash256, + error: String, + }, +} diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 5366b68529e..ef21d1d9dfb 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -30,6 +30,7 @@ pub mod fork_choice_signal; pub mod graffiti_calculator; pub mod historical_blocks; pub mod historical_data_columns; +pub mod internal_events; pub mod invariants; pub mod kzg_utils; pub mod light_client_finality_update_verification; diff --git a/beacon_node/execution_layer/src/eip8025/types.rs b/beacon_node/execution_layer/src/eip8025/types.rs index c9985eb8afd..3f09563a180 100644 --- a/beacon_node/execution_layer/src/eip8025/types.rs +++ b/beacon_node/execution_layer/src/eip8025/types.rs @@ -74,13 +74,64 @@ impl FromStr for ProofType { "reth-risc0" => Ok(Self::RethRisc0), "reth-sp1" => Ok(Self::RethSP1), "reth-zisk" => Ok(Self::RethZisk), - _ => Err(ProofEngineError::InvalidProofType(format!( - "unknown proof type: {s}" - ))), + numeric => numeric.parse::().map_or_else( + |_| { + Err(ProofEngineError::InvalidProofType(format!( + "unknown proof type: {s}" + ))) + }, + Self::from_u8, + ), } } } +#[cfg(test)] +mod tests { + use super::ProofType; + + #[test] + fn proof_type_parses_string_names() { + assert_eq!( + "reth-zisk" + .parse::() + .expect("known proof type should parse"), + ProofType::RethZisk + ); + } + + #[test] + fn proof_type_parses_numeric_ids() { + assert_eq!( + "6".parse::() + .expect("known numeric proof type should parse"), + ProofType::RethZisk + ); + } + + #[test] + fn proof_type_rejects_unknown_names() { + let error = "not-a-proof-type" + .parse::() + .expect_err("unknown proof type should be rejected"); + assert!( + error.to_string().contains("unknown proof type"), + "unexpected error: {error}" + ); + } + + #[test] + fn proof_type_rejects_unknown_numeric_ids() { + let error = "7" + .parse::() + .expect_err("unknown numeric proof type should be rejected"); + assert!( + error.to_string().contains("no mapping for proof type 7"), + "unexpected error: {error}" + ); + } +} + impl fmt::Display for ProofType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) @@ -135,6 +186,22 @@ pub enum ProofEvent { ProofFailure(ProofFailure), } +impl ProofEvent { + pub fn new_payload_request_root(&self) -> Hash256 { + match self { + Self::ProofComplete(complete) => complete.new_payload_request_root, + Self::ProofFailure(failure) => failure.new_payload_request_root, + } + } + + pub fn proof_type(&self) -> u8 { + match self { + Self::ProofComplete(complete) => complete.proof_type, + Self::ProofFailure(failure) => failure.proof_type, + } + } +} + #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct ProofComplete { pub new_payload_request_root: Hash256, diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 8b1619863a2..844c3001848 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -139,6 +139,7 @@ impl TryFrom> for ProvenancedPayload { type PayloadContentsRefTuple<'a, E> = (ExecutionPayloadRef<'a, E>, Option<&'a BlobsBundle>); struct Inner { - engine: Arc, + engine: Option>, builder: ArcSwapOption, execution_engine_forkchoice_lock: Mutex<()>, suggested_fee_recipient: Option
, @@ -517,7 +518,7 @@ impl ExecutionLayer { /// Instantiate `Self` with an Execution engine specified in `Config`, using JSON-RPC via HTTP. pub fn from_config(config: Config, executor: TaskExecutor) -> Result { let Config { - execution_endpoint: url, + execution_endpoint, proof_engine_endpoint, proof_types, builder_url, @@ -532,52 +533,68 @@ impl ExecutionLayer { execution_timeout_multiplier, } = config; - let execution_url = url.ok_or(Error::NoEngine)?; - - // Use the default jwt secret path if not provided via cli. - let secret_file = secret_file.unwrap_or_else(|| default_datadir.join(DEFAULT_JWT_FILE)); - - let jwt_key = if secret_file.exists() { - // Read secret from file if it already exists - std::fs::read_to_string(&secret_file) - .map_err(|e| format!("Failed to read JWT secret file. Error: {:?}", e)) - .and_then(|ref s| { - let secret = JwtKey::from_slice( - &hex::decode(strip_prefix(s.trim_end())) - .map_err(|e| format!("Invalid hex string: {:?}", e))?, - )?; - Ok(secret) - }) - .map_err(Error::InvalidJWTSecret) - } else { - // Create a new file and write a randomly generated secret to it if file does not exist - warn!(path = %secret_file.display(),"No JWT found on disk. Generating"); - std::fs::File::options() - .write(true) - .create_new(true) - .open(&secret_file) - .map_err(|e| format!("Failed to open JWT secret file. Error: {:?}", e)) - .and_then(|mut f| { - let secret = auth::JwtKey::random(); - f.write_all(secret.hex_string().as_bytes()) - .map_err(|e| format!("Failed to write to JWT secret file: {:?}", e))?; - Ok(secret) - }) - .map_err(Error::InvalidJWTSecret) - }?; + if execution_endpoint.is_none() && proof_engine_endpoint.is_none() { + return Err(Error::NoExecutionEndpoint); + } + + let engine = if let Some(execution_url) = execution_endpoint { + // Use the default jwt secret path if not provided via cli. + let secret_file = secret_file.unwrap_or_else(|| default_datadir.join(DEFAULT_JWT_FILE)); + + let jwt_key = if secret_file.exists() { + // Read secret from file if it already exists + std::fs::read_to_string(&secret_file) + .map_err(|e| format!("Failed to read JWT secret file. Error: {:?}", e)) + .and_then(|ref s| { + let secret = JwtKey::from_slice( + &hex::decode(strip_prefix(s.trim_end())) + .map_err(|e| format!("Invalid hex string: {:?}", e))?, + )?; + Ok(secret) + }) + .map_err(Error::InvalidJWTSecret) + } else { + // Create a new file and write a randomly generated secret to it if file does not exist + warn!(path = %secret_file.display(),"No JWT found on disk. Generating"); + std::fs::File::options() + .write(true) + .create_new(true) + .open(&secret_file) + .map_err(|e| format!("Failed to open JWT secret file. Error: {:?}", e)) + .and_then(|mut f| { + let secret = auth::JwtKey::random(); + f.write_all(secret.hex_string().as_bytes()) + .map_err(|e| format!("Failed to write to JWT secret file: {:?}", e))?; + Ok(secret) + }) + .map_err(Error::InvalidJWTSecret) + }?; - let engine: Engine = { let auth = Auth::new(jwt_key, jwt_id, jwt_version); debug!(endpoint = %execution_url, jwt_path = ?secret_file.as_path(),"Loaded execution endpoint"); let api = HttpJsonRpc::new_with_auth(execution_url, auth, execution_timeout_multiplier) .map_err(Error::ApiError)?; - Engine::new(api, executor.clone()) + Some(Arc::new(Engine::new(api, executor.clone()))) + } else { + None }; - let proof_engine = - proof_engine_endpoint.map(|url| Arc::new(eip8025::HttpProofEngine::new(url, None))); + let proof_engine = proof_engine_endpoint.map(|url| { + if let Some(idx) = test_utils::parse_mock_index(url.expose_full().as_str()) { + let mock = test_utils::get_mock_proof_engine::(idx).unwrap_or_else(|| { + debug!( + idx, + "No pre-registered mock proof engine; creating one on demand" + ); + test_utils::register_mock_proof_engine::(idx, 0) + }); + Arc::new(eip8025::HttpProofEngine::with_proof_node(mock)) + } else { + Arc::new(eip8025::HttpProofEngine::new(url, None)) + } + }); let inner = Inner { - engine: Arc::new(engine), + engine, builder: ArcSwapOption::empty(), execution_engine_forkchoice_lock: <_>::default(), suggested_fee_recipient, @@ -606,8 +623,8 @@ impl ExecutionLayer { Ok(el) } - fn engine(&self) -> &Arc { - &self.inner.engine + pub fn engine(&self) -> Option<&Arc> { + self.inner.engine.as_ref() } pub fn proof_engine(&self) -> Option> { @@ -676,20 +693,27 @@ impl ExecutionLayer { /// Get the current difficulty of the PoW chain. pub async fn get_current_difficulty(&self) -> Result, ApiError> { - let block = self - .engine() - .api - .get_block_by_number(BlockByNumberQuery::Tag(LATEST_TAG)) - .await? - .ok_or(ApiError::ExecutionHeadBlockNotFound)?; - Ok(block.total_difficulty) + if let Some(engine) = self.engine() { + let block = engine + .api + .get_block_by_number(BlockByNumberQuery::Tag(LATEST_TAG)) + .await? + .ok_or(ApiError::ExecutionHeadBlockNotFound)?; + Ok(block.total_difficulty) + } else { + Ok(None) + } } /// Gives access to a channel containing if the last engine state is online or not. /// /// This can be called several times. - pub async fn get_responsiveness_watch(&self) -> WatchStream { - self.engine().watch_state().await + pub async fn get_responsiveness_watch(&self) -> Option> { + if let Some(engine) = self.engine() { + Some(engine.watch_state().await) + } else { + None + } } /// Note: this function returns a mutex guard, be careful to avoid deadlocks. @@ -737,7 +761,9 @@ impl ExecutionLayer { /// Performs a single execution of the watchdog routine. pub async fn watchdog_task(&self) { - self.engine().upcheck().await; + if let Some(engine) = self.engine() { + engine.upcheck().await; + } } /// Spawns a routine which cleans the cached proposer data periodically. @@ -780,7 +806,11 @@ impl ExecutionLayer { /// Returns `true` if the execution engine is synced and reachable. pub async fn is_synced(&self) -> bool { - self.engine().is_synced().await + if let Some(engine) = self.engine() { + engine.is_synced().await + } else { + true + } } /// Execution nodes return a "SYNCED" response when they do not have any peers. @@ -791,12 +821,17 @@ impl ExecutionLayer { /// Returns the `Self::is_synced` response if unable to get latest block. pub async fn is_synced_for_notifier(&self, current_slot: Slot) -> bool { let synced = self.is_synced().await; - if synced - && let Ok(Some(block)) = self - .engine() + let block = if let Some(engine) = self.engine() { + engine .api .get_block_by_number(BlockByNumberQuery::Tag(LATEST_TAG)) .await + } else { + Ok(None) + }; + + if synced + && let Ok(Some(block)) = block && block.block_number == 0 && current_slot > 0 { @@ -812,7 +847,12 @@ impl ExecutionLayer { /// be used to give an indication on the HTTP API that the node's execution layer is struggling, /// which can in turn be used by the VC. pub async fn is_offline_or_erroring(&self) -> bool { - self.engine().is_offline().await || *self.inner.last_new_payload_errored.read().await + let engine_offline = if let Some(engine) = self.engine() { + engine.is_offline().await + } else { + false + }; + engine_offline || *self.inner.last_new_payload_errored.read().await } /// Updates the proposer preparation data provided by validators @@ -1340,7 +1380,9 @@ impl ExecutionLayer { .. } = payload_parameters; - self.engine() + let engine = self.engine().ok_or(Error::NoEngine)?; + + engine .request(move |engine| async move { let payload_id = if let Some(id) = engine .get_payload_id(&parent_hash, payload_attributes) @@ -1458,8 +1500,12 @@ impl ExecutionLayer { let block_hash = new_payload_request.block_hash(); let parent_hash = new_payload_request.parent_hash(); - let result = self - .engine() + let Some(engine) = self.engine() else { + *self.inner.last_new_payload_errored.write().await = false; + return Ok(PayloadStatus::Accepted); + }; + + let result = engine .request(|engine| engine.api.new_payload(new_payload_request)) .await; @@ -1487,7 +1533,9 @@ impl ExecutionLayer { /// Update engine sync status. pub async fn upcheck(&self) { - self.engine().upcheck().await; + if let Some(engine) = self.engine() { + engine.upcheck().await; + } } /// Register that the given `validator_index` is going to produce a block at `slot`. @@ -1601,18 +1649,19 @@ impl ExecutionLayer { finalized_block_hash, }; - self.engine() - .set_latest_forkchoice_state(forkchoice_state) - .await; + let result = if let Some(engine) = self.engine() { + engine.set_latest_forkchoice_state(forkchoice_state).await; - let result = self - .engine() - .request(|engine| async move { - engine - .notify_forkchoice_updated(forkchoice_state, payload_attributes) - .await - }) - .await; + engine + .request(|engine| async move { + engine + .notify_forkchoice_updated(forkchoice_state, payload_attributes) + .await + }) + .await + } else { + return Ok(PayloadStatus::Accepted); + }; if let Ok(status) = &result { metrics::inc_counter_vec( @@ -1642,10 +1691,36 @@ impl ExecutionLayer { &self, age_limit: Option, ) -> Result { - self.engine() - .request(|engine| engine.get_engine_capabilities(age_limit)) - .await - .map_err(Into::into) + if let Some(engine) = self.engine() { + engine + .request(|engine| engine.get_engine_capabilities(age_limit)) + .await + .map_err(Into::into) + } else { + Ok(EngineCapabilities { + new_payload_v1: true, + new_payload_v2: true, + new_payload_v3: true, + new_payload_v4: true, + new_payload_v5: true, + forkchoice_updated_v1: true, + forkchoice_updated_v2: true, + forkchoice_updated_v3: true, + forkchoice_updated_v4: true, + get_payload_bodies_by_hash_v1: false, + get_payload_bodies_by_range_v1: false, + get_payload_v1: true, + get_payload_v2: true, + get_payload_v3: true, + get_payload_v4: true, + get_payload_v5: true, + get_payload_v6: true, + get_client_version_v1: false, + get_blobs_v1: false, + get_blobs_v2: false, + get_blobs_v3: false, + }) + } } /// Returns the execution engine version resulting from a call to @@ -1661,27 +1736,33 @@ impl ExecutionLayer { &self, age_limit: Option, ) -> Result, Error> { - let versions = self - .engine() - .request(|engine| engine.get_engine_version(age_limit)) - .await - .map_err(Into::::into)?; - metrics::expose_execution_layer_info(&versions); - - Ok(versions) + if let Some(engine) = self.engine() { + let versions = engine + .request(|engine| engine.get_engine_version(age_limit)) + .await + .map_err(Into::::into)?; + metrics::expose_execution_layer_info(&versions); + Ok(versions) + } else { + Ok(vec![]) + } } pub async fn get_payload_bodies_by_hash( &self, hashes: Vec, ) -> Result>>, Error> { - self.engine() - .request(|engine: &Engine| async move { - engine.api.get_payload_bodies_by_hash_v1(hashes).await - }) - .await - .map_err(Box::new) - .map_err(Error::EngineError) + if let Some(engine) = self.engine() { + engine + .request(|engine: &Engine| async move { + engine.api.get_payload_bodies_by_hash_v1(hashes).await + }) + .await + .map_err(Box::new) + .map_err(Error::EngineError) + } else { + Ok(vec![None; hashes.len()]) + } } pub async fn get_payload_bodies_by_range( @@ -1690,16 +1771,20 @@ impl ExecutionLayer { count: u64, ) -> Result>>, Error> { let _timer = metrics::start_timer(&metrics::EXECUTION_LAYER_GET_PAYLOAD_BODIES_BY_RANGE); - self.engine() - .request(|engine: &Engine| async move { - engine - .api - .get_payload_bodies_by_range_v1(start, count) - .await - }) - .await - .map_err(Box::new) - .map_err(Error::EngineError) + if let Some(engine) = self.engine() { + engine + .request(|engine: &Engine| async move { + engine + .api + .get_payload_bodies_by_range_v1(start, count) + .await + }) + .await + .map_err(Box::new) + .map_err(Error::EngineError) + } else { + Ok(vec![None; count as usize]) + } } /// Fetch a full payload from the execution node. @@ -1759,6 +1844,7 @@ impl ExecutionLayer { if capabilities.get_blobs_v1 { self.engine() + .expect("capabilities only returns get_blobs_v1=true if engine is present") .request(|engine| async move { engine.api.get_blobs_v1(query).await }) .await .map_err(Box::new) @@ -1776,6 +1862,7 @@ impl ExecutionLayer { if capabilities.get_blobs_v2 { self.engine() + .expect("capabilities only returns get_blobs_v2=true if engine is present") .request(|engine| async move { engine.api.get_blobs_v2(query).await }) .await .map_err(Box::new) @@ -1793,6 +1880,7 @@ impl ExecutionLayer { if capabilities.get_blobs_v3 { self.engine() + .expect("capabilities only returns get_blobs_v3=true if engine is present") .request(|engine| async move { engine.api.get_blobs_v3(query).await }) .await .map_err(Box::new) @@ -1806,11 +1894,15 @@ impl ExecutionLayer { &self, query: BlockByNumberQuery<'_>, ) -> Result, Error> { - self.engine() - .request(|engine| async move { engine.api.get_block_by_number(query).await }) - .await - .map_err(Box::new) - .map_err(Error::EngineError) + if let Some(engine) = self.engine() { + engine + .request(|engine| async move { engine.api.get_block_by_number(query).await }) + .await + .map_err(Box::new) + .map_err(Error::EngineError) + } else { + Ok(None) + } } pub async fn propose_blinded_beacon_block( diff --git a/beacon_node/execution_layer/src/test_utils/mock_event_stream.rs b/beacon_node/execution_layer/src/test_utils/mock_event_stream.rs new file mode 100644 index 00000000000..400efdc7e2d --- /dev/null +++ b/beacon_node/execution_layer/src/test_utils/mock_event_stream.rs @@ -0,0 +1,116 @@ +//! Assertion helpers for [`MockClientEvent`] broadcast streams. +//! +//! [`MockEventStream`] wraps a `broadcast::Receiver` and provides +//! ergonomic methods for collecting and asserting on events in integration tests, +//! eliminating the `timeout + loop + counter` boilerplate. + +use super::MockClientEvent; +use std::time::Duration; +use tokio::sync::broadcast; + +/// Error type for [`MockEventStream`] assertions. +#[derive(Debug)] +pub struct MockStreamError(String); + +impl std::fmt::Display for MockStreamError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::error::Error for MockStreamError {} + +/// Wraps a `broadcast::Receiver` with assertion helpers. +pub struct MockEventStream { + rx: broadcast::Receiver, +} + +impl From> for MockEventStream { + fn from(rx: broadcast::Receiver) -> Self { + Self { rx } + } +} + +impl MockEventStream { + /// Collect `n` events matching `predicate` within `timeout`, or return an error. + pub async fn collect_n( + &mut self, + n: usize, + predicate: impl Fn(&MockClientEvent) -> bool, + timeout: Duration, + ) -> Result, MockStreamError> { + tokio::time::timeout(timeout, async { + let mut collected = Vec::with_capacity(n); + loop { + match self.rx.recv().await { + Ok(event) if predicate(&event) => { + collected.push(event); + if collected.len() >= n { + return Ok(collected); + } + } + Ok(_) => {} + Err(broadcast::error::RecvError::Lagged(skipped)) => { + return Err(MockStreamError(format!( + "event stream lagged, skipped {skipped} events" + ))); + } + Err(broadcast::error::RecvError::Closed) => { + return Err(MockStreamError(format!( + "event stream closed before collecting {n} events (got {})", + collected.len() + ))); + } + } + } + }) + .await + .map_err(|_| { + MockStreamError(format!( + "timed out after {timeout:?} waiting for {n} events" + )) + })? + } + + /// Expect at least `n` `ProofRequested` events within `timeout`. + pub async fn expect_proof_requests( + &mut self, + n: usize, + timeout: Duration, + ) -> Result, MockStreamError> { + self.collect_n( + n, + |e| matches!(e, MockClientEvent::ProofRequested { .. }), + timeout, + ) + .await + } + + /// Expect at least `n` `ProofVerified` events within `timeout`. + pub async fn expect_proof_verified( + &mut self, + n: usize, + timeout: Duration, + ) -> Result, MockStreamError> { + self.collect_n( + n, + |e| matches!(e, MockClientEvent::ProofVerified { .. }), + timeout, + ) + .await + } + + /// Expect at least `n` `ProofFetched` events within `timeout`. + pub async fn expect_proof_fetched( + &mut self, + n: usize, + timeout: Duration, + ) -> Result, MockStreamError> { + self.collect_n( + n, + |e| matches!(e, MockClientEvent::ProofFetched { .. }), + timeout, + ) + .await + } +} diff --git a/beacon_node/execution_layer/src/test_utils/mock_proof_node_client.rs b/beacon_node/execution_layer/src/test_utils/mock_proof_node_client.rs new file mode 100644 index 00000000000..408171d25f6 --- /dev/null +++ b/beacon_node/execution_layer/src/test_utils/mock_proof_node_client.rs @@ -0,0 +1,320 @@ +//! Mock [`ProofNodeClient`] for unit testing [`HttpProofEngine`]. +//! +//! [`MockProofNodeClient`] implements [`ProofNodeClient`] entirely in memory — +//! no HTTP server required. It records received requests, broadcasts proof +//! events after a configurable delay, and always returns `Valid` for verification. +//! +//! [`ProofNodeClient`]: crate::eip8025::ProofNodeClient +//! [`HttpProofEngine`]: crate::eip8025::HttpProofEngine + +use crate::eip8025::errors::ProofEngineError; +use crate::eip8025::proof_node_client::ProofNodeClient; +use crate::eip8025::types::{ProofComplete, ProofEvent}; +use bytes::Bytes; +use futures::stream::Stream; +use parking_lot::Mutex; +use ssz::{Decode, Encode}; +use ssz_derive::{Decode as SszDecode, Encode as SszEncode}; +use ssz_types::VariableList; +use std::collections::HashMap; +use std::marker::PhantomData; +use std::pin::Pin; +use std::sync::{Arc, LazyLock}; +use std::time::Duration; +use superstruct::superstruct; +use tokio::sync::broadcast; +use tokio_stream::StreamExt; +use tokio_stream::wrappers::BroadcastStream; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash as TreeHashDerive; +use types::execution::eip8025::{ProofAttributes, ProofStatus}; +use types::{ + BeaconStateError, EthSpec, ExecutionPayloadBellatrix, ExecutionPayloadCapella, + ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, + ExecutionRequests, Hash256, MainnetEthSpec, VersionedHash, +}; + +/// Owned version of `NewPayloadRequest` used only for SSZ decoding inside the mock. +/// +/// The production `NewPayloadRequest<'block, E>` holds `&'block` references (zero-copy +/// during block processing), which prevents deriving `ssz::Decode`. This local owned +/// superstruct enum mirrors all fork variants with owned fields and is used exclusively +/// to decode the SSZ bytes sent to `request_proofs` and compute `tree_hash_root`. +#[superstruct( + variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variant_attributes(derive(SszEncode, SszDecode, TreeHashDerive)), + cast_error( + ty = "BeaconStateError", + expr = "BeaconStateError::IncorrectStateVariant" + ), + partial_getter_error( + ty = "BeaconStateError", + expr = "BeaconStateError::IncorrectStateVariant" + ) +)] +#[derive(SszEncode, SszDecode, TreeHashDerive)] +#[ssz(enum_behaviour = "transparent")] +#[tree_hash(enum_behaviour = "transparent")] +pub struct OwnedNewPayloadRequest { + #[superstruct( + only(Bellatrix), + partial_getter(rename = "execution_payload_bellatrix") + )] + pub execution_payload: ExecutionPayloadBellatrix, + #[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))] + pub execution_payload: ExecutionPayloadCapella, + #[superstruct(only(Deneb), partial_getter(rename = "execution_payload_deneb"))] + pub execution_payload: ExecutionPayloadDeneb, + #[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))] + pub execution_payload: ExecutionPayloadElectra, + #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] + pub execution_payload: ExecutionPayloadFulu, + #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_gloas"))] + pub execution_payload: ExecutionPayloadGloas, + #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + pub versioned_hashes: VariableList, + #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + pub parent_beacon_block_root: Hash256, + #[superstruct(only(Electra, Fulu, Gloas))] + pub execution_requests: ExecutionRequests, +} + +/// Events emitted by [`MockProofNodeClient`] for each method invocation. +/// +/// Subscribe via [`MockProofNodeClient::subscribe_client_events`] to observe +/// calls in tests without polling shared state. +#[derive(Debug, Clone)] +pub enum MockClientEvent { + /// Emitted when [`ProofNodeClient::request_proofs`] is called. + ProofRequested { + ssz_body: Vec, + proof_attributes: ProofAttributes, + root: Hash256, + }, + /// Emitted when [`ProofNodeClient::verify_proof`] is called. + ProofVerified { root: Hash256, proof_type: u8 }, + /// Emitted when [`ProofNodeClient::get_proof`] is called. + ProofFetched { root: Hash256, proof_type: u8 }, +} + +/// The registry stores a concrete `MockProofNodeClient` as a +/// non-generic stand-in. All fields are Arc-wrapped, so `get_mock_proof_engine` +/// can construct a `MockProofNodeClient` for any `E` by sharing those Arcs. +static MOCK_REGISTRY: LazyLock< + parking_lot::Mutex>>>, +> = LazyLock::new(|| parking_lot::Mutex::new(HashMap::new())); + +/// Register a mock at `index`. Must be called before `ExecutionLayer::from_config`. +/// +/// Stores the mock as `MainnetEthSpec` internally and returns a `MockProofNodeClient` +/// that shares the same Arc-backed state but decodes SSZ using `E`. +pub fn register_mock_proof_engine( + index: usize, + callback_delay_ms: u64, +) -> MockProofNodeClient { + let stored = Arc::new(MockProofNodeClient::::new( + callback_delay_ms, + )); + let typed = MockProofNodeClient:: { + requests: stored.requests.clone(), + event_tx: stored.event_tx.clone(), + call_tx: stored.call_tx.clone(), + proof_generation_delay: stored.proof_generation_delay, + _phantom: PhantomData, + }; + MOCK_REGISTRY.lock().insert(index, stored); + typed +} + +/// Fetch a registered mock by index as a `MockProofNodeClient`. +/// +/// Constructs the typed client by sharing the Arc fields of the stored +/// `MockProofNodeClient`, so all state (requests, events) is shared. +pub fn get_mock_proof_engine(index: usize) -> Option> { + MOCK_REGISTRY + .lock() + .get(&index) + .map(|stored| MockProofNodeClient:: { + requests: stored.requests.clone(), + event_tx: stored.event_tx.clone(), + call_tx: stored.call_tx.clone(), + proof_generation_delay: stored.proof_generation_delay, + _phantom: PhantomData, + }) +} + +/// URL encoding an index: `"http://mock/{n}/"`. +pub fn mock_proof_engine_url(index: usize) -> String { + format!("http://mock/{}/", index) +} + +/// Parse the index from a mock URL. Returns `None` for non-mock URLs. +pub fn parse_mock_index(url: &str) -> Option { + url.strip_prefix("http://mock/").map(|s| { + let s = s.strip_suffix('/').unwrap_or(s); + if s.is_empty() { + 0 + } else { + s.parse().unwrap_or(0) + } + }) +} + +/// Build a test SSZ body encoding a `NewPayloadRequestFulu` with the given +/// parent beacon block root. Returns `(ssz_bytes, expected_tree_hash_root)`. +pub fn make_test_fulu_ssz(parent_root: Hash256) -> (Vec, Hash256) { + let request = OwnedNewPayloadRequestFulu:: { + execution_payload: ExecutionPayloadFulu::default(), + versioned_hashes: VariableList::default(), + parent_beacon_block_root: parent_root, + execution_requests: ExecutionRequests::default(), + }; + let request = OwnedNewPayloadRequest::Fulu(request); + (request.as_ssz_bytes(), request.tree_hash_root()) +} + +/// In-memory proof node client for testing, generic over [`EthSpec`]. +/// +/// Each call to [`request_proofs`] decodes the SSZ body using `E`, records the +/// raw SSZ body, and schedules a [`ProofEvent::ProofComplete`] event for each +/// requested proof type after `callback_delay_ms` milliseconds. +/// +/// Call [`subscribe_client_events`] to receive a [`MockClientEvent`] stream +/// that fires once per method invocation — useful for asserting that the proof +/// engine issues the expected calls without polling shared state. +/// +/// [`request_proofs`]: MockProofNodeClient::request_proofs +/// [`subscribe_client_events`]: MockProofNodeClient::subscribe_client_events +pub struct MockProofNodeClient { + /// Received SSZ request bodies in order of arrival. + requests: Arc>>>, + /// Broadcast channel for in-memory SSE events. + event_tx: broadcast::Sender, + /// Broadcast channel for method-invocation events. + call_tx: broadcast::Sender, + /// Delay in milliseconds before broadcasting proof complete events. + proof_generation_delay: u64, + _phantom: PhantomData, +} + +impl Clone for MockProofNodeClient { + fn clone(&self) -> Self { + Self { + requests: self.requests.clone(), + event_tx: self.event_tx.clone(), + call_tx: self.call_tx.clone(), + proof_generation_delay: self.proof_generation_delay, + _phantom: PhantomData, + } + } +} + +impl MockProofNodeClient { + /// Create a new unregistered mock client. + /// + /// `callback_delay_ms` controls how long after `request_proofs` the + /// proof complete events are broadcast. + pub fn new(callback_delay_ms: u64) -> Self { + let (event_tx, _) = broadcast::channel(256); + let (call_tx, _) = broadcast::channel(256); + Self { + requests: Arc::new(Mutex::new(Vec::new())), + event_tx, + call_tx, + proof_generation_delay: callback_delay_ms, + _phantom: PhantomData, + } + } + + /// Returns the number of proof requests received. + pub fn request_count(&self) -> usize { + self.requests.lock().len() + } + + /// Returns a clone of all received SSZ request bodies. + pub fn received_requests(&self) -> Vec> { + self.requests.lock().clone() + } + + /// Subscribe to method-invocation events. + /// + /// Each call to `request_proofs`, `verify_proof`, or `get_proof` on this + /// client sends one [`MockClientEvent`] to all active receivers. Use this + /// in tests to assert that the proof engine issues the expected calls. + pub fn subscribe_client_events(&self) -> broadcast::Receiver { + self.call_tx.subscribe() + } +} + +#[async_trait::async_trait] +impl ProofNodeClient for MockProofNodeClient { + async fn request_proofs( + &self, + ssz_body: Vec, + proof_attributes: ProofAttributes, + ) -> Result { + let root = OwnedNewPayloadRequest::::from_ssz_bytes(&ssz_body) + .map_err(|e| ProofEngineError::InvalidPayload(format!("SSZ decode failed: {e:?}")))? + .tree_hash_root(); + + self.requests.lock().push(ssz_body.clone()); + + let _ = self.call_tx.send(MockClientEvent::ProofRequested { + ssz_body, + proof_attributes: proof_attributes.clone(), + root, + }); + + let event_tx = self.event_tx.clone(); + let delay = self.proof_generation_delay; + let proof_types = proof_attributes.proof_types.clone(); + + tokio::spawn(async move { + tokio::time::sleep(Duration::from_millis(delay)).await; + for proof_type in proof_types { + let _ = event_tx.send(ProofEvent::ProofComplete(ProofComplete { + new_payload_request_root: root, + proof_type, + })); + } + }); + + Ok(root) + } + + async fn verify_proof( + &self, + root: Hash256, + proof_type: u8, + _proof_data: &[u8], + ) -> Result { + let _ = self + .call_tx + .send(MockClientEvent::ProofVerified { root, proof_type }); + Ok(ProofStatus::Valid) + } + + async fn get_proof(&self, root: Hash256, proof_type: u8) -> Result { + let _ = self + .call_tx + .send(MockClientEvent::ProofFetched { root, proof_type }); + Ok(Bytes::from(vec![0xDE, 0xAD, 0xBE, 0xEF])) + } + + fn subscribe_proof_events( + &self, + filter_root: Option, + ) -> Pin> + Send + '_>> { + let rx = self.event_tx.subscribe(); + let stream = BroadcastStream::new(rx).filter_map(move |result| match result { + Ok(event) => { + if filter_root.is_some_and(|root| event.new_payload_request_root() != root) { + return None; + } + Some(Ok(event)) + } + Err(_) => None, + }); + Box::pin(stream) + } +} diff --git a/beacon_node/execution_layer/src/test_utils/mod.rs b/beacon_node/execution_layer/src/test_utils/mod.rs index 4eb03778f80..7c25a2973d0 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -33,7 +33,12 @@ pub use execution_block_generator::{ }; pub use hook::Hook; pub use mock_builder::{MockBuilder, Operation, mock_builder_extra_data}; +pub use mock_event_stream::{MockEventStream, MockStreamError}; pub use mock_execution_layer::MockExecutionLayer; +pub use mock_proof_node_client::{ + MockClientEvent, MockProofNodeClient, OwnedNewPayloadRequest, get_mock_proof_engine, + make_test_fulu_ssz, mock_proof_engine_url, parse_mock_index, register_mock_proof_engine, +}; pub const DEFAULT_JWT_SECRET: [u8; 32] = [42; 32]; pub const DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI: u128 = 10_000_000_000_000_000; @@ -74,7 +79,9 @@ mod execution_block_generator; mod handle_rpc; mod hook; mod mock_builder; +mod mock_event_stream; mod mock_execution_layer; +pub(crate) mod mock_proof_node_client; /// Configuration for the MockExecutionLayer. #[derive(Clone)] diff --git a/beacon_node/http_api/src/beacon/pool.rs b/beacon_node/http_api/src/beacon/pool.rs index 220137a64bf..1b60d018d5a 100644 --- a/beacon_node/http_api/src/beacon/pool.rs +++ b/beacon_node/http_api/src/beacon/pool.rs @@ -67,6 +67,7 @@ pub struct SubmitExecutionProofStatus { /// POST beacon/pool/execution_proofs pub fn post_beacon_pool_execution_proofs( + network_tx_filter: &NetworkTxFilter, beacon_pool_path: &BeaconPoolPathFilter, ) -> ResponseFilter { beacon_pool_path @@ -74,10 +75,12 @@ pub fn post_beacon_pool_execution_proofs( .and(warp::path("execution_proofs")) .and(warp::path::end()) .and(warp_utils::json::json()) + .and(network_tx_filter.clone()) .then( |_task_spawner: TaskSpawner, chain: Arc>, - request: SubmitExecutionProofsRequest| async move { + request: SubmitExecutionProofsRequest, + network_tx: UnboundedSender>| async move { convert_rejection( async move { if chain @@ -108,6 +111,14 @@ pub fn post_beacon_pool_execution_proofs( index, "execution proof is invalid".to_string(), )); + } else if observation.status == ProofStatus::Valid + || (observation.status == ProofStatus::Accepted + && observation.valid_proof_type_count > 0) + { + utils::publish_pubsub_message( + &network_tx, + PubsubMessage::ExecutionProof(Arc::new(proof.clone())), + )?; } statuses.push(SubmitExecutionProofStatus { status: observation.status, diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index fc8bbce94a6..361b33d5e5e 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -1525,7 +1525,8 @@ pub fn serve( post_beacon_pool_bls_to_execution_changes(&network_tx_filter, &beacon_pool_path); // POST beacon/pool/execution_proofs - let post_beacon_pool_execution_proofs = post_beacon_pool_execution_proofs(&beacon_pool_path); + let post_beacon_pool_execution_proofs = + post_beacon_pool_execution_proofs(&network_tx_filter, &beacon_pool_path); // POST validator/proposer_preferences (JSON) let post_validator_proposer_preferences = post_validator_proposer_preferences( diff --git a/beacon_node/lighthouse_network/src/service/utils.rs b/beacon_node/lighthouse_network/src/service/utils.rs index d235e4b28f6..6c9a15ba57c 100644 --- a/beacon_node/lighthouse_network/src/service/utils.rs +++ b/beacon_node/lighthouse_network/src/service/utils.rs @@ -276,6 +276,7 @@ pub(crate) fn create_whitelist_filter( add(ExecutionPayloadBid); add(PayloadAttestation); add(ProposerPreferences); + add(ExecutionProof); add(LightClientFinalityUpdate); add(LightClientOptimisticUpdate); for id in 0..spec.attestation_subnet_count { diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index 571c1a697ce..c9239f2f08e 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -10,6 +10,7 @@ use beacon_chain::data_column_verification::{ GossipVerifiedPartialDataColumnHeader, KzgVerifiedPartialDataColumn, PartialColumnVerificationResult, }; +use beacon_chain::internal_events::InternalBeaconNodeEvent; use beacon_chain::payload_bid_verification::PayloadBidError; use beacon_chain::proposer_preferences_verification::ProposerPreferencesError; use beacon_chain::store::Error; @@ -226,11 +227,34 @@ impl NetworkBeaconProcessor { peer_id: PeerId, execution_proof: Arc, ) { - match self + if self.chain.internal_event_sender().is_some() { + self.chain + .emit_internal_event(InternalBeaconNodeEvent::GossipExecutionProof( + execution_proof.clone(), + )); + } + + let request_root = execution_proof.request_root(); + let verification_result = self .chain .verify_and_observe_execution_proof(&execution_proof, None) - .await - { + .await; + + if self.chain.internal_event_sender().is_some() { + self.chain.emit_internal_event(match &verification_result { + Ok(observation) => InternalBeaconNodeEvent::ExecutionProofVerified { + request_root, + status: observation.status, + block: None, + }, + Err(error) => InternalBeaconNodeEvent::ExecutionProofVerificationFailed { + request_root, + error: format!("{error:?}"), + }, + }); + } + + match verification_result { Ok(observation) => match observation.status { ProofStatus::Valid | ProofStatus::Accepted => { self.propagate_validation_result( @@ -279,11 +303,34 @@ impl NetworkBeaconProcessor { peer_id: PeerId, execution_proof: Arc, ) { - match self + if self.chain.internal_event_sender().is_some() { + self.chain + .emit_internal_event(InternalBeaconNodeEvent::RpcExecutionProof( + execution_proof.clone(), + )); + } + + let request_root = execution_proof.request_root(); + let verification_result = self .chain .verify_and_observe_execution_proof(&execution_proof, None) - .await - { + .await; + + if self.chain.internal_event_sender().is_some() { + self.chain.emit_internal_event(match &verification_result { + Ok(observation) => InternalBeaconNodeEvent::ExecutionProofVerified { + request_root, + status: observation.status, + block: None, + }, + Err(error) => InternalBeaconNodeEvent::ExecutionProofVerificationFailed { + request_root, + error: format!("{error:?}"), + }, + }); + } + + match verification_result { Ok(observation) if observation.status == ProofStatus::Invalid => { self.send_network_message(NetworkMessage::ReportPeer { peer_id, diff --git a/beacon_node/network/src/sync/backfill_sync/mod.rs b/beacon_node/network/src/sync/backfill_sync/mod.rs index 0f80138d240..da46d48a450 100644 --- a/beacon_node/network/src/sync/backfill_sync/mod.rs +++ b/beacon_node/network/src/sync/backfill_sync/mod.rs @@ -81,6 +81,7 @@ impl BatchConfig for BackFillBatchConfig { } /// Return type when attempting to start the backfill sync process. +#[cfg_attr(feature = "disable-backfill", allow(dead_code))] pub enum SyncStart { /// The chain started syncing or is already syncing. Syncing { @@ -159,6 +160,7 @@ pub struct BackFillSync { network_globals: Arc>, } +#[cfg_attr(feature = "disable-backfill", allow(dead_code))] impl BackFillSync { pub fn new( beacon_chain: Arc>, @@ -1210,6 +1212,7 @@ impl BackFillSync { } /// Error kind for attempting to restart the sync from beacon chain parameters. +#[cfg_attr(feature = "disable-backfill", allow(dead_code))] enum ResetEpochError { /// The chain has already completed. SyncCompleted, diff --git a/beacon_node/network/src/sync/custody_backfill_sync/mod.rs b/beacon_node/network/src/sync/custody_backfill_sync/mod.rs index c85610613c6..74318006d19 100644 --- a/beacon_node/network/src/sync/custody_backfill_sync/mod.rs +++ b/beacon_node/network/src/sync/custody_backfill_sync/mod.rs @@ -126,6 +126,7 @@ pub struct CustodyBackFillSync { network_globals: Arc>, } +#[cfg_attr(feature = "disable-backfill", allow(dead_code))] impl CustodyBackFillSync { pub fn new( beacon_chain: Arc>, diff --git a/beacon_node/network/src/sync/manager.rs b/beacon_node/network/src/sync/manager.rs index c8158998621..a8a388dd629 100644 --- a/beacon_node/network/src/sync/manager.rs +++ b/beacon_node/network/src/sync/manager.rs @@ -33,7 +33,9 @@ //! needs to be searched for (i.e if an attestation references an unknown block) this manager can //! search for the block and subsequently search for parents if needed. -use super::backfill_sync::{BackFillSync, ProcessResult, SyncStart}; +#[cfg(not(feature = "disable-backfill"))] +use super::backfill_sync::SyncStart; +use super::backfill_sync::{BackFillSync, ProcessResult}; use super::block_lookups::BlockLookups; use super::network_context::{ CustodyByRootResult, RangeBlockComponent, RangeRequestId, RpcEvent, SyncNetworkContext, @@ -684,6 +686,7 @@ impl SyncManager { // If we synced a peer between status messages, most likely the peer has // advanced and will produce a head chain on re-status. Otherwise it will shift // to being synced + #[cfg(not(feature = "disable-backfill"))] let mut sync_state = { let head = self.chain.best_slot(); let current_slot = self.chain.slot().unwrap_or_else(|_| Slot::new(0)); @@ -705,6 +708,28 @@ impl SyncManager { } }; + #[cfg(feature = "disable-backfill")] + let sync_state = { + let head = self.chain.best_slot(); + let current_slot = self.chain.slot().unwrap_or_else(|_| Slot::new(0)); + + let peers = self.network_globals().peers.read(); + if current_slot >= head + && current_slot.sub(head) <= (SLOT_IMPORT_TOLERANCE as u64) + && head > 0 + { + SyncState::Synced + } else if peers.advanced_peers().next().is_some() { + SyncState::SyncTransition + } else if peers.synced_peers().next().is_none() { + SyncState::Stalled + } else { + // There are no peers that require syncing and we have at least one synced + // peer + SyncState::Synced + } + }; + // If we would otherwise be synced, first check if we need to perform or // complete a backfill sync. #[cfg(not(feature = "disable-backfill"))] @@ -828,7 +853,10 @@ impl SyncManager { .as_ref() .map(|el| el.get_responsiveness_watch()) .into(); - futures::stream::iter(ee_responsiveness_watch.await).flatten() + match ee_responsiveness_watch.await.flatten() { + Some(watch) => watch.left_stream(), + None => futures::stream::empty().right_stream(), + } }; // min(LOOKUP_MAX_DURATION_*) is 15 seconds. The cost of calling prune_lookups more often is diff --git a/beacon_node/network/src/sync/network_context.rs b/beacon_node/network/src/sync/network_context.rs index 34e66c3369c..4c17ddfaab1 100644 --- a/beacon_node/network/src/sync/network_context.rs +++ b/beacon_node/network/src/sync/network_context.rs @@ -23,6 +23,7 @@ use crate::sync::range_data_column_batch_request::RangeDataColumnBatchRequest; use beacon_chain::block_verification_types::LookupBlock; use beacon_chain::block_verification_types::{AsBlock, RangeSyncBlock}; use beacon_chain::eip8025::MissingExecutionProofInfo; +use beacon_chain::internal_events::InternalBeaconNodeEvent; use beacon_chain::{BeaconChain, BeaconChainTypes, BlockProcessStatus, EngineState}; use custody::CustodyRequestResult; use execution_layer::eip8025::types::ProofTypes; @@ -447,6 +448,11 @@ impl SyncNetworkContext { app_request_id: AppRequestId::Sync(SyncRequestId::ExecutionProofsByRange(id)), }) .map_err(|e| RpcRequestSendError::InternalError(e.to_owned()))?; + self.chain + .emit_internal_event(InternalBeaconNodeEvent::OutboundExecutionProofsByRange { + start_slot, + count, + }); debug!( method = "ExecutionProofsByRange", @@ -485,6 +491,7 @@ impl SyncNetworkContext { .max_request_blocks(self.fork_context.current_fork_name()); let request = ExecutionProofsByRootRequest::new(identifiers, max_request_blocks) .map_err(RpcRequestSendError::InternalError)?; + let event_identifiers = request.identifiers.to_vec(); let id = ExecutionProofsByRootRequestId { id: self.next_id() }; self.send_network_msg(NetworkMessage::SendRequest { @@ -493,6 +500,10 @@ impl SyncNetworkContext { app_request_id: AppRequestId::Sync(SyncRequestId::ExecutionProofsByRoot(id)), }) .map_err(|e| RpcRequestSendError::InternalError(e.to_owned()))?; + self.chain + .emit_internal_event(InternalBeaconNodeEvent::OutboundExecutionProofsByRoot { + identifiers: event_identifiers, + }); debug!( method = "ExecutionProofsByRoot", @@ -510,9 +521,10 @@ impl SyncNetworkContext { peer_id: PeerId, ) -> Result { let id = ExecutionProofStatusRequestId { id: self.next_id() }; + let local_status = self.local_execution_proof_status(); self.send_network_msg(NetworkMessage::SendRequest { peer_id, - request: RequestType::ExecutionProofStatus(self.local_execution_proof_status()), + request: RequestType::ExecutionProofStatus(local_status), app_request_id: AppRequestId::Sync(SyncRequestId::ExecutionProofStatus(id)), }) .map_err(|e| RpcRequestSendError::InternalError(e.to_owned()))?; diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index e9fb44209ba..cde57846a8b 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -46,7 +46,10 @@ use ssz::{Decode, Encode}; use std::fmt; use std::future::Future; use std::time::Duration; -use types::{PayloadAttestationData, PayloadAttestationMessage, SignedProposerPreferences}; +use types::{ + PayloadAttestationData, PayloadAttestationMessage, SignedExecutionProof, + SignedProposerPreferences, +}; pub const V1: EndpointVersion = EndpointVersion(1); pub const V2: EndpointVersion = EndpointVersion(2); @@ -1832,6 +1835,30 @@ impl BeaconNodeHttpClient { Ok(()) } + /// `POST beacon/pool/execution_proofs` + pub async fn post_beacon_pool_execution_proofs( + &self, + proofs: &[SignedExecutionProof], + ) -> Result<(), Error> { + #[derive(Serialize)] + struct SubmitExecutionProofsRequest<'a> { + proofs: &'a [SignedExecutionProof], + } + + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("beacon") + .push("pool") + .push("execution_proofs"); + + self.post(path, &SubmitExecutionProofsRequest { proofs }) + .await?; + + Ok(()) + } + /// `POST beacon/pool/bls_to_execution_changes` pub async fn post_beacon_pool_bls_to_execution_changes( &self, diff --git a/consensus/types/src/core/chain_spec.rs b/consensus/types/src/core/chain_spec.rs index 25dcb4ba06c..4ac113685a4 100644 --- a/consensus/types/src/core/chain_spec.rs +++ b/consensus/types/src/core/chain_spec.rs @@ -37,6 +37,7 @@ pub enum Domain { BeaconBuilder, PTCAttester, ProposerPreferences, + ExecutionProof, ApplicationMask(ApplicationDomain), } @@ -147,6 +148,7 @@ pub struct ChainSpec { pub(crate) domain_beacon_builder: u32, pub(crate) domain_ptc_attester: u32, pub(crate) domain_proposer_preferences: u32, + pub(crate) domain_execution_proof: u32, /* * Fork choice @@ -525,6 +527,7 @@ impl ChainSpec { Domain::BeaconBuilder => self.domain_beacon_builder, Domain::PTCAttester => self.domain_ptc_attester, Domain::ProposerPreferences => self.domain_proposer_preferences, + Domain::ExecutionProof => self.domain_execution_proof, Domain::SyncCommittee => self.domain_sync_committee, Domain::ContributionAndProof => self.domain_contribution_and_proof, Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof, @@ -1158,6 +1161,7 @@ impl ChainSpec { domain_beacon_builder: 0x0B, domain_ptc_attester: 0x0C, domain_proposer_preferences: 0x0D, + domain_execution_proof: 0x0D, /* * Fork choice @@ -1583,6 +1587,7 @@ impl ChainSpec { domain_beacon_builder: 0x0B, domain_ptc_attester: 0x0C, domain_proposer_preferences: 0x0D, + domain_execution_proof: 0x0D, /* * Fork choice @@ -2978,6 +2983,7 @@ mod tests { test_domain(Domain::SyncCommittee, spec.domain_sync_committee, &spec); test_domain(Domain::BeaconBuilder, spec.domain_beacon_builder, &spec); test_domain(Domain::PTCAttester, spec.domain_ptc_attester, &spec); + test_domain(Domain::ExecutionProof, spec.domain_execution_proof, &spec); // The builder domain index is zero let builder_domain_pre_mask = [0; 4]; diff --git a/consensus/types/src/core/config_and_preset.rs b/consensus/types/src/core/config_and_preset.rs index 02f9867fcba..acfbd70a3a8 100644 --- a/consensus/types/src/core/config_and_preset.rs +++ b/consensus/types/src/core/config_and_preset.rs @@ -136,6 +136,7 @@ pub fn get_extra_fields(spec: &ChainSpec) -> HashMap { "domain_beacon_builder".to_uppercase() => u32_hex(spec.domain_beacon_builder), "domain_ptc_attester".to_uppercase() => u32_hex(spec.domain_ptc_attester), "domain_proposer_preferences".to_uppercase() => u32_hex(spec.domain_proposer_preferences), + "domain_execution_proof".to_uppercase() => u32_hex(spec.domain_execution_proof), "sync_committee_subnet_count".to_uppercase() => consts::altair::SYNC_COMMITTEE_SUBNET_COUNT.to_string().into(), "target_aggregators_per_sync_subcommittee".to_uppercase() => diff --git a/lighthouse/environment/Cargo.toml b/lighthouse/environment/Cargo.toml index 6d6ffa1725f..669bd0db278 100644 --- a/lighthouse/environment/Cargo.toml +++ b/lighthouse/environment/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.2" authors = ["Paul Hauner "] edition = { workspace = true } +[features] +test-utils = [] + [dependencies] async-channel = { workspace = true } clap = { workspace = true } diff --git a/lighthouse/environment/src/lib.rs b/lighthouse/environment/src/lib.rs index 1431b03f453..d801cf28b64 100644 --- a/lighthouse/environment/src/lib.rs +++ b/lighthouse/environment/src/lib.rs @@ -33,6 +33,9 @@ use { #[cfg(not(target_family = "unix"))] use {futures::channel::oneshot, std::cell::RefCell}; +#[cfg(feature = "test-utils")] +pub mod test_utils; + pub mod tracing_common; pub const SSE_LOG_CHANNEL_SIZE: usize = 2048; @@ -284,6 +287,25 @@ impl EnvironmentBuilder { Ok(self) } + #[cfg(feature = "test-utils")] + pub fn build_test_environment(self) -> Result, String> { + let (signal, exit) = async_channel::bounded(1); + let (signal_tx, signal_rx) = channel(1); + Ok(test_utils::TestEnvironment { + executor: TaskExecutor::new( + tokio::runtime::Handle::try_current().map_err(|e| e.to_string())?, + exit.clone(), + signal_tx, + ), + signal_rx: Some(signal_rx), + signal: Some(signal), + sse_logging_components: self.sse_logging_components, + eth_spec_instance: self.eth_spec_instance, + eth2_config: self.eth2_config, + eth2_network_config: self.eth2_network_config.map(Arc::new), + }) + } + /// Consumes the builder, returning an `Environment`. pub fn build(self) -> Result, String> { let (signal, exit) = async_channel::bounded(1); diff --git a/lighthouse/environment/src/test_utils.rs b/lighthouse/environment/src/test_utils.rs new file mode 100644 index 00000000000..c12fb476574 --- /dev/null +++ b/lighthouse/environment/src/test_utils.rs @@ -0,0 +1,24 @@ +use crate::*; +use task_executor::TaskExecutor; + +pub struct TestEnvironment { + pub executor: TaskExecutor, + pub signal_rx: Option>, + pub signal: Option>, + pub sse_logging_components: Option, + pub eth_spec_instance: E, + pub eth2_config: Eth2Config, + pub eth2_network_config: Option>, +} + +impl TestEnvironment { + pub fn core_context(&self) -> RuntimeContext { + RuntimeContext { + executor: self.executor.clone(), + eth_spec_instance: self.eth_spec_instance.clone(), + eth2_config: self.eth2_config.clone(), + eth2_network_config: self.eth2_network_config.clone(), + sse_logging_components: self.sse_logging_components.clone(), + } + } +} diff --git a/scripts/local_testnet/network_params_eip8025.yaml b/scripts/local_testnet/network_params_eip8025.yaml new file mode 100644 index 00000000000..8ae664ffed2 --- /dev/null +++ b/scripts/local_testnet/network_params_eip8025.yaml @@ -0,0 +1,40 @@ +# EIP-8025 multi-node testnet configuration. +# +# Uses MockProofNodeClient via the http://mock/{n}/ URL pattern. +# See start_eip8025_testnet.sh for usage. +# +# Full configuration reference: https://github.com/ethpandaops/ethereum-package#configuration +participants: + # Supernode participants with proof engine enabled + - cl_type: lighthouse + cl_image: ethpandaops/lighthouse:eth-act-optional-proofs + el_type: geth + el_image: ethereum/client-go:latest + supernode: true + cl_extra_params: + - --target-peers=3 + - --proof-engine-endpoint=http://mock/0/ + vc_extra_params: + - --proof-engine-endpoint=http://mock/0/ + count: 2 + # Non-supernode participants with proof engine enabled + - cl_type: lighthouse + cl_image: ethpandaops/lighthouse:eth-act-optional-proofs + el_type: geth + el_image: ethereum/client-go:latest + supernode: false + cl_extra_params: + - --target-peers=3 + - --proof-engine-endpoint=http://mock/0/ + vc_extra_params: + - --proof-engine-endpoint=http://mock/0/ + count: 2 +network_params: + fulu_fork_epoch: 0 + seconds_per_slot: 6 +snooper_enabled: false +global_log_level: debug +additional_services: + - dora + - prometheus + - grafana diff --git a/scripts/local_testnet/network_params_eip8025_zkboost.yaml b/scripts/local_testnet/network_params_eip8025_zkboost.yaml new file mode 100644 index 00000000000..0a66d9ed3f1 --- /dev/null +++ b/scripts/local_testnet/network_params_eip8025_zkboost.yaml @@ -0,0 +1,73 @@ +# EIP-8025 testnet with zkboost backends via native ethereum-package integration. +# +# Run with: +# kurtosis run --enclave eip8025-zkboost \ +# github.com/ethpandaops/ethereum-package \ +# --args-file scripts/local_testnet/network_params_eip8025_zkboost.yaml +# +# For the mock-only path (no zkboost sidecar), use network_params_eip8025.yaml instead. + +# ── Ethereum package participants ──────────────────────────────────────────── +participants: + # Supernode participants — proof engine points to zkboost-1 + - cl_type: lighthouse + cl_image: ethpandaops/lighthouse:eth-act-optional-proofs + el_type: reth + el_image: ghcr.io/paradigmxyz/reth + supernode: true + cl_extra_params: + - --target-peers=3 + - --proof-engine-endpoint=http://zkboost-1:3000 + - --proof-types=6 + vc_extra_params: + - --proof-engine-endpoint=http://zkboost-1:3000 + - --proof-types=6 + count: 2 + # Non-supernode participants — proof engine points to zkboost-2 + - cl_type: lighthouse + cl_image: ethpandaops/lighthouse:eth-act-optional-proofs + el_type: reth + el_image: ghcr.io/paradigmxyz/reth + supernode: false + cl_extra_params: + - --target-peers=3 + - --proof-engine-endpoint=http://zkboost-2:3000 + - --proof-types=6 + vc_extra_params: + - --proof-engine-endpoint=http://zkboost-2:3000 + - --proof-types=6 + count: 2 + +network_params: + fulu_fork_epoch: 0 + seconds_per_slot: 6 + +snooper_enabled: false +global_log_level: debug + +additional_services: + - zkboost + - dora + - prometheus + - grafana + +# ── zkboost configuration ───────────────────────────────────────────────────── +# Processed natively by ethereum-package; see src/zkboost/zkboost_launcher.star. +zkboost_params: + image: ghcr.io/eth-act/zkboost/zkboost:0.5.0 + env: + RUST_LOG: info,zkboost=debug + # Two instances: each connected to its own EL, serving one group of participants. + # el_participant_index is 0-based into the flat participant list after count expansion: + # 0 = el-1-reth-lighthouse (first supernode) + # 1 = el-2-reth-lighthouse (second supernode) + instances: + - name: zkboost-1 + el_participant_index: 0 + - name: zkboost-2 + el_participant_index: 1 + zkvms: + - kind: mock + proof_type: reth-zisk + mock_proving_time: { kind: constant, ms: 300 } + mock_proof_size: 1024 diff --git a/scripts/local_testnet/network_params_eip8025_zkboost_gpu.yaml b/scripts/local_testnet/network_params_eip8025_zkboost_gpu.yaml new file mode 100644 index 00000000000..58b113e0937 --- /dev/null +++ b/scripts/local_testnet/network_params_eip8025_zkboost_gpu.yaml @@ -0,0 +1,95 @@ +# EIP-8025 testnet with zkboost backed by real GPU (ZisK) provers. +# +# Run with: +# kurtosis run --enclave eip8025-zkboost-gpu \ +# github.com/ethpandaops/ethereum-package \ +# --args-file scripts/local_testnet/network_params_eip8025_zkboost_gpu.yaml +# +# Prerequisites: +# - NVIDIA GPUs with drivers installed (≥8 GPUs recommended: 4 per prover type) +# - NVIDIA Container Toolkit configured for Docker +# - ~5-10 min startup time for ZisK setup on first run + +# ── Ethereum package participants ──────────────────────────────────────────── +participants: + # Supernode participants + - cl_type: lighthouse + cl_image: ethpandaops/lighthouse:eth-act-optional-proofs + el_type: reth + el_image: ghcr.io/paradigmxyz/reth + supernode: true + cl_extra_params: + - --target-peers=3 + - --proof-engine-endpoint=http://zkboost-1:3000 + - --proof-types=6 + vc_extra_params: + - --proof-engine-endpoint=http://zkboost-1:3000 + - --proof-types=6 + count: 2 + # Non-supernode participants + - cl_type: lighthouse + cl_image: ethpandaops/lighthouse:eth-act-optional-proofs + el_type: reth + el_image: ghcr.io/paradigmxyz/reth + supernode: false + cl_extra_params: + - --target-peers=3 + - --proof-engine-endpoint=http://zkboost-1:3000 + - --proof-types=6 + vc_extra_params: + - --proof-engine-endpoint=http://zkboost-1:3000 + - --proof-types=6 + count: 2 + +network_params: + fulu_fork_epoch: 0 + seconds_per_slot: 6 + +snooper_enabled: false +global_log_level: debug + +additional_services: + - zkboost + - dora + - prometheus + - grafana + +# ── zkboost configuration ───────────────────────────────────────────────────── +zkboost_params: + image: ghcr.io/eth-act/zkboost/zkboost:0.5.0 + env: + RUST_LOG: info,zkboost=debug + instances: + - name: zkboost-1 + el_participant_index: 0 + zkvms: + # reth-zisk GPU prover — uses GPUs 0-3 on the host + - kind: ere + proof_type: reth-zisk + image: ghcr.io/eth-act/ere/ere-server-zisk:0.6.1-cuda + program_url: https://github.com/eth-act/ere-guests/releases/download/v0.7.0/stateless-validator-reth-zisk + port: 3000 + gpu: + device_ids: ["0", "1", "2", "3"] + shm_size: 32768 # 32 GiB — ZisK requires large shared memory for GPU proving + ulimits: + memlock: -1 # unlimited memory lock — required for CUDA unified memory + env: + RUST_LOG: info + ERE_ZISK_SETUP_ON_INIT: "1" + ERE_ZISK_START_SERVER_TIMEOUT_SEC: "600" + # ethrex-zisk GPU prover — uses GPUs 4-7 on the host + - kind: ere + proof_type: ethrex-zisk + image: ghcr.io/eth-act/ere/ere-server-zisk:0.6.1-cuda + program_url: https://github.com/eth-act/ere-guests/releases/download/v0.7.0/stateless-validator-ethrex-zisk + port: 3000 + gpu: + device_ids: ["4", "5", "6", "7"] + shm_size: 32768 + ulimits: + memlock: -1 + env: + RUST_LOG: info + ERE_ZISK_SETUP_ON_INIT: "1" + ERE_ZISK_START_SERVER_TIMEOUT_SEC: "600" diff --git a/scripts/local_testnet/start_eip8025_testnet.sh b/scripts/local_testnet/start_eip8025_testnet.sh new file mode 100755 index 00000000000..978ecacd5a8 --- /dev/null +++ b/scripts/local_testnet/start_eip8025_testnet.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash + +# Start a local EIP-8025 testnet using Kurtosis. +# +# Requires: docker, kurtosis +# +# This script builds Lighthouse (optional) and launches a Kurtosis enclave via +# the ethereum-package. The network params file selects the topology: +# network_params_eip8025.yaml — mock proof engines (no zkboost) +# network_params_eip8025_zkboost.yaml — zkboost backends (mock zkVM) +# network_params_eip8025_zkboost_gpu.yaml — zkboost backends (GPU provers) + +set -Eeuo pipefail + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +ROOT_DIR="$SCRIPT_DIR/../.." +ENCLAVE_NAME=eip8025-testnet +NETWORK_PARAMS_FILE=$SCRIPT_DIR/network_params_eip8025.yaml +ETHEREUM_PKG=github.com/ethpandaops/ethereum-package +# Must match the `cl_image` in the network params yaml so a local build is +# picked up by Kurtosis instead of pulling the remote image. +LH_IMAGE_NAME=ethpandaops/lighthouse:eth-act-optional-proofs + +BUILD_IMAGE=true +KEEP_ENCLAVE=false + +# Get options +while getopts "e:n:p:bkh" flag; do + case "${flag}" in + e) ENCLAVE_NAME=${OPTARG};; + n) NETWORK_PARAMS_FILE=${OPTARG};; + p) ETHEREUM_PKG=${OPTARG};; + b) BUILD_IMAGE=false;; + k) KEEP_ENCLAVE=true;; + h) + echo "Start a local EIP-8025 testnet with Kurtosis." + echo + echo "usage: $0 " + echo + echo "Options:" + echo " -e: enclave name default: $ENCLAVE_NAME" + echo " -n: kurtosis network params file path default: $NETWORK_PARAMS_FILE" + echo " -p: ethereum-package path or GitHub ref default: $ETHEREUM_PKG" + echo " -b: skip building Lighthouse docker image" + echo " -k: keep existing enclave (don't destroy first)" + echo " -h: this help" + exit + ;; + esac +done + +for cmd in docker kurtosis; do + if ! command -v "$cmd" &> /dev/null; then + echo "$cmd is not installed. Please install $cmd and try again." + exit 1 + fi +done + +if [ "$KEEP_ENCLAVE" = false ]; then + kurtosis enclave rm -f "$ENCLAVE_NAME" 2>/dev/null || true +fi + +if [ "$BUILD_IMAGE" = true ]; then + echo "Building Lighthouse Docker image ($LH_IMAGE_NAME)." + docker build \ + --build-arg FEATURES=portable,spec-minimal \ + -f "$ROOT_DIR/Dockerfile" \ + -t "$LH_IMAGE_NAME" \ + "$ROOT_DIR" +else + echo "Skipping Lighthouse Docker image build." +fi + +echo "Starting EIP-8025 testnet enclave: $ENCLAVE_NAME" +echo " network params: $NETWORK_PARAMS_FILE" +echo " ethereum-package: $ETHEREUM_PKG" +kurtosis run --enclave "$ENCLAVE_NAME" \ + "$ETHEREUM_PKG" \ + --args-file "$NETWORK_PARAMS_FILE" + +echo +echo "EIP-8025 testnet started!" +echo +echo "Useful commands:" +echo " kurtosis enclave inspect $ENCLAVE_NAME" +echo " kurtosis service logs $ENCLAVE_NAME " +echo " kurtosis enclave rm -f $ENCLAVE_NAME" diff --git a/testing/proof_engine/Cargo.toml b/testing/proof_engine/Cargo.toml new file mode 100644 index 00000000000..235cc15e895 --- /dev/null +++ b/testing/proof_engine/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "proof_engine_test" +edition.workspace = true +version.workspace = true + +[dependencies] +anyhow = { workspace = true } +beacon_chain = { workspace = true } +execution_layer = { workspace = true } +futures = { workspace = true } +network = { workspace = true, features = ["disable-backfill"] } +simulator = { path = "../simulator", features = ["test-utils"] } +task_executor = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +types = { workspace = true } diff --git a/testing/proof_engine/src/lib.rs b/testing/proof_engine/src/lib.rs new file mode 100644 index 00000000000..beef28617f9 --- /dev/null +++ b/testing/proof_engine/src/lib.rs @@ -0,0 +1,298 @@ +//! Integration tests for the EIP-8025 proof engine, using [`ProofEngineTestRig`]. + +mod rig; +pub use rig::ProofEngineTestRig; + +#[cfg(test)] +mod test { + use std::time::Duration; + + use futures::try_join; + use simulator::test_utils::{BeaconNodeHttpClient, Epoch, InternalBeaconNodeEvent, StateId}; + + use super::ProofEngineTestRig; + + async fn wait_for_finalized_epoch( + node: BeaconNodeHttpClient, + min_epoch: Epoch, + timeout: Duration, + ) -> anyhow::Result<()> { + tokio::time::timeout(timeout, async move { + loop { + let checkpoint = node + .get_beacon_states_finality_checkpoints(StateId::Head) + .await + .map_err(|e| anyhow::anyhow!("{e:?}"))? + .ok_or_else(|| anyhow::anyhow!("no finality checkpoint response"))? + .data + .finalized; + if checkpoint.epoch >= min_epoch { + return Ok(()); + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + }) + .await + .map_err(|_| anyhow::anyhow!("timed out waiting for finalized epoch {min_epoch}"))? + } + + #[tokio::test] + #[cfg_attr(debug_assertions, ignore = "too slow in debug mode")] + async fn test_proof_engine_basic() -> anyhow::Result<()> { + let mut rig = ProofEngineTestRig::standard().await?; + rig.fixture.payloads_valid(); + rig.fixture.wait_for_genesis().await?; + + let mut gen_events = rig.proof_generator_events(0)?; + let mut verifier_chain = rig.proof_verifier_chain_events(0)?; + + rig.sign_and_submit_next_generator_proof(0, &mut gen_events) + .await?; + + verifier_chain + .collect_n( + 1, + |e| matches!(e, InternalBeaconNodeEvent::GossipExecutionProof(_)), + Duration::from_secs(60), + ) + .await?; + verifier_chain + .collect_n( + 1, + |e| matches!(e, InternalBeaconNodeEvent::ExecutionProofVerified { .. }), + Duration::from_secs(30), + ) + .await?; + + Ok(()) + } + + /// Assert that the validator client's proof service requests completed proof bytes from the + /// proof node, signs them, submits them to its beacon node, and that the proof reaches a + /// verifier through the normal gossip/verification path. + #[tokio::test] + #[cfg_attr(debug_assertions, ignore = "too slow in debug mode")] + async fn test_validator_client_proof_service_signs_and_submits_proofs() -> anyhow::Result<()> { + let mut rig = ProofEngineTestRig::standard().await?; + rig.fixture.payloads_valid(); + rig.fixture.wait_for_genesis().await?; + + let mut gen_events = rig.proof_generator_events(0)?; + let mut verifier_chain = rig.proof_verifier_chain_events(0)?; + + gen_events + .expect_proof_requests(1, Duration::from_secs(60)) + .await?; + gen_events + .expect_proof_fetched(1, Duration::from_secs(60)) + .await?; + + verifier_chain + .collect_n( + 1, + |e| matches!(e, InternalBeaconNodeEvent::GossipExecutionProof(_)), + Duration::from_secs(60), + ) + .await?; + verifier_chain + .collect_n( + 1, + |e| { + matches!( + e, + InternalBeaconNodeEvent::ExecutionProofVerified { status, .. } + if status.is_valid() || status.is_accepted() + ) + }, + Duration::from_secs(30), + ) + .await?; + + Ok(()) + } + + #[tokio::test] + #[cfg_attr(debug_assertions, ignore = "too slow in debug mode")] + async fn test_proof_engine_sync() -> anyhow::Result<()> { + let mut rig = ProofEngineTestRig::sync_topology().await?; + rig.fixture.payloads_valid(); + rig.fixture.wait_for_genesis().await?; + + wait_for_finalized_epoch( + rig.proof_generator_node(0)?, + Epoch::new(2), + Duration::from_secs(90), + ) + .await?; + + // Create a proof inside the generator's current finalized-to-head request window, then add + // a verifier. The generator should see the verifier's proof-capable ENR and initiate the + // proof-status exchange that lets the verifier request the missed proof by RPC. + let (block_root, _slot, request_root) = rig.latest_generator_payload_request(0).await?; + let proof = rig.sign_execution_proof(request_root, 0, 0)?; + rig.observe_valid_generator_proof(0, block_root, &proof)?; + let (_mock_events, mut verifier_chain) = rig.add_proof_verifier_and_subscribe().await?; + + // The late-joining verifier must issue at least one outbound RPC proof request for missing + // proofs in its finalized-to-head window. + verifier_chain + .collect_n( + 1, + |e| { + matches!( + e, + InternalBeaconNodeEvent::OutboundExecutionProofsByRange { .. } + | InternalBeaconNodeEvent::OutboundExecutionProofsByRoot { .. } + ) + }, + Duration::from_secs(120), + ) + .await?; + + // It must then receive proof data by RPC and verify it. `Accepted` means the proof content + // is verified without flipping execution optimism, which is the default proof policy. + verifier_chain + .collect_n( + 1, + |e| matches!(e, InternalBeaconNodeEvent::RpcExecutionProof(_)), + Duration::from_secs(120), + ) + .await?; + verifier_chain + .collect_n( + 1, + |e| { + matches!( + e, + InternalBeaconNodeEvent::ExecutionProofVerified { status, .. } + if status.is_valid() || status.is_accepted() + ) + }, + Duration::from_secs(30), + ) + .await?; + + Ok(()) + } + + /// Assert that the proof verifier receives gossip proofs from the generator and that the + /// full pipeline — gossip arrival → chain verification — completes successfully. + #[tokio::test] + #[cfg_attr(debug_assertions, ignore = "too slow in debug mode")] + async fn test_proof_verifier_receives_proofs() -> anyhow::Result<()> { + let mut rig = ProofEngineTestRig::standard().await?; + rig.fixture.payloads_valid(); + rig.fixture.wait_for_genesis().await?; + + // Subscribe to both streams before proofs start flowing so no events are missed. + let mut mock_events = rig.proof_verifier_events(0)?; + let mut chain_events = rig.proof_verifier_chain_events(0)?; + let mut gen_events = rig.proof_generator_events(0)?; + + rig.sign_and_submit_next_generator_proof(0, &mut gen_events) + .await?; + + // Mock engine confirms the received proof was verified by the verifier's EL. + mock_events + .expect_proof_verified(1, Duration::from_secs(60)) + .await?; + + // Chain events confirm the full gossip pipeline: arrival then on-chain verification. + // Events are buffered since subscription, so these complete immediately. + chain_events + .collect_n( + 1, + |e| matches!(e, InternalBeaconNodeEvent::GossipExecutionProof(_)), + Duration::from_secs(60), + ) + .await?; + chain_events + .collect_n( + 1, + |e| { + matches!( + e, + InternalBeaconNodeEvent::ExecutionProofVerified { status, .. } + if status.is_valid() + ) + }, + Duration::from_secs(30), + ) + .await?; + + Ok(()) + } + + /// Assert that two independent proof generators each receive proof requests, and that the + /// verifier receives gossip proofs from the network. + #[tokio::test] + #[cfg_attr(debug_assertions, ignore = "too slow in debug mode")] + async fn test_multi_generator_proof_requests() -> anyhow::Result<()> { + let mut rig = ProofEngineTestRig::multi_generator().await?; + rig.fixture.payloads_valid(); + rig.fixture.wait_for_genesis().await?; + + let mut gen0 = rig.proof_generator_events(0)?; + let mut gen1 = rig.proof_generator_events(1)?; + let mut verifier_chain = rig.proof_verifier_chain_events(0)?; + + // Both generators must independently receive proof requests from their EL. Submit the + // first generator's proof once requested so the verifier also exercises gossip. + let (_, _) = try_join!( + rig.sign_and_submit_next_generator_proof(0, &mut gen0), + async { + gen1.expect_proof_requests(1, Duration::from_secs(60)) + .await + .map_err(anyhow::Error::new) + }, + )?; + + verifier_chain + .collect_n( + 1, + |e| matches!(e, InternalBeaconNodeEvent::GossipExecutionProof(_)), + Duration::from_secs(60), + ) + .await?; + verifier_chain + .collect_n( + 1, + |e| matches!(e, InternalBeaconNodeEvent::ExecutionProofVerified { .. }), + Duration::from_secs(30), + ) + .await?; + + Ok(()) + } + + /// Assert that the network reaches finality (epoch ≥ 2) while the proof engine is running. + #[tokio::test] + #[cfg_attr(debug_assertions, ignore = "too slow in debug mode")] + async fn test_network_finalizes_with_proofs() -> anyhow::Result<()> { + let mut rig = ProofEngineTestRig::standard().await?; + rig.fixture.payloads_valid(); + rig.fixture.wait_for_genesis().await?; + + // MinimalEthSpec: 8 slots/epoch. Finality of epoch 2 requires epochs 3-4 to elapse. + // 4 epochs * 8 slots * 1s = 32s minimum; use 45s for margin. + tokio::time::sleep(Duration::from_secs(45)).await; + + // Check finality on the default node and the proof generator independently. + for node in [rig.default_node(0)?, rig.proof_generator_node(0)?] { + let checkpoint = node + .get_beacon_states_finality_checkpoints(StateId::Head) + .await + .map_err(|e| anyhow::anyhow!("{e:?}"))? + .ok_or_else(|| anyhow::anyhow!("no finality checkpoint response"))? + .data + .finalized; + assert!( + checkpoint.epoch >= Epoch::new(2), + "expected finality at epoch ≥ 2, got {}", + checkpoint.epoch + ); + } + + Ok(()) + } +} diff --git a/testing/proof_engine/src/rig.rs b/testing/proof_engine/src/rig.rs new file mode 100644 index 00000000000..dc8d0a289d3 --- /dev/null +++ b/testing/proof_engine/src/rig.rs @@ -0,0 +1,384 @@ +//! [`ProofEngineTestRig`] — a thin wrapper over [`TestNetworkFixture`] for EIP-8025 tests. +//! +//! Provides a clean API for building standard proof engine test topologies and asserting +//! on mock proof engine events, insulating individual tests from `LocalNetwork` internals. + +use anyhow::anyhow; +use beacon_chain::WhenSlotSkipped; +use beacon_chain::eip8025::{compute_execution_proof_domain, compute_signing_root}; +use execution_layer::NewPayloadRequest; +use execution_layer::test_utils::MockClientEvent; +use simulator::test_utils::{ + BeaconNodeHttpClient, Epoch, EventStream, InternalBeaconNodeEvent, LocalNetworkParams, + NodeType, TestNetworkFixture, TestNetworkFixtureBuilder, +}; +use types::test_utils::generate_deterministic_keypair; +use types::{ + EthSpec, ExecutionProof, Hash256, MinimalEthSpec, ProofData, PublicInput, SignedExecutionProof, + Slot, +}; + +pub use simulator::test_utils::MockEventStream; + +pub type E = MinimalEthSpec; + +/// Test harness for EIP-8025 proof engine integration tests. +pub struct ProofEngineTestRig { + pub fixture: TestNetworkFixture, +} + +impl ProofEngineTestRig { + /// Wrap a fixture directly. + pub fn new(fixture: TestNetworkFixture) -> Self { + Self { fixture } + } + + /// Standard topology: 1 vanilla node + 1 proof generator + 1 proof verifier. + /// All forks activate at genesis, 1-second slots. + pub async fn standard() -> anyhow::Result { + Ok(Self::new(base_builder().build().await?)) + } + + /// Sync topology: 1 vanilla node + 1 proof generator, no verifier, 1 delayed node slot. + /// Used for testing late-joining proof verifier sync recovery. + pub async fn sync_topology() -> anyhow::Result { + Ok(Self::new( + base_builder() + .map_spec(|spec| { + // Collapse all columns onto a single subnet so the small network can cover them. + spec.data_column_sidecar_subnet_count = 1; + spec.number_of_custody_groups = 8; + }) + .map_network_params(|params| { + params.proof_verifier_nodes = 0; + params.delayed_nodes = 1; + }) + .build() + .await?, + )) + } + + /// Multi-generator topology: 1 vanilla node + 2 proof generators + 1 proof verifier. + /// Used for testing that each generator is independently wired. + pub async fn multi_generator() -> anyhow::Result { + Ok(Self::new( + base_builder() + .map_network_params(|params| { + params.proof_generator_nodes = 2; + }) + .build() + .await?, + )) + } + + /// Subscribe to the nth proof generator node's event stream (0-indexed). + pub fn proof_generator_events(&self, n: usize) -> anyhow::Result { + let idx = self.fixture.config.network_params.node_count + n; + self.fixture + .network + .node_subscribe_client_events(idx) + .map(MockEventStream::from) + .ok_or_else(|| anyhow!("no proof generator at index {n}")) + } + + /// Subscribe to the nth proof verifier node's event stream (0-indexed). + pub fn proof_verifier_events(&self, n: usize) -> anyhow::Result { + let params = &self.fixture.config.network_params; + let idx = params.node_count + params.proof_generator_nodes + n; + self.fixture + .network + .node_subscribe_client_events(idx) + .map(MockEventStream::from) + .ok_or_else(|| anyhow!("no proof verifier at index {n}")) + } + + /// Subscribe to the internal event bus for the nth default node (0-indexed). + pub fn default_node_chain_events( + &self, + n: usize, + ) -> anyhow::Result> { + self.fixture + .network + .node_subscribe_internal_events(n) + .map(EventStream::from) + .ok_or_else(|| anyhow!("no default node at index {n}")) + } + + /// Subscribe to the internal event bus for the nth proof generator node (0-indexed). + pub fn proof_generator_chain_events( + &self, + n: usize, + ) -> anyhow::Result> { + let idx = self.fixture.config.network_params.node_count + n; + self.fixture + .network + .node_subscribe_internal_events(idx) + .map(EventStream::from) + .ok_or_else(|| anyhow!("no proof generator at index {n}")) + } + + /// Subscribe to the internal event bus for the nth proof verifier node (0-indexed). + pub fn proof_verifier_chain_events( + &self, + n: usize, + ) -> anyhow::Result> { + let params = &self.fixture.config.network_params; + let idx = params.node_count + params.proof_generator_nodes + n; + self.fixture + .network + .node_subscribe_internal_events(idx) + .map(EventStream::from) + .ok_or_else(|| anyhow!("no proof verifier at index {n}")) + } + + /// Return HTTP clients for all beacon nodes in the network. + pub fn remote_nodes(&self) -> anyhow::Result> { + self.fixture + .network + .remote_nodes() + .map_err(anyhow::Error::msg) + } + + /// Return an HTTP client for the nth default node (0-indexed). + pub fn default_node(&self, n: usize) -> anyhow::Result { + let idx = n; + self.fixture + .network + .remote_node(idx) + .ok_or_else(|| anyhow!("no default node at index {n}")) + } + + /// Return an HTTP client for the nth proof generator node (0-indexed). + pub fn proof_generator_node(&self, n: usize) -> anyhow::Result { + let idx = self.fixture.config.network_params.node_count + n; + self.fixture + .network + .remote_node(idx) + .ok_or_else(|| anyhow!("no proof generator node at index {n}")) + } + + /// Return an HTTP client for the nth proof verifier node (0-indexed). + pub fn proof_verifier_node(&self, n: usize) -> anyhow::Result { + let params = &self.fixture.config.network_params; + let idx = params.node_count + params.proof_generator_nodes + n; + self.fixture + .network + .remote_node(idx) + .ok_or_else(|| anyhow!("no proof verifier node at index {n}")) + } + + /// Return the most recent canonical execution payload request in the current + /// finalized-to-head window for the selected proof generator. + pub async fn latest_generator_payload_request( + &self, + generator_index: usize, + ) -> anyhow::Result<(Hash256, Slot, Hash256)> { + let idx = self.fixture.config.network_params.node_count + generator_index; + let chain = self + .fixture + .network + .beacon_nodes + .read() + .get(idx) + .and_then(|node| node.client.beacon_chain()) + .ok_or_else(|| anyhow!("no proof generator chain at index {generator_index}"))?; + + let head = chain.canonical_head.cached_head(); + let start_slot = head + .finalized_checkpoint() + .epoch + .start_slot(E::slots_per_epoch()); + let end_slot = head.head_slot(); + + for slot in (start_slot.as_u64()..=end_slot.as_u64()).rev() { + let slot = Slot::new(slot); + let Some(block_root) = chain + .block_root_at_slot(slot, WhenSlotSkipped::None) + .map_err(|error| anyhow!("{error:?}"))? + else { + continue; + }; + let Some(block) = chain + .get_block(&block_root) + .await + .map_err(|error| anyhow!("{error:?}"))? + else { + continue; + }; + let request = NewPayloadRequest::try_from(block.message()) + .map_err(|error| anyhow!("{error:?}"))?; + return Ok((block_root, slot, request.request_root())); + } + + Err(anyhow!( + "no canonical execution payload request in finalized-to-head window" + )) + } + + /// Store a valid proof on the selected generator without publishing it through gossip. + pub fn observe_valid_generator_proof( + &self, + generator_index: usize, + block_root: Hash256, + proof: &SignedExecutionProof, + ) -> anyhow::Result<()> { + let idx = self.fixture.config.network_params.node_count + generator_index; + let chain = self + .fixture + .network + .beacon_nodes + .read() + .get(idx) + .and_then(|node| node.client.beacon_chain()) + .ok_or_else(|| anyhow!("no proof generator chain at index {generator_index}"))?; + + let observation = chain + .observe_valid_execution_proof(proof, Some(block_root)) + .map_err(|error| anyhow!("{error:?}"))?; + anyhow::ensure!( + observation.block_root == Some(block_root), + "proof was not associated with the expected block root" + ); + Ok(()) + } + + /// Add a proof verifier node dynamically and return its mock and internal event streams. + pub async fn add_proof_verifier_and_subscribe( + &self, + ) -> anyhow::Result<(MockEventStream, EventStream)> { + let client_config = self.fixture.config.client.clone(); + let exec_config = self.fixture.config.execution.clone(); + + // Await the node start so we know its index in beacon_nodes before subscribing. + // Spawning + sleeping is unreliable on slow CI runners where node startup takes + // longer than the fixed sleep duration. + self.fixture + .network + .add_beacon_node(client_config, exec_config, NodeType::ProofVerifier) + .await + .map_err(anyhow::Error::msg)?; + + // The new verifier is the last beacon node; subscribe to both event streams. + let idx = self + .fixture + .network + .beacon_nodes + .read() + .len() + .saturating_sub(1); + let mock = self + .fixture + .network + .node_subscribe_client_events(idx) + .map(MockEventStream::from) + .ok_or_else(|| anyhow!("newly added verifier node has no mock event stream"))?; + let chain = self + .fixture + .network + .node_subscribe_internal_events(idx) + .map(EventStream::from) + .ok_or_else(|| anyhow!("newly added verifier node has no beacon chain"))?; + + Ok((mock, chain)) + } + + /// Wait for a proof request from the selected generator, sign a matching proof, submit it to + /// that generator's beacon node HTTP API, and return the signed proof. + pub async fn sign_and_submit_next_generator_proof( + &self, + generator_index: usize, + events: &mut MockEventStream, + ) -> anyhow::Result { + let request = events + .expect_proof_requests(1, std::time::Duration::from_secs(60)) + .await? + .into_iter() + .next() + .ok_or_else(|| anyhow!("expected one proof request event"))?; + + let MockClientEvent::ProofRequested { + root, + proof_attributes, + .. + } = request + else { + return Err(anyhow!("unexpected mock proof event")); + }; + let proof_type = proof_attributes + .proof_types + .first() + .copied() + .ok_or_else(|| anyhow!("proof request did not include any proof types"))?; + + let proof = self.sign_execution_proof(root, proof_type, 0)?; + self.proof_generator_node(generator_index)? + .post_beacon_pool_execution_proofs(std::slice::from_ref(&proof)) + .await + .map_err(|error| anyhow!("{error:?}"))?; + Ok(proof) + } + + pub fn sign_execution_proof( + &self, + request_root: Hash256, + proof_type: u8, + validator_index: u64, + ) -> anyhow::Result { + let chain = self + .fixture + .network + .beacon_nodes + .read() + .first() + .and_then(|node| node.client.beacon_chain()) + .ok_or_else(|| anyhow!("network has no beacon chain"))?; + let fork_name = chain.spec.fork_name_at_slot::(Slot::new(0)); + let keypair = generate_deterministic_keypair(validator_index as usize); + let proof = ExecutionProof { + proof_data: ProofData::new(vec![0xDE, 0xAD, 0xBE, 0xEF])?, + proof_type, + public_input: PublicInput { + new_payload_request_root: request_root, + }, + }; + let domain = + compute_execution_proof_domain(fork_name, chain.genesis_validators_root, &chain.spec); + let signing_root = compute_signing_root(&proof, domain); + + Ok(SignedExecutionProof { + message: proof, + validator_index, + signature: keypair.sk.sign(signing_root).into(), + }) + } + + /// Builder escape hatch for custom topologies. + pub fn builder() -> TestNetworkFixtureBuilder { + base_builder() + } +} + +/// Base builder shared by all standard topologies. +fn base_builder() -> TestNetworkFixtureBuilder { + TestNetworkFixture::builder() + .map_spec(|spec| { + *spec = spec.clone().set_slot_duration_ms::(1000); + spec.min_genesis_time = 0; + spec.altair_fork_epoch = Some(Epoch::new(0)); + spec.bellatrix_fork_epoch = Some(Epoch::new(0)); + spec.capella_fork_epoch = Some(Epoch::new(0)); + spec.deneb_fork_epoch = Some(Epoch::new(0)); + spec.electra_fork_epoch = Some(Epoch::new(0)); + spec.fulu_fork_epoch = Some(Epoch::new(0)); + }) + .with_network_params(LocalNetworkParams { + validator_count: 4, + node_count: 1, + proposer_nodes: 0, + extra_nodes: 0, + proof_generator_nodes: 1, + proof_verifier_nodes: 1, + delayed_nodes: 0, + genesis_delay: 40, + }) +} diff --git a/testing/proof_engine_zkboost/Cargo.lock b/testing/proof_engine_zkboost/Cargo.lock new file mode 100644 index 00000000000..ad907047cec --- /dev/null +++ b/testing/proof_engine_zkboost/Cargo.lock @@ -0,0 +1,9908 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addchain" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e33f6a175ec6a9e0aca777567f9ff7c3deefc255660df887e7fa3585e9801d8" +dependencies = [ + "num-bigint 0.3.3", + "num-integer", + "num-traits", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if 1.0.4", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if 1.0.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "alloy-chains" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e9e31d834fe25fe991b8884e4b9f0e59db4a97d86e05d1464d6899c013cd62" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "num_enum", + "serde", + "strum", +] + +[[package]] +name = "alloy-consensus" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20d33348e61388eb90da06e176030abf496120e54795273210eeea2cd76339c4" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-trie 0.9.5", + "alloy-tx-macros", + "auto_impl", + "borsh", + "c-kzg", + "derive_more 2.1.1", + "either", + "k256", + "once_cell", + "rand 0.8.5", + "secp256k1", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-consensus-any" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d145b64057ea2b66c6146817bee0ffc3adfb3f51c2b60f282f5200b23a6b4bfa" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-eip2124" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "crc", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eip2930" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "k256", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eip7928" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", +] + +[[package]] +name = "alloy-eips" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5194943ebbbf25d308e13797b275dd1bf41487f22156dd2c8e17d24726a03888" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-eip7928", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "auto_impl", + "borsh", + "c-kzg", + "derive_more 2.1.1", + "either", + "serde", + "serde_with", + "sha2", +] + +[[package]] +name = "alloy-evm" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b99ba7b74a87176f31ee1cd26768f7155b0eeff61ed925f59b13085ffe5f891" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-hardforks", + "alloy-primitives", + "alloy-sol-types", + "auto_impl", + "derive_more 2.1.1", + "revm", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-genesis" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3de7e0b146edaf966a1d2b5320903ce4a3e4e8452e4710585c0a22967216366" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "alloy-trie 0.9.5", + "borsh", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-hardforks" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ba208044232d14d4adbfa77e57d6329f51bc1acc21f5667bb7db72d88a0831" +dependencies = [ + "alloy-chains", + "alloy-eip2124", + "alloy-primitives", + "auto_impl", + "dyn-clone", +] + +[[package]] +name = "alloy-json-abi" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dbe713da0c737d9e5e387b0ba790eb98b14dd207fe53eef50e19a5a8ec3dac" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-network-primitives" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6e4eef41e348207945ae12d445d1d168ade6ada09161b0c9b05169f47ca81f" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if 1.0.4", + "const-hex", + "derive_more 2.1.1", + "foldhash 0.2.0", + "getrandom 0.4.2", + "hashbrown 0.16.1", + "indexmap 2.13.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.9.2", + "rapidhash", + "ruint", + "rustc-hash", + "serde", + "sha3", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93e50f64a77ad9c5470bf2ad0ca02f228da70c792a8f06634801e202579f35e" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8849c74c9ca0f5a03da1c865e3eb6f768df816e67dd3721a398a8a7e398011" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "alloy-rpc-types-debug" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d701b7e29f69634e6cd5111e7cee333278557f7bf5d5e4764026e56adec75e1e" +dependencies = [ + "alloy-primitives", + "derive_more 2.1.1", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-rpc-types-engine" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0bb761c5b208dcb938badf0b1243f623b1b80c15282f85fbeb45efa18f1120" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more 2.1.1", + "jsonwebtoken", + "rand 0.8.5", + "serde", + "strum", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f71d86495861c7b6cff4ed7e0c114f13c8d12c5406f03e88981b834c18715aa8" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.14.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-serde" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54d6afd8bd1ec6d34a01d03d3a6c547cfcb197dd631cc8908d183da69b7c4c92" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-sol-macro" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap 2.13.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "sha3", + "syn 2.0.117", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" +dependencies = [ + "const-hex", + "dunce", + "heck", + "macro-string", + "proc-macro2", + "quote", + "syn 2.0.117", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" +dependencies = [ + "serde", + "winnow 0.7.15", +] + +[[package]] +name = "alloy-sol-types" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64612d29379782a5dde6f4b6570d9c756d734d760c0c94c254d361e678a6591f" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "serde", +] + +[[package]] +name = "alloy-trie" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "983d99aa81f586cef9dae38443245e585840fcf0fc58b09aee0b1f27aed1d500" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec", + "derive_more 2.1.1", + "nybbles 0.3.4", + "smallvec", + "tracing", +] + +[[package]] +name = "alloy-trie" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "derive_more 2.1.1", + "nybbles 0.4.8", + "serde", + "smallvec", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "alloy-tx-macros" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7a6b9edd1f6ea02b4f131d76b1aaf01b6ae53fbf91edec97a4e18ef60c1a0a" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arc-swap" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" +dependencies = [ + "rustversion", +] + +[[package]] +name = "archery" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a8da9bc4c4053ee067669762bcaeea6e241841295a2b6c948312dad6ef4cc02" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-poly", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "aurora-engine-modexp" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518bc5745a6264b5fd7b09dffb9667e400ee9e2bbe18555fac75e1fe9afa0df9" +dependencies = [ + "hex", + "num", +] + +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core 0.4.5", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "itoa", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core 0.5.6", + "axum-macros", + "base64 0.22.1", + "bytes", + "form_urlencoded", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "itoa", + "matchit 0.8.4", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-extra" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96" +dependencies = [ + "axum 0.8.8", + "axum-core 0.5.6", + "bytes", + "futures-util", + "headers 0.4.1", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "serde_core", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base256emoji" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" +dependencies = [ + "const-str", + "match-lookup", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "serde", + "unty", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "serde", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if 1.0.4", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bls" +version = "0.2.0" +dependencies = [ + "alloy-primitives", + "blst", + "ethereum_hashing 0.8.0", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "fixed_bytes 0.1.0", + "hex", + "rand 0.9.2", + "safe_arith", + "serde", + "tree_hash 0.12.1", + "zeroize", +] + +[[package]] +name = "bls" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "alloy-primitives", + "blst", + "ethereum_hashing 0.8.0", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "fixed_bytes 0.1.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "hex", + "rand 0.9.2", + "safe_arith", + "serde", + "tree_hash 0.12.1", + "zeroize", +] + +[[package]] +name = "bls12_381" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c196a77437e7cc2fb515ce413a6401291578b5afc8ecb29a3c7ab957f05941" +dependencies = [ + "ff 0.12.1", + "group 0.12.1", + "pairing 0.22.0", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "git+https://github.com/lambdaclass/bls12_381?branch=expose-fp-struct#219174187bd78154cec35b0809799fc2c991a579" +dependencies = [ + "digest 0.10.7", + "ff 0.13.1", + "group 0.13.0", + "pairing 0.23.0", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "blst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "blstrs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8a8ed6fefbeef4a8c7b460e4110e12c5e22a5b7cf32621aae6ad650c4dcf29" +dependencies = [ + "blst", + "byte-slice-cast", + "ff 0.13.1", + "group 0.13.0", + "pairing 0.23.0", + "rand_core 0.6.4", + "serde", + "subtle", +] + +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "builder_client" +version = "0.1.0" +dependencies = [ + "bls 0.2.0", + "context_deserialize", + "eth2", + "ethereum_ssz 0.10.1", + "lighthouse_version", + "reqwest", + "sensitive_url", + "serde", + "serde_json", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "bytecheck" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0caa33a2c0edca0419d15ac723dff03f1956f7978329b1e3b5fdaaaed9d3ca8b" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "rancor", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89385e82b5d1821d2219e0b095efa2cc1f246cbf99080f3be46a1a85c0d392d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] + +[[package]] +name = "c-kzg" +version = "2.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.27", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "compare_fields" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f45d0b4d61b582303179fb7a1a142bc9d647b7583db3b0d5f25a21d286fab9" +dependencies = [ + "compare_fields_derive", + "itertools 0.14.0", +] + +[[package]] +name = "compare_fields_derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ff1dbbda10d495b2c92749c002b2025e0be98f42d1741ecc9ff820d2f04dce" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "concat-kdf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d72c1252426a83be2092dd5884a5f6e3b8e7180f6891b6263d2c21b92ec8816" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "const-hex" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" +dependencies = [ + "cfg-if 1.0.4", + "cpufeatures", + "proptest", + "serde_core", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "context_deserialize" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c523eea4af094b5970c321f4604abc42c5549d3cbae332e98325403fbbdbf70" +dependencies = [ + "context_deserialize_derive", + "serde", +] + +[[package]] +name = "context_deserialize_derive" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7bf98c48ffa511b14bb3c76202c24a8742cea1efa9570391c5d41373419a09" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if 1.0.4", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" +dependencies = [ + "cfg-if 0.1.10", + "crossbeam-channel 0.4.4", + "crossbeam-deque 0.7.4", + "crossbeam-epoch 0.8.2", + "crossbeam-queue 0.2.3", + "crossbeam-utils 0.7.2", +] + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel 0.5.15", + "crossbeam-deque 0.8.6", + "crossbeam-epoch 0.9.18", + "crossbeam-queue 0.3.12", + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20ff29ded3204c5106278a81a38f4b482636ed4fa1e6cfbeef193291beb29ed" +dependencies = [ + "crossbeam-epoch 0.8.2", + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch 0.9.18", + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "crossbeam-utils 0.7.2", + "lazy_static", + "maybe-uninit", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +dependencies = [ + "cfg-if 0.1.10", + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if 1.0.4", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version 0.4.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "serde", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "data-encoding-macro" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" +dependencies = [ + "data-encoding", + "syn 2.0.117", +] + +[[package]] +name = "datatest-stable" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833306ca7eec4d95844e65f0d7502db43888c5c1006c6c517e8cf51a27d15431" +dependencies = [ + "camino", + "fancy-regex", + "libtest-mimic", + "walkdir", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive-where" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl 2.1.1", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "convert_case 0.6.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "unicode-xid", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case 0.10.0", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.117", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "eip4844" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ab45fc63db6bbe5c3eb7c79303b2aff7ee529c991b2111c46879d1ea38407e" +dependencies = [ + "ekzg-bls12-381", + "ekzg-maybe-rayon", + "ekzg-polynomial", + "ekzg-serialization", + "ekzg-single-open", + "ekzg-trusted-setup", + "hex", + "itertools 0.14.0", + "serde", + "serde_json", + "sha2", +] + +[[package]] +name = "eip_3076" +version = "0.1.0" +dependencies = [ + "bls 0.2.0", + "ethereum_serde_utils", + "fixed_bytes 0.1.0", + "serde", + "types 0.2.1", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "ekzg-bls12-381" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c599a59deba6188afd9f783507e4d89efc997f0fa340a758f0d0992b322416" +dependencies = [ + "blst", + "blstrs", + "ff 0.13.1", + "group 0.13.0", + "pairing 0.23.0", + "subtle", +] + +[[package]] +name = "ekzg-erasure-codes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8474a41a30ddd2b651798b1aa9ce92011207c3667186fe9044184683250109e7" +dependencies = [ + "ekzg-bls12-381", + "ekzg-polynomial", +] + +[[package]] +name = "ekzg-maybe-rayon" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf94d1385185c1f7caef4973be49702c7d9ffdeaf832d126dbb9ed6efe09d40" + +[[package]] +name = "ekzg-multi-open" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d37456a32cf79bdbddd6685a2adec73210e2d60332370bc0e9a502b6d93beb" +dependencies = [ + "ekzg-bls12-381", + "ekzg-maybe-rayon", + "ekzg-polynomial", + "sha2", +] + +[[package]] +name = "ekzg-polynomial" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704751bac85af4754bb8a14457ef24d820738062d0b6f3763534d0980b1a1e81" +dependencies = [ + "ekzg-bls12-381", + "ekzg-maybe-rayon", +] + +[[package]] +name = "ekzg-serialization" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb983d9f75b2804c00246def8d52c01cf05f70c22593b8d314fbcf0cf89042b" +dependencies = [ + "ekzg-bls12-381", + "hex", +] + +[[package]] +name = "ekzg-single-open" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799d5806d51e1453fa0f528d6acf4127e2a89e98312c826151ebc24ee3448ec3" +dependencies = [ + "ekzg-bls12-381", + "ekzg-polynomial", + "itertools 0.14.0", +] + +[[package]] +name = "ekzg-trusted-setup" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85314d56718dc2c6dd77c3b3630f1839defcb6f47d9c20195608a0f7976095ab" +dependencies = [ + "ekzg-bls12-381", + "ekzg-serialization", + "hex", + "serde", + "serde_json", +] + +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff 0.13.1", + "generic-array", + "group 0.13.0", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if 1.0.4", +] + +[[package]] +name = "enr" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "851bd664a3d3a3c175cff92b2f0df02df3c541b4895d0ae307611827aae46152" +dependencies = [ + "alloy-rlp", + "base64 0.22.1", + "bytes", + "ed25519-dalek", + "hex", + "k256", + "log", + "rand 0.8.5", + "serde", + "sha3", + "zeroize", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "envy" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" +dependencies = [ + "serde", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "ere-io" +version = "0.3.0" +source = "git+https://github.com/eth-act/ere?tag=v0.3.0#ffc0e230cf4387fb22eb755f0fb323b38294520f" +dependencies = [ + "bincode 2.0.1", + "rkyv", + "serde", +] + +[[package]] +name = "ere-platform-trait" +version = "0.3.0" +source = "git+https://github.com/eth-act/ere?tag=v0.3.0#ffc0e230cf4387fb22eb755f0fb323b38294520f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "ere-server" +version = "0.3.0" +source = "git+https://github.com/eth-act/ere?tag=v0.3.0#ffc0e230cf4387fb22eb755f0fb323b38294520f" +dependencies = [ + "anyhow", + "bincode 2.0.1", + "ere-zkvm-interface", + "prost", + "serde", + "thiserror 2.0.18", + "tokio", + "twirp", +] + +[[package]] +name = "ere-zkvm-interface" +version = "0.3.0" +source = "git+https://github.com/eth-act/ere?tag=v0.3.0#ffc0e230cf4387fb22eb755f0fb323b38294520f" +dependencies = [ + "anyhow", + "auto_impl", + "bincode 2.0.1", + "clap", + "indexmap 2.13.0", + "serde", + "strum", + "thiserror 2.0.18", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "escape8259" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" + +[[package]] +name = "eth2" +version = "0.1.0" +dependencies = [ + "bls 0.2.0", + "context_deserialize", + "educe", + "eip_3076", + "enr", + "eth2_keystore", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "futures", + "futures-util", + "libp2p-identity", + "mediatype", + "multiaddr", + "pretty_reqwest_error", + "proto_array", + "reqwest", + "reqwest-eventsource", + "sensitive_url", + "serde", + "serde_json", + "ssz_types 0.14.0", + "superstruct", + "types 0.2.1", + "zeroize", +] + +[[package]] +name = "eth2_interop_keypairs" +version = "0.2.0" +dependencies = [ + "bls 0.2.0", + "ethereum_hashing 0.8.0", + "hex", + "num-bigint 0.4.6", + "serde", + "yaml_serde", +] + +[[package]] +name = "eth2_interop_keypairs" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "bls 0.2.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "ethereum_hashing 0.8.0", + "hex", + "num-bigint 0.4.6", + "serde", + "serde_yaml", +] + +[[package]] +name = "eth2_key_derivation" +version = "0.1.0" +dependencies = [ + "bls 0.2.0", + "num-bigint-dig", + "ring", + "sha2", + "zeroize", +] + +[[package]] +name = "eth2_keystore" +version = "0.1.0" +dependencies = [ + "aes", + "bls 0.2.0", + "cipher", + "ctr", + "eth2_key_derivation", + "hex", + "hmac", + "pbkdf2", + "rand 0.9.2", + "scrypt", + "serde", + "serde_json", + "serde_repr", + "sha2", + "unicode-normalization", + "uuid", + "zeroize", +] + +[[package]] +name = "ethbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c321610643004cf908ec0f5f2aa0d8f1f8e14b540562a2887a1111ff1ecbf7b" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab15ed80916029f878e0267c3a9f92b67df55e79af370bf66199059ae2b4ee3" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types 0.13.1", + "uint 0.10.0", +] + +[[package]] +name = "ethereum_hashing" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c853bd72c9e5787f8aafc3df2907c2ed03cff3150c3acd94e2e53a98ab70a8ab" +dependencies = [ + "cpufeatures", + "ring", + "sha2", +] + +[[package]] +name = "ethereum_hashing" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa93f58bb1eb3d1e556e4f408ef1dac130bad01ac37db4e7ade45de40d1c86a" +dependencies = [ + "cpufeatures", + "ring", + "sha2", +] + +[[package]] +name = "ethereum_serde_utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dc1355dbb41fbbd34ec28d4fb2a57d9a70c67ac3c19f6a5ca4d4a176b9e997a" +dependencies = [ + "alloy-primitives", + "hex", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "ethereum_ssz" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dcddb2554d19cde19b099fadddde576929d7a4d0c1cd3512d1fd95cf174375c" +dependencies = [ + "alloy-primitives", + "ethereum_serde_utils", + "itertools 0.13.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] + +[[package]] +name = "ethereum_ssz" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2128a84f7a3850d54ee343334e3392cca61f9f6aa9441eec481b9394b43c238b" +dependencies = [ + "alloy-primitives", + "context_deserialize", + "ethereum_serde_utils", + "itertools 0.14.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] + +[[package]] +name = "ethereum_ssz_derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a657b6b3b7e153637dc6bdc6566ad9279d9ee11a15b12cfb24a2e04360637e9f" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ethereum_ssz_derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd596f91cff004fc8d02be44c21c0f9b93140a04b66027ae052f5f8e05b48eba" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ethrex-blockchain" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "bytes", + "ethrex-common", + "ethrex-crypto", + "ethrex-metrics", + "ethrex-rlp", + "ethrex-storage", + "ethrex-trie", + "ethrex-vm", + "hex", + "rustc-hash", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "ethrex-common" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "bytes", + "crc32fast", + "ethereum-types", + "ethrex-crypto", + "ethrex-rlp", + "ethrex-trie", + "hex", + "hex-literal", + "k256", + "kzg-rs", + "lazy_static", + "libc", + "once_cell", + "rayon", + "rkyv", + "rustc-hash", + "serde", + "serde_json", + "sha2", + "sha3", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "url", +] + +[[package]] +name = "ethrex-crypto" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "c-kzg", + "kzg-rs", + "thiserror 2.0.18", + "tiny-keccak", +] + +[[package]] +name = "ethrex-l2-common" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "bytes", + "ethereum-types", + "ethrex-common", + "ethrex-crypto", + "ethrex-rlp", + "ethrex-storage", + "ethrex-trie", + "ethrex-vm", + "hex", + "k256", + "lambdaworks-crypto", + "rkyv", + "serde", + "serde_with", + "sha3", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "ethrex-levm" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff 0.5.0", + "bitvec", + "bls12_381 0.8.0", + "bytes", + "datatest-stable", + "derive_more 1.0.0", + "ethrex-common", + "ethrex-crypto", + "ethrex-rlp", + "k256", + "lambdaworks-math", + "lazy_static", + "malachite", + "p256", + "ripemd", + "rustc-hash", + "serde", + "serde_json", + "sha2", + "sha3", + "strum", + "thiserror 2.0.18", + "walkdir", +] + +[[package]] +name = "ethrex-metrics" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "axum 0.8.8", + "ethrex-common", + "prometheus 0.13.4", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "ethrex-p2p" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "aes", + "async-trait", + "bytes", + "concat-kdf", + "crossbeam 0.8.4", + "ctr", + "ethereum-types", + "ethrex-blockchain", + "ethrex-common", + "ethrex-crypto", + "ethrex-rlp", + "ethrex-storage", + "ethrex-threadpool", + "ethrex-trie", + "futures", + "hex", + "hmac", + "indexmap 2.13.0", + "lazy_static", + "prometheus 0.14.0", + "rand 0.8.5", + "rayon", + "rustc-hash", + "secp256k1", + "serde", + "serde_json", + "sha2", + "snap", + "spawned-concurrency", + "spawned-rt", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "ethrex-rlp" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "bytes", + "ethereum-types", + "hex", + "lazy_static", + "snap", + "thiserror 2.0.18", + "tinyvec", +] + +[[package]] +name = "ethrex-rpc" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "axum 0.8.8", + "axum-extra", + "bytes", + "envy", + "ethereum-types", + "ethrex-blockchain", + "ethrex-common", + "ethrex-crypto", + "ethrex-metrics", + "ethrex-p2p", + "ethrex-rlp", + "ethrex-storage", + "ethrex-trie", + "ethrex-vm", + "hex", + "hex-literal", + "jsonwebtoken", + "rand 0.8.5", + "reqwest", + "secp256k1", + "serde", + "serde_json", + "sha2", + "spawned-concurrency", + "spawned-rt", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tower-http", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "ethrex-storage" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "ethereum-types", + "ethrex-common", + "ethrex-crypto", + "ethrex-rlp", + "ethrex-trie", + "hex", + "lru 0.16.3", + "qfilter", + "rayon", + "rustc-hash", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "ethrex-threadpool" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "crossbeam 0.8.4", +] + +[[package]] +name = "ethrex-trie" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "anyhow", + "bytes", + "crossbeam 0.8.4", + "digest 0.10.7", + "ethereum-types", + "ethrex-crypto", + "ethrex-rlp", + "ethrex-threadpool", + "hex", + "lazy_static", + "rkyv", + "rustc-hash", + "serde", + "serde_json", + "smallvec", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "ethrex-vm" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "bincode 1.3.3", + "bytes", + "derive_more 1.0.0", + "dyn-clone", + "ethereum-types", + "ethrex-common", + "ethrex-crypto", + "ethrex-levm", + "ethrex-rlp", + "ethrex-trie", + "lazy_static", + "rkyv", + "serde", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom", + "pin-project-lite", +] + +[[package]] +name = "execution_layer" +version = "0.1.0" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "arc-swap", + "async-stream", + "async-trait", + "bls 0.2.0", + "builder_client", + "bytes", + "eth2", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "fixed_bytes 0.1.0", + "fork_choice", + "futures", + "hash-db", + "hash256-std-hasher", + "hex", + "jsonwebtoken", + "keccak-hash", + "kzg 0.1.0", + "lighthouse_version", + "logging", + "lru 0.12.5", + "metrics 0.2.0", + "parking_lot", + "pretty_reqwest_error", + "rand 0.9.2", + "reqwest", + "reqwest-eventsource", + "sensitive_url", + "serde", + "serde_json", + "sha2", + "slot_clock", + "ssz_types 0.14.0", + "state_processing", + "strum", + "superstruct", + "task_executor", + "tempfile", + "tokio", + "tokio-stream", + "tracing", + "tree_hash 0.12.1", + "tree_hash_derive 0.12.1", + "triehash", + "typenum", + "types 0.2.1", + "warp", + "zeroize", +] + +[[package]] +name = "fancy-regex" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "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 = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "bitvec", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "byteorder", + "ff_derive", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "ff_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f10d12652036b0e99197587c6ba87a8fc3031986499973c030d8b44fcc151b60" +dependencies = [ + "addchain", + "num-bigint 0.3.3", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixed-map" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ed19add84e8cb9e8cc5f7074de0324247149ffef0b851e215fb0edc50c229b" +dependencies = [ + "fixed-map-derive", +] + +[[package]] +name = "fixed-map-derive" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dc7a9cb3326bafb80642c5ce99b39a2c0702d4bfa8ee8a3e773791a6cbe2407" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fixed_bytes" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "safe_arith", +] + +[[package]] +name = "fixed_bytes" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "alloy-primitives", + "safe_arith", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "fork_choice" +version = "0.1.0" +dependencies = [ + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "fixed_bytes 0.1.0", + "logging", + "metrics 0.2.0", + "proto_array", + "state_processing", + "superstruct", + "tracing", + "types 0.2.1", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if 1.0.4", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if 1.0.4", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if 1.0.4", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "memuse", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.1", + "rand 0.8.5", + "rand_core 0.6.4", + "rand_xorshift 0.3.0", + "subtle", +] + +[[package]] +name = "guest" +version = "0.5.0" +source = "git+https://github.com/eth-act/ere-guests?tag=v0.6.0#64c94bb3da631101a6cb2f276c89392cb7c3426f" +dependencies = [ + "ere-io", + "ere-platform-trait", + "sha2", +] + +[[package]] +name = "guest_program" +version = "9.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?tag=v9.0.0#e88175e2d49f1192cc9f2fdeae6fde1392d0759d" +dependencies = [ + "bincode 1.3.3", + "bytes", + "ethrex-blockchain", + "ethrex-common", + "ethrex-crypto", + "ethrex-l2-common", + "ethrex-rlp", + "ethrex-storage", + "ethrex-trie", + "ethrex-vm", + "hex", + "rkyv", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "halo2" +version = "0.1.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a23c779b38253fe1538102da44ad5bd5378495a61d2c4ee18d64eaa61ae5995" +dependencies = [ + "halo2_proofs", +] + +[[package]] +name = "halo2_proofs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e925780549adee8364c7f2b685c753f6f3df23bde520c67416e93bf615933760" +dependencies = [ + "blake2b_simd", + "ff 0.12.1", + "group 0.12.1", + "pasta_curves 0.4.1", + "rand_core 0.6.4", + "rayon", +] + +[[package]] +name = "hash-db" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", + "serde", + "serde_core", +] + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core 0.2.0", + "http 0.2.12", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +dependencies = [ + "base64 0.22.1", + "bytes", + "headers-core 0.3.0", + "http 1.4.0", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.12", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http 1.4.0", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper 1.8.1", + "hyper-util", + "rustls 0.23.37", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.3", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-codec" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ed8ad1f3877f7e775b8cbf30ed1bd3209a95401817f19a0eb4402d13f8cf90" +dependencies = [ + "rlp 0.6.1", +] + +[[package]] +name = "impl-serde" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "int_to_bytes" +version = "0.2.0" +dependencies = [ + "bytes", +] + +[[package]] +name = "int_to_bytes" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "bytes", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e7418f59cc01c88316161279a7f665217ae316b388e58a0d10e29f54f1e5eb" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "jubjub" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a575df5f985fe1cd5b2b05664ff6accfc46559032b954529fd225a2168d27b0f" +dependencies = [ + "bitvec", + "bls12_381 0.7.1", + "ff 0.12.1", + "group 0.12.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if 1.0.4", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "sha2", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-asm" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + +[[package]] +name = "keccak-hash" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b286e6b663fb926e1eeb68528e69cb70ed46c6d65871a21b2215ae8154c6d3c" +dependencies = [ + "primitive-types 0.12.2", + "tiny-keccak", +] + +[[package]] +name = "kzg" +version = "0.1.0" +dependencies = [ + "educe", + "ethereum_hashing 0.8.0", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "hex", + "rayon", + "rust_eth_kzg", + "serde", + "serde_json", + "tracing", + "tree_hash 0.12.1", +] + +[[package]] +name = "kzg" +version = "0.1.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "educe", + "ethereum_hashing 0.8.0", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "hex", + "rayon", + "rust_eth_kzg", + "serde", + "serde_json", + "tracing", + "tree_hash 0.12.1", +] + +[[package]] +name = "kzg-rs" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8b4f55c3dedcfaa8668de1dfc8469e7a32d441c28edf225ed1f566fb32977d" +dependencies = [ + "ff 0.13.1", + "hex", + "serde_arrays", + "sha2", + "sp1_bls12_381", + "spin", +] + +[[package]] +name = "lambdaworks-crypto" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b1a1c1102a5a7fbbda117b79fb3a01e033459c738a3c1642269603484fd1c1" +dependencies = [ + "lambdaworks-math", + "rand 0.8.5", + "rand_chacha 0.3.1", + "serde", + "sha2", + "sha3", +] + +[[package]] +name = "lambdaworks-math" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "018a95aa873eb49896a858dee0d925c33f3978d073c64b08dd4f2c9b35a017c6" +dependencies = [ + "getrandom 0.2.17", + "num-bigint 0.4.6", + "num-traits", + "rand 0.8.5", + "rayon", + "serde", + "serde_json", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libp2p-identity" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3" +dependencies = [ + "bs58", + "hkdf", + "multihash", + "sha2", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "libtest-mimic" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e6ba06f0ade6e504aff834d7c34298e5155c6baca353cc6a4aaff2f9fd7f33" +dependencies = [ + "anstream", + "anstyle", + "clap", + "escape8259", +] + +[[package]] +name = "libyaml-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e126dda6f34391ab7b444f9922055facc83c07a910da3eb16f1e4d9c45dc777" + +[[package]] +name = "lighthouse_version" +version = "8.1.3" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "logging" +version = "0.2.0" +dependencies = [ + "chrono", + "logroller", + "metrics 0.2.0", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-appender", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "workspace_members", +] + +[[package]] +name = "logroller" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83db12bbf439ebe64c0b0e4402f435b6f866db498fc1ae17e1b5d1a01625e2be" +dependencies = [ + "chrono", + "flate2", + "regex", + "thiserror 1.0.69", +] + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "malachite" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec410515e231332b14cd986a475d1c3323bcfa4c7efc038bfa1d5b410b1c57e4" +dependencies = [ + "malachite-base", + "malachite-nz", + "malachite-q", +] + +[[package]] +name = "malachite-base" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c738d3789301e957a8f7519318fcbb1b92bb95863b28f6938ae5a05be6259f34" +dependencies = [ + "hashbrown 0.15.5", + "itertools 0.14.0", + "libm", + "ryu", +] + +[[package]] +name = "malachite-nz" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1707c9a1fa36ce21749b35972bfad17bbf34cf5a7c96897c0491da321e387d3b" +dependencies = [ + "itertools 0.14.0", + "libm", + "malachite-base", + "wide", +] + +[[package]] +name = "malachite-q" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d764801aa4e96bbb69b389dcd03b50075345131cd63ca2e380bca71cc37a3675" +dependencies = [ + "itertools 0.14.0", + "malachite-base", + "malachite-nz", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "match-lookup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "mediatype" +version = "0.19.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33746aadcb41349ec291e7f2f0a3aa6834d1d7c58066fb4b01f68efc4c4b7631" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memuse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d97bbf43eb4f088f8ca469930cde17fa036207c9a5e02ccc5107c4e8b17c964" + +[[package]] +name = "merkle_proof" +version = "0.2.0" +dependencies = [ + "alloy-primitives", + "ethereum_hashing 0.8.0", + "fixed_bytes 0.1.0", + "safe_arith", +] + +[[package]] +name = "merkle_proof" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "alloy-primitives", + "ethereum_hashing 0.8.0", + "fixed_bytes 0.1.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "safe_arith", +] + +[[package]] +name = "metastruct" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969a1be9bd80794bdf93b23ab552c2ec6f3e83b33164824553fd996cdad513b8" +dependencies = [ + "metastruct_macro", +] + +[[package]] +name = "metastruct_macro" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de9164f767d73a507c19205868c84da411dc7795f4bdabf497d3dd93cfef9930" +dependencies = [ + "darling 0.23.0", + "itertools 0.14.0", + "proc-macro2", + "quote", + "smallvec", + "syn 2.0.117", +] + +[[package]] +name = "metrics" +version = "0.2.0" +dependencies = [ + "prometheus 0.13.4", +] + +[[package]] +name = "metrics" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +dependencies = [ + "ahash", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" +dependencies = [ + "base64 0.22.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls", + "hyper-util", + "indexmap 2.13.0", + "ipnet", + "metrics 0.24.3", + "metrics-util", + "quanta", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-util" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" +dependencies = [ + "crossbeam-epoch 0.9.18", + "crossbeam-utils 0.8.21", + "hashbrown 0.15.5", + "metrics 0.24.3", + "quanta", + "rand 0.9.2", + "rand_xoshiro", + "sketches-ddsketch", +] + +[[package]] +name = "milhouse" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "259dd9da2ae5e0278b95da0b7ecef9c18c309d0a2d9e6db57ed33b9e8910c5e7" +dependencies = [ + "alloy-primitives", + "context_deserialize", + "educe", + "ethereum_hashing 0.8.0", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "itertools 0.13.0", + "parking_lot", + "rayon", + "serde", + "smallvec", + "tree_hash 0.12.1", + "triomphe", + "typenum", + "vec_map", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "mpt" +version = "0.1.0" +source = "git+https://github.com/eth-act/zkvm-ethereum-mpt.git?rev=a1e44638c49c4e16751a0b915593fce98ab6bdef#a1e44638c49c4e16751a0b915593fce98ab6bdef" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-trie 0.8.1", + "arrayvec", +] + +[[package]] +name = "multiaddr" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "libp2p-identity", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint", + "url", +] + +[[package]] +name = "multibase" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" +dependencies = [ + "base-x", + "base256emoji", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +dependencies = [ + "core2", + "unsigned-varint", +] + +[[package]] +name = "munge" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint 0.4.6", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint 0.4.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "nybbles" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" +dependencies = [ + "const-hex", + "smallvec", +] + +[[package]] +name = "nybbles" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d49ff0c0d00d4a502b39df9af3a525e1efeb14b9dabb5bb83335284c1309210" +dependencies = [ + "alloy-rlp", + "cfg-if 1.0.4", + "proptest", + "ruint", + "serde", + "smallvec", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "op-alloy-consensus" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736381a95471d23e267263cfcee9e1d96d30b9754a94a2819148f83379de8a86" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more 2.1.1", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "openssl" +version = "0.10.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +dependencies = [ + "bitflags", + "cfg-if 1.0.4", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p3-bn254-fr" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9abf208fbfe540d6e2a6caaa2a9a345b1c8cb23ffdcdfcc6987244525d4fc821" +dependencies = [ + "ff 0.13.1", + "num-bigint 0.4.6", + "p3-field", + "p3-poseidon2", + "p3-symmetric", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "p3-challenger" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b725b453bbb35117a1abf0ddfd900b0676063d6e4231e0fa6bb0d76018d8ad" +dependencies = [ + "p3-field", + "p3-maybe-rayon", + "p3-symmetric", + "p3-util", + "serde", + "tracing", +] + +[[package]] +name = "p3-dft" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56a1f81101bff744b7ebba7f4497e917a2c6716d6e62736e4a56e555a2d98cb7" +dependencies = [ + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "tracing", +] + +[[package]] +name = "p3-field" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36459d4acb03d08097d713f336c7393990bb489ab19920d4f68658c7a5c10968" +dependencies = [ + "itertools 0.12.1", + "num-bigint 0.4.6", + "num-traits", + "p3-util", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "p3-koala-bear" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1f52bcb6be38bdc8fa6b38b3434d4eedd511f361d4249fd798c6a5ef817b40" +dependencies = [ + "num-bigint 0.4.6", + "p3-field", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "p3-matrix" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e9cd136a4095a25c41a9edfdcce2dfae58ef01639317813bdbbd5b55c583" +dependencies = [ + "itertools 0.12.1", + "p3-field", + "p3-maybe-rayon", + "p3-util", + "rand 0.8.5", + "serde", + "tracing", +] + +[[package]] +name = "p3-maybe-rayon" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e524d47a49fb4265611303339c4ef970d892817b006cc330dad18afb91e411b1" + +[[package]] +name = "p3-mds" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f6cb8edcb276033d43769a3725570c340d2ed6f35c3cca4cddeee07718fa376" +dependencies = [ + "itertools 0.12.1", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-symmetric", + "p3-util", + "rand 0.8.5", +] + +[[package]] +name = "p3-poseidon2" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a26197df2097b98ab7038d59a01e1fe1a0f545e7e04aa9436b2454b1836654f" +dependencies = [ + "gcd", + "p3-field", + "p3-mds", + "p3-symmetric", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "p3-symmetric" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1d3b5202096bca57cde912fbbb9cbaedaf5ac7c42a924c7166b98709d64d21" +dependencies = [ + "itertools 0.12.1", + "p3-field", + "serde", +] + +[[package]] +name = "p3-util" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec5f0388aa6d935ca3a17444086120f393f0b2f0816010b5ff95998c1c4095e3" +dependencies = [ + "serde", +] + +[[package]] +name = "pairing" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" +dependencies = [ + "group 0.12.1", +] + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group 0.13.0", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if 1.0.4", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pasta_curves" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc65faf8e7313b4b1fbaa9f7ca917a0eed499a9663be71477f87993604341d8" +dependencies = [ + "blake2b_simd", + "ff 0.12.1", + "group 0.12.1", + "lazy_static", + "rand 0.8.5", + "static_assertions", + "subtle", +] + +[[package]] +name = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff 0.13.1", + "group 0.13.0", + "lazy_static", + "rand 0.8.5", + "static_assertions", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_reqwest_error" +version = "0.1.0" +dependencies = [ + "reqwest", + "sensitive_url", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec 0.6.0", + "uint 0.9.5", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "impl-codec 0.7.1", + "impl-rlp", + "impl-serde", + "uint 0.10.0", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.8+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "procfs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" +dependencies = [ + "bitflags", + "hex", + "lazy_static", + "procfs-core", + "rustix 0.38.44", +] + +[[package]] +name = "procfs-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" +dependencies = [ + "bitflags", + "hex", +] + +[[package]] +name = "prometheus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +dependencies = [ + "cfg-if 1.0.4", + "fnv", + "lazy_static", + "libc", + "memchr", + "parking_lot", + "procfs", + "protobuf 2.28.0", + "thiserror 1.0.69", +] + +[[package]] +name = "prometheus" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" +dependencies = [ + "cfg-if 1.0.4", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "protobuf 3.7.2", + "thiserror 2.0.18", +] + +[[package]] +name = "proof_engine_zkboost_test" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum 0.7.9", + "bytes", + "ethereum_ssz 0.10.1", + "execution_layer", + "futures", + "metrics-exporter-prometheus", + "reqwest", + "sensitive_url", + "serde", + "serde_json", + "strum", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "tree_hash 0.12.1", + "types 0.2.1", + "url", + "zkboost-server", + "zkboost-types", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift 0.4.0", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proto_array" +version = "0.2.0" +dependencies = [ + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "fixed_bytes 0.1.0", + "safe_arith", + "serde", + "smallvec", + "superstruct", + "typenum", + "types 0.2.1", + "yaml_serde", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + +[[package]] +name = "protobuf" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror 1.0.69", +] + +[[package]] +name = "protobuf-support" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" +dependencies = [ + "thiserror 1.0.69", +] + +[[package]] +name = "ptr_meta" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "qfilter" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "746341cd2357c9a4df2d951522b4a8dd1ef553e543119899ad7bf87e938c8fbe" +dependencies = [ + "xxhash-rust", +] + +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils 0.8.21", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.37", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls 0.23.37", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.3", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rancor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a063ea72381527c2a0561da9c80000ef822bdd7c3241b1cc1b12100e3df081ee" +dependencies = [ + "ptr_meta", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", + "serde", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", + "serde", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rapidhash" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +dependencies = [ + "rustversion", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque 0.8.6", + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rend" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadadef317c2f20755a64d7fdc48f9e7178ee6b0e1f7fce33fa60f1d68a276e6" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.37", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.4", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "reqwest-eventsource" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde" +dependencies = [ + "eventsource-stream", + "futures-core", + "futures-timer", + "mime", + "nom", + "pin-project-lite", + "reqwest", + "thiserror 1.0.69", +] + +[[package]] +name = "reth-chainspec" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-genesis", + "alloy-primitives", + "alloy-trie 0.9.5", + "auto_impl", + "derive_more 2.1.1", + "reth-ethereum-forks", + "reth-network-peers", + "reth-primitives-traits", + "serde_json", +] + +[[package]] +name = "reth-codecs" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-trie 0.9.5", + "bytes", + "modular-bitfield", + "op-alloy-consensus", + "reth-codecs-derive", + "reth-zstd-compressors", + "serde", +] + +[[package]] +name = "reth-codecs-derive" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "reth-consensus" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "auto_impl", + "reth-execution-types", + "reth-primitives-traits", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-consensus-common" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "reth-chainspec", + "reth-consensus", + "reth-primitives-traits", +] + +[[package]] +name = "reth-db-models" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "reth-primitives-traits", +] + +[[package]] +name = "reth-errors" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "reth-consensus", + "reth-execution-errors", + "reth-storage-errors", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-ethereum-consensus" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "reth-chainspec", + "reth-consensus", + "reth-consensus-common", + "reth-execution-types", + "reth-primitives-traits", + "tracing", +] + +[[package]] +name = "reth-ethereum-forks" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-eip2124", + "alloy-hardforks", + "alloy-primitives", + "auto_impl", + "once_cell", +] + +[[package]] +name = "reth-ethereum-primitives" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-serde", + "reth-codecs", + "reth-primitives-traits", + "serde", + "serde_with", +] + +[[package]] +name = "reth-evm" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "auto_impl", + "derive_more 2.1.1", + "futures-util", + "reth-execution-errors", + "reth-execution-types", + "reth-primitives-traits", + "reth-storage-api", + "reth-storage-errors", + "reth-trie-common", + "revm", +] + +[[package]] +name = "reth-evm-ethereum" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "alloy-rpc-types-engine", + "reth-chainspec", + "reth-ethereum-forks", + "reth-ethereum-primitives", + "reth-evm", + "reth-execution-types", + "reth-primitives-traits", + "reth-storage-errors", + "revm", +] + +[[package]] +name = "reth-execution-errors" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-evm", + "alloy-primitives", + "alloy-rlp", + "nybbles 0.4.8", + "reth-storage-errors", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-execution-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "derive_more 2.1.1", + "reth-ethereum-primitives", + "reth-primitives-traits", + "reth-trie-common", + "revm", +] + +[[package]] +name = "reth-network-peers" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "serde_with", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "reth-payload-validator" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-rpc-types-engine", + "reth-primitives-traits", +] + +[[package]] +name = "reth-primitives-traits" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-trie 0.9.5", + "auto_impl", + "bytes", + "derive_more 2.1.1", + "once_cell", + "op-alloy-consensus", + "reth-codecs", + "revm-bytecode", + "revm-primitives", + "revm-state", + "secp256k1", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-prune-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "derive_more 2.1.1", + "strum", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-revm" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "reth-primitives-traits", + "reth-storage-api", + "reth-storage-errors", + "revm", +] + +[[package]] +name = "reth-stages-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "reth-trie-common", +] + +[[package]] +name = "reth-stateless" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-debug", + "alloy-trie 0.9.5", + "itertools 0.14.0", + "k256", + "reth-chainspec", + "reth-consensus", + "reth-errors", + "reth-ethereum-consensus", + "reth-ethereum-primitives", + "reth-evm", + "reth-primitives-traits", + "reth-revm", + "reth-trie-common", + "reth-trie-sparse", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-static-file-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "derive_more 2.1.1", + "fixed-map", + "serde", + "strum", +] + +[[package]] +name = "reth-storage-api" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rpc-types-engine", + "auto_impl", + "reth-chainspec", + "reth-db-models", + "reth-ethereum-primitives", + "reth-execution-types", + "reth-primitives-traits", + "reth-prune-types", + "reth-stages-types", + "reth-storage-errors", + "reth-trie-common", + "revm-database", +] + +[[package]] +name = "reth-storage-errors" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "derive_more 2.1.1", + "reth-primitives-traits", + "reth-prune-types", + "reth-static-file-types", + "revm-database-interface", + "revm-state", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-trie-common" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "alloy-trie 0.9.5", + "derive_more 2.1.1", + "itertools 0.14.0", + "nybbles 0.4.8", + "reth-primitives-traits", + "revm-database", +] + +[[package]] +name = "reth-trie-sparse" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-trie 0.9.5", + "auto_impl", + "reth-execution-errors", + "reth-primitives-traits", + "reth-trie-common", + "smallvec", + "tracing", +] + +[[package]] +name = "reth-zstd-compressors" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "zstd", +] + +[[package]] +name = "revm" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2aabdebaa535b3575231a88d72b642897ae8106cf6b0d12eafc6bfdf50abfc7" +dependencies = [ + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database", + "revm-database-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-bytecode" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d1e5c1eaa44d39d537f668bc5c3409dc01e5c8be954da6c83370bbdf006457" +dependencies = [ + "bitvec", + "phf", + "revm-primitives", + "serde", +] + +[[package]] +name = "revm-context" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "892ff3e6a566cf8d72ffb627fdced3becebbd9ba64089c25975b9b028af326a5" +dependencies = [ + "bitvec", + "cfg-if 1.0.4", + "derive-where", + "revm-bytecode", + "revm-context-interface", + "revm-database-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-context-interface" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f61cc6d23678c4840af895b19f8acfbbd546142ec8028b6526c53cc1c16c98" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "auto_impl", + "either", + "revm-database-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-database" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "529528d0b05fe646be86223032c3e77aa8b05caa2a35447d538c55965956a511" +dependencies = [ + "revm-bytecode", + "revm-database-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-database-interface" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7bf93ac5b91347c057610c0d96e923db8c62807e03f036762d03e981feddc1d" +dependencies = [ + "auto_impl", + "either", + "revm-primitives", + "revm-state", + "thiserror 2.0.18", +] + +[[package]] +name = "revm-handler" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cd0e43e815a85eded249df886c4badec869195e70cdd808a13cfca2794622d2" +dependencies = [ + "auto_impl", + "derive-where", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database-interface", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-inspector" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3ccad59db91ef93696536a0dbaf2f6f17cfe20d4d8843ae118edb7e97947ef" +dependencies = [ + "auto_impl", + "either", + "revm-context", + "revm-database-interface", + "revm-handler", + "revm-interpreter", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-interpreter" +version = "32.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11406408597bc249392d39295831c4b641b3a6f5c471a7c41104a7a1e3564c07" +dependencies = [ + "revm-bytecode", + "revm-context-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-precompile" +version = "32.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ec11f45deec71e4945e1809736bb20d454285f9167ab53c5159dae1deb603f" +dependencies = [ + "ark-bls12-381", + "ark-bn254", + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "arrayref", + "aurora-engine-modexp", + "cfg-if 1.0.4", + "k256", + "p256", + "revm-primitives", + "ripemd", + "sha2", +] + +[[package]] +name = "revm-primitives" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfb5ce6cf18b118932bcdb7da05cd9c250f2cb9f64131396b55f3fe3537c35" +dependencies = [ + "alloy-primitives", + "num_enum", + "once_cell", + "serde", +] + +[[package]] +name = "revm-state" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "311720d4f0f239b041375e7ddafdbd20032a33b7bae718562ea188e188ed9fd3" +dependencies = [ + "alloy-eip7928", + "bitflags", + "revm-bytecode", + "revm-primitives", + "serde", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if 1.0.4", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "rkyv" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a30e631b7f4a03dee9056b8ef6982e8ba371dd5bedb74d3ec86df4499132c70" +dependencies = [ + "bytecheck", + "bytes", + "hashbrown 0.16.1", + "indexmap 2.13.0", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8100bb34c0a1d0f907143db3149e6b4eea3c33b9ee8b189720168e818303986f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rlp" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rpds" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ef5140bcb576bfd6d56cd2de709a7d17851ac1f3805e67fe9d99e42a11821f" +dependencies = [ + "archery", +] + +[[package]] +name = "ruint" +version = "1.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types 0.12.2", + "proptest", + "rand 0.8.5", + "rand 0.9.2", + "rlp 0.5.2", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rust_eth_kzg" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1522b7a740cd7f5bc52ea49863618511c8de138dcdf3f8a80b15b3f764942a5b" +dependencies = [ + "eip4844", + "ekzg-bls12-381", + "ekzg-erasure-codes", + "ekzg-multi-open", + "ekzg-serialization", + "ekzg-trusted-setup", + "hex", + "serde", + "serde_json", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.27", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "aws-lc-rs", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.10", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "safe_arith" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b147bb6111014916d3ef9d4c85173124a8e12193a67f6176d67244afd558d6c1" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + +[[package]] +name = "sensitive_url" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7b0221fa9905eec4163dbf7660b1876cc95663af1deddc3e19ebe49167c58c" +dependencies = [ + "serde", + "url", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_arrays" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a16b99c5ea4fe3daccd14853ad260ec00ea043b2708d1fd1da3106dcd8d9df" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_spanned" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if 1.0.4", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if 1.0.4", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a" +dependencies = [ + "cc", + "cfg-if 1.0.4", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "sketches-ddsketch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slop-algebra" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "691beea96fd18d4881f9ca1cb4e58194dac6366f24956a2fdae00c8ee382a0c9" +dependencies = [ + "itertools 0.14.0", + "p3-field", + "serde", +] + +[[package]] +name = "slop-bn254" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1852499c245f7f3dec23408b4930b3ea7570ae914b9c31f12950ac539d85ee" +dependencies = [ + "ff 0.13.1", + "p3-bn254-fr", + "serde", + "slop-algebra", + "slop-challenger", + "slop-poseidon2", + "slop-symmetric", + "zkhash", +] + +[[package]] +name = "slop-challenger" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4349af93602f3876a3eda948a74d9d16d774c401dfe25f41a45ffd84f230bc1" +dependencies = [ + "futures", + "p3-challenger", + "serde", + "slop-algebra", + "slop-symmetric", +] + +[[package]] +name = "slop-koala-bear" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "574784c044d11cf9d8238dc18bce9b897bc34d0fb1daaceafd75ebb400084016" +dependencies = [ + "lazy_static", + "p3-koala-bear", + "serde", + "slop-algebra", + "slop-challenger", + "slop-poseidon2", + "slop-symmetric", +] + +[[package]] +name = "slop-poseidon2" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5af617970b63e8d7199204bc02996745b6c35c39f2b513a118c62c7b1a0b2f1b" +dependencies = [ + "p3-poseidon2", +] + +[[package]] +name = "slop-primitives" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58d82c53508f3ebff8acdabb5db2584f37686257a2549a17c977cf30cd9e24e6" +dependencies = [ + "slop-algebra", +] + +[[package]] +name = "slop-symmetric" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15acfa7f567ffa4f36de134492632a397c33fa6af2e48894e50978b52eeeb871" +dependencies = [ + "p3-symmetric", +] + +[[package]] +name = "slot_clock" +version = "0.2.0" +dependencies = [ + "metrics 0.2.0", + "parking_lot", + "types 0.2.1", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "sp1-lib" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "517e820776910468611149dda66791bdb700c1b7d68b96f0ea2e604f00ad8771" +dependencies = [ + "bincode 1.3.3", + "serde", + "sp1-primitives", +] + +[[package]] +name = "sp1-primitives" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f395525b4fc46d37136f45be264c81718a67f4409c14c547ff491a263e019e7" +dependencies = [ + "bincode 1.3.3", + "blake3", + "elf", + "hex", + "itertools 0.14.0", + "lazy_static", + "num-bigint 0.4.6", + "serde", + "sha2", + "slop-algebra", + "slop-bn254", + "slop-challenger", + "slop-koala-bear", + "slop-poseidon2", + "slop-primitives", + "slop-symmetric", +] + +[[package]] +name = "sp1_bls12_381" +version = "0.8.0-sp1-6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23e41cd36168cc2e51e5d3e35ff0c34b204d945769a65591a76286d04b51e43" +dependencies = [ + "cfg-if 1.0.4", + "ff 0.13.1", + "group 0.13.0", + "pairing 0.23.0", + "rand_core 0.6.4", + "sp1-lib", + "subtle", +] + +[[package]] +name = "sparsestate" +version = "0.1.0" +source = "git+https://github.com/eth-act/zkvm-ethereum-mpt.git?rev=a1e44638c49c4e16751a0b915593fce98ab6bdef#a1e44638c49c4e16751a0b915593fce98ab6bdef" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-trie 0.9.5", + "mpt", + "reth-errors", + "reth-revm", + "reth-stateless", + "reth-trie-common", +] + +[[package]] +name = "spawned-concurrency" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3ec6b3c003075f7d1c4c6475308243e853c9a78149b84b1f8b64d5bed49d49" +dependencies = [ + "futures", + "pin-project-lite", + "spawned-rt", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "spawned-rt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca60c56b1c60b94dd314edce5ea1a98b6037cca3b44d73828e647bad4dae46c" +dependencies = [ + "crossbeam 0.7.3", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "ssz_types" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b55bedc9a18ed2860a46d6beb4f4082416ee1d60be0cc364cebdcdddc7afd4" +dependencies = [ + "ethereum_serde_utils", + "ethereum_ssz 0.9.1", + "itertools 0.13.0", + "serde", + "serde_derive", + "smallvec", + "tree_hash 0.10.0", + "typenum", +] + +[[package]] +name = "ssz_types" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc20a89bab2dabeee65e9c9eb96892dc222c23254b401e1319b85efd852fa31" +dependencies = [ + "context_deserialize", + "educe", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "itertools 0.14.0", + "serde", + "serde_derive", + "smallvec", + "tree_hash 0.12.1", + "typenum", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "state_processing" +version = "0.2.0" +dependencies = [ + "bls 0.2.0", + "educe", + "ethereum_hashing 0.8.0", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "fixed_bytes 0.1.0", + "int_to_bytes 0.2.0", + "integer-sqrt", + "itertools 0.14.0", + "merkle_proof 0.2.0", + "metrics 0.2.0", + "milhouse", + "rand 0.9.2", + "rayon", + "safe_arith", + "smallvec", + "ssz_types 0.14.0", + "tracing", + "tree_hash 0.12.1", + "typenum", + "types 0.2.1", +] + +[[package]] +name = "stateless-validator-common" +version = "0.5.0" +source = "git+https://github.com/eth-act/ere-guests?tag=v0.6.0#64c94bb3da631101a6cb2f276c89392cb7c3426f" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "anyhow", + "ethereum_ssz 0.9.1", + "ethereum_ssz_derive 0.9.1", + "rkyv", + "serde", + "serde_with", + "sha2", + "ssz_types 0.11.0", + "tree_hash 0.10.0", + "tree_hash_derive 0.10.0", + "typenum", +] + +[[package]] +name = "stateless-validator-ethrex" +version = "0.5.0" +source = "git+https://github.com/eth-act/ere-guests?tag=v0.6.0#64c94bb3da631101a6cb2f276c89392cb7c3426f" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-rlp", + "anyhow", + "bytes", + "ere-io", + "ere-zkvm-interface", + "ethrex-common", + "ethrex-rlp", + "ethrex-rpc", + "ethrex-vm", + "guest", + "guest_program", + "reth-stateless", + "rkyv", + "stateless-validator-common", + "stateless-validator-reth", +] + +[[package]] +name = "stateless-validator-reth" +version = "0.5.0" +source = "git+https://github.com/eth-act/ere-guests?tag=v0.6.0#64c94bb3da631101a6cb2f276c89392cb7c3426f" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-engine", + "anyhow", + "ere-io", + "ere-zkvm-interface", + "ethereum_ssz 0.9.1", + "guest", + "once_cell", + "reth-chainspec", + "reth-ethereum-primitives", + "reth-evm-ethereum", + "reth-payload-validator", + "reth-primitives-traits", + "reth-stateless", + "serde", + "serde_with", + "sha2", + "sparsestate", + "ssz_types 0.11.0", + "stateless-validator-common", + "tree_hash 0.10.0", + "tree_hash_derive 0.10.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "superstruct" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae4a9ccd7882533c1f210e400763ec6ee64c390fc12248c238276281863719e" +dependencies = [ + "darling 0.23.0", + "itertools 0.14.0", + "proc-macro2", + "quote", + "smallvec", + "syn 2.0.117", +] + +[[package]] +name = "swap_or_not_shuffle" +version = "0.2.0" +dependencies = [ + "alloy-primitives", + "ethereum_hashing 0.8.0", + "fixed_bytes 0.1.0", +] + +[[package]] +name = "swap_or_not_shuffle" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "alloy-primitives", + "ethereum_hashing 0.8.0", + "fixed_bytes 0.1.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "task_executor" +version = "0.1.0" +dependencies = [ + "async-channel", + "futures", + "metrics 0.2.0", + "num_cpus", + "rayon", + "tokio", + "tracing", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "test_random_derive" +version = "0.2.0" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if 1.0.4", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.37", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "futures-util", + "pin-project-lite", + "slab", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.24.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01f2eadbbc6b377a847be05f60791ef1058d9f696ecb51d2c07fe911d8569d8e" +dependencies = [ + "indexmap 2.13.0", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.8+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime 1.1.0+spec-1.1.0", + "toml_parser", + "winnow 1.0.0", +] + +[[package]] +name = "toml_parser" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +dependencies = [ + "winnow 1.0.0", +] + +[[package]] +name = "toml_writer" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +dependencies = [ + "crossbeam-channel 0.5.15", + "thiserror 2.0.18", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "tree_hash" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee44f4cef85f88b4dea21c0b1f58320bdf35715cf56d840969487cff00613321" +dependencies = [ + "alloy-primitives", + "ethereum_hashing 0.7.0", + "ethereum_ssz 0.9.1", + "smallvec", + "typenum", +] + +[[package]] +name = "tree_hash" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fd51aa83d2eb83b04570808430808b5d24fdbf479a4d5ac5dee4a2e2dd2be4" +dependencies = [ + "alloy-primitives", + "ethereum_hashing 0.8.0", + "ethereum_ssz 0.10.1", + "smallvec", + "typenum", +] + +[[package]] +name = "tree_hash_derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bee2ea1551f90040ab0e34b6fb7f2fa3bad8acc925837ac654f2c78a13e3089" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tree_hash_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8840ad4d852e325d3afa7fde8a50b2412f89dce47d7eb291c0cc7f87cd040f38" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "triehash" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1631b201eb031b563d2e85ca18ec8092508e262a3196ce9bd10a67ec87b9f5c" +dependencies = [ + "hash-db", + "rlp 0.5.2", +] + +[[package]] +name = "triomphe" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" +dependencies = [ + "serde", + "stable_deref_trait", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "rand 0.9.2", + "sha1", + "thiserror 2.0.18", + "utf-8", +] + +[[package]] +name = "twirp" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c52cc4e4423b6b3e2e2659523c8c9e19af514a06422fe77a95d86f6bf3478a" +dependencies = [ + "anyhow", + "async-trait", + "axum 0.8.8", + "futures", + "http 1.4.0", + "http-body-util", + "hyper 1.8.1", + "prost", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tower", + "url", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "types" +version = "0.2.1" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "bls 0.2.0", + "compare_fields", + "context_deserialize", + "educe", + "eth2_interop_keypairs 0.2.0", + "ethereum_hashing 0.8.0", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "fixed_bytes 0.1.0", + "hex", + "int_to_bytes 0.2.0", + "itertools 0.14.0", + "kzg 0.1.0", + "maplit", + "merkle_proof 0.2.0", + "metastruct", + "milhouse", + "parking_lot", + "paste", + "rand 0.9.2", + "rand_xorshift 0.4.0", + "rayon", + "regex", + "rpds", + "safe_arith", + "serde", + "serde_json", + "smallvec", + "ssz_types 0.14.0", + "superstruct", + "swap_or_not_shuffle 0.2.0", + "tempfile", + "tracing", + "tree_hash 0.12.1", + "tree_hash_derive 0.12.1", + "typenum", + "yaml_serde", +] + +[[package]] +name = "types" +version = "0.2.1" +source = "git+https://github.com/sigp/lighthouse?branch=unstable#c7055b604f9958db410b2e42023763cb19dd7138" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "bls 0.2.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "compare_fields", + "context_deserialize", + "educe", + "eth2_interop_keypairs 0.2.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "ethereum_hashing 0.8.0", + "ethereum_serde_utils", + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "fixed_bytes 0.1.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "hex", + "int_to_bytes 0.2.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "itertools 0.14.0", + "kzg 0.1.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "maplit", + "merkle_proof 0.2.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "metastruct", + "milhouse", + "parking_lot", + "rand 0.9.2", + "rand_xorshift 0.4.0", + "rayon", + "regex", + "rpds", + "safe_arith", + "serde", + "serde_json", + "serde_yaml", + "smallvec", + "ssz_types 0.14.0", + "superstruct", + "swap_or_not_shuffle 0.2.0 (git+https://github.com/sigp/lighthouse?branch=unstable)", + "tempfile", + "test_random_derive", + "tracing", + "tree_hash 0.12.1", + "tree_hash_derive 0.12.1", + "typenum", +] + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da36089a805484bcccfffe0739803392c8298778a2d2f09febf76fac5ad9025b" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "unsigned-varint" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers 0.3.9", + "http 0.2.12", + "hyper 0.14.32", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project", + "rustls-pemfile", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls 0.25.0", + "tokio-util", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if 1.0.4", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +dependencies = [ + "cfg-if 1.0.4", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver 1.0.27", +] + +[[package]] +name = "web-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.13.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "workspace_members" +version = "0.1.0" +dependencies = [ + "cargo_metadata", + "quote", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "yaml_serde" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c7c1b1a6a7c8a6b2741a6c21a4f8918e51899b111cfa08d1288202656e3975" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "libyaml-rs", + "ryu", + "serde", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "serde", + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zkboost-server" +version = "0.1.0" +source = "git+https://github.com/eth-act/zkboost?branch=master#cbeae7023bf32b4441751c76fc5d2f400524153a" +dependencies = [ + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "anyhow", + "axum 0.8.8", + "bincode 1.3.3", + "bytes", + "clap", + "ere-server", + "ere-zkvm-interface", + "lru 0.12.5", + "metrics 0.24.3", + "metrics-exporter-prometheus", + "rand 0.9.2", + "reqwest", + "reth-ethereum-primitives", + "reth-stateless", + "serde", + "serde_json", + "sha2", + "stateless-validator-ethrex", + "stateless-validator-reth", + "strum", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "toml_edit 0.24.1+spec-1.1.0", + "tower-http", + "tracing", + "tracing-subscriber", + "url", + "zkboost-types", +] + +[[package]] +name = "zkboost-types" +version = "0.1.0" +source = "git+https://github.com/eth-act/zkboost?branch=master#cbeae7023bf32b4441751c76fc5d2f400524153a" +dependencies = [ + "ethereum_ssz 0.10.1", + "ethereum_ssz_derive 0.10.1", + "serde", + "serde_json", + "ssz_types 0.14.0", + "strum", + "superstruct", + "tree_hash 0.12.1", + "tree_hash_derive 0.12.1", + "types 0.2.1 (git+https://github.com/sigp/lighthouse?branch=unstable)", +] + +[[package]] +name = "zkhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4352d1081da6922701401cdd4cbf29a2723feb4cfabb5771f6fee8e9276da1c7" +dependencies = [ + "ark-ff 0.4.2", + "ark-std 0.4.0", + "bitvec", + "blake2", + "bls12_381 0.7.1", + "byteorder", + "cfg-if 1.0.4", + "group 0.12.1", + "group 0.13.0", + "halo2", + "hex", + "jubjub", + "lazy_static", + "pasta_curves 0.5.1", + "rand 0.8.5", + "serde", + "sha2", + "sha3", + "subtle", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/testing/proof_engine_zkboost/Cargo.toml b/testing/proof_engine_zkboost/Cargo.toml new file mode 100644 index 00000000000..ac7f950f53e --- /dev/null +++ b/testing/proof_engine_zkboost/Cargo.toml @@ -0,0 +1,38 @@ +[workspace] +members = ["."] +resolver = "2" + +[workspace.dependencies] +zkboost-server = { git = "https://github.com/eth-act/zkboost", branch = "master" } +zkboost-types = { git = "https://github.com/eth-act/zkboost", branch = "master" } + +[package] +name = "proof_engine_zkboost_test" +version = "0.1.0" +edition = "2024" + +[features] +portable = ["types/portable"] + +[dependencies] +anyhow = "1" +axum = "0.7" +bytes = "1" +ethereum_ssz = { version = "0.10.0", features = ["context_deserialize"] } +execution_layer = { path = "../../beacon_node/execution_layer" } +futures = "0.3" +metrics-exporter-prometheus = "0.16" +reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "stream", "rustls-tls"] } +sensitive_url = { version = "0.1", features = ["serde"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +strum = { version = "0.27", features = ["derive"] } +tokio = { version = "1", features = ["rt-multi-thread", "sync", "signal", "macros"] } +tokio-stream = { version = "0.1", features = ["sync"] } +tokio-util = { version = "0.7", features = ["codec", "compat", "time"] } +tracing = "0.1" +tree_hash = "0.12.0" +types = { path = "../../consensus/types" } +url = "2" +zkboost-server = { workspace = true } +zkboost-types = { workspace = true } diff --git a/testing/proof_engine_zkboost/src/lib.rs b/testing/proof_engine_zkboost/src/lib.rs new file mode 100644 index 00000000000..2ba7f4e985d --- /dev/null +++ b/testing/proof_engine_zkboost/src/lib.rs @@ -0,0 +1,306 @@ +//! Integration tests verifying wire-level compatibility between Lighthouse's +//! [`HttpProofNodeClient`] and the **real** zkBoost server. +//! +//! ## Architecture +//! +//! This test crate starts the real `zkBoostServer` (from the `zkboost-server` crate) +//! with mock zkVM backends, and validates that Lighthouse's `HttpProofNodeClient` +//! speaks the correct wire protocol against it. +//! +//! A lightweight mock Execution Layer serves fixture data (chain config + +//! execution witness) so the server can generate witnesses without a real node. +//! +//! ## What is validated +//! +//! - Lighthouse sends zkBoost string proof types in query params, URL paths, SSE +//! - The real server accepts Lighthouse's requests and returns valid responses +//! - SSE events with string `proof_type` values are correctly deserialized to u8 +//! - Full lifecycle: request → SSE event → proof download → verification + +pub mod zkboost_harness; + +#[cfg(test)] +mod tests { + use crate::zkboost_harness::{FIXTURE_NEW_PAYLOAD_REQUEST, ZkboostTestHarness}; + use execution_layer::eip8025::{HttpProofNodeClient, ProofNodeClient, ProofType}; + use execution_layer::test_utils::OwnedNewPayloadRequest; + use futures::StreamExt; + use sensitive_url::SensitiveUrl; + use ssz::Decode; + use std::time::Duration; + use tokio::time::timeout; + use tree_hash::TreeHash; + use types::MainnetEthSpec; + use types::execution::eip8025::ProofAttributes; + use zkboost_types::ProofType as ZkBoostProofType; + + /// Helper: create an `HttpProofNodeClient` pointing at the test server. + fn client_for(url: &str) -> HttpProofNodeClient { + let sensitive_url = SensitiveUrl::parse(url).expect("server URL should be valid"); + HttpProofNodeClient::new(sensitive_url, None) + } + + /// The u8 value for `EthrexZisk` (our default test proof type). + fn ethrex_zisk_u8() -> u8 { + ProofType::EthrexZisk.to_u8() + } + + // ─── Test 1: request_proofs succeeds against real server ───────────────── + + /// Verifies that `HttpProofNodeClient::request_proofs` sends the correct + /// wire format (string proof types in query param, SSZ body) and the real + /// zkBoost server accepts it and returns a root. + #[tokio::test] + async fn test_request_proofs_accepted_by_real_server() { + let harness = ZkboostTestHarness::start(3000).await; + let client = client_for(&harness.url()); + + let attrs = ProofAttributes { + proof_types: vec![ethrex_zisk_u8()], + }; + + let root = client + .request_proofs(FIXTURE_NEW_PAYLOAD_REQUEST.to_vec(), attrs) + .await + .expect("request_proofs should succeed against real server"); + + let expected_root = + OwnedNewPayloadRequest::::from_ssz_bytes(FIXTURE_NEW_PAYLOAD_REQUEST) + .expect("fixture SSZ should decode to a valid NewPayloadRequest") + .tree_hash_root(); + + assert_eq!( + root, expected_root, + "server root should match tree_hash_root of fixture payload" + ); + } + + // ─── Test 2: SSE events from real server are parsed correctly ──────────── + + /// Verifies that SSE events from the real zkBoost server (which use string + /// proof types like `"ethrex-zisk"`) are correctly deserialized by + /// Lighthouse's client back to u8 values. + #[tokio::test] + async fn test_sse_events_from_real_server() { + let harness = ZkboostTestHarness::start(1000).await; + let client = client_for(&harness.url()); + + let attrs = ProofAttributes { + proof_types: vec![ethrex_zisk_u8()], + }; + + // Subscribe to events before requesting proofs. + let mut event_stream = client.subscribe_proof_events(None); + + let root = client + .request_proofs(FIXTURE_NEW_PAYLOAD_REQUEST.to_vec(), attrs) + .await + .expect("request_proofs should succeed"); + + // Wait for a proof event from the real server. + let event = timeout(Duration::from_secs(30), event_stream.next()) + .await + .expect("timed out waiting for SSE event") + .expect("stream ended") + .expect("stream error"); + + assert_eq!(event.new_payload_request_root(), root); + assert_eq!( + event.proof_type(), + ethrex_zisk_u8(), + "string 'ethrex-zisk' from real server should deserialize to u8 {}", + ethrex_zisk_u8() + ); + } + + // ─── Test 3: get_proof downloads proof from real server ────────────────── + + /// Verifies that `get_proof` uses the string proof type in the URL path + /// and successfully downloads a proof from the real server after completion. + #[tokio::test] + async fn test_get_proof_from_real_server() { + let harness = ZkboostTestHarness::start(1000).await; + let client = client_for(&harness.url()); + + let attrs = ProofAttributes { + proof_types: vec![ethrex_zisk_u8()], + }; + + // Subscribe and wait for proof completion. + let mut events = client.subscribe_proof_events(None); + + let root = client + .request_proofs(FIXTURE_NEW_PAYLOAD_REQUEST.to_vec(), attrs) + .await + .expect("request should succeed"); + + // Wait for proof_complete event. + let _event = timeout(Duration::from_secs(30), events.next()) + .await + .expect("timed out waiting for event") + .expect("stream ended") + .expect("stream error"); + + // Download the proof using string proof type in URL path. + let proof_bytes = client + .get_proof(root, ethrex_zisk_u8()) + .await + .expect("get_proof should succeed with string proof type in URL"); + + assert!(!proof_bytes.is_empty(), "proof should not be empty"); + } + + // ─── Test 4: verify_proof against real server ──────────────────────────── + + /// Verifies that `verify_proof` sends the string proof type in query params + /// and the real server accepts the verification request. + #[tokio::test] + async fn test_verify_proof_against_real_server() { + let harness = ZkboostTestHarness::start(1000).await; + let client = client_for(&harness.url()); + + let attrs = ProofAttributes { + proof_types: vec![ethrex_zisk_u8()], + }; + + let mut events = client.subscribe_proof_events(None); + + let root = client + .request_proofs(FIXTURE_NEW_PAYLOAD_REQUEST.to_vec(), attrs) + .await + .expect("request should succeed"); + + // Wait for completion. + let _event = timeout(Duration::from_secs(30), events.next()) + .await + .expect("timed out") + .expect("stream ended") + .expect("stream error"); + + // Download proof. + let proof = client + .get_proof(root, ethrex_zisk_u8()) + .await + .expect("get_proof should succeed"); + + // Verify proof. + let status = client + .verify_proof(root, ethrex_zisk_u8(), &proof) + .await + .expect("verify_proof should succeed against real server"); + + assert_eq!(status, types::execution::eip8025::ProofStatus::Valid); + } + + // ─── Test 5: invalid u8 proof type is rejected by client ───────────────── + + /// Verifies that an unmapped u8 value (e.g. 99) fails at the Lighthouse + /// client level before even reaching the server. + #[tokio::test] + async fn test_invalid_proof_type_rejected_by_client() { + let harness = ZkboostTestHarness::start(0).await; + let client = client_for(&harness.url()); + + let result = client + .get_proof(types::Hash256::repeat_byte(0xAA), 99) + .await; + assert!( + result.is_err(), + "u8 value 99 has no zkBoost mapping — should error at client level" + ); + } + + // ─── Test 6: ZkBoostProofType matches zkboost-types::ProofType ────────── + + /// Validates that Lighthouse's `ZkBoostProofType` enum covers all known + /// zkBoost proof types with matching string representations. + #[tokio::test] + async fn test_zkboost_proof_type_matches_upstream() { + // Collect all upstream ProofType variants. + let upstream: Vec<(String, usize)> = ProofType::all() + .iter() + .enumerate() + .map(|(i, pt)| (pt.as_str().to_string(), i)) + .collect(); + + // Verify Lighthouse's ZkBoostProofType has matching variants. + for (s, i) in &upstream { + let pt: ZkBoostProofType = s + .parse() + .unwrap_or_else(|_| panic!("'{s}' should parse as ZkBoostProofType")); + assert_eq!( + pt.as_str(), + s.as_str(), + "string representation should match upstream" + ); + assert_eq!( + pt as u8, *i as u8, + "u8 mapping for '{s}' should match upstream ordinal {i}" + ); + } + + // Verify all Lighthouse variants are in the upstream list. + let upstream_strs: Vec<&str> = upstream.iter().map(|(s, _)| s.as_str()).collect(); + for pt in ProofType::all() { + assert!( + upstream_strs.contains(&pt.as_str()), + "Lighthouse variant {:?} should exist in upstream zkBoost", + pt + ); + } + + // Counts should match. + assert_eq!( + ProofType::all().len(), + upstream.len(), + "variant count should match between Lighthouse and zkBoost" + ); + } + + // ─── Test 7: full lifecycle (request → SSE → download → verify) ───────── + + /// End-to-end lifecycle against the real zkBoost server. + #[tokio::test] + async fn test_full_lifecycle_against_real_server() { + let harness = ZkboostTestHarness::start(1000).await; + let client = client_for(&harness.url()); + + let attrs = ProofAttributes { + proof_types: vec![ethrex_zisk_u8()], + }; + + let mut events = client.subscribe_proof_events(None); + + // Step 1: Request proof. + let root = client + .request_proofs(FIXTURE_NEW_PAYLOAD_REQUEST.to_vec(), attrs) + .await + .expect("request should succeed"); + + assert!(!root.is_zero()); + + // Step 2: Wait for SSE proof_complete event. + let event = timeout(Duration::from_secs(30), events.next()) + .await + .expect("timed out waiting for event") + .expect("stream ended") + .expect("stream error"); + + assert_eq!(event.new_payload_request_root(), root); + assert_eq!(event.proof_type(), ethrex_zisk_u8()); + + // Step 3: Download proof. + let proof = client + .get_proof(root, ethrex_zisk_u8()) + .await + .expect("get_proof should succeed"); + assert!(!proof.is_empty()); + + // Step 4: Verify proof. + let status = client + .verify_proof(root, ethrex_zisk_u8(), &proof) + .await + .expect("verify_proof should succeed"); + assert_eq!(status, types::execution::eip8025::ProofStatus::Valid); + } +} diff --git a/testing/proof_engine_zkboost/src/zkboost_harness.rs b/testing/proof_engine_zkboost/src/zkboost_harness.rs new file mode 100644 index 00000000000..7e37fb2ca57 --- /dev/null +++ b/testing/proof_engine_zkboost/src/zkboost_harness.rs @@ -0,0 +1,173 @@ +//! Test harness that starts the **real** zkBoost server with mock zkVM backends. +//! +//! This validates that Lighthouse's [`HttpProofNodeClient`] speaks the correct +//! wire protocol by testing it against the actual zkBoost server implementation. +//! +//! ## Architecture +//! +//! 1. A lightweight mock Execution Layer (EL) that serves fixture data for +//! `debug_chainConfig` and `debug_executionWitnessByBlockHash` JSON-RPC methods. +//! 2. The real `zkBoostServer` configured with `zkVMConfig::Mock` backends. +//! 3. Lighthouse's `HttpProofNodeClient` as the system under test. + +use axum::{Json, extract::State, routing::post}; +use bytes::Bytes; +use metrics_exporter_prometheus::PrometheusBuilder; +use serde_json::Value; +use std::net::Ipv4Addr; +use std::sync::Arc; +use tokio::net::TcpListener; +use tokio_util::sync::CancellationToken; +use zkboost_server::{ + config::{Config, zkVMConfig}, + server::zkBoostServer, +}; +use zkboost_types::ProofType; + +// ─── Fixture Data ──────────────────────────────────────────────────────────── + +/// SSZ-encoded NewPayloadRequest from zkBoost's test fixture. +pub const FIXTURE_NEW_PAYLOAD_REQUEST: &[u8] = + include_bytes!("../tests/fixture/new_payload_request.ssz"); + +/// Chain config JSON from zkBoost's test fixture. +const FIXTURE_CHAIN_CONFIG: &str = include_str!("../tests/fixture/chain_config.json"); + +/// Execution witness JSON from zkBoost's test fixture. +const FIXTURE_EXECUTION_WITNESS: &str = include_str!("../tests/fixture/execution_witness.json"); + +// ─── Mock Execution Layer ──────────────────────────────────────────────────── + +struct MockElState { + chain_config: Value, + witness: Value, +} + +/// Mock EL handler that responds to JSON-RPC requests with fixture data. +async fn mock_el_handler(State(state): State>, body: Bytes) -> Json { + let request: Value = serde_json::from_slice(&body).unwrap_or_default(); + let method = request["method"].as_str().unwrap_or(""); + + let result = match method { + "debug_chainConfig" => state.chain_config.clone(), + "debug_executionWitnessByBlockHash" => state.witness.clone(), + _ => Value::Null, + }; + + Json(serde_json::json!({ + "jsonrpc": "2.0", + "result": result, + "id": request["id"], + })) +} + +/// Start a mock execution layer server that serves fixture data. +async fn start_mock_el() -> url::Url { + let chain_config: Value = serde_json::from_str(FIXTURE_CHAIN_CONFIG) + .expect("fixture chain_config.json should be valid JSON"); + let witness: Value = serde_json::from_str(FIXTURE_EXECUTION_WITNESS) + .expect("fixture execution_witness.json should be valid JSON"); + + let state = Arc::new(MockElState { + chain_config, + witness, + }); + + let app = axum::Router::new() + .route("/", post(mock_el_handler)) + .with_state(state); + + let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)) + .await + .expect("failed to bind mock EL"); + let port = listener.local_addr().expect("no local addr").port(); + + tokio::spawn(async move { axum::serve(listener, app).await }); + + format!("http://127.0.0.1:{port}").parse().unwrap() +} + +// ─── Real zkBoost Server ───────────────────────────────────────────────────── + +/// Start the real zkBoost server with mock zkVM backends. +async fn start_zkboost_server( + el_endpoint: url::Url, + zkvm_configs: Vec, +) -> (url::Url, CancellationToken) { + let config = Config { + port: 0, + el_endpoint, + chain_config_path: None, + witness_timeout_secs: 120, + proof_timeout_secs: 120, + proof_cache_size: 128, + witness_cache_size: 128, + zkvm: zkvm_configs, + }; + + let metrics = PrometheusBuilder::new().build_recorder().handle(); + let shutdown = CancellationToken::new(); + let server = zkBoostServer::new(config, metrics) + .await + .expect("failed to create zkBoost server"); + let (addr, _handles) = server + .run(shutdown.clone()) + .await + .expect("failed to start zkBoost server"); + + let endpoint = format!("http://127.0.0.1:{}", addr.port()).parse().unwrap(); + (endpoint, shutdown) +} + +// ─── Test Harness ──────────────────────────────────────────────────────────── + +/// Test harness that manages a real zkBoost server with mock backends. +pub struct ZkboostTestHarness { + /// Base URL of the running zkBoost server. + pub endpoint: url::Url, + /// The proof type configured for the mock backend. + pub proof_type: ProofType, + /// Cancellation token for graceful shutdown. + shutdown: CancellationToken, +} + +impl ZkboostTestHarness { + /// Start a test harness with a single mock zkVM backend. + /// + /// The mock backend uses `EthrexZisk` by default (same as zkBoost's own + /// integration tests) with a configurable proving delay. + pub async fn start(mock_proving_time_ms: u64) -> Self { + Self::start_with_proof_type(ProofType::EthrexZisk, mock_proving_time_ms).await + } + + /// Start a test harness with a specific proof type. + pub async fn start_with_proof_type(proof_type: ProofType, mock_proving_time_ms: u64) -> Self { + let el_endpoint = start_mock_el().await; + + let zkvm_config = zkVMConfig::Mock { + proof_type, + mock_proving_time_ms, + mock_proof_size: 1024, + mock_failure: false, + }; + + let (endpoint, shutdown) = start_zkboost_server(el_endpoint, vec![zkvm_config]).await; + + Self { + endpoint, + proof_type, + shutdown, + } + } + + /// Return the base URL as a string. + pub fn url(&self) -> String { + self.endpoint.to_string().trim_end_matches('/').to_string() + } +} + +impl Drop for ZkboostTestHarness { + fn drop(&mut self) { + self.shutdown.cancel(); + } +} diff --git a/testing/proof_engine_zkboost/tests/fixture/chain_config.json b/testing/proof_engine_zkboost/tests/fixture/chain_config.json new file mode 100644 index 00000000000..82be0f85904 --- /dev/null +++ b/testing/proof_engine_zkboost/tests/fixture/chain_config.json @@ -0,0 +1,45 @@ +{ + "chainId": 3151908, + "homesteadBlock": 0, + "daoForkSupport": false, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "mergeNetsplitBlock": 0, + "shanghaiTime": 0, + "cancunTime": 0, + "pragueTime": 0, + "osakaTime": 0, + "bpo1Time": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true, + "depositContractAddress": "0x00000000219ab540356cbb839cbe05303d7705fa", + "blobSchedule": { + "bpo1": { + "baseFeeUpdateFraction": 8346193, + "max": 15, + "target": 10 + }, + "cancun": { + "baseFeeUpdateFraction": 3338477, + "max": 6, + "target": 3 + }, + "osaka": { + "baseFeeUpdateFraction": 5007716, + "max": 9, + "target": 6 + }, + "prague": { + "baseFeeUpdateFraction": 5007716, + "max": 9, + "target": 6 + } + } +} \ No newline at end of file diff --git a/testing/proof_engine_zkboost/tests/fixture/execution_witness.json b/testing/proof_engine_zkboost/tests/fixture/execution_witness.json new file mode 100644 index 00000000000..65887064f82 --- /dev/null +++ b/testing/proof_engine_zkboost/tests/fixture/execution_witness.json @@ -0,0 +1,50 @@ +{ + "state": [ + "0xf90171a046a9f1217c365990825b7d161fc23cae5688cfb6b2307efe4b732c723e03795880a0c0e0b54cb105bad41b4b925883507463ddfae71c619ba2e41d6d57da2a28effea0793c9db0e252f8f5c79a9d872efc5385ab632a9dc31217637b3509fcf6f0b010a077c059a2b360e9c967686a1302a40994cd63a81aa80a841991d8f3d7379b68eb80a0386a1e942dbe86342b17e2e8b28a259d6db65df8e05f944951a089bb9f3d989fa0315b6e4145b520b88ff5fb638b922671ee1ecbcb65b57b9a4be650ab1fce1d39a066e01acc8a9826bc3d5f5286819fc5883dfa30943331f1e7ff2968bfc57ea2d0a00f7041c0b666de2c820d816b27347738f0e8e2d4d7e1e94e2908b88bc3665a338080a012794aea34d39f220863a2977506ebe5555c2b6488a9469fed918b744f67d6d9a0ace6b45485050162428ffe70f5214d2350ed4890b94322bda3ba63a17342983aa0e20d629ffd2bee3848106f86b98c50a9de755283203bb778c19fa269c8ddb2e38080", + "0xe214a0daadd0f2cf85d5b6a644144de38d5eea115a4546c5efc75c3aee9934f46754a0", + "0xf90131a0e9355b99a40b0e92cc489d34c25f68648461fe0dfcefe3c861f1042ae7cbd522a0766a5ea5b9545a72a463a0fc6151efd1ea0e13f7bd151789dcbff75a1e73cd7180a06e27501c46d61120352d54c49863fcf0eaafcfbffdab9e9e09847d62beef79d88080a0731b30b1211ab24c3e719ad1774d6d450379c926217e248edc5c2a6812e0169480a02978c23bea458e7f47cdc57a9938245e2c763f556847e7e320f7f1bd844127628080a047b4dd8c12aa7dec12a56beb24dff26bd425669991ba54e9ec3225fe6293da24a039f39136138de3527d38db1831c98f8897eecd0a75a77129f6386184f28c779ba0fa149b424c332acc1c6967d908eab8e4922f27e00499ed6a1aca3b9975d87ef580a09f297d5e53d34bc2096cce66773c42bf5b177714e3bb9f180521045b34f7127d80", + "0xf90211a0fcf8a530a63eb8575eb9a70c95332fc1047b567be3d1da03a21c9917d92b14a6a006d47616df479b46b302f2a8b7ed03cb537f6cf7c551c15421c65db4e00fa97fa038e34f9e0e4830343ba24f5fcf0eba28d79cb86397adfb16a0169ee7f0180036a004f7ae295715850712c9dc7f1b9f973797b89ef0f46991203ac789210330a517a0dd3420839babaee761e7eaa38ad5f596b1a9b8716e7e9b9261949a964a5a7d61a0eea64374052ac460957bbc34a071cb8b25dcdf44d96785a55b34242914c83f9fa008763a217b516bcfeadc7f6849e812b392643f50a1d25f002ceec6c2ca0adcafa083c6979e463c02818ffeadaeeb8abc9f2f51e767fb9151a7fc89989eb40b57aca0cbcdc1d226a540c50cb1e615e7af99f171d4365b45734940e22d47ec4aa23a14a0be88e4724326382a8b56e2328eeef0ad51f18d5bae0e84296afe14c4028c4af9a018e0f191e57d4186717e0f3c9379d2438cec0babd12d3903a4ad560f017331bfa01796617427e67ed10cdf8a72b02689a700ba71eb93186a1b120c9ad0b0e56eaea0ad0bb86b47186c04223e85a9c33dd1c87dd6e5c17f753f4fd0a56772d8a78399a065fb94808e31ca248fb2d9de329b81735b22f75d109f389678c9965418bf1f16a06a2b50671c3f299bfd4b6cf43d6e5d6aafd4d3677c38a8af52a0cd7680de2b94a037ff00fbe2105bce0e6ed9ea80a1d67b8a476b1ff3d177ac9597a53241e47aa780", + "0xf901b1a027db720cbe694541a361e08b5450894ddce39b11113fe952080ad5f54ada6f4a80a0d2e57f615a47508c6e60935353428b9fc1cc75677a3eb8f5f73d61dd0aaff5f5a0ca976997ddaf06f18992f6207e4f6a05979d07acead96568058789017cc6d06ba04d78166b48044fdc28ed22d2fd39c8df6f8aaa04cb71d3a17286856f6893ff8380a0fc3b71c33e2e6b77c5e494c1db7fdbb447473f003daf378c7a63ba9bf3f0049da0a9c8e462df1860757a204a01fccc87b873837b0a32cbcc645fb663f3eb12a705a07b8e7a21c1178d28074f157b50fca85ee25c12568ff8e9706dcbcdacb77bf854a0973274526811393ea0bf4811ca9077531db00d06b86237a2ecd683f55ba4bcb0a091d9c76bfbc066e84f0b415c737ab8c477498701d920526db41690050cfade99a06aa67101d011d1c22fe739ef83b04b5214a3e2f8e1a2625d8bfdb116b447e86fa0244e4282dfec33c9bb765162ceee4f2e6390033a94b620d50a2fc6943ebd82fca0f3b039a4f32349e85c782d1164c1890e5bf16badc9ee4cf827db6afd2229dde6a0d9240a9d2d5851d05a97ff3305334dfdb0101e1e321fc279d2bb3cad6afa8fc88080", + "0xf90171a06664dd6bcbb08b83f84324db8cbaf2ceb221e49e66971369dd2257e947a3b13d80a0f4ec365c37413b5f9e7d38c3c6409922fa2a593757ef6176b7291ede5ae2b2d780a0614ab7fe84bea831a68e5e39c6e2d339db432b94dcd29ac75de694cfc6641496a036750a0cdda09ef53dc4a7510eb69e87fbafb1739f51d52c60214b7e0d276ddda04eb05cc2337a47e5d315fc9e2972f88b2282caecf7b79cb486ccf4e64ddf54cd80a0044dadb95a10fad8f922e38449d128807ed6c4b3e6af52d0faa865be8cb8847480a0d53e862eebd81f90452eada8434dfdd03a7ef3d06d6db3e68cbc7d05dff81ec0a0eb47388255e7ca68b42fa56180019c61e2dd301bfe20226d6a74d795f6b016a6a0c522d5defc176e5fa5fc0f16d95ad335f25668067c2c9a55db7d901fd8ac04c6a06c457c05a87c557f84f6d98cfb3754a20c1ded0550ef405433d3514f332c77df80a0d5758f21c6c63a45c81d16ecca352c41af637c1729f8866900efcf731dc10db280", + "0xf901518080a0f1a60e8881cfcb2dc50ba58c326ccc9a6da8287c1e5f56d2017563be700058c4a0616362468a3391221e3782da42e2d6fb8ea41da6bdd2d679e20bf0375c06158680a0ed2fba131fadeadeb1082f565fff16ceb008f693056e3140204716c0739cf1e08080a0cfcecd85b5b3b2b03c196589d3d3b9bcd0ddfc01f000cde9fe3cab41dc6a0a16a0b2a5565ce39d8b7fabb242f087f05b7273aef44094f4166046cddd978751c4bea06234ead07239df2c23d50d21d2e045332bb3e2fb0a402aae5780b823e7d5308680a0ebe51b14fea6aaa5c097f2506874e990813c36cd31399ee3d72666de2dde3fcca051eac0e6e8747ed945c8119613a8359cb76220e714610cf783388ce900153208a0e16e6773b65ff27c428b07407a2d2e479712166515a4a43ecc3c4444d77d4f34a0105bafd3bfbb01dd5f28afe06b314ccf6d5f1bddd1e2135dcc010cb3aedd1d4380", + "0xf869a02086c581c7d7b44eecbb92fd9e5867945ec1acdc0ea5bbabda21d17dddf06473b846f8440180a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a00345a365d2f4c5975b9f1599abe0a2ee76b7a3a731bc68781bd04c84e4858f50", + "0xf8518080a0a63eaef203909ce313085e71f47b6855ccd4fffe444fba1ec1efdab787203351808080a079af4179331361fad570767001c2b705c100b691c849c2bda966d070709c4bf880808080808080808080", + "0xf8f18080a0936cc4aad97b5838a8bc0dfa95a5ad0a0fe2c4681ffe209a0228098aad0c619080a0985a93e071c1474beffb3b3feefcf343ea7e4e002d8c6c7675de86cc9ebc27c0a022652c87d9810e05a254c5942729b67343e1069440f4bee452c8c7bb88d193e8a0975e4f968cf4537118047c31c634177fbec68949fd40003601aab2ff822bcc5580a04b66d0874dc47dba19ba179183342befb18cd80e6d4f85516123426f86e0d7afa081ce69bcb065377bda0ac34c8b05086357838440690dbcccba20f3e8cab1b88680808080a0dc281265feb5bcd82bc162628f62f92dc649b77c80a3ac5d14bdf3d367c495238080", + "0xf90211a040da929897ecf8fb0ecdda44d1c6aa37c7b5d19d0a6f1255c3aaac43b77f2d4ca05dbd3e7becc744398948292f4810e753b166b91cc1a763b214b24718e2bc432aa0d8222bc84a44e1d1d03bdae69910ff3b244c815fa99ef1a9aa6bb568cad6b35ca050e569e8e5e77ab130db2842f7598cee50d0b42cc2504a9df287175c307b23c3a01fd4c856668574229bec8b57377eb317351e0695f8c7c8239aa1016a73001b16a0e0cf581054d8c2bab1359dddde660c659c0e5d70ca2c03e667419d1bc9e45f05a0ebe2e281fc5af1d9bc149c1bf210d264f9b283a2c1760abf0ad5f48e08499ac8a08be4370ed1686f92ab5478848e85a1abab751c9c80e7f8d68daa8c3d8232356aa02fb840ef5765a4ceb26d610badea7ea799c28544f3b329b986c400ff272967d2a0e8393cb9738eea3a5031110dd9c2043e360267072374de576cdc9bc4fa015d39a046ff1faf6df6476a5a4d8f6ba32c8f38582b3a7bd4e12893c1712894ea39c017a008afbb10c9064b061ba3a17cfaf8c083b376a402c60704bc0afaa7a55d27f5c9a0582d0c27b5152cb3f3247a8752888739769fb2b6e3f7842298bc26b616773b88a0db4a8cc49ce3a0fefe00143359d4f0fa86026559ea073bb061b7aacd217ac037a0f31e8aa4efb4024c99d873f31485f1c496f484c345b1ec664f4ba723499e03f8a0672f74dceafee2ee98a97fb19f4afdb991ba8c1ee019438f15b809da4b427b5a80", + "0xf90211a04efbc90ce3b15216a559cdb50fb788b0af3916ef1777a585e7093e27cf4bc16da0047b79502e6ba90c8c1b4863e8380b3e6cc23da1c208f8e39c348a936af31ea5a03db8dd4c19ae2b67a736b757995cb7b57ed55ccdd34fb0ebe979a2dee0c66339a0471db2263571236146b863a32d0d1abe6e21a984998b2d7c0376b4243dca42d2a024e8f92fe5bdde58f4954f534b6f91659a8c0f889abdbf7eec9ab77a26478072a0f8afbd19dafaf176fc835595483ea85f554b1b840e8709b3c2a07715411ecb08a034cb0ac81dbce62a5c9855fe0311bd6827fecbb9aa741a7c8e8b7427f73b8716a0a2a9a28a4324e79e625b104a232620f515ff4a3428c78257bbec3621343ec11aa0b030f3e6c8e7b40bc5bea3da238bcf7546c521b7d6b72dbd98e3fbffb0d604ada0de4bf15b56b7a96707c9c6072d1f413322e563f04ec3c3f9fcf7719b073ed285a0c641efacb85f02a412724d2ba1a107c767d66f5258ae33c9c64bd1bcc4a64540a02e14db6c4900768cf91528d8e1b746f9ab032f277077459f5cc79d16b6be0dc3a006c495fb6961358f4bde6c279838bbc557f9927391b42070bd44b30ab824430fa07415c94beb78124e62f7f63ad7a64076cf7b004809565b8a63dedcacc1434ac5a092712479fda69c5e14b2085716b5e5ab229494f395740b941280432b831ed221a0176a9fce68e6fd07098e5bd0e742a828173ba4a7feed5b6455794caad04462a880", + "0xf869a0209d57be05dd69371c4dd2e871bce6e9f4124236825bb612ee18a45e5675be51b846f8440180a0a247228347f628c6463d5f2932202f269bcabe3dbc08a56392c2dc88e7e04249a06e49e66782037c0555897870e29fa5e552daf4719552131a0abce779daec0a5d", + "0xf90211a0e66e395bd17cd8e5cea8b1c1aad2bb861eeb8a2bb096ea6eddeb34422497bdaaa04c03fe869c8cde143d01ab6bfc09226ea42d9ad99a53263f69716a7186c0bf0aa077e46fe2af85fe2ea2de398481c148651e7ee82f27176160eb18b3a802661798a0c52146e012e5094a13d00fc9dbe596a0639c59e2587b7ac55038d3e52d4f4936a044cb808faa3a8e993889588681b030c9a97babe7e15fdb71be950e9a88a7e402a03deea8359c1b0971aa68d701e9cd18016134f5310b0e4a7d9833247db460a1f1a02cedc09ae6f35f5e75e4a65cee5fc753b113311d912b25fc289a872885415a8ca080b9f7d63a5ea0d7b20ace0018da20977a795543c0ab2d4035b60885e5d60828a0b8f2aa8b6816e39e58f9193d23f9573f75e4c0dea753b325da153a6fbdbaabd2a05126fa3c18c632812536718c92ed0747e4a610c245ea1234acbca7533f1506f2a014116df18532e1f44477d3cf371240e82d2cf7c02542d6da6ab56861626a0c24a0ad7eb60b7242bb4abab99f42056bcc64ae2de2b6182550cb6864c404b059fb3fa04e222b8402af16d6151aba0426b59a029db34ba31592f254ba8d6f64e59e07eba0eae43e73dfb5784c88f6424e4da4ac7aae2aa29f09cd528aab89a4003e3a4da7a023fc581b6065c3d34578d7119f3385df16ae9a24aab09a98877d36fd844f2933a0a4cb53144ee264a09401aabbebb43c80264ebfce063a70c28595e1b0c52fdd9c80", + "0xf90211a0f53fd45e8a28bfc7c92543aac0f242249bd15dc550b8d1d43defabfe1ff4622ba072d67f642876a04c9733ce298d4bb2fdc2eb041b6760ed0a3be006785b0705e0a0a86c39e9a32652492ee5240d1715c6a63537351d350754b62952760d8e1f944ba0e79513901b1f313c826300a31dff17f6adf9e2aeb895f730dbb93f0a96a86d9fa031c4646963f14566afb0e50a6c400d69c834c3b1fdb3909677856cbb576db4e5a09cdcc334e9d1c6451e5f5230efdd07ac62f48223d3a71b7082d1c9f3faee6af1a0f5ea37b375d1f04089104149dd9204aa0ff3c90167f7aca7da201905594300e4a076972cc63f4fabea810e87083ec1899b687d8748d26fe16fd4b6a13ae3e303f1a04ff31ed8ee553088b2e578f36bf3ed50d5cbd58611261be37633294dc61acca9a028b05d809456d53fc06c9b102d216cff567a7aca7c9d1cb4cdca67965f0ef4cda01556f03106eeb9fc5a473e8f7f042e57d827b78b76a5f7a8f5b187f8d897515da0f762ae6fe61a92321fb8d528b2f5f4b1b97a94ebf2d5ec0899e8f703fba9790da036affb194c9227b46dece3bc3e1e5ef56403db6c8e34fe1b8bb3ae197158b5d6a0db08702017c418fb841716b9c2454676fa632f607d5b261f55c7434dbc69c4d6a0d4da88e24a26de50f4f0d35a348e12da471480c6e612dacccbc594a61f58d74ca0aaba74a722fd0645b8b7a8886d0e891e04c4e57914480568f5334d7514391f6680", + "0x80", + "0xf8429f37d5f9b51ca71bda3c02250aa5ededabaa712e18e5f1714fde16280d94a4a3a1a0d5848dbf659bcc407318ddcac1ad62fb7b58c53df808ed0a560c8d4a94ac3e6f", + "0xf8689f3aea581b220579a2b99819299dd32c7c28a420018ecb0bde93af007ad89a31b846f8440180a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a078c6cb5202685228bbcbfb992b1c4e116c7ec5ef11e25b8e92716cfc628ddd60", + "0xf869a020d65eaa92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e42b846f8440180a0a52d06d7443bb469a8ddfecb744e9750fa7284a237b31f7168562123b84c3547a0f57acd40259872606d76197ef052f3d35588dadf919ee1f0e3cb9b62d3f4b02c", + "0xf90211a065cb9654d83c2c587ff35d995153e55908ccc8d12f99cec6f0fca2174d0d4887a072c2cce9f8770d341a4cb7c7cdc53d75d6308b55e9f991bc8ba67b29434b61dfa0f2b29241a79b4cf67be8c19e0fc49894bbc908bdfaa864f313e640a9656271cca0a4f08ea6851799ebadce763bdb22c8511a37106f2b1f1a2e1da77743588a4751a0e473037e78e7f6b59faf7c818971524734244419165e3b52fd6747e4acbb3235a09f871e9dc9ad7e80a33f12dfb19ec657a944edd24ecd975367a4675d7a2760a0a0f3e41d9e7b89a679eba0c449b24e2f6b074dd4e65abc10fee304b97893689673a0ba956ceecf3546a048edbdb0e93c6bd5f9437ee2bc2eb547d95cad86e16e791ba0be49e1efa56a6325758e40aa25985c3f71f2d20888daa9efd8e2e9cd0d70826ca05d4d0edd678514b0b449d8689f7971252fd7b86378a102395d5ee769d709c2a1a0fcacd3004b2d9f8c601c667041baea5c7ad53bde430303ab3d2f5c765804cd82a0b7195c41d29afbb5b45413885333d6a19b0679d3a92a9f1198ab04689ac0518ca06675b419aca5f5ab938080fd8245ae9c388c144521ad7d4a57e8f36212e218a2a0ddfccdcd7960367614d844e7fca5cb92573ace5ca42ad9381dfc2c69e7f0f890a04651f6d80d233d28e5cda8940d11319698f604ee414041a9374a5ee3d7305b1fa0da847328820b77fcc53e716178f77359797b68b90e53117251c9115ee6fc428880" + ], + "codes": [ + "0x3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101f457600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603814608857366101f457346101f4575f5260205ff35b34106101f457600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260385f601437604c5fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101835782810160030260040181604c02815460601b8152601401816001015481526020019060020154807fffffffffffffffffffffffffffffffff00000000000000000000000000000000168252906010019060401c908160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360010160e1565b910180921461019557906002556101a0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14156101cd57505f5b6001546002828201116101e25750505f6101e8565b01600290035b5f555f600155604c025ff35b5f5ffd", + "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500", + "0x3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd" + ], + "keys": [ + "0x000f3df6d732807ef1319fb7b8bb8522d0beac02", + "0x0000000000000000000000000000000000000000000000000000000000003808", + "0x0000000000000000000000000000000000000000000000000000000000001809", + "0x00000961ef480eb55e80d19ad83579a64c007002", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000f90827f1c53a10cb7a02335b175320002935", + "0x00000000000000000000000000000000000000000000000000000000000004af", + "0x0000bbddc7ce488642fb579f8b00f3a590007251", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ], + "headers": [ + "0xf9026fa084a5904e068368b6581e5afa05f96e3912068ab8ceee08ca76bdb9719bd1c090a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948943545177806ed17b9f23f0a21ee5948ecaa776a03bb7c2e1c292bc41a27064b9160eb131723e6c345851ee0c386f09115da5fae6a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808204af8402255100808469aeca6d92726574682f76312e31302e312f6c696e7578a0f2940bf2aad7139113b79fcd654cb699530e993a33dc05a31ebfcf017643b55888000000000000000007a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a037afc7de70547b71e752341e78303f688e6f5b87e47367b747947d5d34af77a0a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + ] +} \ No newline at end of file diff --git a/testing/proof_engine_zkboost/tests/fixture/new_payload_request.ssz b/testing/proof_engine_zkboost/tests/fixture/new_payload_request.ssz new file mode 100644 index 0000000000000000000000000000000000000000..6ffe35cc644bd4693ec456d34ea58d0157808fa6 GIT binary patch literal 602 zcmdO4U|{fLVqlnNl~Q$@&-3_K7WP>eJf4@^h(*Z@dXzuZ`oNs zmYuxh$C7g2b3yCPDOY?C%wvs|?^cXIUg3G#G~hzm3wd$rGoj1=H@iNYbl^u`w8sPK znK^3@Fed45eVn{S5$L=T4Q3m?syAN6vi>+7%S^a+1qu7VS696w3aIf*u_Ri{C@P{k^RzVaZ@{b m?q7d=ObKWP2&03d)RGMSGDAH>13g3ioXot^3Lc;m7zO|#B6p+! literal 0 HcmV?d00001 diff --git a/testing/simulator/Cargo.toml b/testing/simulator/Cargo.toml index a1b1b6f95d2..1a8a9427195 100644 --- a/testing/simulator/Cargo.toml +++ b/testing/simulator/Cargo.toml @@ -3,22 +3,32 @@ name = "simulator" version = "0.2.0" authors = ["Paul Hauner "] edition = { workspace = true } + +[features] +test-utils = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = { workspace = true } +beacon_chain = { workspace = true } clap = { workspace = true } -environment = { workspace = true } +environment = { workspace = true, features = ["test-utils"] } +eth2 = { workspace = true, features = ["events"] } execution_layer = { workspace = true } futures = { workspace = true } kzg = { workspace = true } +lighthouse_network = { workspace = true } logging = { workspace = true } +network_utils = { workspace = true } node_test_rig = { path = "../node_test_rig" } parking_lot = { workspace = true } rayon = { workspace = true } sensitive_url = { workspace = true } serde_json = { workspace = true } +task_executor = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } typenum = { workspace = true } types = { workspace = true } +validator_http_api = { workspace = true } diff --git a/testing/simulator/src/basic_sim.rs b/testing/simulator/src/basic_sim.rs index 79581ee5299..6dd74292285 100644 --- a/testing/simulator/src/basic_sim.rs +++ b/testing/simulator/src/basic_sim.rs @@ -1,4 +1,5 @@ use crate::local_network::LocalNetworkParams; +use crate::local_network::NodeType; use crate::local_network::TERMINAL_BLOCK; use crate::{LocalNetwork, checks}; use clap::ArgMatches; @@ -208,8 +209,11 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { LocalNetworkParams { validator_count: total_validator_count, node_count, - extra_nodes, + extra_nodes: 0, proposer_nodes, + proof_generator_nodes: 0, + proof_verifier_nodes: 0, + delayed_nodes: extra_nodes, genesis_delay, }, context.clone(), @@ -220,7 +224,11 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { // Add nodes to the network. for _ in 0..node_count { network - .add_beacon_node(beacon_config.clone(), mock_execution_config.clone(), false) + .add_beacon_node( + beacon_config.clone(), + mock_execution_config.clone(), + NodeType::Default, + ) .await?; } @@ -230,7 +238,11 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { for _ in 0..proposer_nodes { println!("Adding a proposer node"); network - .add_beacon_node(beacon_config.clone(), mock_execution_config.clone(), true) + .add_beacon_node( + beacon_config.clone(), + mock_execution_config.clone(), + NodeType::Proposer, + ) .await?; } @@ -261,7 +273,7 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { .await } else { network_1 - .add_validator_client(validator_config, i, files) + .add_validator_client(validator_config, i, files, NodeType::Default) .await } .expect("should add validator"); diff --git a/testing/simulator/src/fallback_sim.rs b/testing/simulator/src/fallback_sim.rs index 06f4478c5e6..69e0858076c 100644 --- a/testing/simulator/src/fallback_sim.rs +++ b/testing/simulator/src/fallback_sim.rs @@ -1,4 +1,4 @@ -use crate::local_network::LocalNetworkParams; +use crate::local_network::{LocalNetworkParams, NodeType}; use crate::{LocalNetwork, checks}; use clap::ArgMatches; @@ -218,6 +218,9 @@ pub fn run_fallback_sim(matches: &ArgMatches) -> Result<(), String> { node_count, extra_nodes: 0, proposer_nodes: 0, + proof_generator_nodes: 0, + proof_verifier_nodes: 0, + delayed_nodes: 0, genesis_delay, }, context.clone(), @@ -228,7 +231,11 @@ pub fn run_fallback_sim(matches: &ArgMatches) -> Result<(), String> { // Add nodes to the network. for _ in 0..node_count { network - .add_beacon_node(beacon_config.clone(), mock_execution_config.clone(), false) + .add_beacon_node( + beacon_config.clone(), + mock_execution_config.clone(), + NodeType::Default, + ) .await?; } diff --git a/testing/simulator/src/lib.rs b/testing/simulator/src/lib.rs new file mode 100644 index 00000000000..b6c70d44969 --- /dev/null +++ b/testing/simulator/src/lib.rs @@ -0,0 +1,26 @@ +//! This crate provides various simulations that create both beacon nodes and validator clients, +//! each with `v` validators. +//! +//! When a simulation runs, there are checks made to ensure that all components are operating +//! as expected. If any of these checks fail, the simulation will exit immediately. +//! +//! ## Future works +//! +//! Presently all the beacon nodes and validator clients all log to stdout. Additionally, the +//! simulation uses `println` to communicate some info. It might be nice if the nodes logged to +//! easy-to-find files and stdout only contained info from the simulation. +//! +pub mod basic_sim; +pub mod checks; +pub mod cli; +pub mod fallback_sim; +pub mod local_network; +pub mod retry; + +pub use local_network::LocalNetwork; +pub use types::MinimalEthSpec; + +pub type E = MinimalEthSpec; + +#[cfg(feature = "test-utils")] +pub mod test_utils; diff --git a/testing/simulator/src/local_network.rs b/testing/simulator/src/local_network.rs index 2beb9c0efcf..dd151aeff0f 100644 --- a/testing/simulator/src/local_network.rs +++ b/testing/simulator/src/local_network.rs @@ -1,8 +1,11 @@ use crate::checks::epoch_delay; +use beacon_chain::custody_context::NodeCustodyType; use kzg::trusted_setup::get_trusted_setup; +use lighthouse_network::types::Enr; +use network_utils::listen_addr::ListenAddress; use node_test_rig::{ ClientConfig, ClientGenesis, LocalBeaconNode, LocalExecutionNode, LocalValidatorClient, - MockExecutionConfig, MockServerConfig, ValidatorConfig, ValidatorFiles, + MockExecutionConfig, ValidatorConfig, ValidatorFiles, environment::RuntimeContext, eth2::{BeaconNodeHttpClient, types::StateId}, testing_client_config, @@ -15,23 +18,78 @@ use std::{ sync::Arc, time::{Duration, SystemTime, UNIX_EPOCH}, }; +use task_executor::TaskExecutor; use types::{ChainSpec, Epoch, EthSpec}; +use validator_http_api::{Config as ValidatorHttpConfig, PK_FILENAME}; -const BOOTNODE_PORT: u16 = 42424; -const QUIC_PORT: u16 = 43424; +pub const TERMINAL_BLOCK: u64 = 0; -pub const EXECUTION_PORT: u16 = 4000; +#[derive(Debug, Copy, Clone)] +pub enum NodeType { + Default, + Proposer, + ProofVerifier, + ProofGenerator, +} -pub const TERMINAL_BLOCK: u64 = 0; +impl NodeType { + pub fn is_proposer(self) -> bool { + matches!(self, NodeType::Proposer) + } + + pub fn is_proof_verifier(self) -> bool { + matches!(self, NodeType::ProofVerifier) + } + + pub fn is_proof_generator(self) -> bool { + matches!(self, NodeType::ProofGenerator) + } + + pub fn requires_proof_node(self) -> bool { + matches!(self, NodeType::ProofVerifier | NodeType::ProofGenerator) + } + + pub fn requires_execution_node(self) -> bool { + matches!( + self, + NodeType::Default | NodeType::Proposer | NodeType::ProofGenerator + ) + } +} +#[derive(Debug, Clone)] pub struct LocalNetworkParams { pub validator_count: usize, pub node_count: usize, pub proposer_nodes: usize, + pub proof_generator_nodes: usize, + pub proof_verifier_nodes: usize, pub extra_nodes: usize, + pub delayed_nodes: usize, pub genesis_delay: u64, } +impl LocalNetworkParams { + pub fn node_type(&self, node_idx: usize) -> NodeType { + if node_idx < self.node_count { + NodeType::Default + } else if node_idx < self.node_count + self.proposer_nodes { + NodeType::Proposer + } else if node_idx < self.node_count + self.proposer_nodes + self.proof_generator_nodes { + NodeType::ProofGenerator + } else if node_idx + < self.node_count + + self.proposer_nodes + + self.proof_generator_nodes + + self.proof_verifier_nodes + { + NodeType::ProofVerifier + } else { + panic!("Invalid node index: {}", node_idx); + } + } +} + fn default_client_config(network_params: LocalNetworkParams, genesis_time: u64) -> ClientConfig { let mut beacon_config = testing_client_config(); @@ -39,22 +97,19 @@ fn default_client_config(network_params: LocalNetworkParams, genesis_time: u64) validator_count: network_params.validator_count, genesis_time, }; - beacon_config.network.target_peers = - network_params.node_count + network_params.proposer_nodes + network_params.extra_nodes - 1; + beacon_config.network.target_peers = network_params.node_count + + network_params.proposer_nodes + + network_params.proof_generator_nodes + + network_params.proof_verifier_nodes + + network_params.extra_nodes + + network_params.delayed_nodes + - 1; beacon_config.network.enr_address = (Some(Ipv4Addr::LOCALHOST), None); beacon_config.network.enable_light_client_server = true; beacon_config.network.discv5_config.enable_packet_filter = false; beacon_config.chain.enable_light_client_server = true; beacon_config.chain.optimistic_finalized_sync = false; beacon_config.trusted_setup = get_trusted_setup(); - - let el_config = execution_layer::Config { - execution_endpoint: Some( - SensitiveUrl::parse(&format!("http://localhost:{}", EXECUTION_PORT)).unwrap(), - ), - ..Default::default() - }; - beacon_config.execution_layer = Some(el_config); beacon_config } @@ -62,13 +117,7 @@ fn default_mock_execution_config( spec: &ChainSpec, genesis_time: u64, ) -> MockExecutionConfig { - let mut mock_execution_config = MockExecutionConfig { - server_config: MockServerConfig { - listen_port: EXECUTION_PORT, - ..Default::default() - }, - ..Default::default() - }; + let mut mock_execution_config = MockExecutionConfig::default(); if let Some(capella_fork_epoch) = spec.capella_fork_epoch { mock_execution_config.shanghai_time = Some( @@ -200,21 +249,28 @@ impl LocalNetwork { self.validator_clients.read().len() } + pub fn executor(&self) -> &TaskExecutor { + &self.context.executor + } + async fn construct_boot_node( &self, mut beacon_config: ClientConfig, mock_execution_config: MockExecutionConfig, ) -> Result<(LocalBeaconNode, LocalExecutionNode), String> { + let listen = ListenAddress::unused_v4_ports(); + let v4 = listen.v4().expect("unused_v4_ports always returns V4"); beacon_config.network.set_ipv4_listening_address( - std::net::Ipv4Addr::UNSPECIFIED, - BOOTNODE_PORT, - BOOTNODE_PORT, - QUIC_PORT, + Ipv4Addr::UNSPECIFIED, + v4.tcp_port, + v4.disc_port, + v4.quic_port, ); - - beacon_config.network.enr_udp4_port = Some(BOOTNODE_PORT.try_into().expect("non zero")); - beacon_config.network.enr_tcp4_port = Some(BOOTNODE_PORT.try_into().expect("non zero")); + beacon_config.network.enr_udp4_port = std::num::NonZeroU16::new(v4.disc_port); + beacon_config.network.enr_tcp4_port = std::num::NonZeroU16::new(v4.tcp_port); + beacon_config.network.enr_quic4_port = std::num::NonZeroU16::new(v4.quic_port); beacon_config.network.discv5_config.table_filter = |_| true; + beacon_config.chain.node_custody_type = NodeCustodyType::Supernode; let execution_node = LocalExecutionNode::new(self.context.clone(), mock_execution_config); @@ -233,37 +289,62 @@ impl LocalNetwork { async fn construct_beacon_node( &self, mut beacon_config: ClientConfig, - mut mock_execution_config: MockExecutionConfig, - is_proposer: bool, - ) -> Result<(LocalBeaconNode, LocalExecutionNode), String> { - let count = (self.beacon_node_count() + self.proposer_node_count()) as u16; - - // Set config. - let libp2p_tcp_port = BOOTNODE_PORT + count; - let discv5_port = BOOTNODE_PORT + count; + mock_execution_config: MockExecutionConfig, + node_type: NodeType, + ) -> Result<(LocalBeaconNode, Option>), String> { + let listen = ListenAddress::unused_v4_ports(); + let v4 = listen.v4().expect("unused_v4_ports always returns V4"); beacon_config.network.set_ipv4_listening_address( - std::net::Ipv4Addr::UNSPECIFIED, - libp2p_tcp_port, - discv5_port, - QUIC_PORT + count, + Ipv4Addr::UNSPECIFIED, + v4.tcp_port, + v4.disc_port, + v4.quic_port, ); - beacon_config.network.enr_udp4_port = Some(discv5_port.try_into().unwrap()); - beacon_config.network.enr_tcp4_port = Some(libp2p_tcp_port.try_into().unwrap()); + beacon_config.network.enr_udp4_port = std::num::NonZeroU16::new(v4.disc_port); + beacon_config.network.enr_tcp4_port = std::num::NonZeroU16::new(v4.tcp_port); + beacon_config.network.enr_quic4_port = std::num::NonZeroU16::new(v4.quic_port); beacon_config.network.discv5_config.table_filter = |_| true; - beacon_config.network.proposer_only = is_proposer; - - mock_execution_config.server_config.listen_port = EXECUTION_PORT + count; + beacon_config.network.proposer_only = node_type.is_proposer(); + + let execution_node = if node_type.requires_execution_node() { + let execution_node = + LocalExecutionNode::new(self.context.clone(), mock_execution_config); + + beacon_config.execution_layer = Some(execution_layer::Config { + execution_endpoint: Some( + SensitiveUrl::parse(&execution_node.server.url()).unwrap(), + ), + default_datadir: execution_node.datadir.path().to_path_buf(), + secret_file: Some(execution_node.datadir.path().join("jwt.hex")), + ..Default::default() + }); + Some(execution_node) + } else { + beacon_config.execution_layer = None; + None + }; - // Construct execution node. - let execution_node = LocalExecutionNode::new(self.context.clone(), mock_execution_config); + if node_type.requires_proof_node() { + beacon_config.network.enable_execution_proof = true; + let bn_idx = self.beacon_nodes.read().len(); + let _: execution_layer::test_utils::MockProofNodeClient = + execution_layer::test_utils::register_mock_proof_engine(bn_idx, 400); + let mock_url = + SensitiveUrl::parse(&execution_layer::test_utils::mock_proof_engine_url(bn_idx)) + .expect("mock URL is valid"); + if let Some(el_config) = beacon_config.execution_layer.as_mut() { + el_config.proof_engine_endpoint = Some(mock_url); + } else { + beacon_config.execution_layer = Some(execution_layer::Config { + proof_engine_endpoint: Some(mock_url), + ..Default::default() + }); + } + } - // Pair the beacon node and execution node. - beacon_config.execution_layer = Some(execution_layer::Config { - execution_endpoint: Some(SensitiveUrl::parse(&execution_node.server.url()).unwrap()), - default_datadir: execution_node.datadir.path().to_path_buf(), - secret_file: Some(execution_node.datadir.path().join("jwt.hex")), - ..Default::default() - }); + if node_type.is_proof_verifier() { + beacon_config.chain.optimistic_finalized_sync = true; + } // Construct beacon node using the config, let beacon_node = LocalBeaconNode::production(self.context.clone(), beacon_config).await?; @@ -271,44 +352,55 @@ impl LocalNetwork { Ok((beacon_node, execution_node)) } + async fn boot_node_enr(&self) -> Result, String> { + if self.beacon_nodes.read().is_empty() { + return Ok(None); + } + + for _ in 0..100 { + if let Some(enr) = self + .beacon_nodes + .read() + .first() + .and_then(|bn| bn.client.enr()) + .filter(|e| e.tcp4().is_some_and(|p| p != 0) && e.udp4().is_some_and(|p| p != 0)) + { + return Ok(Some(enr)); + } + tokio::time::sleep(Duration::from_millis(100)).await; + } + Err("Boot node ENR did not get valid TCP and UDP ports within 10 seconds".to_string()) + } + /// Adds a beacon node to the network, connecting to the 0'th beacon node via ENR. pub async fn add_beacon_node( &self, mut beacon_config: ClientConfig, mock_execution_config: MockExecutionConfig, - is_proposer: bool, + node_type: NodeType, ) -> Result<(), String> { - let first_bn_exists: bool; - { - let read_lock = self.beacon_nodes.read(); - let boot_node = read_lock.first(); - first_bn_exists = boot_node.is_some(); - - if let Some(boot_node) = boot_node { - // Modify beacon_config to add boot node details. - beacon_config.network.boot_nodes_enr.push( - boot_node - .client - .enr() - .expect("Bootnode must have a network."), - ); - } - } - let (beacon_node, execution_node) = if first_bn_exists { - // Network already exists. We construct a new node. - self.construct_beacon_node(beacon_config, mock_execution_config, is_proposer) + let (beacon_node, execution_node) = if let Some(boot_node) = self.boot_node_enr().await? { + beacon_config.network.boot_nodes_enr.push(boot_node); + self.construct_beacon_node(beacon_config, mock_execution_config, node_type) .await? } else { // Network does not exist. We construct a boot node. - self.construct_boot_node(beacon_config, mock_execution_config) - .await? + let (bn, en) = self + .construct_boot_node(beacon_config, mock_execution_config) + .await?; + (bn, Some(en)) }; // Add nodes to the network. - self.execution_nodes.write().push(execution_node); - if is_proposer { - self.proposer_nodes.write().push(beacon_node); - } else { - self.beacon_nodes.write().push(beacon_node); + if let Some(execution_node) = execution_node { + self.execution_nodes.write().push(execution_node); + } + match node_type { + NodeType::Proposer => { + self.proposer_nodes.write().push(beacon_node); + } + _ => { + self.beacon_nodes.write().push(beacon_node); + } } Ok(()) } @@ -325,7 +417,7 @@ impl LocalNetwork { ) -> Result<(), String> { epoch_delay(Epoch::new(wait_until_epoch), slot_duration, slots_per_epoch).await; - self.add_beacon_node(beacon_config, mock_execution_config, false) + self.add_beacon_node(beacon_config, mock_execution_config, NodeType::Default) .await?; Ok(()) @@ -338,7 +430,9 @@ impl LocalNetwork { mut validator_config: ValidatorConfig, beacon_node: usize, validator_files: ValidatorFiles, + node_type: NodeType, ) -> Result<(), String> { + let beacon_node_idx = beacon_node; let context = self.context.clone(); let self_1 = self.clone(); let socket_addr = { @@ -368,6 +462,36 @@ impl LocalNetwork { .unwrap(); validator_config.beacon_nodes = vec![beacon_node]; + if node_type.is_proof_generator() { + let token_dir = std::env::temp_dir().join(format!( + "lighthouse-vc-proof-token-{}-{}", + std::process::id(), + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|_| "should get system time")? + .as_nanos() + )); + std::fs::create_dir_all(&token_dir) + .map_err(|e| format!("Unable to create validator API token dir: {e}"))?; + let token_path = token_dir.join(PK_FILENAME); + validator_config.http_api = ValidatorHttpConfig { + enabled: true, + listen_addr: Ipv4Addr::LOCALHOST.into(), + listen_port: 0, + allow_origin: None, + allow_keystore_export: true, + store_passwords_in_secrets_dir: false, + http_token_path: token_path, + bn_long_timeouts: false, + }; + validator_config.proof_engine_endpoint = Some( + SensitiveUrl::parse(&execution_layer::test_utils::mock_proof_engine_url( + beacon_node_idx, + )) + .expect("mock URL is valid"), + ); + } + // If we have a proposer node established, use it. if let Some(proposer_socket_addr) = proposer_socket_addr { let url = SensitiveUrl::parse( @@ -431,6 +555,14 @@ impl LocalNetwork { Ok(()) } + /// Return a HTTP client for the beacon node at `index`. + pub fn remote_node(&self, index: usize) -> Option { + self.beacon_nodes + .read() + .get(index) + .and_then(|n| n.remote_node().ok()) + } + /// For all beacon nodes in `Self`, return a HTTP client to access each nodes HTTP API. pub fn remote_nodes(&self) -> Result, String> { let beacon_nodes = self.beacon_nodes.read(); @@ -443,6 +575,30 @@ impl LocalNetwork { .collect() } + /// Subscribe to mock proof-client events for a beacon node at a specific index. + pub fn node_subscribe_client_events( + &self, + index: usize, + ) -> Option> + { + execution_layer::test_utils::get_mock_proof_engine::(index) + .map(|mock| mock.subscribe_client_events()) + } + + /// Subscribe to the internal event bus for a beacon node at a specific index. + pub fn node_subscribe_internal_events( + &self, + index: usize, + ) -> Option< + tokio::sync::broadcast::Receiver, + > { + self.beacon_nodes.read().get(index).and_then(|bn| { + bn.client + .beacon_chain() + .map(|chain| chain.subscribe_internal_events()) + }) + } + /// Return current epoch of bootnode. pub async fn _bootnode_epoch(&self) -> Result { let nodes = self.remote_nodes().expect("Failed to get remote nodes"); diff --git a/testing/simulator/src/test_utils/builder.rs b/testing/simulator/src/test_utils/builder.rs new file mode 100644 index 00000000000..c2892235eee --- /dev/null +++ b/testing/simulator/src/test_utils/builder.rs @@ -0,0 +1,341 @@ +use crate::local_network::NodeType; + +use super::*; + +type ClientConfigTransform = Box; +type SpecTransform = Box; + +/// Builder for creating test networks with configurable parameters. +pub struct TestNetworkFixtureBuilder { + env: EnvironmentBuilder, + network_params: LocalNetworkParams, + logger_config: LoggerConfig, + disable_stdout: bool, + client_config_transform: Option, + spec_transform: Option, +} + +impl Default for TestNetworkFixtureBuilder { + fn default() -> Self { + Self { + env: EnvironmentBuilder::minimal(), + network_params: LocalNetworkParams { + validator_count: 4, + node_count: 2, + proposer_nodes: 0, + proof_generator_nodes: 0, + proof_verifier_nodes: 0, + extra_nodes: 0, + delayed_nodes: 0, + genesis_delay: 38, + }, + logger_config: LoggerConfig::default(), + disable_stdout: false, + client_config_transform: None, + spec_transform: None, + } + } +} + +impl TestNetworkFixtureBuilder { + /// Set the `EnvironmentBuilder` to use for the network. + pub fn with_env(mut self, env: EnvironmentBuilder) -> Self { + self.env = env; + self + } + + /// Apply an arbitrary modification to the `EnvironmentBuilder` used for the network. + pub fn map_env(mut self, f: impl FnOnce(&mut EnvironmentBuilder)) -> Self { + f(&mut self.env); + self + } + + /// Apply an arbitrary modification to the `ChainSpec` used for the network. + pub fn map_spec(mut self, f: impl FnOnce(&mut ChainSpec) + Send + 'static) -> Self { + self.spec_transform = Some(match self.spec_transform.take() { + None => Box::new(f), + Some(prev) => Box::new(move |spec| { + prev(spec); + f(spec); + }), + }); + self + } + + /// Set the log level. + pub fn with_log_level(mut self, level: LevelFilter) -> Self { + self.logger_config.debug_level = level; + self.logger_config.logfile_debug_level = level; + self + } + + /// Set the log directory. + pub fn with_log_dir(mut self, log_dir: PathBuf) -> Self { + self.logger_config.path = Some(log_dir); + self + } + + /// Apply an arbitrary modification to the `LoggerConfig` used for the network. + pub fn map_logger_config(mut self, f: impl FnOnce(&mut LoggerConfig)) -> Self { + f(&mut self.logger_config); + self + } + + /// Set the network params. + pub fn with_network_params(mut self, network_params: LocalNetworkParams) -> Self { + self.network_params = network_params; + self + } + + /// Apply an arbitrary modification to the `LocalNetworkParams` used for the network. + pub fn map_network_params(mut self, f: impl FnOnce(&mut LocalNetworkParams)) -> Self { + f(&mut self.network_params); + self + } + + /// Apply an arbitrary modification to the `ClientConfig` used for all beacon nodes. + /// + /// Multiple calls are composed in order: the first registered transform runs first. + pub fn map_client_config(mut self, f: impl FnOnce(&mut ClientConfig) + Send + 'static) -> Self { + self.client_config_transform = Some(match self.client_config_transform.take() { + None => Box::new(f), + Some(prev) => Box::new(move |config| { + prev(config); + f(config); + }), + }); + self + } + + /// Build the test network fixture with the specified configuration. + pub async fn build(self) -> anyhow::Result> { + info!(target: "simulator", "Building test network fixture"); + + // initialize the network + let (env, network_params, network, beacon_config, mock_execution_config) = + self.init_network().await?; + + // Initialize beacon nodes + Self::init_beacon_nodes( + &network, + &network_params, + &beacon_config, + &mock_execution_config, + ) + .await?; + + // Initialize validator clients + Self::init_validators(&network, &network_params).await?; + + Ok(TestNetworkFixture { + env, + network, + config: TestConfig { + client: beacon_config, + execution: mock_execution_config, + network_params, + }, + }) + } + + async fn init_validators( + network: &LocalNetwork, + network_params: &LocalNetworkParams, + ) -> anyhow::Result<()> { + info!(target: "simulator", "Building validator clients for {} validators", network_params.validator_count); + let network_params = network_params.clone(); + let task_executor = network.executor(); + + // Generate validator keystores in parallel to speed up setup time + let validator_files = task_executor + .spawn_blocking_handle( + move || -> anyhow::Result> { + let num_beacon_nodes = + network_params.node_count + network_params.proof_generator_nodes; + let validators_per_node = network_params.validator_count / num_beacon_nodes; + + (0..num_beacon_nodes) + .into_par_iter() + .map(|i| -> anyhow::Result { + info!(target: "simulator", + "Generating keystores for validator {} of {}", + i + 1, + num_beacon_nodes + ); + + let indices = (i * validators_per_node..(i + 1) * validators_per_node) + .collect::>(); + + ValidatorFiles::with_keystores(&indices).map_err(anyhow::Error::msg) + }) + .collect::>>() + }, + "validator_keystore_generation", + ) + .ok_or_else(|| anyhow::anyhow!("Failed to spawn blocking task"))? + .await??; + + for (i, files) in validator_files.into_iter().enumerate() { + let network = network.clone(); + let network_params = network_params.clone(); + + task_executor.spawn( + async move { + let mut validator_config = testing_validator_config(); + validator_config.validator_store.fee_recipient = + Some(Into::
::into(SUGGESTED_FEE_RECIPIENT)); + + // Enable broadcast on every 2nd node. + // TODO: do we need this? + if i % 4 == 0 { + validator_config.broadcast_topics = ApiTopic::all(); + let beacon_nodes = vec![i, (i + 1) % network_params.node_count]; + network + .add_validator_client_with_fallbacks( + validator_config, + beacon_nodes, + files, + ) + .await + } else { + let node_type = network_params.node_type(i); + network + .add_validator_client(validator_config, i, files, node_type) + .await + } + .expect("should add validator"); + }, + "validator_client_setup", + ) + } + + Ok(()) + } + + async fn init_beacon_nodes( + network: &LocalNetwork, + network_params: &LocalNetworkParams, + beacon_config: &ClientConfig, + mock_execution_config: &MockExecutionConfig, + ) -> anyhow::Result<()> { + // Build the full list of (NodeType, count) pairs, then spawn all nodes concurrently. + let node_types = [ + (NodeType::Default, network_params.node_count), + (NodeType::Proposer, network_params.proposer_nodes), + ( + NodeType::ProofGenerator, + network_params.proof_generator_nodes, + ), + (NodeType::ProofVerifier, network_params.proof_verifier_nodes), + ]; + + let total: usize = node_types.iter().map(|(_, n)| n).sum(); + info!(target: "simulator", "Spawning {total} beacon nodes"); + + for (node_type, count) in node_types { + for _ in 0..count { + network + .add_beacon_node( + beacon_config.clone(), + mock_execution_config.clone(), + node_type, + ) + .await + .map_err(anyhow::Error::msg)?; + } + } + + Ok(()) + } + + /// Initialize the network environment and create the local network instance. + async fn init_network( + self, + ) -> anyhow::Result<( + TestEnvironment, + LocalNetworkParams, + LocalNetwork, + ClientConfig, + MockExecutionConfig, + )> { + info!(target: "simulator", "Initializing test network environment and local network"); + let Self { + env, + network_params, + logger_config, + disable_stdout, + client_config_transform, + spec_transform, + } = self; + + // Initialize logging + info!(target: "simulator", "Initializing logging with config: {:?}", logger_config); + + let file_mode = if logger_config.is_restricted { + 0o600 + } else { + 0o644 + }; + let (env, stdout_logging_layer, file_logging_layer, _see_logging_layer) = + env.init_tracing(logger_config.clone(), "lighthouse", file_mode); + + //TODO: optionally add discv5 logging layer for network tests + // Instantiate logging layers + let filters = build_workspace_filter().expect("should build workspace filter"); + let mut layers = vec![]; + + if let Some(layer) = (!disable_stdout).then(|| { + stdout_logging_layer + .with_filter(logger_config.debug_level) + .with_filter(filters.clone()) + .boxed() + }) { + layers.push(layer); + } + if let Some(file_logging_layer) = file_logging_layer { + layers.push( + file_logging_layer + .with_filter(logger_config.logfile_debug_level) + .with_filter(filters.clone()) + .boxed(), + ); + } + // Initialize the subscriber with the configured layers + tracing_subscriber::registry().with(layers).try_init()?; + + // Instantiate the environment. + let mut env = env.build_test_environment().map_err(anyhow::Error::msg)?; + + let mut spec = (*env.eth2_config.spec).clone(); + spec.genesis_delay = network_params.genesis_delay; + spec.min_genesis_active_validator_count = network_params.validator_count as u64; + if let Some(transform) = spec_transform { + transform(&mut spec); + } + env.eth2_config.spec = std::sync::Arc::new(spec); + + // Instantiate the local network + info!(target: "simulator", "Initializing local network with params: {:?}", network_params); + let (network, mut beacon_config, mock_execution_config) = + Box::pin(LocalNetwork::create_local_network( + None, + None, + network_params.clone(), + env.core_context(), + )) + .await + .map_err(anyhow::Error::msg)?; + + if let Some(transform) = client_config_transform { + transform(&mut beacon_config); + } + + Ok(( + env, + network_params, + network, + beacon_config, + mock_execution_config, + )) + } +} diff --git a/testing/simulator/src/test_utils/event_stream.rs b/testing/simulator/src/test_utils/event_stream.rs new file mode 100644 index 00000000000..ddd92835f2a --- /dev/null +++ b/testing/simulator/src/test_utils/event_stream.rs @@ -0,0 +1,56 @@ +//! Generic event stream wrapper for broadcast channels. +//! +//! [`EventStream`] wraps a `broadcast::Receiver` and provides ergonomic timeout-based +//! collection helpers used across simulator integration tests. + +use std::time::Duration; +use tokio::sync::broadcast; + +/// Wraps a `broadcast::Receiver` with assertion helpers. +pub struct EventStream { + rx: broadcast::Receiver, +} + +impl From> for EventStream { + fn from(rx: broadcast::Receiver) -> Self { + Self { rx } + } +} + +impl EventStream { + /// Collect `n` events matching `predicate` within `timeout`, or return an error. + pub async fn collect_n( + &mut self, + n: usize, + predicate: impl Fn(&E) -> bool, + timeout: Duration, + ) -> anyhow::Result> { + tokio::time::timeout(timeout, async { + let mut collected = Vec::with_capacity(n); + loop { + match self.rx.recv().await { + Ok(event) if predicate(&event) => { + collected.push(event); + if collected.len() >= n { + return Ok(collected); + } + } + Ok(_) => {} + Err(broadcast::error::RecvError::Lagged(skipped)) => { + return Err(anyhow::anyhow!( + "event stream lagged, skipped {skipped} events" + )); + } + Err(broadcast::error::RecvError::Closed) => { + return Err(anyhow::anyhow!( + "event stream closed before collecting {n} events (got {})", + collected.len() + )); + } + } + } + }) + .await + .map_err(|_| anyhow::anyhow!("timed out after {timeout:?} waiting for {n} events"))? + } +} diff --git a/testing/simulator/src/test_utils/mod.rs b/testing/simulator/src/test_utils/mod.rs new file mode 100644 index 00000000000..6083e9d392d --- /dev/null +++ b/testing/simulator/src/test_utils/mod.rs @@ -0,0 +1,98 @@ +//! Test network builder for creating local beacon node networks. +//! +//! Provides a builder pattern for setting up test networks with beacon nodes, +//! validator clients, and execution nodes. Used by simulator tests like +//! `basic_sim` and `proof_service_sim`. + +pub use crate::local_network::{LocalNetwork, LocalNetworkParams, NodeType}; +pub use beacon_chain::internal_events::InternalBeaconNodeEvent; +pub use environment::{LoggerConfig, test_utils::TestEnvironment}; +pub use eth2::{BeaconNodeHttpClient, types::StateId}; +pub use execution_layer::test_utils::{MockClientEvent, MockEventStream}; +mod event_stream; +pub use event_stream::EventStream; +pub use logging::build_workspace_filter; +pub use node_test_rig::ApiTopic; +pub use node_test_rig::{ + ClientConfig, MockExecutionConfig, ValidatorFiles, environment::EnvironmentBuilder, + testing_validator_config, +}; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use std::path::PathBuf; +pub use tracing::{info, level_filters::LevelFilter}; +use tracing_subscriber::{Layer, layer::SubscriberExt, util::SubscriberInitExt}; +pub use types::{Address, ChainSpec, Epoch, EthSpec, MinimalEthSpec}; + +const SUGGESTED_FEE_RECIPIENT: [u8; 20] = + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; + +mod builder; +pub use builder::TestNetworkFixtureBuilder; + +pub struct TestNetworkFixture { + pub env: TestEnvironment, + pub network: LocalNetwork, + pub config: TestConfig, +} + +pub struct TestConfig { + pub client: ClientConfig, + pub execution: MockExecutionConfig, + pub network_params: LocalNetworkParams, +} + +impl TestNetworkFixture { + pub fn builder() -> TestNetworkFixtureBuilder { + TestNetworkFixtureBuilder::default() + } + + /// Mark all payloads as valid on execution nodes. + pub fn payloads_valid(&mut self) { + self.network + .execution_nodes + .write() + .iter() + .for_each(|node| { + node.server.all_payloads_valid(); + }); + } + + /// Wait for the network to reach genesis by sleeping until the genesis time. + /// If genesis has already passed (late-joining node), returns immediately. + pub async fn wait_for_genesis(&self) -> anyhow::Result<()> { + if let Ok(duration) = self.network.duration_to_genesis().await { + tokio::time::sleep(duration).await; + } + Ok(()) + } +} + +// Ignore this for now because it conflicts with the `proof_engine` testing crate. +// We should migrate to defaulting to unused ports assigned by the OS instead of hardcoding ports. +#[tokio::test] +#[ignore] +async fn test_network_fixture_build() -> anyhow::Result<()> { + let mut fixture = TestNetworkFixtureBuilder::default() + .map_network_params(|params| { + params.genesis_delay = 20; + }) + .map_spec(|spec| { + *spec = spec.clone().set_slot_duration_ms::(1000); + spec.min_genesis_time = 0; + spec.altair_fork_epoch = Some(Epoch::new(0)); + spec.bellatrix_fork_epoch = Some(Epoch::new(0)); + spec.capella_fork_epoch = Some(Epoch::new(0)); + spec.deneb_fork_epoch = Some(Epoch::new(0)); + spec.electra_fork_epoch = Some(Epoch::new(0)); + spec.fulu_fork_epoch = Some(Epoch::new(2)); + }) + .build() + .await?; + fixture.payloads_valid(); + + fixture.wait_for_genesis().await?; + + tokio::time::sleep(std::time::Duration::from_secs(60)).await; + + Ok(()) +} diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 6990a2f61a7..5d03314506a 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -18,6 +18,7 @@ dirs = { workspace = true } doppelganger_service = { workspace = true } environment = { workspace = true } eth2 = { workspace = true } +execution_layer = { workspace = true } fdlimit = "0.3.0" graffiti_file = { workspace = true } hyper = { workspace = true } @@ -33,6 +34,7 @@ slashing_protection = { workspace = true } slot_clock = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } +typenum = { workspace = true } types = { workspace = true } validator_http_api = { workspace = true } validator_http_metrics = { workspace = true } diff --git a/validator_client/http_api/src/lib.rs b/validator_client/http_api/src/lib.rs index 8e9c077e57b..7ab2ae40290 100644 --- a/validator_client/http_api/src/lib.rs +++ b/validator_client/http_api/src/lib.rs @@ -58,6 +58,7 @@ use tracing::{info, warn}; use types::{ChainSpec, ConfigAndPreset, EthSpec}; use validator_dir::Builder as ValidatorDirBuilder; use validator_services::block_service::BlockService; +use validator_services::proof_service::ProofService; use warp::{Filter, reply::Response, sse::Event}; use warp_utils::reject::convert_rejection; use warp_utils::task::blocking_json_task; @@ -83,7 +84,7 @@ impl From for Error { /// A wrapper around all the items required to spawn the HTTP server. /// /// The server will gracefully handle the case where any fields are `None`. -pub struct Context { +pub struct Context { pub task_executor: TaskExecutor, pub api_secret: ApiSecret, pub block_service: Option, T>>, @@ -96,6 +97,7 @@ pub struct Context { pub config: Config, pub sse_logging_components: Option, pub slot_clock: T, + pub proof_service: Option, T>>>, } /// Configuration for the HTTP server. diff --git a/validator_client/lighthouse_validator_store/src/lib.rs b/validator_client/lighthouse_validator_store/src/lib.rs index cc9729b44d9..38700870b8d 100644 --- a/validator_client/lighthouse_validator_store/src/lib.rs +++ b/validator_client/lighthouse_validator_store/src/lib.rs @@ -1,5 +1,5 @@ use account_utils::validator_definitions::{PasswordStorage, ValidatorDefinition}; -use bls::{PublicKeyBytes, Signature}; +use bls::{PublicKeyBytes, Signature, SignatureBytes}; use doppelganger_service::DoppelgangerService; use eth2::types::PublishBlockRequest; use futures::{Stream, future::join_all, stream}; @@ -20,14 +20,14 @@ use task_executor::TaskExecutor; use tracing::{Instrument, debug, error, info, info_span, instrument, warn}; use types::{ AbstractExecPayload, Address, AggregateAndProof, Attestation, BeaconBlock, BlindedPayload, - ChainSpec, ContributionAndProof, Domain, Epoch, EthSpec, ExecutionPayloadEnvelope, Fork, - FullPayload, Graffiti, Hash256, PayloadAttestationData, PayloadAttestationMessage, - ProposerPreferences, SelectionProof, SignedAggregateAndProof, SignedBeaconBlock, - SignedContributionAndProof, SignedExecutionPayloadEnvelope, SignedProposerPreferences, - SignedRoot, SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, - SyncAggregatorSelectionData, SyncCommitteeContribution, SyncCommitteeMessage, - SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData, VoluntaryExit, - graffiti::GraffitiString, + ChainSpec, ContributionAndProof, Domain, Epoch, EthSpec, ExecutionPayloadEnvelope, + ExecutionProof, Fork, FullPayload, Graffiti, Hash256, PayloadAttestationData, + PayloadAttestationMessage, ProposerPreferences, SelectionProof, SignedAggregateAndProof, + SignedBeaconBlock, SignedContributionAndProof, SignedExecutionPayloadEnvelope, + SignedExecutionProof, SignedProposerPreferences, SignedRoot, SignedValidatorRegistrationData, + SignedVoluntaryExit, Slot, SyncAggregatorSelectionData, SyncCommitteeContribution, + SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData, + VoluntaryExit, graffiti::GraffitiString, }; use validator_store::{ AggregateToSign, AttestationToSign, ContributionToSign, DoppelgangerStatus, @@ -1348,6 +1348,43 @@ impl ValidatorStore for LighthouseValidatorS }) } + async fn sign_execution_proof( + &self, + validator_pubkey: PublicKeyBytes, + execution_proof: ExecutionProof, + signing_epoch: Epoch, + ) -> Result { + let signing_context = self.signing_context(Domain::ExecutionProof, signing_epoch); + let signing_method = self.doppelganger_bypassed_signing_method(validator_pubkey)?; + + let signature = signing_method + .get_signature::>( + SignableMessage::ExecutionProof(&execution_proof), + signing_context, + &self.spec, + &self.task_executor, + ) + .await?; + + let validator_index = self + .validator_index(&validator_pubkey) + .ok_or(ValidatorStoreError::UnknownPubkey(validator_pubkey))?; + + let signature = SignatureBytes::deserialize(&signature.serialize()) + .map_err(|_| Error::Middleware("Failed to serialize signature".to_string()))?; + + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_EXECUTION_PROOFS_TOTAL, + &[validator_metrics::SUCCESS], + ); + + Ok(SignedExecutionProof { + message: execution_proof, + validator_index, + signature, + }) + } + /// Prune the slashing protection database so that it remains performant. /// /// This function will only do actual pruning periodically, so it should usually be diff --git a/validator_client/signing_method/src/lib.rs b/validator_client/signing_method/src/lib.rs index 0dfde989464..932ca46b31f 100644 --- a/validator_client/signing_method/src/lib.rs +++ b/validator_client/signing_method/src/lib.rs @@ -49,6 +49,7 @@ pub enum SignableMessage<'a, E: EthSpec, Payload: AbstractExecPayload = FullP SignedContributionAndProof(&'a ContributionAndProof), ValidatorRegistration(&'a ValidatorRegistrationData), VoluntaryExit(&'a VoluntaryExit), + ExecutionProof(&'a ExecutionProof), ExecutionPayloadEnvelope(&'a ExecutionPayloadEnvelope), PayloadAttestationData(&'a PayloadAttestationData), ProposerPreferences(&'a ProposerPreferences), @@ -73,6 +74,7 @@ impl> SignableMessage<'_, E, Payload SignableMessage::SignedContributionAndProof(c) => c.signing_root(domain), SignableMessage::ValidatorRegistration(v) => v.signing_root(domain), SignableMessage::VoluntaryExit(exit) => exit.signing_root(domain), + SignableMessage::ExecutionProof(proof) => proof.signing_root(domain), SignableMessage::ExecutionPayloadEnvelope(e) => e.signing_root(domain), SignableMessage::PayloadAttestationData(d) => d.signing_root(domain), SignableMessage::ProposerPreferences(p) => p.signing_root(domain), @@ -239,6 +241,7 @@ impl SigningMethod { Web3SignerObject::ValidatorRegistration(v) } SignableMessage::VoluntaryExit(e) => Web3SignerObject::VoluntaryExit(e), + SignableMessage::ExecutionProof(p) => Web3SignerObject::ExecutionProof(p), SignableMessage::ExecutionPayloadEnvelope(e) => { Web3SignerObject::ExecutionPayloadEnvelope(e) } diff --git a/validator_client/signing_method/src/web3signer.rs b/validator_client/signing_method/src/web3signer.rs index baabb379479..2ac3e722105 100644 --- a/validator_client/signing_method/src/web3signer.rs +++ b/validator_client/signing_method/src/web3signer.rs @@ -19,6 +19,7 @@ pub enum MessageType { SyncCommitteeSelectionProof, SyncCommitteeContributionAndProof, ValidatorRegistration, + ExecutionProof, // TODO(gloas) verify w/ web3signer specs ExecutionPayloadEnvelope, PayloadAttestation, @@ -79,6 +80,7 @@ pub enum Web3SignerObject<'a, E: EthSpec, Payload: AbstractExecPayload> { SyncAggregatorSelectionData(&'a SyncAggregatorSelectionData), ContributionAndProof(&'a ContributionAndProof), ValidatorRegistration(&'a ValidatorRegistrationData), + ExecutionProof(&'a ExecutionProof), ExecutionPayloadEnvelope(&'a ExecutionPayloadEnvelope), PayloadAttestationData(&'a PayloadAttestationData), ProposerPreferences(&'a ProposerPreferences), @@ -147,6 +149,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> Web3SignerObject<'a, E, Pa MessageType::SyncCommitteeContributionAndProof } Web3SignerObject::ValidatorRegistration(_) => MessageType::ValidatorRegistration, + Web3SignerObject::ExecutionProof(_) => MessageType::ExecutionProof, Web3SignerObject::ExecutionPayloadEnvelope(_) => MessageType::ExecutionPayloadEnvelope, Web3SignerObject::PayloadAttestationData(_) => MessageType::PayloadAttestation, Web3SignerObject::ProposerPreferences(_) => MessageType::ProposerPreferences, diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index 0eb0e9e5dda..46c772d9b7f 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -519,4 +519,26 @@ pub struct ValidatorClient { display_order = 0 )] pub web3_signer_max_idle_connections: Option, + + #[clap( + long, + value_name = "HTTP-JSON-RPC-URL", + help = "URL of the proof engine HTTP JSON-RPC endpoint for EIP-8025 execution proofs. \ + When set, the validator client will monitor new blocks, request proofs from this \ + endpoint, sign completed proofs, and submit them to the beacon node.", + display_order = 0 + )] + pub proof_engine_endpoint: Option, + + #[clap( + long, + value_name = "TYPES", + value_delimiter = ',', + requires = "proof_engine_endpoint", + help = "Comma-separated list of proof type identifiers (u8) to request from the proof engine \ + (e.g., 0,1,2). If not specified, defaults to '0,1,2,3' \ + (EthrexRisc0, EthrexSP1, EthrexZisk, RethOpenVM).", + display_order = 0 + )] + pub proof_types: Option>, } diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index d68a78b705f..2dc8878ef31 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -8,6 +8,7 @@ use directory::{ get_network_dir, }; use eth2::types::{Graffiti, GraffitiPolicy}; +use execution_layer::eip8025::types::ProofTypes; use graffiti_file::GraffitiFile; use initialized_validators::Config as InitializedValidatorsConfig; use lighthouse_validator_store::Config as ValidatorStoreConfig; @@ -18,7 +19,9 @@ use std::net::IpAddr; use std::path::PathBuf; use std::time::Duration; use tracing::{info, warn}; +use typenum::Unsigned; use types::GRAFFITI_BYTES_LEN; +use types::execution::eip8025::MaxExecutionProofsPerPayload; use validator_http_api::{self, PK_FILENAME}; use validator_http_metrics; @@ -92,6 +95,11 @@ pub struct Config { #[serde(flatten)] pub initialized_validators: InitializedValidatorsConfig, pub disable_attesting: bool, + /// URL of the proof engine HTTP JSON-RPC endpoint for EIP-8025 execution proofs. + pub proof_engine_endpoint: Option, + /// Proof types to request from the proof engine. + #[serde(default)] + pub proof_types: ProofTypes, } impl Default for Config { @@ -139,6 +147,8 @@ impl Default for Config { distributed: false, initialized_validators: <_>::default(), disable_attesting: false, + proof_engine_endpoint: None, + proof_types: ProofTypes::default(), } } } @@ -284,6 +294,36 @@ impl Config { .web3_signer_max_idle_connections = Some(n); } + /* + * Proof Engine (EIP-8025) + */ + if let Some(proof_engine_endpoint) = validator_client_config.proof_engine_endpoint.as_ref() + { + config.proof_engine_endpoint = Some( + SensitiveUrl::parse(proof_engine_endpoint) + .map_err(|e| format!("Unable to parse proof engine URL: {:?}", e))?, + ); + } + + config.proof_types = if let Some(vals) = &validator_client_config.proof_types { + use execution_layer::eip8025::types::ProofType; + let proof_types = vals + .iter() + .copied() + .map(ProofType::from_u8) + .collect::, _>>() + .map_err(|e| format!("Invalid --proof-types value: {e:?}"))?; + if proof_types.len() > MaxExecutionProofsPerPayload::to_usize() { + return Err(format!( + "--proof-types supports at most {} values", + MaxExecutionProofsPerPayload::to_usize() + )); + } + ProofTypes::from(proof_types) + } else { + ProofTypes::default() + }; + /* * Http API server */ diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 71d93334935..7e7556c66f1 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -47,6 +47,7 @@ use validator_services::{ latency_service, payload_attestation_service::PayloadAttestationService, preparation_service::{PreparationService, PreparationServiceBuilder}, + proof_service::ProofService, proposer_preferences_service::ProposerPreferencesService, sync_committee_service::SyncCommitteeService, }; @@ -95,6 +96,7 @@ pub struct ProductionValidatorClient { http_api_listen_addr: Option, config: Config, genesis_time: u64, + proof_service: Option, SystemTimeSlotClock>>>, } impl ProductionValidatorClient { @@ -575,6 +577,35 @@ impl ProductionValidatorClient { context.eth2_config.spec.clone(), ); + let proof_service = config.proof_engine_endpoint.as_ref().map(|endpoint| { + info!(endpoint = %endpoint, "Initializing proof engine client"); + let url_str = endpoint.expose_full(); + let proof_engine_client = Arc::new( + if let Some(idx) = execution_layer::test_utils::parse_mock_index(url_str.as_str()) { + let mock = execution_layer::test_utils::get_mock_proof_engine::(idx) + .unwrap_or_else(|| { + debug!( + idx, + "No pre-registered mock; creating MockProofNodeClient on the fly" + ); + execution_layer::test_utils::register_mock_proof_engine::(idx, 0) + }); + execution_layer::eip8025::HttpProofEngine::with_proof_node(mock) + } else { + execution_layer::eip8025::HttpProofEngine::new(endpoint.clone(), None) + }, + ); + + Arc::new(ProofService::new( + validator_store.clone(), + beacon_nodes.clone(), + proof_engine_client, + slot_clock.clone(), + context.executor.clone(), + config.proof_types.clone(), + )) + }); + Ok(Self { context, duties_service, @@ -590,6 +621,7 @@ impl ProductionValidatorClient { slot_clock, http_api_listen_addr: None, genesis_time, + proof_service, }) } @@ -616,6 +648,7 @@ impl ProductionValidatorClient { config: self.config.http_api.clone(), sse_logging_components: self.context.sse_logging_components.clone(), slot_clock: self.slot_clock.clone(), + proof_service: self.proof_service.clone(), }); let exit = self.context.executor.exit(); @@ -684,6 +717,13 @@ impl ProductionValidatorClient { info!("Doppelganger protection disabled.") } + if let Some(proof_service) = &self.proof_service { + proof_service + .clone() + .start_service() + .map_err(|e| format!("Unable to start proof service: {}", e))?; + } + let context = self.context.clone(); spawn_notifier( self.duties_service.clone(), diff --git a/validator_client/validator_metrics/src/lib.rs b/validator_client/validator_metrics/src/lib.rs index 46a86381f91..0d1fb4a8cfd 100644 --- a/validator_client/validator_metrics/src/lib.rs +++ b/validator_client/validator_metrics/src/lib.rs @@ -126,6 +126,13 @@ pub static SIGNED_VALIDATOR_REGISTRATIONS_TOTAL: LazyLock> &["status"], ) }); +pub static SIGNED_EXECUTION_PROOFS_TOTAL: LazyLock> = LazyLock::new(|| { + try_create_int_counter_vec( + "vc_signed_execution_proofs_total", + "Total count of ExecutionProof signings", + &["status"], + ) +}); pub static DUTIES_SERVICE_TIMES: LazyLock> = LazyLock::new(|| { try_create_histogram_vec( "vc_duties_service_task_times_seconds", diff --git a/validator_client/validator_services/Cargo.toml b/validator_client/validator_services/Cargo.toml index 25829682655..93ee2dcfb26 100644 --- a/validator_client/validator_services/Cargo.toml +++ b/validator_client/validator_services/Cargo.toml @@ -8,7 +8,8 @@ authors = ["Sigma Prime "] beacon_node_fallback = { workspace = true } bls = { workspace = true } either = { workspace = true } -eth2 = { workspace = true } +eth2 = { workspace = true, features = ["events"] } +execution_layer = { workspace = true } futures = { workspace = true } graffiti_file = { workspace = true } logging = { workspace = true } diff --git a/validator_client/validator_services/src/lib.rs b/validator_client/validator_services/src/lib.rs index c39ef4499b7..23e69faf348 100644 --- a/validator_client/validator_services/src/lib.rs +++ b/validator_client/validator_services/src/lib.rs @@ -5,6 +5,7 @@ pub mod latency_service; pub mod notifier_service; pub mod payload_attestation_service; pub mod preparation_service; +pub mod proof_service; pub mod proposer_preferences_service; pub mod sync; pub mod sync_committee_service; diff --git a/validator_client/validator_services/src/proof_service.rs b/validator_client/validator_services/src/proof_service.rs new file mode 100644 index 00000000000..05dc1c01aaf --- /dev/null +++ b/validator_client/validator_services/src/proof_service.rs @@ -0,0 +1,387 @@ +//! EIP-8025 execution proof service. +//! +//! This service requests execution proofs, signs completed local proof material, +//! and submits signed proofs to the beacon node. + +use beacon_node_fallback::BeaconNodeFallback; +use eth2::types::{BlockId, EventKind, EventTopic}; +use execution_layer::NewPayloadRequest; +use execution_layer::eip8025::types::ProofTypes; +use execution_layer::eip8025::{HttpProofEngine, ProofEvent}; +use futures::StreamExt; +use parking_lot::RwLock; +use slot_clock::SlotClock; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use task_executor::TaskExecutor; +use tracing::{debug, error, info, warn}; +use types::execution::eip8025::{ProofAttributes, ProofData, PublicInput}; +use types::{EthSpec, ExecutionProof, Hash256}; +use validator_store::{DoppelgangerStatus, ValidatorStore}; + +/// Discard tracking entries older than this. +const PROOF_REQUEST_STALE_TIMEOUT: Duration = Duration::from_secs(300); + +/// An outstanding proof request awaiting completion from the proof engine. +struct OutstandingProofRequest { + /// Proof types we are still waiting for. + pending_proof_types: HashSet, + /// Slot of the block, for epoch derivation during signing. + slot: types::Slot, + /// When the request was made. + requested_at: Instant, +} + +/// Background service for execution proof handling. +pub struct ProofService { + inner: Arc>, +} + +struct Inner { + validator_store: Arc, + beacon_nodes: Arc>, + proof_engine: Arc, + executor: TaskExecutor, + proof_types: Vec, + outstanding_requests: RwLock>, +} + +impl ProofService { + /// Create a new proof service. + pub fn new( + validator_store: Arc, + beacon_nodes: Arc>, + proof_engine: Arc, + _slot_clock: T, + executor: TaskExecutor, + proof_types: ProofTypes, + ) -> Self { + let proof_types = proof_types + .iter() + .map(|proof_type| proof_type.to_u8()) + .collect(); + + Self { + inner: Arc::new(Inner { + validator_store, + beacon_nodes, + proof_engine, + executor, + proof_types, + outstanding_requests: RwLock::new(HashMap::new()), + }), + } + } + + /// Start the proof service background task. + pub fn start_service(self: Arc) -> Result<(), String> { + let inner = self.inner.clone(); + self.inner.executor.spawn( + async move { inner.monitor_task().await }, + "proof_service_monitor", + ); + + info!("Proof service started - monitoring beacon and proof engine events"); + Ok(()) + } +} + +impl Inner { + async fn subscribe_to_events( + &self, + ) -> Result< + impl futures::Stream, eth2::Error>>, + String, + > { + self.beacon_nodes + .first_success( + |node| async move { node.get_events::(&[EventTopic::Block]).await }, + ) + .await + .map_err(|e| format!("All beacon nodes failed to provide event stream: {}", e)) + } + + /// Monitor beacon-node block events and proof-engine events over SSE. + async fn monitor_task(self: Arc) { + info!("Starting proof service event monitoring via SSE"); + + loop { + let mut beacon_stream = match self.subscribe_to_events().await { + Ok(stream) => { + info!("Successfully subscribed to block events"); + stream + } + Err(e) => { + error!(error = %e, "Failed to subscribe to block events, retrying"); + tokio::time::sleep(Duration::from_secs(2)).await; + continue; + } + }; + let mut proof_stream = self.proof_engine.subscribe_proof_events(None); + let mut cleanup_interval = tokio::time::interval(PROOF_REQUEST_STALE_TIMEOUT); + cleanup_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + cleanup_interval.tick().await; + + loop { + tokio::select! { + event_result = beacon_stream.next() => { + match event_result { + Some(Ok(EventKind::Block(sse_block))) => { + if sse_block.execution_optimistic { + debug!( + slot = sse_block.slot.as_u64(), + "Skipping execution optimistic block" + ); + continue; + } + self.handle_block_event(sse_block.block, sse_block.slot).await; + } + Some(Ok(_)) => {} + Some(Err(e)) => { + warn!(error = %e, "Beacon event stream error, will reconnect"); + break; + } + None => { + warn!("Beacon event stream ended, reconnecting"); + break; + } + } + } + event = proof_stream.next() => { + match event { + Some(Ok(proof_event)) => { + self.handle_proof_engine_event(proof_event).await; + } + Some(Err(e)) => { + warn!(error = %e, "Proof engine SSE error, will reconnect"); + break; + } + None => { + warn!("Proof engine SSE stream ended, reconnecting"); + break; + } + } + } + _ = cleanup_interval.tick() => { + self.cleanup_stale_requests(); + } + } + } + + tokio::time::sleep(Duration::from_secs(2)).await; + } + } + + /// Handle a new block event by fetching the full block via RPC then requesting proofs. + async fn handle_block_event(&self, block_root: Hash256, slot: types::Slot) { + info!( + slot = slot.as_u64(), + block = %block_root, + "New block detected, fetching full block via RPC" + ); + + let signed_block = match self + .beacon_nodes + .first_success(|node| async move { + node.get_beacon_blocks::(BlockId::Root(block_root)) + .await + }) + .await + { + Ok(Some(response)) => response.data().clone(), + Ok(None) => { + warn!(block = %block_root, "Block not found on beacon node"); + return; + } + Err(e) => { + error!(block = %block_root, error = %e, "Failed to fetch block via RPC"); + return; + } + }; + + let new_payload_request = match NewPayloadRequest::try_from(signed_block.message()) { + Ok(req) => req, + Err(e) => { + error!(block = %block_root, error = ?e, "Failed to construct NewPayloadRequest"); + return; + } + }; + + let proof_attributes = ProofAttributes { + proof_types: self.proof_types.clone(), + }; + + match self + .proof_engine + .request_proofs(new_payload_request, proof_attributes) + .await + { + Ok(new_payload_request_root) => { + let pending_proof_types = self.proof_types.iter().copied().collect::>(); + let num_types = pending_proof_types.len(); + self.outstanding_requests.write().insert( + new_payload_request_root, + OutstandingProofRequest { + pending_proof_types, + slot, + requested_at: Instant::now(), + }, + ); + debug!( + root = %new_payload_request_root, + block = %block_root, + num_proof_types = num_types, + "Proof generation requested, tracking for completion" + ); + } + Err(e) => { + error!(block = %block_root, error = ?e, "Failed to request proofs from proof engine"); + } + } + } + + /// Process a single proof-engine SSE event. + async fn handle_proof_engine_event(&self, event: ProofEvent) { + let root = event.new_payload_request_root(); + let proof_type = event.proof_type(); + + let is_tracked = self + .outstanding_requests + .read() + .get(&root) + .map(|req| req.pending_proof_types.contains(&proof_type)) + .unwrap_or(false); + + if !is_tracked { + return; + } + + match event { + ProofEvent::ProofComplete(complete) => { + self.handle_proof_complete(complete.new_payload_request_root, complete.proof_type) + .await; + } + ProofEvent::ProofFailure(failure) => { + warn!( + root = %failure.new_payload_request_root, + proof_type = failure.proof_type, + reason = ?failure.reason, + error = %failure.error, + "Proof generation failed" + ); + self.remove_pending_proof_type( + failure.new_payload_request_root, + failure.proof_type, + ); + } + } + } + + /// Fetch a completed proof from the proof engine, sign it, and submit to the beacon node. + async fn handle_proof_complete(&self, root: Hash256, proof_type: u8) { + let proof_bytes = match self.proof_engine.get_proof(root, proof_type).await { + Ok(bytes) => bytes, + Err(e) => { + error!(root = %root, proof_type, error = ?e, "Failed to fetch completed proof"); + return; + } + }; + + let proof_data = match ProofData::new(proof_bytes.to_vec()) { + Ok(data) => data, + Err(e) => { + error!(root = %root, proof_type, error = ?e, "Proof data exceeds max size"); + return; + } + }; + + let execution_proof = ExecutionProof { + proof_data, + proof_type, + public_input: PublicInput { + new_payload_request_root: root, + }, + }; + + let epoch = self + .outstanding_requests + .read() + .get(&root) + .map(|req| req.slot.epoch(S::E::slots_per_epoch())); + + let Some(epoch) = epoch else { + return; + }; + + let Some(pubkey) = self + .validator_store + .voting_pubkeys::, _>(DoppelgangerStatus::only_safe) + .first() + .cloned() + else { + warn!("No safe validators available to sign completed proof"); + return; + }; + + match self + .validator_store + .sign_execution_proof(pubkey, execution_proof, epoch) + .await + { + Ok(signed_proof) => { + match self + .beacon_nodes + .first_success(move |node| { + let proof = signed_proof.clone(); + async move { node.post_beacon_pool_execution_proofs(&[proof]).await } + }) + .await + { + Ok(_) => { + info!(root = %root, proof_type, ?pubkey, "Completed proof signed and submitted"); + } + Err(e) => { + warn!(root = %root, proof_type, error = %e, "Failed to submit completed proof"); + } + } + } + Err(e) => { + warn!(root = %root, proof_type, error = ?e, "Failed to sign completed proof"); + } + } + + self.remove_pending_proof_type(root, proof_type); + } + + /// Remove a single proof type from an outstanding request. + /// + /// If all requested proof types have been resolved the entry is removed entirely. + fn remove_pending_proof_type(&self, root: Hash256, proof_type: u8) { + let mut requests = self.outstanding_requests.write(); + if let Some(entry) = requests.get_mut(&root) { + entry.pending_proof_types.remove(&proof_type); + if entry.pending_proof_types.is_empty() { + requests.remove(&root); + debug!(root = %root, "All proof types resolved, removing from tracker"); + } + } + } + + /// Remove outstanding requests that have exceeded the stale timeout. + fn cleanup_stale_requests(&self) { + let mut requests = self.outstanding_requests.write(); + let before = requests.len(); + requests.retain(|root, req| { + let stale = req.requested_at.elapsed() > PROOF_REQUEST_STALE_TIMEOUT; + if stale { + warn!(root = %root, "Removing stale proof request"); + } + !stale + }); + let removed = before - requests.len(); + if removed > 0 { + info!(removed, "Cleaned up stale proof requests"); + } + } +} diff --git a/validator_client/validator_store/src/lib.rs b/validator_client/validator_store/src/lib.rs index d40c7994f11..b096df32de7 100644 --- a/validator_client/validator_store/src/lib.rs +++ b/validator_client/validator_store/src/lib.rs @@ -7,11 +7,12 @@ use std::future::Future; use std::sync::Arc; use types::{ Address, Attestation, AttestationError, BlindedBeaconBlock, Epoch, EthSpec, - ExecutionPayloadEnvelope, Graffiti, Hash256, PayloadAttestationData, PayloadAttestationMessage, - ProposerPreferences, SelectionProof, SignedAggregateAndProof, SignedBlindedBeaconBlock, - SignedContributionAndProof, SignedExecutionPayloadEnvelope, SignedProposerPreferences, - SignedValidatorRegistrationData, Slot, SyncCommitteeContribution, SyncCommitteeMessage, - SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData, + ExecutionPayloadEnvelope, ExecutionProof, Graffiti, Hash256, PayloadAttestationData, + PayloadAttestationMessage, ProposerPreferences, SelectionProof, SignedAggregateAndProof, + SignedBlindedBeaconBlock, SignedContributionAndProof, SignedExecutionPayloadEnvelope, + SignedExecutionProof, SignedProposerPreferences, SignedValidatorRegistrationData, Slot, + SyncCommitteeContribution, SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, + ValidatorRegistrationData, }; #[derive(Debug, PartialEq, Clone)] @@ -192,6 +193,14 @@ pub trait ValidatorStore: Send + Sync { contributions: Vec>, ) -> impl Stream>, Error>> + Send; + /// Sign an execution proof for EIP-8025 optional execution verification. + fn sign_execution_proof( + &self, + validator_pubkey: PublicKeyBytes, + execution_proof: ExecutionProof, + signing_epoch: Epoch, + ) -> impl Future>> + Send; + /// Prune the slashing protection database so that it remains performant. /// /// This function will only do actual pruning periodically, so it should usually be