diff --git a/.github/workflows/nightly-tests.yml b/.github/workflows/nightly-tests.yml index be52c5b84d..636d0ea0dd 100644 --- a/.github/workflows/nightly-tests.yml +++ b/.github/workflows/nightly-tests.yml @@ -6,6 +6,11 @@ on: # Run at 8:30 AM UTC every day - cron: '30 8 * * *' workflow_dispatch: # Allow manual triggering + inputs: + branch: + description: 'Branch to test' + required: false + default: 'unstable' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -47,6 +52,8 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v5 + with: + ref: ${{ inputs.branch || 'unstable' }} - name: Get latest version of stable Rust uses: moonrepo/setup-rust@v1 with: @@ -57,28 +64,6 @@ jobs: run: make test-beacon-chain-${{ matrix.fork }} timeout-minutes: 60 - http-api-tests: - name: http-api-tests - needs: setup-matrix - runs-on: 'ubuntu-latest' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - strategy: - matrix: - fork: ${{ fromJson(needs.setup-matrix.outputs.forks) }} - fail-fast: false - steps: - - uses: actions/checkout@v5 - - name: Get latest version of stable Rust - uses: moonrepo/setup-rust@v1 - with: - channel: stable - cache-target: release - bins: cargo-nextest - - name: Run http_api tests for ${{ matrix.fork }} - run: make test-http-api-${{ matrix.fork }} - timeout-minutes: 60 - op-pool-tests: name: op-pool-tests needs: setup-matrix @@ -91,6 +76,8 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v5 + with: + ref: ${{ inputs.branch || 'unstable' }} - name: Get latest version of stable Rust uses: moonrepo/setup-rust@v1 with: @@ -113,6 +100,8 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v5 + with: + ref: ${{ inputs.branch || 'unstable' }} - name: Get latest version of stable Rust uses: moonrepo/setup-rust@v1 with: diff --git a/Cargo.lock b/Cargo.lock index 622ff88183..913382fe66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1608,9 +1608,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] @@ -3098,7 +3098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4729,7 +4729,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6291,7 +6291,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7199,7 +7199,7 @@ dependencies = [ "once_cell", "socket2 0.6.1", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -7716,7 +7716,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -8781,7 +8781,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix 1.1.2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -10132,7 +10132,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2726af13a1..78c63875d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,7 +116,7 @@ bincode = "1" bitvec = "1" bls = { path = "crypto/bls" } byteorder = "1" -bytes = "1" +bytes = "1.11.1" # Turn off c-kzg's default features which include `blst/portable`. We can turn on blst's portable # feature ourselves when desired. c-kzg = { version = "2.1", default-features = false } diff --git a/account_manager/src/validator/exit.rs b/account_manager/src/validator/exit.rs index 5ea77f284e..b0b5c8a59d 100644 --- a/account_manager/src/validator/exit.rs +++ b/account_manager/src/validator/exit.rs @@ -102,7 +102,7 @@ pub fn cli_run(matches: &ArgMatches, env: Environment) -> Result< let client = BeaconNodeHttpClient::new( SensitiveUrl::parse(&server_url) .map_err(|e| format!("Failed to parse beacon http server: {:?}", e))?, - Timeouts::set_all(Duration::from_secs(env.eth2_config.spec.seconds_per_slot)), + Timeouts::set_all(env.eth2_config.spec.get_slot_duration()), ); let eth2_network_config = env @@ -230,7 +230,7 @@ async fn publish_voluntary_exit( loop { // Sleep for a slot duration and then check if voluntary exit was processed // by checking the validator status. - sleep(Duration::from_secs(spec.seconds_per_slot)).await; + sleep(spec.get_slot_duration()).await; let validator_data = get_validator_data(client, &keypair.pk).await?; match validator_data.status { @@ -251,7 +251,9 @@ async fn publish_voluntary_exit( eprintln!("Please keep your validator running till exit epoch"); eprintln!( "Exit epoch in approximately {} secs", - (exit_epoch - current_epoch) * spec.seconds_per_slot * E::slots_per_epoch() + (exit_epoch - current_epoch) + * spec.get_slot_duration().as_secs() + * E::slots_per_epoch() ); break; } @@ -350,7 +352,7 @@ fn get_current_epoch(genesis_time: u64, spec: &ChainSpec) -> Option< let slot_clock = SystemTimeSlotClock::new( spec.genesis_slot, Duration::from_secs(genesis_time), - Duration::from_secs(spec.seconds_per_slot), + spec.get_slot_duration(), ); slot_clock.now().map(|s| s.epoch(E::slots_per_epoch())) } diff --git a/beacon_node/beacon_chain/src/beacon_block_streamer.rs b/beacon_node/beacon_chain/src/beacon_block_streamer.rs index a462376cc0..edbdd6d4d9 100644 --- a/beacon_node/beacon_chain/src/beacon_block_streamer.rs +++ b/beacon_node/beacon_chain/src/beacon_block_streamer.rs @@ -748,7 +748,8 @@ mod tests { .execution_block_generator() .move_to_terminal_block() .expect("should move to terminal block"); - let timestamp = harness.get_timestamp_at_slot() + harness.spec.seconds_per_slot; + let timestamp = + harness.get_timestamp_at_slot() + harness.spec.get_slot_duration().as_secs(); harness .execution_block_generator() .modify_last_block(|block| { diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 97cd757ca3..ae160431cd 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -4738,7 +4738,8 @@ impl BeaconChain { // 1. It seems we have time to propagate and still receive the proposer boost. // 2. The current head block was seen late. // 3. The `get_proposer_head` conditions from fork choice pass. - let proposing_on_time = slot_delay < self.config.re_org_cutoff(self.spec.seconds_per_slot); + let proposing_on_time = + slot_delay < self.config.re_org_cutoff(self.spec.get_slot_duration()); if !proposing_on_time { debug!(reason = "not proposing on time", "Not attempting re-org"); return None; @@ -4932,7 +4933,7 @@ impl BeaconChain { let proposal_epoch = proposal_slot.epoch(T::EthSpec::slots_per_epoch()); if head_state.current_epoch() == proposal_epoch { return get_expected_withdrawals(&unadvanced_state, &self.spec) - .map(|(withdrawals, _)| withdrawals) + .map(Into::into) .map_err(Error::PrepareProposerFailed); } @@ -4950,7 +4951,7 @@ impl BeaconChain { &self.spec, )?; get_expected_withdrawals(&advanced_state, &self.spec) - .map(|(withdrawals, _)| withdrawals) + .map(Into::into) .map_err(Error::PrepareProposerFailed) } @@ -5028,7 +5029,7 @@ impl BeaconChain { .and_then(|slot_start| { let now = self.slot_clock.now_duration()?; let slot_delay = now.saturating_sub(slot_start); - Some(slot_delay <= self.config.re_org_cutoff(self.spec.seconds_per_slot)) + Some(slot_delay <= self.config.re_org_cutoff(self.spec.get_slot_duration())) }) .unwrap_or(false) } else { @@ -5145,7 +5146,7 @@ impl BeaconChain { ); block_delays .observed - .is_some_and(|delay| delay >= self.slot_clock.unagg_attestation_production_delay()) + .is_some_and(|delay| delay >= self.spec.get_unaggregated_attestation_due()) } /// Produce a block for some `slot` upon the given `state`. diff --git a/beacon_node/beacon_chain/src/bellatrix_readiness.rs b/beacon_node/beacon_chain/src/bellatrix_readiness.rs index 412870354b..88ccc21b85 100644 --- a/beacon_node/beacon_chain/src/bellatrix_readiness.rs +++ b/beacon_node/beacon_chain/src/bellatrix_readiness.rs @@ -147,7 +147,7 @@ impl BeaconChain { if let Some(bellatrix_epoch) = self.spec.bellatrix_fork_epoch { let bellatrix_slot = bellatrix_epoch.start_slot(T::EthSpec::slots_per_epoch()); let bellatrix_readiness_preparation_slots = - BELLATRIX_READINESS_PREPARATION_SECONDS / self.spec.seconds_per_slot; + BELLATRIX_READINESS_PREPARATION_SECONDS / self.spec.get_slot_duration().as_secs(); if self.execution_layer.is_some() { // The user has already configured an execution layer, start checking for readiness diff --git a/beacon_node/beacon_chain/src/block_verification_types.rs b/beacon_node/beacon_chain/src/block_verification_types.rs index 84e600cd40..6a028e6c98 100644 --- a/beacon_node/beacon_chain/src/block_verification_types.rs +++ b/beacon_node/beacon_chain/src/block_verification_types.rs @@ -279,7 +279,7 @@ impl AvailabilityPendingExecutedBlock { } } -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct BlockImportData { pub block_root: Hash256, pub state: BeaconState, diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index 04aba2d2a4..59004367fa 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -984,7 +984,6 @@ where complete_blob_backfill, slot_clock.clone(), self.kzg.clone(), - store.clone(), custody_context.clone(), self.spec.clone(), ) @@ -1238,7 +1237,6 @@ fn build_data_columns_from_blobs( if block.fork_name_unchecked().gloas_enabled() { build_data_column_sidecars_gloas( - kzg_commitments, block.message().tree_hash_root(), block.slot(), blob_cells_and_proofs_vec, diff --git a/beacon_node/beacon_chain/src/canonical_head.rs b/beacon_node/beacon_chain/src/canonical_head.rs index 417d7f4e2f..db071db166 100644 --- a/beacon_node/beacon_chain/src/canonical_head.rs +++ b/beacon_node/beacon_chain/src/canonical_head.rs @@ -856,6 +856,7 @@ impl BeaconChain { .as_utf8_lossy(), &self.slot_clock, self.event_handler.as_ref(), + &self.spec, ); if is_epoch_transition || reorg_distance.is_some() { @@ -1292,6 +1293,7 @@ fn observe_head_block_delays( head_block_graffiti: String, slot_clock: &S, event_handler: Option<&ServerSentEventHandler>, + spec: &ChainSpec, ) { let Some(block_time_set_as_head) = slot_clock.now_duration() else { // Practically unreachable: the slot clock's time should not be before the UNIX epoch. @@ -1421,7 +1423,7 @@ fn observe_head_block_delays( // Determine whether the block has been set as head too late for proper attestation // production. - let late_head = attestable_delay >= slot_clock.unagg_attestation_production_delay(); + let late_head = attestable_delay >= spec.get_unaggregated_attestation_due(); // If the block was enshrined as head too late for attestations to be created for it, // log a debug warning and increment a metric. diff --git a/beacon_node/beacon_chain/src/chain_config.rs b/beacon_node/beacon_chain/src/chain_config.rs index 1f5abc4891..711ffdc99c 100644 --- a/beacon_node/beacon_chain/src/chain_config.rs +++ b/beacon_node/beacon_chain/src/chain_config.rs @@ -168,11 +168,9 @@ impl Default for ChainConfig { impl ChainConfig { /// The latest delay from the start of the slot at which to attempt a 1-slot re-org. - pub fn re_org_cutoff(&self, seconds_per_slot: u64) -> Duration { + pub fn re_org_cutoff(&self, slot_duration: Duration) -> Duration { self.re_org_cutoff_millis .map(Duration::from_millis) - .unwrap_or_else(|| { - Duration::from_secs(seconds_per_slot) / DEFAULT_RE_ORG_CUTOFF_DENOMINATOR - }) + .unwrap_or_else(|| slot_duration / DEFAULT_RE_ORG_CUTOFF_DENOMINATOR) } } diff --git a/beacon_node/beacon_chain/src/custody_context.rs b/beacon_node/beacon_chain/src/custody_context.rs index c512ce616a..72f62db1b4 100644 --- a/beacon_node/beacon_chain/src/custody_context.rs +++ b/beacon_node/beacon_chain/src/custody_context.rs @@ -113,8 +113,9 @@ impl ValidatorRegistrations { // Apply the change from the next epoch after adding some delay buffer to ensure // the node has enough time to subscribe to subnets etc, and to avoid having // inconsistent column counts within an epoch. - let effective_delay_slots = - CUSTODY_CHANGE_DA_EFFECTIVE_DELAY_SECONDS / spec.seconds_per_slot; + let effective_delay_slots = CUSTODY_CHANGE_DA_EFFECTIVE_DELAY_SECONDS + .checked_div(spec.get_slot_duration().as_secs()) + .unwrap_or(1); let effective_epoch = (current_slot + effective_delay_slots).epoch(E::slots_per_epoch()) + 1; self.epoch_validator_custody_requirements diff --git a/beacon_node/beacon_chain/src/data_availability_checker.rs b/beacon_node/beacon_chain/src/data_availability_checker.rs index 846a55557a..a7738b0a3e 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker.rs @@ -6,9 +6,7 @@ use crate::data_availability_checker::overflow_lru_cache::{ DataAvailabilityCheckerInner, ReconstructColumnsDecision, }; use crate::data_availability_router::AvailabilityCache; -use crate::{ - BeaconChain, BeaconChainTypes, BeaconStore, BlockProcessStatus, CustodyContext, metrics, -}; +use crate::{BeaconChain, BeaconChainTypes, BlockProcessStatus, CustodyContext, metrics}; use educe::Educe; use kzg::Kzg; use slot_clock::SlotClock; @@ -28,7 +26,6 @@ use types::{ mod error; mod overflow_lru_cache; -mod state_lru_cache; use crate::data_availability_checker::error::Error; use crate::data_column_verification::{ @@ -54,7 +51,6 @@ use types::new_non_zero_usize; /// `PendingComponents` are now never removed from the cache manually are only removed via LRU /// eviction to prevent race conditions (#7961), so we expect this cache to be full all the time. const OVERFLOW_LRU_CAPACITY_NON_ZERO: NonZeroUsize = new_non_zero_usize(32); -const STATE_LRU_CAPACITY_NON_ZERO: NonZeroUsize = new_non_zero_usize(32); /// Cache to hold fully valid data that can't be imported to fork-choice yet. After Dencun hard-fork /// blocks have a sidecar of data that is received separately from the network. We call the concept @@ -123,13 +119,11 @@ impl DataAvailabilityChecker { complete_blob_backfill: bool, slot_clock: T::SlotClock, kzg: Arc, - store: BeaconStore, custody_context: Arc>, spec: Arc, ) -> Result { let inner = DataAvailabilityCheckerInner::new( OVERFLOW_LRU_CAPACITY_NON_ZERO, - store, custody_context.clone(), spec.clone(), )?; @@ -360,7 +354,6 @@ impl DataAvailabilityChecker { /// Collects metrics from the data availability checker. pub fn metrics(&self) -> DataAvailabilityCheckerMetrics { DataAvailabilityCheckerMetrics { - state_cache_size: self.availability_cache.state_cache_size(), block_cache_size: self.availability_cache.block_cache_size(), } } @@ -575,7 +568,6 @@ impl AvailabilityCache for DataAvailabilityChecker { /// Helper struct to group data availability checker metrics. pub struct DataAvailabilityCheckerMetrics { - pub state_cache_size: usize, pub block_cache_size: usize, } @@ -927,7 +919,6 @@ mod test { use std::collections::HashSet; use std::sync::Arc; use std::time::Duration; - use store::HotColdDB; use types::data::DataColumn; use types::{ ChainSpec, ColumnIndex, DataColumnSidecarFulu, EthSpec, ForkName, MainnetEthSpec, Slot, @@ -1135,7 +1126,7 @@ mod test { let invalid_sidecar = DataColumnSidecar::Fulu(DataColumnSidecarFulu { column: DataColumn::::empty(), index: *d.index(), - kzg_commitments: d.kzg_commitments().clone(), + kzg_commitments: d.kzg_commitments().unwrap().clone(), kzg_proofs: d.kzg_proofs().clone(), signed_block_header: d.signed_block_header().unwrap().clone(), kzg_commitments_inclusion_proof: d @@ -1265,10 +1256,9 @@ mod test { let slot_clock = TestingSlotClock::new( Slot::new(0), Duration::from_secs(0), - Duration::from_secs(spec.seconds_per_slot), + spec.get_slot_duration(), ); let kzg = get_kzg(&spec); - let store = Arc::new(HotColdDB::open_ephemeral(<_>::default(), spec.clone()).unwrap()); let ordered_custody_column_indices = generate_data_column_indices_rand_order::(); let custody_context = Arc::new(CustodyContext::new( NodeCustodyType::Fullnode, @@ -1280,7 +1270,6 @@ mod test { complete_blob_backfill, slot_clock, kzg, - store, custody_context, spec, ) diff --git a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs index ff098a827d..f7bd646f82 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs @@ -1,7 +1,5 @@ use super::AvailableBlockData; -use super::state_lru_cache::{DietAvailabilityPendingExecutedBlock, StateLRUCache}; use crate::CustodyContext; -use crate::beacon_chain::BeaconStore; use crate::blob_verification::KzgVerifiedBlob; use crate::block_verification_types::{ AvailabilityPendingExecutedBlock, AvailableBlock, AvailableExecutedBlock, @@ -23,10 +21,9 @@ use types::{ DataColumnSidecarList, Epoch, EthSpec, Hash256, SignedBeaconBlock, }; -#[derive(Clone)] pub enum CachedBlock { PreExecution(Arc>, BlockImportSource), - Executed(Box>), + Executed(Box>), } impl CachedBlock { @@ -43,7 +40,7 @@ impl CachedBlock { fn as_block(&self) -> &SignedBeaconBlock { match self { CachedBlock::PreExecution(b, _) => b, - CachedBlock::Executed(b) => b.as_block(), + CachedBlock::Executed(b) => b.block.as_ref(), } } @@ -84,14 +81,6 @@ impl PendingComponents { &self.verified_blobs } - #[cfg(test)] - fn get_diet_block(&self) -> Option<&DietAvailabilityPendingExecutedBlock> { - self.block.as_ref().and_then(|block| match block { - CachedBlock::Executed(block) => Some(block.as_ref()), - _ => None, - }) - } - /// Returns an immutable reference to the cached data column. pub fn get_cached_data_column( &self, @@ -129,7 +118,7 @@ impl PendingComponents { } /// Inserts an executed block into the cache. - pub fn insert_executed_block(&mut self, block: DietAvailabilityPendingExecutedBlock) { + pub fn insert_executed_block(&mut self, block: AvailabilityPendingExecutedBlock) { self.block = Some(CachedBlock::Executed(Box::new(block))) } @@ -201,7 +190,7 @@ impl PendingComponents { /// Inserts a new block and revalidates the existing blobs against it. /// /// Blobs that don't match the new block's commitments are evicted. - pub fn merge_block(&mut self, block: DietAvailabilityPendingExecutedBlock) { + pub fn merge_block(&mut self, block: AvailabilityPendingExecutedBlock) { self.insert_executed_block(block); let reinsert = self.get_cached_blobs_mut().take(); self.merge_blobs(reinsert); @@ -209,21 +198,11 @@ impl PendingComponents { /// Returns Some if the block has received all its required data for import. The return value /// must be persisted in the DB along with the block. - /// - /// WARNING: This function can potentially take a lot of time if the state needs to be - /// reconstructed from disk. Ensure you are not holding any write locks while calling this. - pub fn make_available( + pub fn make_available( &self, spec: &Arc, num_expected_columns_opt: Option, - recover: R, - ) -> Result>, AvailabilityCheckError> - where - R: FnOnce( - DietAvailabilityPendingExecutedBlock, - &Span, - ) -> Result, AvailabilityCheckError>, - { + ) -> Result>, AvailabilityCheckError> { let Some(CachedBlock::Executed(block)) = &self.block else { // Block not available yet return Ok(None); @@ -266,7 +245,7 @@ impl PendingComponents { ))); } Ordering::Equal => { - let max_blobs = spec.max_blobs_per_block(block.epoch()) as usize; + let max_blobs = spec.max_blobs_per_block(block.block.epoch()) as usize; let blobs_vec = self .verified_blobs .iter() @@ -311,11 +290,11 @@ impl PendingComponents { block, import_data, payload_verification_outcome, - } = recover(*block.clone(), &self.span)?; + } = block.as_ref(); let available_block = AvailableBlock { block_root: self.block_root, - block, + block: block.clone(), blob_data, blobs_available_timestamp, spec: spec.clone(), @@ -326,8 +305,8 @@ impl PendingComponents { }); Ok(Some(AvailableExecutedBlock::new( available_block, - import_data, - payload_verification_outcome, + import_data.clone(), + payload_verification_outcome.clone(), ))) } @@ -399,9 +378,6 @@ impl PendingComponents { pub struct DataAvailabilityCheckerInner { /// Contains all the data we keep in memory, protected by an RwLock critical: RwLock>>, - /// This cache holds a limited number of states in memory and reconstructs them - /// from disk when necessary. This is necessary until we merge tree-states - state_cache: StateLRUCache, custody_context: Arc>, spec: Arc, } @@ -418,13 +394,11 @@ pub(crate) enum ReconstructColumnsDecision { impl DataAvailabilityCheckerInner { pub fn new( capacity: NonZeroUsize, - beacon_store: BeaconStore, custody_context: Arc>, spec: Arc, ) -> Result { Ok(Self { critical: RwLock::new(LruCache::new(capacity)), - state_cache: StateLRUCache::new(beacon_store, spec.clone()), custody_context, spec, }) @@ -441,7 +415,7 @@ impl DataAvailabilityCheckerInner { BlockProcessStatus::NotValidated(b.clone(), *source) } CachedBlock::Executed(b) => { - BlockProcessStatus::ExecutionValidated(b.block_cloned()) + BlockProcessStatus::ExecutionValidated(b.block.clone()) } }) }) @@ -580,11 +554,9 @@ impl DataAvailabilityCheckerInner { pending_components: MappedRwLockReadGuard<'_, PendingComponents>, num_expected_columns_opt: Option, ) -> Result, AvailabilityCheckError> { - if let Some(available_block) = pending_components.make_available( - &self.spec, - num_expected_columns_opt, - |block, span| self.state_cache.recover_pending_executed_block(block, span), - )? { + if let Some(available_block) = + pending_components.make_available(&self.spec, num_expected_columns_opt)? + { // Explicitly drop read lock before acquiring write lock drop(pending_components); if let Some(components) = self.critical.write().get_mut(&block_root) { @@ -739,14 +711,9 @@ impl DataAvailabilityCheckerInner { let epoch = executed_block.as_block().epoch(); let block_root = executed_block.import_data.block_root; - // register the block to get the diet block - let diet_executed_block = self - .state_cache - .register_pending_executed_block(executed_block); - let pending_components = self.update_or_insert_pending_components(block_root, epoch, |pending_components| { - pending_components.merge_block(diet_executed_block); + pending_components.merge_block(executed_block); Ok(()) })?; @@ -780,9 +747,6 @@ impl DataAvailabilityCheckerInner { /// maintain the cache pub fn do_maintenance(&self, cutoff_epoch: Epoch) -> Result<(), AvailabilityCheckError> { - // clean up any lingering states in the state cache - self.state_cache.do_maintenance(cutoff_epoch); - // Collect keys of pending blocks from a previous epoch to cutoff let mut write_lock = self.critical.write(); let mut keys_to_remove = vec![]; @@ -801,17 +765,6 @@ impl DataAvailabilityCheckerInner { Ok(()) } - #[cfg(test)] - /// get the state cache for inspection (used only for tests) - pub fn state_lru_cache(&self) -> &StateLRUCache { - &self.state_cache - } - - /// Number of states stored in memory in the cache. - pub fn state_cache_size(&self) -> usize { - self.state_cache.lru_cache().read().len() - } - /// Number of pending component entries in memory in the cache. pub fn block_cache_size(&self) -> usize { self.critical.read().len() @@ -828,21 +781,18 @@ mod test { block_verification::PayloadVerificationOutcome, block_verification_types::{AsBlock, BlockImportData}, custody_context::NodeCustodyType, - data_availability_checker::STATE_LRU_CAPACITY_NON_ZERO, test_utils::{BaseHarnessType, BeaconChainHarness, DiskHarnessType}, }; use fork_choice::PayloadVerificationStatus; use logging::create_test_tracing_subscriber; use state_processing::ConsensusContext; - use std::collections::VecDeque; use store::{HotColdDB, ItemStore, StoreConfig, database::interface::BeaconNodeBackend}; use tempfile::{TempDir, tempdir}; - use tracing::{debug_span, info}; + use tracing::info; use types::new_non_zero_usize; use types::{ExecPayload, MinimalEthSpec}; const LOW_VALIDATOR_COUNT: usize = 32; - const STATE_LRU_CAPACITY: usize = STATE_LRU_CAPACITY_NON_ZERO.get(); fn get_store_with_spec( db_path: &TempDir, @@ -1021,7 +971,6 @@ mod test { let chain_db_path = tempdir().expect("should get temp dir"); let harness = get_deneb_chain(&chain_db_path).await; let spec = harness.spec.clone(); - let test_store = harness.chain.store.clone(); let capacity_non_zero = new_non_zero_usize(capacity); let custody_context = Arc::new(CustodyContext::new( NodeCustodyType::Fullnode, @@ -1031,7 +980,6 @@ mod test { let cache = Arc::new( DataAvailabilityCheckerInner::::new( capacity_non_zero, - test_store, custody_context, spec.clone(), ) @@ -1137,121 +1085,6 @@ mod test { "cache should still have available block" ); } - - #[tokio::test] - // ensure the state cache keeps memory usage low and that it can properly recover states - // THIS TEST CAN BE DELETED ONCE TREE STATES IS MERGED AND WE RIP OUT THE STATE CACHE - async fn overflow_cache_test_state_cache() { - type E = MinimalEthSpec; - type T = DiskHarnessType; - let capacity = STATE_LRU_CAPACITY * 2; - let (harness, cache, _path) = setup_harness_and_cache::(capacity).await; - - let mut pending_blocks = VecDeque::new(); - let mut states = Vec::new(); - let mut state_roots = Vec::new(); - // Get enough blocks to fill the cache to capacity, ensuring all blocks have blobs - while pending_blocks.len() < capacity { - let (mut pending_block, _) = availability_pending_block(&harness).await; - if pending_block.num_blobs_expected() == 0 { - // we need blocks with blobs - continue; - } - let state_root = pending_block.import_data.state.canonical_root().unwrap(); - states.push(pending_block.import_data.state.clone()); - pending_blocks.push_back(pending_block); - state_roots.push(state_root); - } - - let state_cache = cache.state_lru_cache().lru_cache(); - let mut pushed_diet_blocks = VecDeque::new(); - - for i in 0..capacity { - let pending_block = pending_blocks.pop_front().expect("should have block"); - let block_root = pending_block.as_block().canonical_root(); - - assert_eq!( - state_cache.read().len(), - std::cmp::min(i, STATE_LRU_CAPACITY), - "state cache should be empty at start" - ); - - if i >= STATE_LRU_CAPACITY { - let lru_root = state_roots[i - STATE_LRU_CAPACITY]; - assert_eq!( - state_cache.read().peek_lru().map(|(root, _)| root), - Some(&lru_root), - "lru block should be in cache" - ); - } - - // put the block in the cache - let availability = cache - .put_executed_block(pending_block) - .expect("should put block"); - - // grab the diet block from the cache for later testing - let diet_block = cache - .critical - .read() - .peek(&block_root) - .and_then(|pending_components| pending_components.get_diet_block().cloned()) - .expect("should exist"); - pushed_diet_blocks.push_back(diet_block); - - // should be unavailable since we made sure all blocks had blobs - assert!( - matches!(availability, Availability::MissingComponents(_)), - "should be pending blobs" - ); - - if i >= STATE_LRU_CAPACITY { - let evicted_index = i - STATE_LRU_CAPACITY; - let evicted_root = state_roots[evicted_index]; - assert!( - state_cache.read().peek(&evicted_root).is_none(), - "lru root should be evicted" - ); - // get the diet block via direct conversion (testing only) - let diet_block = pushed_diet_blocks.pop_front().expect("should have block"); - // reconstruct the pending block by replaying the block on the parent state - let recovered_pending_block = cache - .state_lru_cache() - .recover_pending_executed_block(diet_block, &debug_span!("test")) - .expect("should reconstruct pending block"); - - // assert the recovered state is the same as the original - assert_eq!( - recovered_pending_block.import_data.state, states[evicted_index], - "recovered state should be the same as the original" - ); - } - } - - // now check the last block - let last_block = pushed_diet_blocks.pop_back().expect("should exist").clone(); - // the state should still be in the cache - assert!( - state_cache - .read() - .peek(&last_block.as_block().state_root()) - .is_some(), - "last block state should still be in cache" - ); - // get the diet block via direct conversion (testing only) - let diet_block = last_block.clone(); - // recover the pending block from the cache - let recovered_pending_block = cache - .state_lru_cache() - .recover_pending_executed_block(diet_block, &debug_span!("test")) - .expect("should reconstruct pending block"); - // assert the recovered state is the same as the original - assert_eq!( - Some(&recovered_pending_block.import_data.state), - states.last(), - "recovered state should be the same as the original" - ); - } } #[cfg(test)] @@ -1307,7 +1140,7 @@ mod pending_components_tests { } type PendingComponentsSetup = ( - DietAvailabilityPendingExecutedBlock, + AvailabilityPendingExecutedBlock, RuntimeFixedVector>>, RuntimeFixedVector>>, ); @@ -1351,7 +1184,7 @@ mod pending_components_tests { is_valid_merge_transition_block: false, }, }; - (block.into(), blobs, invalid_blobs) + (block, blobs, invalid_blobs) } pub fn assert_cache_consistent(cache: PendingComponents, max_len: usize) { diff --git a/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs b/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs deleted file mode 100644 index 24f9237e3c..0000000000 --- a/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs +++ /dev/null @@ -1,215 +0,0 @@ -use crate::block_verification_types::AsBlock; -use crate::{ - AvailabilityPendingExecutedBlock, BeaconChainTypes, BeaconStore, PayloadVerificationOutcome, - block_verification_types::BlockImportData, - data_availability_checker::{AvailabilityCheckError, STATE_LRU_CAPACITY_NON_ZERO}, -}; -use lru::LruCache; -use parking_lot::RwLock; -use state_processing::BlockReplayer; -use std::sync::Arc; -use store::OnDiskConsensusContext; -use tracing::{Span, debug_span, instrument}; -use types::{BeaconState, BlindedPayload, ChainSpec, Epoch, EthSpec, Hash256, SignedBeaconBlock}; - -/// This mirrors everything in the `AvailabilityPendingExecutedBlock`, except -/// that it is much smaller because it contains only a state root instead of -/// a full `BeaconState`. -#[derive(Clone)] -pub struct DietAvailabilityPendingExecutedBlock { - block: Arc>, - state_root: Hash256, - parent_block: SignedBeaconBlock>, - consensus_context: OnDiskConsensusContext, - payload_verification_outcome: PayloadVerificationOutcome, -} - -/// just implementing the same methods as `AvailabilityPendingExecutedBlock` -impl DietAvailabilityPendingExecutedBlock { - pub fn as_block(&self) -> &SignedBeaconBlock { - &self.block - } - - pub fn block_cloned(&self) -> Arc> { - self.block.clone() - } - - pub fn num_blobs_expected(&self) -> usize { - self.block - .message() - .body() - .blob_kzg_commitments() - .map_or(0, |commitments| commitments.len()) - } - - /// Returns the epoch corresponding to `self.slot()`. - pub fn epoch(&self) -> Epoch { - self.block.slot().epoch(E::slots_per_epoch()) - } -} - -/// This LRU cache holds BeaconStates used for block import. If the cache overflows, -/// the least recently used state will be dropped. If the dropped state is needed -/// later on, it will be recovered from the parent state and replaying the block. -/// -/// WARNING: This cache assumes the parent block of any `AvailabilityPendingExecutedBlock` -/// has already been imported into ForkChoice. If this is not the case, the cache -/// will fail to recover the state when the cache overflows because it can't load -/// the parent state! -pub struct StateLRUCache { - states: RwLock>>, - store: BeaconStore, - spec: Arc, -} - -impl StateLRUCache { - pub fn new(store: BeaconStore, spec: Arc) -> Self { - Self { - states: RwLock::new(LruCache::new(STATE_LRU_CAPACITY_NON_ZERO)), - store, - spec, - } - } - - /// This will store the state in the LRU cache and return a - /// `DietAvailabilityPendingExecutedBlock` which is much cheaper to - /// keep around in memory. - pub fn register_pending_executed_block( - &self, - executed_block: AvailabilityPendingExecutedBlock, - ) -> DietAvailabilityPendingExecutedBlock { - let state = executed_block.import_data.state; - let state_root = executed_block.block.state_root(); - self.states.write().put(state_root, state); - - DietAvailabilityPendingExecutedBlock { - block: executed_block.block, - state_root, - parent_block: executed_block.import_data.parent_block, - consensus_context: OnDiskConsensusContext::from_consensus_context( - executed_block.import_data.consensus_context, - ), - payload_verification_outcome: executed_block.payload_verification_outcome, - } - } - - /// Recover the `AvailabilityPendingExecutedBlock` from the diet version. - /// This method will first check the cache and if the state is not found - /// it will reconstruct the state by loading the parent state from disk and - /// replaying the block. - #[instrument(skip_all, parent = _span, level = "debug")] - pub fn recover_pending_executed_block( - &self, - diet_executed_block: DietAvailabilityPendingExecutedBlock, - _span: &Span, - ) -> Result, AvailabilityCheckError> { - // Keep the state in the cache to prevent reconstruction in race conditions - let state = if let Some(state) = self.states.write().get(&diet_executed_block.state_root) { - state.clone() - } else { - self.reconstruct_state(&diet_executed_block)? - }; - let block_root = diet_executed_block.block.canonical_root(); - Ok(AvailabilityPendingExecutedBlock { - block: diet_executed_block.block, - import_data: BlockImportData { - block_root, - state, - parent_block: diet_executed_block.parent_block, - consensus_context: diet_executed_block - .consensus_context - .into_consensus_context(), - }, - payload_verification_outcome: diet_executed_block.payload_verification_outcome, - }) - } - - /// Reconstruct the state by loading the parent state from disk and replaying - /// the block. - #[instrument(skip_all, level = "debug")] - fn reconstruct_state( - &self, - diet_executed_block: &DietAvailabilityPendingExecutedBlock, - ) -> Result, AvailabilityCheckError> { - let parent_block_root = diet_executed_block.parent_block.canonical_root(); - let parent_block_state_root = diet_executed_block.parent_block.state_root(); - let (parent_state_root, parent_state) = self - .store - .get_advanced_hot_state( - parent_block_root, - diet_executed_block.parent_block.slot(), - parent_block_state_root, - ) - .map_err(AvailabilityCheckError::StoreError)? - .ok_or(AvailabilityCheckError::ParentStateMissing( - parent_block_state_root, - ))?; - - let state_roots = vec![ - Ok((parent_state_root, diet_executed_block.parent_block.slot())), - Ok(( - diet_executed_block.state_root, - diet_executed_block.block.slot(), - )), - ]; - - let block_replayer: BlockReplayer<'_, T::EthSpec, AvailabilityCheckError, _> = - BlockReplayer::new(parent_state, &self.spec) - .no_signature_verification() - .state_root_iter(state_roots.into_iter()) - .minimal_block_root_verification(); - - let block_replayer = debug_span!("reconstruct_state_apply_blocks").in_scope(|| { - block_replayer.apply_blocks(vec![diet_executed_block.block.clone_as_blinded()], None) - }); - - block_replayer - .map(|block_replayer| block_replayer.into_state()) - .and_then(|mut state| { - state - .build_exit_cache(&self.spec) - .map_err(AvailabilityCheckError::RebuildingStateCaches)?; - state - .update_tree_hash_cache() - .map_err(AvailabilityCheckError::RebuildingStateCaches)?; - Ok(state) - }) - } - - /// returns the state cache for inspection - pub fn lru_cache(&self) -> &RwLock>> { - &self.states - } - - /// remove any states from the cache from before the given epoch - pub fn do_maintenance(&self, cutoff_epoch: Epoch) { - let mut write_lock = self.states.write(); - while let Some((_, state)) = write_lock.peek_lru() { - if state.slot().epoch(T::EthSpec::slots_per_epoch()) < cutoff_epoch { - write_lock.pop_lru(); - } else { - break; - } - } - } -} - -/// This can only be used during testing. The intended way to -/// obtain a `DietAvailabilityPendingExecutedBlock` is to call -/// `register_pending_executed_block` on the `StateLRUCache`. -#[cfg(test)] -impl From> - for DietAvailabilityPendingExecutedBlock -{ - fn from(mut value: AvailabilityPendingExecutedBlock) -> Self { - Self { - block: value.block, - state_root: value.import_data.state.canonical_root().unwrap(), - parent_block: value.import_data.parent_block, - consensus_context: OnDiskConsensusContext::from_consensus_context( - value.import_data.consensus_context, - ), - payload_verification_outcome: value.payload_verification_outcome, - } - } -} diff --git a/beacon_node/beacon_chain/src/data_column_verification.rs b/beacon_node/beacon_chain/src/data_column_verification.rs index 29c31d6c7f..20952552ef 100644 --- a/beacon_node/beacon_chain/src/data_column_verification.rs +++ b/beacon_node/beacon_chain/src/data_column_verification.rs @@ -576,12 +576,18 @@ fn verify_data_column_sidecar( *data_column.index(), )); } - if data_column.kzg_commitments().is_empty() { + + // TODO(gloas): implement Gloas verification that takes kzg_commitments from block as parameter + let commitments_len = match data_column { + DataColumnSidecar::Fulu(dc) => dc.kzg_commitments.len(), + DataColumnSidecar::Gloas(_) => return Err(GossipDataColumnError::InvalidVariant), + }; + + if commitments_len == 0 { return Err(GossipDataColumnError::UnexpectedDataColumn); } let cells_len = data_column.column().len(); - let commitments_len = data_column.kzg_commitments().len(); let proofs_len = data_column.kzg_proofs().len(); let max_blobs_per_block = spec.max_blobs_per_block(data_column.epoch()) as usize; diff --git a/beacon_node/beacon_chain/src/early_attester_cache.rs b/beacon_node/beacon_chain/src/early_attester_cache.rs index 8b26cb2f58..8d9eb950f3 100644 --- a/beacon_node/beacon_chain/src/early_attester_cache.rs +++ b/beacon_node/beacon_chain/src/early_attester_cache.rs @@ -254,4 +254,12 @@ impl EarlyAttesterCache { .filter(|item| item.beacon_block_root == block_root) .map(|item| item.proto_block.clone()) } + + /// Fetch the slot and block root of the current head block. + pub fn get_head_block_root(&self) -> Option<(Slot, Hash256)> { + self.item + .read() + .as_ref() + .map(|item| (item.block.slot(), item.beacon_block_root)) + } } diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index 9459b1acd7..bdf3ab9594 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -371,7 +371,7 @@ pub fn get_execution_payload( let latest_execution_payload_header_block_hash = latest_execution_payload_header.block_hash(); let latest_execution_payload_header_gas_limit = latest_execution_payload_header.gas_limit(); let withdrawals = if state.fork_name_unchecked().capella_enabled() { - Some(get_expected_withdrawals(state, spec)?.0.into()) + Some(Withdrawals::::from(get_expected_withdrawals(state, spec)?).into()) } else { None }; diff --git a/beacon_node/beacon_chain/src/kzg_utils.rs b/beacon_node/beacon_chain/src/kzg_utils.rs index bef96836f1..33b3260361 100644 --- a/beacon_node/beacon_chain/src/kzg_utils.rs +++ b/beacon_node/beacon_chain/src/kzg_utils.rs @@ -75,7 +75,21 @@ where proofs.push(Bytes48::from(proof)); } - for &commitment in data_column.kzg_commitments() { + // In Gloas, commitments come from the block's ExecutionPayloadBid, not the sidecar. + // This function requires Fulu sidecars with embedded commitments. + let kzg_commitments = match data_column.as_ref() { + DataColumnSidecar::Fulu(dc) => &dc.kzg_commitments, + DataColumnSidecar::Gloas(_) => { + return Err(( + Some(col_index), + KzgError::InconsistentArrayLength( + "Gloas data columns require commitments from block".to_string(), + ), + )); + } + }; + + for &commitment in kzg_commitments.iter() { commitments.push(Bytes48::from(commitment)); } @@ -209,7 +223,6 @@ pub fn blobs_to_data_column_sidecars( if block.fork_name_unchecked().gloas_enabled() { build_data_column_sidecars_gloas( - kzg_commitments.clone(), signed_block_header.message.tree_hash_root(), block.slot(), blob_cells_and_proofs_vec, @@ -321,7 +334,6 @@ pub(crate) fn build_data_column_sidecars_fulu( } pub(crate) fn build_data_column_sidecars_gloas( - kzg_commitments: KzgCommitments, beacon_block_root: Hash256, slot: Slot, blob_cells_and_proofs_vec: Vec, @@ -374,7 +386,6 @@ pub(crate) fn build_data_column_sidecars_gloas( index: index as u64, column: DataColumn::::try_from(col) .map_err(|e| format!("MaxBlobCommitmentsPerBlock exceeded: {e:?}"))?, - kzg_commitments: kzg_commitments.clone(), kzg_proofs: VariableList::try_from(proofs) .map_err(|e| format!("MaxBlobCommitmentsPerBlock exceeded: {e:?}"))?, beacon_block_root, @@ -412,7 +423,12 @@ pub fn reconstruct_blobs( let blob_indices: Vec = match blob_indices_opt { Some(indices) => indices.into_iter().map(|i| i as usize).collect(), None => { - let num_of_blobs = first_data_column.kzg_commitments().len(); + // TODO(gloas): support blob reconstruction for Gloas + // https://github.com/sigp/lighthouse/issues/7413 + let num_of_blobs = first_data_column + .kzg_commitments() + .map_err(|_| "Gloas blob reconstruction not yet supported".to_string())? + .len(); (0..num_of_blobs).collect() } }; @@ -497,7 +513,16 @@ pub fn reconstruct_data_columns( "data_columns should have at least one element".to_string(), ))?; - let num_of_blobs = first_data_column.kzg_commitments().len(); + // TODO(gloas): support data column reconstruction for Gloas + // https://github.com/sigp/lighthouse/issues/7413 + let num_of_blobs = first_data_column + .kzg_commitments() + .map_err(|_| { + KzgError::InconsistentArrayLength( + "Gloas data column reconstruction not yet supported".to_string(), + ) + })? + .len(); let blob_cells_and_proofs_vec = (0..num_of_blobs) .into_par_iter() @@ -530,7 +555,6 @@ pub fn reconstruct_data_columns( .map_err(KzgError::ReconstructFailed) } DataColumnSidecar::Gloas(first_column) => build_data_column_sidecars_gloas( - first_column.kzg_commitments.clone(), first_column.beacon_block_root, first_column.slot, blob_cells_and_proofs_vec, @@ -629,11 +653,14 @@ mod test { for (idx, col_sidecar) in column_sidecars.iter().enumerate() { assert_eq!(*col_sidecar.index(), idx as u64); - assert_eq!(col_sidecar.kzg_commitments().len(), num_of_blobs); + assert_eq!(col_sidecar.kzg_commitments().unwrap().len(), num_of_blobs); assert_eq!(col_sidecar.column().len(), num_of_blobs); assert_eq!(col_sidecar.kzg_proofs().len(), num_of_blobs); - assert_eq!(col_sidecar.kzg_commitments().clone(), block_kzg_commitments); + assert_eq!( + col_sidecar.kzg_commitments().unwrap().clone(), + block_kzg_commitments + ); assert_eq!( col_sidecar .kzg_commitments_inclusion_proof() diff --git a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs index 2dc4de7d04..81bc03a402 100644 --- a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs +++ b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs @@ -75,9 +75,9 @@ impl VerifiedLightClientFinalityUpdate { .slot_clock .start_of(rcv_finality_update.signature_slot()) .ok_or(Error::SigSlotStartIsNone)?; - let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); + let sync_message_due = chain.spec.get_sync_message_due(); if seen_timestamp + chain.spec.maximum_gossip_clock_disparity() - < start_time + one_third_slot_duration + < start_time + sync_message_due { return Err(Error::TooEarly); } diff --git a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs index 4079a374f8..826f170b80 100644 --- a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs +++ b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs @@ -70,9 +70,11 @@ impl VerifiedLightClientOptimisticUpdate { .slot_clock .start_of(rcv_optimistic_update.signature_slot()) .ok_or(Error::SigSlotStartIsNone)?; - let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); + + let sync_message_due = chain.spec.get_sync_message_due(); + if seen_timestamp + chain.spec.maximum_gossip_clock_disparity() - < start_time + one_third_slot_duration + < start_time + sync_message_due { return Err(Error::TooEarly); } diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index 7e4878c3d1..f8044b9175 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -1869,13 +1869,6 @@ pub static DATA_AVAILABILITY_OVERFLOW_MEMORY_BLOCK_CACHE_SIZE: LazyLock> = - LazyLock::new(|| { - try_create_int_gauge( - "data_availability_overflow_memory_state_cache_size", - "Number of entries in the data availability overflow state memory cache.", - ) - }); pub static DATA_AVAILABILITY_RECONSTRUCTION_TIME: LazyLock> = LazyLock::new(|| { try_create_histogram( @@ -1985,10 +1978,6 @@ pub fn scrape_for_metrics(beacon_chain: &BeaconChain) { &DATA_AVAILABILITY_OVERFLOW_MEMORY_BLOCK_CACHE_SIZE, da_checker_metrics.block_cache_size, ); - set_gauge_by_usize( - &DATA_AVAILABILITY_OVERFLOW_MEMORY_STATE_CACHE_SIZE, - da_checker_metrics.state_cache_size, - ); if let Some((size, num_lookups)) = beacon_chain.pre_finalization_block_cache.metrics() { set_gauge_by_usize(&PRE_FINALIZATION_BLOCK_CACHE_SIZE, size); diff --git a/beacon_node/beacon_chain/src/observed_data_sidecars.rs b/beacon_node/beacon_chain/src/observed_data_sidecars.rs index b53eb3955c..894b8d3444 100644 --- a/beacon_node/beacon_chain/src/observed_data_sidecars.rs +++ b/beacon_node/beacon_chain/src/observed_data_sidecars.rs @@ -312,7 +312,6 @@ mod tests { Arc::new(DataColumnSidecar::Gloas(DataColumnSidecarGloas { index, column: vec![].try_into().unwrap(), - kzg_commitments: vec![].try_into().unwrap(), kzg_proofs: vec![].try_into().unwrap(), slot: slot.into(), beacon_block_root, diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index d68613cdfe..6f82c672d2 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -222,7 +222,6 @@ pub fn test_da_checker( Duration::from_secs(spec.seconds_per_slot), ); let kzg = get_kzg(&spec); - let store = Arc::new(HotColdDB::open_ephemeral(<_>::default(), spec.clone()).unwrap()); let ordered_custody_column_indices = generate_data_column_indices_rand_order::(); let custody_context = Arc::new(CustodyContext::new( node_custody_type, @@ -234,7 +233,6 @@ pub fn test_da_checker( complete_blob_backfill, slot_clock, kzg, - store, custody_context, spec, ) @@ -531,21 +529,26 @@ where .expect("cannot recalculate fork times without spec"); mock.server.execution_block_generator().shanghai_time = spec.capella_fork_epoch.map(|epoch| { - genesis_time + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64() + genesis_time + + spec.get_slot_duration().as_secs() * E::slots_per_epoch() * epoch.as_u64() }); mock.server.execution_block_generator().cancun_time = spec.deneb_fork_epoch.map(|epoch| { - genesis_time + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64() + genesis_time + + spec.get_slot_duration().as_secs() * E::slots_per_epoch() * epoch.as_u64() }); mock.server.execution_block_generator().prague_time = spec.electra_fork_epoch.map(|epoch| { - genesis_time + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64() + genesis_time + + spec.get_slot_duration().as_secs() * E::slots_per_epoch() * epoch.as_u64() }); mock.server.execution_block_generator().osaka_time = spec.fulu_fork_epoch.map(|epoch| { - genesis_time + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64() + genesis_time + + spec.get_slot_duration().as_secs() * E::slots_per_epoch() * epoch.as_u64() }); mock.server.execution_block_generator().amsterdam_time = spec.gloas_fork_epoch.map(|epoch| { - genesis_time + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64() + genesis_time + + spec.get_slot_duration().as_secs() * E::slots_per_epoch() * epoch.as_u64() }); self @@ -590,7 +593,6 @@ where let (shutdown_tx, shutdown_receiver) = futures::channel::mpsc::channel(1); let spec = self.spec.expect("cannot build without spec"); - let seconds_per_slot = spec.seconds_per_slot; let validator_keypairs = self .validator_keypairs .expect("cannot build without validator keypairs"); @@ -635,7 +637,7 @@ where builder.slot_clock(testing_slot_clock) } else if builder.get_slot_clock().is_none() { builder - .testing_slot_clock(Duration::from_secs(seconds_per_slot)) + .testing_slot_clock(spec.get_slot_duration()) .expect("should configure testing slot clock") } else { builder @@ -662,19 +664,24 @@ pub fn mock_execution_layer_from_parts( task_executor: TaskExecutor, ) -> MockExecutionLayer { let shanghai_time = spec.capella_fork_epoch.map(|epoch| { - HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64() + HARNESS_GENESIS_TIME + + (spec.get_slot_duration().as_secs()) * E::slots_per_epoch() * epoch.as_u64() }); let cancun_time = spec.deneb_fork_epoch.map(|epoch| { - HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64() + HARNESS_GENESIS_TIME + + (spec.get_slot_duration().as_secs()) * E::slots_per_epoch() * epoch.as_u64() }); let prague_time = spec.electra_fork_epoch.map(|epoch| { - HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64() + HARNESS_GENESIS_TIME + + (spec.get_slot_duration().as_secs()) * E::slots_per_epoch() * epoch.as_u64() }); let osaka_time = spec.fulu_fork_epoch.map(|epoch| { - HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64() + HARNESS_GENESIS_TIME + + (spec.get_slot_duration().as_secs()) * E::slots_per_epoch() * epoch.as_u64() }); let amsterdam_time = spec.gloas_fork_epoch.map(|epoch| { - HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64() + HARNESS_GENESIS_TIME + + (spec.get_slot_duration().as_secs()) * E::slots_per_epoch() * epoch.as_u64() }); let kzg = get_kzg(&spec); @@ -3551,7 +3558,6 @@ pub fn generate_data_column_sidecars_from_block( vec![(cells.try_into().unwrap(), proofs.try_into().unwrap()); kzg_commitments.len()]; build_data_column_sidecars_gloas( - kzg_commitments.clone(), signed_block_header.message.tree_hash_root(), signed_block_header.message.slot, blob_cells_and_proofs_vec, diff --git a/beacon_node/beacon_chain/src/validator_monitor.rs b/beacon_node/beacon_chain/src/validator_monitor.rs index 2a76d65d32..fdc7d27320 100644 --- a/beacon_node/beacon_chain/src/validator_monitor.rs +++ b/beacon_node/beacon_chain/src/validator_monitor.rs @@ -1263,6 +1263,7 @@ impl ValidatorMonitor { signed_aggregate_and_proof: &SignedAggregateAndProof, indexed_attestation: &IndexedAttestation, slot_clock: &S, + spec: &ChainSpec, ) { self.register_aggregated_attestation( "gossip", @@ -1270,6 +1271,7 @@ impl ValidatorMonitor { signed_aggregate_and_proof, indexed_attestation, slot_clock, + spec, ) } @@ -1280,6 +1282,7 @@ impl ValidatorMonitor { signed_aggregate_and_proof: &SignedAggregateAndProof, indexed_attestation: &IndexedAttestation, slot_clock: &S, + spec: &ChainSpec, ) { self.register_aggregated_attestation( "api", @@ -1287,6 +1290,7 @@ impl ValidatorMonitor { signed_aggregate_and_proof, indexed_attestation, slot_clock, + spec, ) } @@ -1297,13 +1301,14 @@ impl ValidatorMonitor { signed_aggregate_and_proof: &SignedAggregateAndProof, indexed_attestation: &IndexedAttestation, slot_clock: &S, + spec: &ChainSpec, ) { let data = indexed_attestation.data(); let epoch = data.slot.epoch(E::slots_per_epoch()); let delay = get_message_delay_ms( seen_timestamp, data.slot, - slot_clock.agg_attestation_production_delay(), + spec.get_aggregate_attestation_due(), slot_clock, ); @@ -1488,12 +1493,14 @@ impl ValidatorMonitor { seen_timestamp: Duration, sync_committee_message: &SyncCommitteeMessage, slot_clock: &S, + spec: &ChainSpec, ) { self.register_sync_committee_message( "gossip", seen_timestamp, sync_committee_message, slot_clock, + spec, ) } @@ -1503,12 +1510,14 @@ impl ValidatorMonitor { seen_timestamp: Duration, sync_committee_message: &SyncCommitteeMessage, slot_clock: &S, + spec: &ChainSpec, ) { self.register_sync_committee_message( "api", seen_timestamp, sync_committee_message, slot_clock, + spec, ) } @@ -1519,15 +1528,15 @@ impl ValidatorMonitor { seen_timestamp: Duration, sync_committee_message: &SyncCommitteeMessage, slot_clock: &S, + spec: &ChainSpec, ) { if let Some(validator) = self.get_validator(sync_committee_message.validator_index) { let id = &validator.id; - let epoch = sync_committee_message.slot.epoch(E::slots_per_epoch()); let delay = get_message_delay_ms( seen_timestamp, sync_committee_message.slot, - slot_clock.sync_committee_message_production_delay(), + spec.get_sync_message_due(), slot_clock, ); @@ -1568,6 +1577,7 @@ impl ValidatorMonitor { sync_contribution: &SignedContributionAndProof, participant_pubkeys: &[PublicKeyBytes], slot_clock: &S, + spec: &ChainSpec, ) { self.register_sync_committee_contribution( "gossip", @@ -1575,6 +1585,7 @@ impl ValidatorMonitor { sync_contribution, participant_pubkeys, slot_clock, + spec, ) } @@ -1585,6 +1596,7 @@ impl ValidatorMonitor { sync_contribution: &SignedContributionAndProof, participant_pubkeys: &[PublicKeyBytes], slot_clock: &S, + spec: &ChainSpec, ) { self.register_sync_committee_contribution( "api", @@ -1592,6 +1604,7 @@ impl ValidatorMonitor { sync_contribution, participant_pubkeys, slot_clock, + spec, ) } @@ -1603,6 +1616,7 @@ impl ValidatorMonitor { sync_contribution: &SignedContributionAndProof, participant_pubkeys: &[PublicKeyBytes], slot_clock: &S, + spec: &ChainSpec, ) { let slot = sync_contribution.message.contribution.slot; let epoch = slot.epoch(E::slots_per_epoch()); @@ -1610,7 +1624,7 @@ impl ValidatorMonitor { let delay = get_message_delay_ms( seen_timestamp, slot, - slot_clock.sync_committee_contribution_production_delay(), + spec.get_contribution_message_due(), slot_clock, ); diff --git a/beacon_node/beacon_chain/tests/bellatrix.rs b/beacon_node/beacon_chain/tests/bellatrix.rs index 5d466dd1d3..fc0f96ef88 100644 --- a/beacon_node/beacon_chain/tests/bellatrix.rs +++ b/beacon_node/beacon_chain/tests/bellatrix.rs @@ -174,7 +174,7 @@ async fn base_altair_bellatrix_with_terminal_block_after_fork() { .unwrap(); // Add a slot duration to get to the next slot - let timestamp = harness.get_timestamp_at_slot() + harness.spec.seconds_per_slot; + let timestamp = harness.get_timestamp_at_slot() + harness.spec.get_slot_duration().as_secs(); harness .execution_block_generator() diff --git a/beacon_node/beacon_chain/tests/block_verification.rs b/beacon_node/beacon_chain/tests/block_verification.rs index f1bc135445..74526261c8 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -285,7 +285,7 @@ fn update_data_column_signed_header( let new_column_sidecar = Arc::new(DataColumnSidecar::Fulu(DataColumnSidecarFulu { index: *old_column_sidecar.index(), column: old_column_sidecar.column().clone(), - kzg_commitments: old_column_sidecar.kzg_commitments().clone(), + kzg_commitments: old_column_sidecar.kzg_commitments().unwrap().clone(), kzg_proofs: old_column_sidecar.kzg_proofs().clone(), signed_block_header: signed_block.signed_block_header(), kzg_commitments_inclusion_proof: signed_block diff --git a/beacon_node/beacon_chain/tests/capella.rs b/beacon_node/beacon_chain/tests/capella.rs index 2c2ba8e01a..e8ab795366 100644 --- a/beacon_node/beacon_chain/tests/capella.rs +++ b/beacon_node/beacon_chain/tests/capella.rs @@ -103,7 +103,7 @@ async fn base_altair_bellatrix_capella() { .unwrap(); // Add a slot duration to get to the next slot - let timestamp = harness.get_timestamp_at_slot() + harness.spec.seconds_per_slot; + let timestamp = harness.get_timestamp_at_slot() + harness.spec.get_slot_duration().as_secs(); harness .execution_block_generator() .modify_last_block(|block| { diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index 3bc23283ac..03ce894485 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -3011,7 +3011,6 @@ async fn weak_subjectivity_sync_test( let temp2 = tempdir().unwrap(); let store = get_store(&temp2); let spec = test_spec::(); - let seconds_per_slot = spec.seconds_per_slot; let kzg = get_kzg(&spec); @@ -3025,7 +3024,7 @@ async fn weak_subjectivity_sync_test( let slot_clock = TestingSlotClock::new( Slot::new(0), Duration::from_secs(harness.chain.genesis_time), - Duration::from_secs(seconds_per_slot), + spec.get_slot_duration(), ); slot_clock.set_slot(harness.get_current_slot().as_u64()); @@ -3941,8 +3940,6 @@ async fn revert_minority_fork_on_resume() { let mut spec2 = MinimalEthSpec::default_spec(); spec2.altair_fork_epoch = Some(fork_epoch); - let seconds_per_slot = spec1.seconds_per_slot; - let all_validators = (0..validator_count).collect::>(); // Chain with no fork epoch configured. @@ -4062,7 +4059,7 @@ async fn revert_minority_fork_on_resume() { builder = builder .resume_from_db() .unwrap() - .testing_slot_clock(Duration::from_secs(seconds_per_slot)) + .testing_slot_clock(spec2.get_slot_duration()) .unwrap(); builder .get_slot_clock() diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index 28481f8c40..ede19d6b37 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -316,7 +316,7 @@ where let deneb_time = genesis_time + (deneb_fork_epoch.as_u64() * E::slots_per_epoch() - * spec.seconds_per_slot); + * spec.get_slot_duration().as_secs()); // Shrink the blob availability window so users don't start // a sync right before blobs start to disappear from the P2P @@ -326,7 +326,7 @@ where .saturating_sub(BLOB_AVAILABILITY_REDUCTION_EPOCHS); let blob_availability_window = reduced_p2p_availability_epochs * E::slots_per_epoch() - * spec.seconds_per_slot; + * spec.get_slot_duration().as_secs(); if now > deneb_time + blob_availability_window { return Err( @@ -593,17 +593,17 @@ where .network_globals .clone() .ok_or("slot_notifier requires a libp2p network")?; - let seconds_per_slot = self + let slot_duration = self .chain_spec .as_ref() .ok_or("slot_notifier requires a chain spec")? - .seconds_per_slot; + .get_slot_duration(); spawn_notifier( context.executor, beacon_chain, network_globals, - seconds_per_slot, + slot_duration, ) .map_err(|e| format!("Unable to start slot notifier: {}", e))?; @@ -911,7 +911,7 @@ where let slot_clock = SystemTimeSlotClock::new( spec.genesis_slot, Duration::from_secs(genesis_time), - Duration::from_secs(spec.seconds_per_slot), + spec.get_slot_duration(), ); self.slot_clock = Some(slot_clock); diff --git a/beacon_node/client/src/notifier.rs b/beacon_node/client/src/notifier.rs index 52a3b92cb6..3f01622c35 100644 --- a/beacon_node/client/src/notifier.rs +++ b/beacon_node/client/src/notifier.rs @@ -44,10 +44,8 @@ pub fn spawn_notifier( executor: task_executor::TaskExecutor, beacon_chain: Arc>, network: Arc>, - seconds_per_slot: u64, + slot_duration: Duration, ) -> Result<(), String> { - let slot_duration = Duration::from_secs(seconds_per_slot); - let speedo = Mutex::new(Speedo::default()); // Keep track of sync state and reset the speedo on specific sync state changes. @@ -568,8 +566,8 @@ fn find_next_fork_to_prepare( // Find the first fork that is scheduled and close to happen if let Some(fork_epoch) = fork_epoch { let fork_slot = fork_epoch.start_slot(T::EthSpec::slots_per_epoch()); - let preparation_slots = - FORK_READINESS_PREPARATION_SECONDS / beacon_chain.spec.seconds_per_slot; + let preparation_slots = FORK_READINESS_PREPARATION_SECONDS + / beacon_chain.spec.get_slot_duration().as_secs(); let in_fork_preparation_period = current_slot + preparation_slots > fork_slot; if in_fork_preparation_period { return Some(*fork); diff --git a/beacon_node/execution_layer/src/test_utils/mock_builder.rs b/beacon_node/execution_layer/src/test_utils/mock_builder.rs index 0016db9e0c..464879288b 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -860,7 +860,8 @@ impl MockBuilder { .data .genesis_time }; - let timestamp = (slots_since_genesis * self.spec.seconds_per_slot) + genesis_time; + let timestamp = + (slots_since_genesis * self.spec.get_slot_duration().as_secs()) + genesis_time; let head_state: BeaconState = self .beacon_client diff --git a/beacon_node/http_api/src/builder_states.rs b/beacon_node/http_api/src/builder_states.rs index 7c05dd00d2..73e01debcd 100644 --- a/beacon_node/http_api/src/builder_states.rs +++ b/beacon_node/http_api/src/builder_states.rs @@ -32,7 +32,7 @@ pub fn get_next_withdrawals( } match get_expected_withdrawals(&state, &chain.spec) { - Ok((withdrawals, _)) => Ok(withdrawals), + Ok(expected_withdrawals) => Ok(expected_withdrawals.into()), Err(e) => Err(warp_utils::reject::custom_server_error(format!( "failed to get expected withdrawal: {:?}", e diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 58cd2a3bdb..4d7c76eb20 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -1190,7 +1190,28 @@ pub fn serve( Priority::P1 }; task_spawner.blocking_json_task(priority, move || { - let (block_root, execution_optimistic, finalized) = block_id.root(&chain)?; + // Fast-path for the head block root. We read from the early attester cache + // so that we can produce sync committee messages for the new head prior + // to it being fully imported (written to the DB/etc). We also check that the + // cache is not stale or out of date by comparing against the cached head + // prior to using it. + // + // See: https://github.com/sigp/lighthouse/issues/8667 + let (block_root, execution_optimistic, finalized) = + if let BlockId(eth2::types::BlockId::Head) = block_id + && let Some((head_block_slot, head_block_root)) = + chain.early_attester_cache.get_head_block_root() + && head_block_slot >= chain.canonical_head.cached_head().head_slot() + { + // We know execution is NOT optimistic if the block is from the early + // attester cache because only properly validated blocks are added. + // Similarly we know it is NOT finalized. + let execution_optimistic = false; + let finalized = false; + (head_block_root, execution_optimistic, finalized) + } else { + block_id.root(&chain)? + }; Ok( api_types::GenericResponse::from(api_types::RootData::from(block_root)) .add_execution_optimistic_finalized(execution_optimistic, finalized), diff --git a/beacon_node/http_api/src/publish_blocks.rs b/beacon_node/http_api/src/publish_blocks.rs index bfa6e8c0cb..9f1da8111a 100644 --- a/beacon_node/http_api/src/publish_blocks.rs +++ b/beacon_node/http_api/src/publish_blocks.rs @@ -21,7 +21,6 @@ use futures::TryFutureExt; use lighthouse_network::PubsubMessage; use network::NetworkMessage; use rand::prelude::SliceRandom; -use slot_clock::SlotClock; use std::marker::PhantomData; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; @@ -771,7 +770,7 @@ fn late_block_logging>( // // Check to see the thresholds are non-zero to avoid logging errors with small // slot times (e.g., during testing) - let too_late_threshold = chain.slot_clock.unagg_attestation_production_delay(); + let too_late_threshold = chain.spec.get_unaggregated_attestation_due(); let delayed_threshold = too_late_threshold / 2; if delay >= too_late_threshold { error!( diff --git a/beacon_node/http_api/src/sync_committees.rs b/beacon_node/http_api/src/sync_committees.rs index 6e2f4c9585..efba0056b9 100644 --- a/beacon_node/http_api/src/sync_committees.rs +++ b/beacon_node/http_api/src/sync_committees.rs @@ -235,6 +235,7 @@ pub fn process_sync_committee_signatures( seen_timestamp, verified.sync_message(), &chain.slot_clock, + &chain.spec, ); verified_for_pool = Some(verified); @@ -376,6 +377,7 @@ pub fn process_signed_contribution_and_proofs( verified_contribution.aggregate(), verified_contribution.participant_pubkeys(), &chain.slot_clock, + &chain.spec, ); verified_contributions.push((index, verified_contribution)); diff --git a/beacon_node/http_api/src/validator/mod.rs b/beacon_node/http_api/src/validator/mod.rs index 8baf7c5245..b1ab4c648a 100644 --- a/beacon_node/http_api/src/validator/mod.rs +++ b/beacon_node/http_api/src/validator/mod.rs @@ -862,6 +862,7 @@ pub fn post_validator_aggregate_and_proofs( verified_aggregate.aggregate(), verified_aggregate.indexed_attestation(), &chain.slot_clock, + &chain.spec, ); verified_aggregates.push((index, verified_aggregate)); diff --git a/beacon_node/http_api/tests/interactive_tests.rs b/beacon_node/http_api/tests/interactive_tests.rs index b04c812773..21458057c4 100644 --- a/beacon_node/http_api/tests/interactive_tests.rs +++ b/beacon_node/http_api/tests/interactive_tests.rs @@ -634,7 +634,7 @@ pub async fn proposer_boost_re_org_test( assert_eq!(state_b.slot(), slot_b); let pre_advance_withdrawals = get_expected_withdrawals(&state_b, &harness.chain.spec) .unwrap() - .0 + .withdrawals() .to_vec(); complete_state_advance(&mut state_b, None, slot_c, &harness.chain.spec).unwrap(); @@ -724,7 +724,7 @@ pub async fn proposer_boost_re_org_test( get_expected_withdrawals(&state_b, &harness.chain.spec) } .unwrap() - .0 + .withdrawals() .to_vec(); let payload_attribs_withdrawals = payload_attribs.withdrawals().unwrap(); assert_eq!(expected_withdrawals, *payload_attribs_withdrawals); diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index a763db6421..bef9fe6acd 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -52,7 +52,7 @@ use types::{ type E = MainnetEthSpec; -const SECONDS_PER_SLOT: u64 = 12; +const SLOT_DURATION_MS: u64 = 12_000; const SLOTS_PER_EPOCH: u64 = 32; const VALIDATOR_COUNT: usize = SLOTS_PER_EPOCH as usize; const CHAIN_LENGTH: u64 = SLOTS_PER_EPOCH * 5 - 1; // Make `next_block` an epoch transition @@ -323,7 +323,7 @@ impl ApiTester { let client = BeaconNodeHttpClient::new( beacon_url, - Timeouts::set_all(Duration::from_secs(SECONDS_PER_SLOT)), + Timeouts::set_all(Duration::from_millis(SLOT_DURATION_MS)), ); Self { @@ -411,7 +411,7 @@ impl ApiTester { listening_socket.port() )) .unwrap(), - Timeouts::set_all(Duration::from_secs(SECONDS_PER_SLOT)), + Timeouts::set_all(Duration::from_millis(SLOT_DURATION_MS)), ); Self { @@ -6660,7 +6660,8 @@ impl ApiTester { } let expected_withdrawals = get_expected_withdrawals(&state, &self.chain.spec) .unwrap() - .0; + .withdrawals() + .to_vec(); // fetch expected withdrawals from the client let result = self.client.get_expected_withdrawals(&state_id).await; @@ -6668,7 +6669,7 @@ impl ApiTester { Ok(withdrawal_response) => { assert_eq!(withdrawal_response.execution_optimistic, Some(false)); assert_eq!(withdrawal_response.finalized, Some(false)); - assert_eq!(withdrawal_response.data, expected_withdrawals.to_vec()); + assert_eq!(withdrawal_response.data, expected_withdrawals); } Err(_) => { panic!("query failed incorrectly"); diff --git a/beacon_node/lighthouse_network/src/config.rs b/beacon_node/lighthouse_network/src/config.rs index c14d207484..9940cb9f7f 100644 --- a/beacon_node/lighthouse_network/src/config.rs +++ b/beacon_node/lighthouse_network/src/config.rs @@ -443,7 +443,7 @@ pub fn gossipsub_config( network_load: u8, fork_context: Arc, gossipsub_config_params: GossipsubConfigParams, - seconds_per_slot: u64, + slot_duration: Duration, slots_per_epoch: u64, idontwant_message_size_threshold: usize, ) -> gossipsub::Config { @@ -487,7 +487,7 @@ pub fn gossipsub_config( // To accommodate the increase, we should increase the duplicate cache time to filter older seen messages. // 2 epochs is quite sane for pre-deneb network parameters as well. // Hence we keep the same parameters for pre-deneb networks as well to avoid switching at the fork. - let duplicate_cache_time = Duration::from_secs(slots_per_epoch * seconds_per_slot * 2); + let duplicate_cache_time = Duration::from_secs(slots_per_epoch * slot_duration.as_secs() * 2); gossipsub::ConfigBuilder::default() .max_transmit_size(gossipsub_config_params.gossipsub_max_transmit_size) diff --git a/beacon_node/lighthouse_network/src/service/gossipsub_scoring_parameters.rs b/beacon_node/lighthouse_network/src/service/gossipsub_scoring_parameters.rs index 2cfae67281..7d1fa2d4dc 100644 --- a/beacon_node/lighthouse_network/src/service/gossipsub_scoring_parameters.rs +++ b/beacon_node/lighthouse_network/src/service/gossipsub_scoring_parameters.rs @@ -54,7 +54,7 @@ pub struct PeerScoreSettings { impl PeerScoreSettings { pub fn new(chain_spec: &ChainSpec, mesh_n: usize) -> PeerScoreSettings { - let slot = Duration::from_secs(chain_spec.seconds_per_slot); + let slot = chain_spec.get_slot_duration(); let beacon_attestation_subnet_weight = 1.0 / chain_spec.attestation_subnet_count as f64; let max_positive_score = (MAX_IN_MESH_SCORE + MAX_FIRST_MESSAGE_DELIVERIES_SCORE) * (BEACON_BLOCK_WEIGHT diff --git a/beacon_node/lighthouse_network/src/service/mod.rs b/beacon_node/lighthouse_network/src/service/mod.rs index 8b52096b38..74b1fb4b98 100644 --- a/beacon_node/lighthouse_network/src/service/mod.rs +++ b/beacon_node/lighthouse_network/src/service/mod.rs @@ -232,7 +232,7 @@ impl Network { config.network_load, ctx.fork_context.clone(), gossipsub_config_params, - ctx.chain_spec.seconds_per_slot, + ctx.chain_spec.get_slot_duration(), E::slots_per_epoch(), config.idontwant_message_size_threshold, ); @@ -240,13 +240,12 @@ impl Network { let score_settings = PeerScoreSettings::new(&ctx.chain_spec, gs_config.mesh_n()); let gossip_cache = { - let slot_duration = std::time::Duration::from_secs(ctx.chain_spec.seconds_per_slot); - let half_epoch = std::time::Duration::from_secs( - ctx.chain_spec.seconds_per_slot * E::slots_per_epoch() / 2, + let half_epoch = std::time::Duration::from_millis( + (ctx.chain_spec.get_slot_duration().as_millis() as u64) * E::slots_per_epoch() / 2, ); GossipCache::builder() - .beacon_block_timeout(slot_duration) + .beacon_block_timeout(ctx.chain_spec.get_slot_duration()) .aggregates_timeout(half_epoch) .attestation_timeout(half_epoch) .voluntary_exit_timeout(half_epoch * 2) diff --git a/beacon_node/lighthouse_network/tests/rpc_tests.rs b/beacon_node/lighthouse_network/tests/rpc_tests.rs index c569d7172b..d73a3957cb 100644 --- a/beacon_node/lighthouse_network/tests/rpc_tests.rs +++ b/beacon_node/lighthouse_network/tests/rpc_tests.rs @@ -1019,7 +1019,6 @@ fn test_tcp_columns_by_root_chunked_rpc_for_fork(fork_name: ForkName) { column: vec![vec![0; E::bytes_per_cell()].try_into().unwrap()] .try_into() .unwrap(), - kzg_commitments: vec![KzgCommitment::empty_for_testing()].try_into().unwrap(), kzg_proofs: vec![KzgProof::empty()].try_into().unwrap(), })) } else { @@ -1190,7 +1189,6 @@ fn test_tcp_columns_by_range_chunked_rpc_for_fork(fork_name: ForkName) { column: vec![vec![0; E::bytes_per_cell()].try_into().unwrap()] .try_into() .unwrap(), - kzg_commitments: vec![KzgCommitment::empty_for_testing()].try_into().unwrap(), kzg_proofs: vec![KzgProof::empty()].try_into().unwrap(), })) } else { 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 75b7ebffd8..368ff55811 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -540,6 +540,7 @@ impl NetworkBeaconProcessor { aggregate, indexed_attestation, &self.chain.slot_clock, + &self.chain.spec, ); metrics::inc_counter( @@ -809,7 +810,7 @@ impl NetworkBeaconProcessor { Ok(gossip_verified_blob) => { metrics::inc_counter(&metrics::BEACON_PROCESSOR_GOSSIP_BLOB_VERIFIED_TOTAL); - if delay >= self.chain.slot_clock.unagg_attestation_production_delay() { + if delay >= self.chain.spec.get_unaggregated_attestation_due() { metrics::inc_counter(&metrics::BEACON_BLOB_GOSSIP_ARRIVED_LATE_TOTAL); debug!( block_root = ?gossip_verified_blob.block_root(), @@ -1238,7 +1239,7 @@ impl NetworkBeaconProcessor { let verified_block = match verification_result { Ok(verified_block) => { - if block_delay >= self.chain.slot_clock.unagg_attestation_production_delay() { + if block_delay >= self.chain.spec.get_unaggregated_attestation_due() { metrics::inc_counter(&metrics::BEACON_BLOCK_DELAY_GOSSIP_ARRIVED_LATE_TOTAL); debug!( block_root = ?verified_block.block_root, @@ -1915,6 +1916,7 @@ impl NetworkBeaconProcessor { seen_timestamp, sync_signature.sync_message(), &self.chain.slot_clock, + &self.chain.spec, ); metrics::inc_counter(&metrics::BEACON_PROCESSOR_SYNC_MESSAGE_VERIFIED_TOTAL); @@ -1977,6 +1979,7 @@ impl NetworkBeaconProcessor { sync_contribution.aggregate(), sync_contribution.participant_pubkeys(), &self.chain.slot_clock, + &self.chain.spec, ); metrics::inc_counter(&metrics::BEACON_PROCESSOR_SYNC_CONTRIBUTION_VERIFIED_TOTAL); diff --git a/beacon_node/network/src/network_beacon_processor/sync_methods.rs b/beacon_node/network/src/network_beacon_processor/sync_methods.rs index dff8fbabff..6f33344b94 100644 --- a/beacon_node/network/src/network_beacon_processor/sync_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/sync_methods.rs @@ -297,7 +297,7 @@ impl NetworkBeaconProcessor { && current_slot == slot { // Note: this metric is useful to gauge how long it takes to receive blobs requested - // over rpc. Since we always send the request for block components at `slot_clock.single_lookup_delay()` + // over rpc. Since we always send the request for block components at `get_unaggregated_attestation_due() / 2` // we can use that as a baseline to measure against. let delay = get_slot_delay_ms(seen_timestamp, slot, &self.chain.slot_clock); diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index 0869b442ae..af56b80822 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -862,9 +862,11 @@ impl NetworkService { self.next_digest_update = Box::pin(next_digest_delay(&self.beacon_chain).into()); // Set the next_unsubscribe delay. - let epoch_duration = - self.beacon_chain.spec.seconds_per_slot * T::EthSpec::slots_per_epoch(); - let unsubscribe_delay = Duration::from_secs(UNSUBSCRIBE_DELAY_EPOCHS * epoch_duration); + let unsubscribe_delay = Duration::from_secs( + UNSUBSCRIBE_DELAY_EPOCHS + * self.beacon_chain.spec.get_slot_duration().as_secs() + * T::EthSpec::slots_per_epoch(), + ); // Update the `next_topic_subscriptions` timer if the next change in the fork digest is known. self.next_topic_subscriptions = @@ -915,7 +917,7 @@ fn next_topic_subscriptions_delay( ) -> Option { if let Some((_, duration_to_epoch)) = beacon_chain.duration_to_next_digest() { let duration_to_subscription = duration_to_epoch.saturating_sub(Duration::from_secs( - beacon_chain.spec.seconds_per_slot * SUBSCRIBE_DELAY_SLOTS, + beacon_chain.spec.get_slot_duration().as_secs() * SUBSCRIBE_DELAY_SLOTS, )); if !duration_to_subscription.is_zero() { return Some(tokio::time::sleep(duration_to_subscription)); diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 26dd3b6642..2e5a045502 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -766,10 +766,7 @@ pub fn get_config( client_config.chain.prepare_payload_lookahead = clap_utils::parse_optional(cli_args, "prepare-payload-lookahead")? .map(Duration::from_millis) - .unwrap_or_else(|| { - Duration::from_secs(spec.seconds_per_slot) - / DEFAULT_PREPARE_PAYLOAD_LOOKAHEAD_FACTOR - }); + .unwrap_or_else(|| spec.get_slot_duration() / DEFAULT_PREPARE_PAYLOAD_LOOKAHEAD_FACTOR); client_config.chain.always_prepare_payload = cli_args.get_flag("always-prepare-payload"); diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index a287aba1ef..6e165702a2 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -93,7 +93,6 @@ struct BlockCache { block_cache: LruCache>, blob_cache: LruCache>, data_column_cache: LruCache>>>, - payload_envelope_cache: LruCache>, data_column_custody_info_cache: Option, } @@ -103,7 +102,6 @@ impl BlockCache { block_cache: LruCache::new(size), blob_cache: LruCache::new(size), data_column_cache: LruCache::new(size), - payload_envelope_cache: LruCache::new(size), data_column_custody_info_cache: None, } } @@ -118,14 +116,6 @@ impl BlockCache { .get_or_insert_mut(block_root, Default::default) .insert(*data_column.index(), data_column); } - pub fn put_payload_envelope( - &mut self, - block_root: Hash256, - payload_envelope: SignedExecutionPayloadEnvelope, - ) { - self.payload_envelope_cache - .put(block_root, payload_envelope); - } pub fn put_data_column_custody_info( &mut self, data_column_custody_info: Option, @@ -149,12 +139,6 @@ impl BlockCache { .get(block_root) .and_then(|map| map.get(column_index).cloned()) } - pub fn get_payload_envelope<'a>( - &'a mut self, - block_root: &Hash256, - ) -> Option<&'a SignedExecutionPayloadEnvelope> { - self.payload_envelope_cache.get(block_root) - } pub fn get_data_column_custody_info(&self) -> Option { self.data_column_custody_info_cache.clone() } @@ -167,14 +151,10 @@ impl BlockCache { pub fn delete_data_columns(&mut self, block_root: &Hash256) { let _ = self.data_column_cache.pop(block_root); } - pub fn delete_payload_envelope(&mut self, block_root: &Hash256) { - let _ = self.payload_envelope_cache.pop(block_root); - } pub fn delete(&mut self, block_root: &Hash256) { self.delete_block(block_root); self.delete_blobs(block_root); self.delete_data_columns(block_root); - self.delete_payload_envelope(block_root); } } @@ -528,10 +508,6 @@ impl, Cold: ItemStore> HotColdDB &metrics::STORE_BEACON_BLOB_CACHE_SIZE, cache.blob_cache.len() as i64, ); - metrics::set_gauge( - &metrics::STORE_BEACON_PAYLOAD_ENVELOPE_CACHE_SIZE, - cache.payload_envelope_cache.len() as i64, - ); } let state_cache = self.state_cache.lock(); metrics::set_gauge( @@ -773,16 +749,6 @@ impl, Cold: ItemStore> HotColdDB &self, block_root: &Hash256, ) -> Result>, Error> { - // Check the cache. - if let Some(envelope) = self - .block_cache - .as_ref() - .and_then(|cache| cache.lock().get_payload_envelope(block_root).cloned()) - { - metrics::inc_counter(&metrics::BEACON_PAYLOAD_ENVELOPE_CACHE_HIT_COUNT); - return Ok(Some(envelope)); - } - let key = block_root.as_slice(); match self @@ -791,29 +757,14 @@ impl, Cold: ItemStore> HotColdDB { Some(bytes) => { let envelope = SignedExecutionPayloadEnvelope::from_ssz_bytes(&bytes)?; - self.block_cache.as_ref().inspect(|cache| { - cache - .lock() - .put_payload_envelope(*block_root, envelope.clone()) - }); Ok(Some(envelope)) } None => Ok(None), } } - /// Check if the payload envelope for a block exists on disk or in cache. + /// Check if the payload envelope for a block exists on disk. pub fn payload_envelope_exists(&self, block_root: &Hash256) -> Result { - // Check the cache first. - if self - .block_cache - .as_ref() - .and_then(|cache| cache.lock().get_payload_envelope(block_root).cloned()) - .is_some() - { - return Ok(true); - } - self.hot_db.key_exists( SignedExecutionPayloadEnvelope::::db_column(), block_root.as_slice(), @@ -1126,13 +1077,7 @@ impl, Cold: ItemStore> HotColdDB SignedExecutionPayloadEnvelope::::db_column(), block_root.as_slice(), &payload_envelope.as_ssz_bytes(), - )?; - self.block_cache.as_ref().inspect(|cache| { - cache - .lock() - .put_payload_envelope(*block_root, payload_envelope) - }); - Ok(()) + ) } /// Store a state in the store. @@ -1651,9 +1596,7 @@ impl, Cold: ItemStore> HotColdDB StoreOp::PutDataColumns(_, _) => (), - StoreOp::PutPayloadEnvelope(block_root, payload_envelope) => { - guard.put_payload_envelope(block_root, (*payload_envelope).clone()); - } + StoreOp::PutPayloadEnvelope(_, _) => (), StoreOp::PutState(_, _) => (), @@ -1663,9 +1606,7 @@ impl, Cold: ItemStore> HotColdDB guard.delete_block(&block_root); } - StoreOp::DeletePayloadEnvelope(block_root) => { - guard.delete_payload_envelope(&block_root); - } + StoreOp::DeletePayloadEnvelope(_) => (), StoreOp::DeleteState(_, _) => (), diff --git a/beacon_node/store/src/metrics.rs b/beacon_node/store/src/metrics.rs index 59fd583a46..2f83457b63 100644 --- a/beacon_node/store/src/metrics.rs +++ b/beacon_node/store/src/metrics.rs @@ -270,13 +270,6 @@ pub static STORE_BEACON_BLOB_CACHE_SIZE: LazyLock> = LazyLock:: "Current count of items in beacon store blob cache", ) }); -pub static STORE_BEACON_PAYLOAD_ENVELOPE_CACHE_SIZE: LazyLock> = - LazyLock::new(|| { - try_create_int_gauge( - "store_beacon_payload_envelope_cache_size", - "Current count of items in beacon store payload envelope cache", - ) - }); pub static STORE_BEACON_STATE_CACHE_SIZE: LazyLock> = LazyLock::new(|| { try_create_int_gauge( "store_beacon_state_cache_size", diff --git a/book/src/api_vc_endpoints.md b/book/src/api_vc_endpoints.md index d128b13b2f..cc9dd362f8 100644 --- a/book/src/api_vc_endpoints.md +++ b/book/src/api_vc_endpoints.md @@ -249,6 +249,7 @@ Example Response Body "FULU_FORK_VERSION": "0x70000910", "FULU_FORK_EPOCH": "18446744073709551615", "SECONDS_PER_SLOT": "12", + "SLOT_DURATION_MS": "12000", "SECONDS_PER_ETH1_BLOCK": "12", "MIN_VALIDATOR_WITHDRAWABILITY_DELAY": "256", "SHARD_COMMITTEE_PERIOD": "256", diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index f02a4f736d..ba0d2ca3fb 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -1016,7 +1016,11 @@ impl SseDataColumnSidecar { pub fn from_data_column_sidecar( data_column_sidecar: &DataColumnSidecar, ) -> SseDataColumnSidecar { - let kzg_commitments = data_column_sidecar.kzg_commitments().to_vec(); + // TODO(gloas): fetch kzg_commitments from block for Gloas SSE events + let kzg_commitments: Vec = match data_column_sidecar { + DataColumnSidecar::Fulu(dc) => dc.kzg_commitments.to_vec(), + DataColumnSidecar::Gloas(_) => vec![], + }; let versioned_hashes = kzg_commitments .iter() .map(|c| c.calculate_versioned_hash()) diff --git a/common/eth2_network_config/built_in_network_configs/chiado/config.yaml b/common/eth2_network_config/built_in_network_configs/chiado/config.yaml index eafe1ad38c..f0c04d891a 100644 --- a/common/eth2_network_config/built_in_network_configs/chiado/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/chiado/config.yaml @@ -58,6 +58,8 @@ GLOAS_FORK_EPOCH: 18446744073709551615 # --------------------------------------------------------------- # 5 seconds SECONDS_PER_SLOT: 5 +# 5 seconds +SLOT_DURATION_MS: 5000 # 6 (estimate from xDai mainnet) SECONDS_PER_ETH1_BLOCK: 6 # 2**8 (= 256) epochs ~5.7 hours @@ -66,6 +68,18 @@ MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 SHARD_COMMITTEE_PERIOD: 256 # 2**10 (= 1024) ~1.4 hour ETH1_FOLLOW_DISTANCE: 1024 +# 1667 basis points, ~17% of SLOT_DURATION_MS +PROPOSER_REORG_CUTOFF_BPS: 1667 +# 3333 basis points, ~33% of SLOT_DURATION_MS +ATTESTATION_DUE_BPS: 3333 +# 6667 basis points, ~67% of SLOT_DURATION_MS +AGGREGATE_DUE_BPS: 6667 + +# Altair +# 3333 basis points, ~33% of SLOT_DURATION_MS +SYNC_MESSAGE_DUE_BPS: 3333 +# 6667 basis points, ~67% of SLOT_DURATION_MS +CONTRIBUTION_DUE_BPS: 6667 # Validator cycle # --------------------------------------------------------------- diff --git a/common/eth2_network_config/built_in_network_configs/gnosis/config.yaml b/common/eth2_network_config/built_in_network_configs/gnosis/config.yaml index 2beeb45b25..34313aa393 100644 --- a/common/eth2_network_config/built_in_network_configs/gnosis/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/gnosis/config.yaml @@ -55,6 +55,8 @@ GLOAS_FORK_EPOCH: 18446744073709551615 # --------------------------------------------------------------- # 5 seconds SECONDS_PER_SLOT: 5 +# 5 seconds +SLOT_DURATION_MS: 5000 # 6 (estimate from Gnosis Chain) SECONDS_PER_ETH1_BLOCK: 6 # 2**8 (= 256) epochs ~8 hours @@ -63,6 +65,18 @@ MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 SHARD_COMMITTEE_PERIOD: 256 # 2**10 (= 1024) ~1.4 hour ETH1_FOLLOW_DISTANCE: 1024 +# 1667 basis points, ~17% of SLOT_DURATION_MS +PROPOSER_REORG_CUTOFF_BPS: 1667 +# 3333 basis points, ~33% of SLOT_DURATION_MS +ATTESTATION_DUE_BPS: 3333 +# 6667 basis points, ~67% of SLOT_DURATION_MS +AGGREGATE_DUE_BPS: 6667 + +# Altair +# 3333 basis points, ~33% of SLOT_DURATION_MS +SYNC_MESSAGE_DUE_BPS: 3333 +# 6667 basis points, ~67% of SLOT_DURATION_MS +CONTRIBUTION_DUE_BPS: 6667 # Validator cycle # --------------------------------------------------------------- diff --git a/common/slot_clock/src/lib.rs b/common/slot_clock/src/lib.rs index e51bc3f647..abfab547b9 100644 --- a/common/slot_clock/src/lib.rs +++ b/common/slot_clock/src/lib.rs @@ -9,7 +9,6 @@ pub use crate::manual_slot_clock::ManualSlotClock; pub use crate::system_time_slot_clock::SystemTimeSlotClock; pub use metrics::scrape_for_metrics; pub use types::Slot; -use types::consts::bellatrix::INTERVALS_PER_SLOT; /// A clock that reports the current slot. /// @@ -77,30 +76,6 @@ pub trait SlotClock: Send + Sync + Sized + Clone { .or_else(|| Some(self.genesis_slot())) } - /// Returns the delay between the start of the slot and when unaggregated attestations should be - /// produced. - fn unagg_attestation_production_delay(&self) -> Duration { - self.slot_duration() / INTERVALS_PER_SLOT as u32 - } - - /// Returns the delay between the start of the slot and when sync committee messages should be - /// produced. - fn sync_committee_message_production_delay(&self) -> Duration { - self.slot_duration() / INTERVALS_PER_SLOT as u32 - } - - /// Returns the delay between the start of the slot and when aggregated attestations should be - /// produced. - fn agg_attestation_production_delay(&self) -> Duration { - self.slot_duration() * 2 / INTERVALS_PER_SLOT as u32 - } - - /// Returns the delay between the start of the slot and when partially aggregated `SyncCommitteeContribution` should be - /// produced. - fn sync_committee_contribution_production_delay(&self) -> Duration { - self.slot_duration() * 2 / INTERVALS_PER_SLOT as u32 - } - /// Returns the `Duration` since the start of the current `Slot` at seconds precision. Useful in determining whether to apply proposer boosts. fn seconds_from_current_slot_start(&self) -> Option { self.now_duration() @@ -134,13 +109,4 @@ pub trait SlotClock: Send + Sync + Sized + Clone { slot_clock.set_current_time(freeze_at); slot_clock } - - /// Returns the delay between the start of the slot and when a request for block components - /// missed over gossip in the current slot should be made via RPC. - /// - /// Currently set equal to 1/2 of the `unagg_attestation_production_delay`, but this may be - /// changed in the future. - fn single_lookup_delay(&self) -> Duration { - self.unagg_attestation_production_delay() / 2 - } } diff --git a/consensus/fork_choice/src/fork_choice.rs b/consensus/fork_choice/src/fork_choice.rs index 9a8cae0c36..9744b9fa08 100644 --- a/consensus/fork_choice/src/fork_choice.rs +++ b/consensus/fork_choice/src/fork_choice.rs @@ -21,7 +21,6 @@ use types::{ AbstractExecPayload, AttestationShufflingId, AttesterSlashingRef, BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec, Checkpoint, Epoch, EthSpec, ExecPayload, ExecutionBlockHash, Hash256, IndexedAttestationRef, RelativeEpoch, SignedBeaconBlock, Slot, - consts::bellatrix::INTERVALS_PER_SLOT, }; #[derive(Debug)] @@ -77,6 +76,7 @@ pub enum Error { }, UnrealizedVoteProcessing(state_processing::EpochProcessingError), ValidatorStatuses(BeaconStateError), + ChainSpecError(String), } impl From for Error { @@ -727,9 +727,10 @@ where })); } + let attestation_threshold = spec.get_unaggregated_attestation_due(); + // Add proposer score boost if the block is timely. - let is_before_attesting_interval = - block_delay < Duration::from_secs(spec.seconds_per_slot / INTERVALS_PER_SLOT); + let is_before_attesting_interval = block_delay < attestation_threshold; let is_first_block = self.fc_store.proposer_boost_root().is_zero(); if current_slot == block.slot() && is_before_attesting_interval && is_first_block { diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index cd1c1b9849..1de5083f6f 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -1,7 +1,7 @@ use crate::consensus_context::ConsensusContext; use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid}; use rayon::prelude::*; -use safe_arith::{ArithError, SafeArith, SafeArithIter}; +use safe_arith::{ArithError, SafeArith}; use signature_sets::{block_proposal_signature_set, get_pubkey_from_state, randao_signature_set}; use std::borrow::Cow; use tree_hash::TreeHash; @@ -24,9 +24,11 @@ pub use verify_deposit::{ get_existing_validator_index, is_valid_deposit_signature, verify_deposit_merkle_proof, }; pub use verify_exit::verify_exit; +pub use withdrawals::get_expected_withdrawals; pub mod altair; pub mod block_signature_verifier; +pub mod builder; pub mod deneb; pub mod errors; mod is_valid_indexed_attestation; @@ -39,8 +41,8 @@ mod verify_bls_to_execution_change; mod verify_deposit; mod verify_exit; mod verify_proposer_slashing; +pub mod withdrawals; -use crate::common::decrease_balance; use crate::common::update_progressive_balances_cache::{ initialize_progressive_balances_cache, update_progressive_balances_metrics, }; @@ -172,14 +174,21 @@ pub fn per_block_processing>( // previous block. if is_execution_enabled(state, block.body()) { let body = block.body(); - // TODO(EIP-7732): build out process_withdrawals variant for gloas - process_withdrawals::(state, body.execution_payload()?, spec)?; - process_execution_payload::(state, body, spec)?; + if state.fork_name_unchecked().gloas_enabled() { + withdrawals::gloas::process_withdrawals::(state, spec)?; + // TODO(EIP-7732): process execution payload bid + } else { + if state.fork_name_unchecked().capella_enabled() { + withdrawals::capella_electra::process_withdrawals::( + state, + body.execution_payload()?, + spec, + )?; + } + process_execution_payload::(state, body, spec)?; + } } - // TODO(EIP-7732): build out process_execution_bid - // process_execution_bid(state, block, verify_signatures, spec)?; - process_randao(state, block, verify_randao, ctxt, spec)?; process_eth1_data(state, block.body().eth1_data())?; process_operations(state, block.body(), verify_signatures, ctxt, spec)?; @@ -510,193 +519,6 @@ pub fn compute_timestamp_at_slot( ) -> Result { let slots_since_genesis = block_slot.as_u64().safe_sub(spec.genesis_slot.as_u64())?; slots_since_genesis - .safe_mul(spec.seconds_per_slot) + .safe_mul(spec.get_slot_duration().as_secs()) .and_then(|since_genesis| state.genesis_time().safe_add(since_genesis)) } - -/// Compute the next batch of withdrawals which should be included in a block. -/// -/// https://github.com/ethereum/consensus-specs/blob/dev/specs/electra/beacon-chain.md#new-get_expected_withdrawals -pub fn get_expected_withdrawals( - state: &BeaconState, - spec: &ChainSpec, -) -> Result<(Withdrawals, Option), BlockProcessingError> { - let epoch = state.current_epoch(); - let mut withdrawal_index = state.next_withdrawal_index()?; - let mut validator_index = state.next_withdrawal_validator_index()?; - let mut withdrawals = Vec::::with_capacity(E::max_withdrawals_per_payload()); - let fork_name = state.fork_name_unchecked(); - - // [New in Electra:EIP7251] - // Consume pending partial withdrawals - let processed_partial_withdrawals_count = - if let Ok(pending_partial_withdrawals) = state.pending_partial_withdrawals() { - let mut processed_partial_withdrawals_count = 0; - for withdrawal in pending_partial_withdrawals { - if withdrawal.withdrawable_epoch > epoch - || withdrawals.len() == spec.max_pending_partials_per_withdrawals_sweep as usize - { - break; - } - - let validator = state.get_validator(withdrawal.validator_index as usize)?; - - let has_sufficient_effective_balance = - validator.effective_balance >= spec.min_activation_balance; - let total_withdrawn = withdrawals - .iter() - .filter_map(|w| { - (w.validator_index == withdrawal.validator_index).then_some(w.amount) - }) - .safe_sum()?; - let balance = state - .get_balance(withdrawal.validator_index as usize)? - .safe_sub(total_withdrawn)?; - let has_excess_balance = balance > spec.min_activation_balance; - - if validator.exit_epoch == spec.far_future_epoch - && has_sufficient_effective_balance - && has_excess_balance - { - let withdrawable_balance = std::cmp::min( - balance.safe_sub(spec.min_activation_balance)?, - withdrawal.amount, - ); - withdrawals.push(Withdrawal { - index: withdrawal_index, - validator_index: withdrawal.validator_index, - address: validator - .get_execution_withdrawal_address(spec) - .ok_or(BeaconStateError::NonExecutionAddressWithdrawalCredential)?, - amount: withdrawable_balance, - }); - withdrawal_index.safe_add_assign(1)?; - } - processed_partial_withdrawals_count.safe_add_assign(1)?; - } - Some(processed_partial_withdrawals_count) - } else { - None - }; - - let bound = std::cmp::min( - state.validators().len() as u64, - spec.max_validators_per_withdrawals_sweep, - ); - for _ in 0..bound { - let validator = state.get_validator(validator_index as usize)?; - let partially_withdrawn_balance = withdrawals - .iter() - .filter_map(|withdrawal| { - (withdrawal.validator_index == validator_index).then_some(withdrawal.amount) - }) - .safe_sum()?; - let balance = state - .balances() - .get(validator_index as usize) - .ok_or(BeaconStateError::BalancesOutOfBounds( - validator_index as usize, - ))? - .safe_sub(partially_withdrawn_balance)?; - if validator.is_fully_withdrawable_validator(balance, epoch, spec, fork_name) { - withdrawals.push(Withdrawal { - index: withdrawal_index, - validator_index, - address: validator - .get_execution_withdrawal_address(spec) - .ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?, - amount: balance, - }); - withdrawal_index.safe_add_assign(1)?; - } else if validator.is_partially_withdrawable_validator(balance, spec, fork_name) { - withdrawals.push(Withdrawal { - index: withdrawal_index, - validator_index, - address: validator - .get_execution_withdrawal_address(spec) - .ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?, - amount: balance.safe_sub(validator.get_max_effective_balance(spec, fork_name))?, - }); - withdrawal_index.safe_add_assign(1)?; - } - if withdrawals.len() == E::max_withdrawals_per_payload() { - break; - } - validator_index = validator_index - .safe_add(1)? - .safe_rem(state.validators().len() as u64)?; - } - - Ok(( - withdrawals - .try_into() - .map_err(BlockProcessingError::SszTypesError)?, - processed_partial_withdrawals_count, - )) -} - -/// Apply withdrawals to the state. -/// TODO(EIP-7732): abstract this out and create gloas variant -pub fn process_withdrawals>( - state: &mut BeaconState, - payload: Payload::Ref<'_>, - spec: &ChainSpec, -) -> Result<(), BlockProcessingError> { - if state.fork_name_unchecked().capella_enabled() { - let (expected_withdrawals, processed_partial_withdrawals_count) = - get_expected_withdrawals(state, spec)?; - let expected_root = expected_withdrawals.tree_hash_root(); - let withdrawals_root = payload.withdrawals_root()?; - - if expected_root != withdrawals_root { - return Err(BlockProcessingError::WithdrawalsRootMismatch { - expected: expected_root, - found: withdrawals_root, - }); - } - - for withdrawal in expected_withdrawals.iter() { - decrease_balance( - state, - withdrawal.validator_index as usize, - withdrawal.amount, - )?; - } - - // Update pending partial withdrawals [New in Electra:EIP7251] - if let Some(processed_partial_withdrawals_count) = processed_partial_withdrawals_count { - state - .pending_partial_withdrawals_mut()? - .pop_front(processed_partial_withdrawals_count)?; - } - - // Update the next withdrawal index if this block contained withdrawals - if let Some(latest_withdrawal) = expected_withdrawals.last() { - *state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?; - - // Update the next validator index to start the next withdrawal sweep - if expected_withdrawals.len() == E::max_withdrawals_per_payload() { - // Next sweep starts after the latest withdrawal's validator index - let next_validator_index = latest_withdrawal - .validator_index - .safe_add(1)? - .safe_rem(state.validators().len() as u64)?; - *state.next_withdrawal_validator_index_mut()? = next_validator_index; - } - } - - // Advance sweep by the max length of the sweep if there was not a full set of withdrawals - if expected_withdrawals.len() != E::max_withdrawals_per_payload() { - let next_validator_index = state - .next_withdrawal_validator_index()? - .safe_add(spec.max_validators_per_withdrawals_sweep)? - .safe_rem(state.validators().len() as u64)?; - *state.next_withdrawal_validator_index_mut()? = next_validator_index; - } - - Ok(()) - } else { - // these shouldn't even be encountered but they're here for completeness - Ok(()) - } -} diff --git a/consensus/state_processing/src/per_block_processing/builder.rs b/consensus/state_processing/src/per_block_processing/builder.rs new file mode 100644 index 0000000000..cbaac92c64 --- /dev/null +++ b/consensus/state_processing/src/per_block_processing/builder.rs @@ -0,0 +1,13 @@ +use types::{builder::BuilderIndex, consts::gloas::BUILDER_INDEX_FLAG}; + +pub fn is_builder_index(validator_index: u64) -> bool { + validator_index & BUILDER_INDEX_FLAG != 0 +} + +pub fn convert_builder_index_to_validator_index(builder_index: BuilderIndex) -> u64 { + builder_index | BUILDER_INDEX_FLAG +} + +pub fn convert_validator_index_to_builder_index(validator_index: u64) -> BuilderIndex { + validator_index & !BUILDER_INDEX_FLAG +} diff --git a/consensus/state_processing/src/per_block_processing/errors.rs b/consensus/state_processing/src/per_block_processing/errors.rs index ff7c0204e2..d0cf7b46d9 100644 --- a/consensus/state_processing/src/per_block_processing/errors.rs +++ b/consensus/state_processing/src/per_block_processing/errors.rs @@ -90,6 +90,14 @@ pub enum BlockProcessingError { found: Hash256, }, WithdrawalCredentialsInvalid, + /// This should be unreachable unless there's a logical flaw in the spec for withdrawals. + WithdrawalsLimitExceeded { + limit: usize, + prior_withdrawals: usize, + }, + /// Unreachable unless there's a logic error in LH. + IncorrectExpectedWithdrawalsVariant, + MissingLastWithdrawal, PendingAttestationInElectra, } diff --git a/consensus/state_processing/src/per_block_processing/withdrawals.rs b/consensus/state_processing/src/per_block_processing/withdrawals.rs new file mode 100644 index 0000000000..72c3339b10 --- /dev/null +++ b/consensus/state_processing/src/per_block_processing/withdrawals.rs @@ -0,0 +1,535 @@ +use crate::common::decrease_balance; +use crate::per_block_processing::builder::{ + convert_builder_index_to_validator_index, convert_validator_index_to_builder_index, + is_builder_index, +}; +use crate::per_block_processing::errors::BlockProcessingError; +use milhouse::List; +use safe_arith::{SafeArith, SafeArithIter}; +use tree_hash::TreeHash; +use types::{ + AbstractExecPayload, BeaconState, BeaconStateError, ChainSpec, EthSpec, ExecPayload, + ExpectedWithdrawals, ExpectedWithdrawalsCapella, ExpectedWithdrawalsElectra, + ExpectedWithdrawalsGloas, Validator, Withdrawal, Withdrawals, +}; + +/// Compute the next batch of withdrawals which should be included in a block. +/// +/// https://ethereum.github.io/consensus-specs/specs/gloas/beacon-chain/#modified-get_expected_withdrawals +#[allow(clippy::type_complexity)] +pub fn get_expected_withdrawals( + state: &BeaconState, + spec: &ChainSpec, +) -> Result, BlockProcessingError> { + let mut withdrawal_index = state.next_withdrawal_index()?; + let mut withdrawals = Vec::::with_capacity(E::max_withdrawals_per_payload()); + + // [New in Gloas:EIP7732] + // Get builder withdrawals + let processed_builder_withdrawals_count = + get_builder_withdrawals(state, &mut withdrawal_index, &mut withdrawals)?; + + // [New in Electra:EIP7251] + // Get partial withdrawals. + let processed_partial_withdrawals_count = + get_pending_partial_withdrawals(state, &mut withdrawal_index, &mut withdrawals, spec)?; + + // [New in Gloas:EIP7732] + // Get builders sweep withdrawals + let processed_builders_sweep_count = + get_builders_sweep_withdrawals(state, &mut withdrawal_index, &mut withdrawals)?; + + // Get validators sweep withdrawals + let processed_sweep_withdrawals_count = + get_validators_sweep_withdrawals(state, &mut withdrawal_index, &mut withdrawals, spec)?; + + let withdrawals = withdrawals + .try_into() + .map_err(BlockProcessingError::SszTypesError)?; + + let fork_name = state.fork_name_unchecked(); + if fork_name.gloas_enabled() { + Ok(ExpectedWithdrawals::Gloas(ExpectedWithdrawalsGloas { + withdrawals, + processed_builder_withdrawals_count: processed_builder_withdrawals_count + .ok_or(BlockProcessingError::IncorrectExpectedWithdrawalsVariant)?, + processed_partial_withdrawals_count: processed_partial_withdrawals_count + .ok_or(BlockProcessingError::IncorrectExpectedWithdrawalsVariant)?, + processed_builders_sweep_count: processed_builders_sweep_count + .ok_or(BlockProcessingError::IncorrectExpectedWithdrawalsVariant)?, + processed_sweep_withdrawals_count, + })) + } else if fork_name.electra_enabled() { + Ok(ExpectedWithdrawals::Electra(ExpectedWithdrawalsElectra { + withdrawals, + processed_partial_withdrawals_count: processed_partial_withdrawals_count + .ok_or(BlockProcessingError::IncorrectExpectedWithdrawalsVariant)?, + processed_sweep_withdrawals_count, + })) + } else { + Ok(ExpectedWithdrawals::Capella(ExpectedWithdrawalsCapella { + withdrawals, + processed_sweep_withdrawals_count, + })) + } +} + +pub fn get_builder_withdrawals( + state: &BeaconState, + withdrawal_index: &mut u64, + withdrawals: &mut Vec, +) -> Result, BlockProcessingError> { + let Ok(builder_pending_withdrawals) = state.builder_pending_withdrawals() else { + // Pre-Gloas, nothing to do. + return Ok(None); + }; + + let withdrawals_limit = E::max_withdrawals_per_payload().safe_sub(1)?; + + block_verify!( + withdrawals.len() <= withdrawals_limit, + BlockProcessingError::WithdrawalsLimitExceeded { + limit: withdrawals_limit, + prior_withdrawals: withdrawals.len() + } + ); + + let mut processed_count = 0; + for withdrawal in builder_pending_withdrawals { + let has_reached_limit = withdrawals.len() == withdrawals_limit; + + if has_reached_limit { + break; + } + + let builder_index = withdrawal.builder_index; + + withdrawals.push(Withdrawal { + index: *withdrawal_index, + validator_index: convert_builder_index_to_validator_index(builder_index), + address: withdrawal.fee_recipient, + amount: withdrawal.amount, + }); + withdrawal_index.safe_add_assign(1)?; + processed_count.safe_add_assign(1)?; + } + Ok(Some(processed_count)) +} + +pub fn get_pending_partial_withdrawals( + state: &BeaconState, + withdrawal_index: &mut u64, + withdrawals: &mut Vec, + spec: &ChainSpec, +) -> Result, BlockProcessingError> { + let Ok(pending_partial_withdrawals) = state.pending_partial_withdrawals() else { + // Pre-Electra nothing to do. + return Ok(None); + }; + let epoch = state.current_epoch(); + + let withdrawals_limit = std::cmp::min( + withdrawals + .len() + .safe_add(spec.max_pending_partials_per_withdrawals_sweep as usize)?, + E::max_withdrawals_per_payload().safe_sub(1)?, + ); + + block_verify!( + withdrawals.len() <= withdrawals_limit, + BlockProcessingError::WithdrawalsLimitExceeded { + limit: withdrawals_limit, + prior_withdrawals: withdrawals.len() + } + ); + + let mut processed_count = 0; + for withdrawal in pending_partial_withdrawals { + let is_withdrawable = withdrawal.withdrawable_epoch <= epoch; + let has_reached_limit = withdrawals.len() >= withdrawals_limit; + + if !is_withdrawable || has_reached_limit { + break; + } + + let validator_index = withdrawal.validator_index; + let validator = state.get_validator(validator_index as usize)?; + let balance = get_balance_after_withdrawals(state, validator_index, withdrawals)?; + + if is_eligible_for_partial_withdrawals(validator, balance, spec) { + let withdrawal_amount = std::cmp::min( + balance.safe_sub(spec.min_activation_balance)?, + withdrawal.amount, + ); + withdrawals.push(Withdrawal { + index: *withdrawal_index, + validator_index, + address: validator + .get_execution_withdrawal_address(spec) + .ok_or(BeaconStateError::NonExecutionAddressWithdrawalCredential)?, + amount: withdrawal_amount, + }); + withdrawal_index.safe_add_assign(1)?; + } + processed_count.safe_add_assign(1)?; + } + + Ok(Some(processed_count)) +} + +/// Get withdrawals from the builders sweep. +/// +/// This function iterates through builders starting from `next_withdrawal_builder_index` +/// and adds withdrawals for builders whose withdrawable_epoch has been reached and have balance. +/// +/// https://ethereum.github.io/consensus-specs/specs/gloas/beacon-chain/#new-get_builders_sweep_withdrawals +pub fn get_builders_sweep_withdrawals( + state: &BeaconState, + withdrawal_index: &mut u64, + withdrawals: &mut Vec, +) -> Result, BlockProcessingError> { + let Ok(builders) = state.builders() else { + // Pre-Gloas, nothing to do. + return Ok(None); + }; + + if builders.is_empty() { + return Ok(Some(0)); + } + + let epoch = state.current_epoch(); + let builders_limit = std::cmp::min(builders.len(), E::max_builders_per_withdrawals_sweep()); + + let withdrawals_limit = E::max_withdrawals_per_payload().safe_sub(1)?; + + block_verify!( + withdrawals.len() <= withdrawals_limit, + BlockProcessingError::WithdrawalsLimitExceeded { + limit: withdrawals_limit, + prior_withdrawals: withdrawals.len() + } + ); + + let mut processed_count: u64 = 0; + let mut builder_index = state.next_withdrawal_builder_index()?; + + for _ in 0..builders_limit { + if withdrawals.len() >= withdrawals_limit { + break; + } + + let builder = builders + .get(builder_index as usize) + .ok_or(BeaconStateError::UnknownBuilder(builder_index))?; + + if builder.withdrawable_epoch <= epoch && builder.balance > 0 { + withdrawals.push(Withdrawal { + index: *withdrawal_index, + validator_index: convert_builder_index_to_validator_index(builder_index), + address: builder.execution_address, + amount: builder.balance, + }); + withdrawal_index.safe_add_assign(1)?; + } + + builder_index = builder_index.safe_add(1)?.safe_rem(builders.len() as u64)?; + processed_count.safe_add_assign(1)?; + } + + Ok(Some(processed_count)) +} + +/// Get withdrawals from the validator sweep. +/// +/// This function iterates through validators starting from `next_withdrawal_validator_index` +/// and adds full or partial withdrawals for eligible validators. +/// +/// https://ethereum.github.io/consensus-specs/specs/capella/beacon-chain/#new-get_validators_sweep_withdrawals +pub fn get_validators_sweep_withdrawals( + state: &BeaconState, + withdrawal_index: &mut u64, + withdrawals: &mut Vec, + spec: &ChainSpec, +) -> Result { + let epoch = state.current_epoch(); + let fork_name = state.fork_name_unchecked(); + let mut validator_index = state.next_withdrawal_validator_index()?; + let validators_limit = std::cmp::min( + state.validators().len() as u64, + spec.max_validators_per_withdrawals_sweep, + ); + let withdrawals_limit = E::max_withdrawals_per_payload(); + + // There must be at least one space reserved for validator sweep withdrawals + block_verify!( + withdrawals.len() < withdrawals_limit, + BlockProcessingError::WithdrawalsLimitExceeded { + limit: withdrawals_limit, + prior_withdrawals: withdrawals.len() + } + ); + + let mut processed_count: u64 = 0; + + for _ in 0..validators_limit { + if withdrawals.len() >= withdrawals_limit { + break; + } + + let validator = state.get_validator(validator_index as usize)?; + let balance = get_balance_after_withdrawals(state, validator_index, withdrawals)?; + + if validator.is_fully_withdrawable_validator(balance, epoch, spec, fork_name) { + withdrawals.push(Withdrawal { + index: *withdrawal_index, + validator_index, + address: validator + .get_execution_withdrawal_address(spec) + .ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?, + amount: balance, + }); + withdrawal_index.safe_add_assign(1)?; + } else if validator.is_partially_withdrawable_validator(balance, spec, fork_name) { + withdrawals.push(Withdrawal { + index: *withdrawal_index, + validator_index, + address: validator + .get_execution_withdrawal_address(spec) + .ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?, + amount: balance.safe_sub(validator.get_max_effective_balance(spec, fork_name))?, + }); + withdrawal_index.safe_add_assign(1)?; + } + + validator_index = validator_index + .safe_add(1)? + .safe_rem(state.validators().len() as u64)?; + processed_count.safe_add_assign(1)?; + } + + Ok(processed_count) +} + +pub fn get_balance_after_withdrawals( + state: &BeaconState, + validator_index: u64, + withdrawals: &[Withdrawal], +) -> Result { + let withdrawn = withdrawals + .iter() + .filter(|withdrawal| withdrawal.validator_index == validator_index) + .map(|withdrawal| withdrawal.amount) + .safe_sum()?; + state + .get_balance(validator_index as usize)? + .safe_sub(withdrawn) + .map_err(Into::into) +} + +fn is_eligible_for_partial_withdrawals( + validator: &Validator, + balance: u64, + spec: &ChainSpec, +) -> bool { + let has_sufficient_effective_balance = + validator.effective_balance >= spec.min_activation_balance; + let has_excess_balance = balance > spec.min_activation_balance; + validator.exit_epoch == spec.far_future_epoch + && has_sufficient_effective_balance + && has_excess_balance +} + +fn update_next_withdrawal_index( + state: &mut BeaconState, + withdrawals: &Withdrawals, +) -> Result<(), BlockProcessingError> { + // Update the next withdrawal index if this block contained withdrawals + if let Some(latest_withdrawal) = withdrawals.last() { + *state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?; + } + Ok(()) +} + +fn update_payload_expected_withdrawals( + state: &mut BeaconState, + withdrawals: &Withdrawals, +) -> Result<(), BlockProcessingError> { + *state.payload_expected_withdrawals_mut()? = List::new(withdrawals.to_vec())?; + Ok(()) +} + +fn update_builder_pending_withdrawals( + state: &mut BeaconState, + processed_builder_withdrawals_count: u64, +) -> Result<(), BlockProcessingError> { + state + .builder_pending_withdrawals_mut()? + .pop_front(processed_builder_withdrawals_count as usize)?; + Ok(()) +} + +fn update_pending_partial_withdrawals( + state: &mut BeaconState, + processed_partial_withdrawals_count: u64, +) -> Result<(), BlockProcessingError> { + state + .pending_partial_withdrawals_mut()? + .pop_front(processed_partial_withdrawals_count as usize)?; + Ok(()) +} + +fn update_next_withdrawal_builder_index( + state: &mut BeaconState, + processed_builders_sweep_count: u64, +) -> Result<(), BlockProcessingError> { + if !state.builders()?.is_empty() { + // Update the next builder index to start the next withdrawal sweep + let next_index = state + .next_withdrawal_builder_index()? + .safe_add(processed_builders_sweep_count)?; + let next_builder_index = next_index.safe_rem(state.builders()?.len() as u64)?; + *state.next_withdrawal_builder_index_mut()? = next_builder_index; + } + Ok(()) +} + +fn update_next_withdrawal_validator_index( + state: &mut BeaconState, + withdrawals: &Withdrawals, + spec: &ChainSpec, +) -> Result<(), BlockProcessingError> { + // Update the next validator index to start the next withdrawal sweep + if withdrawals.len() == E::max_withdrawals_per_payload() { + // Next sweep starts after the latest withdrawal's validator index + let latest_withdrawal = withdrawals + .last() + .ok_or(BlockProcessingError::MissingLastWithdrawal)?; + let next_validator_index = latest_withdrawal + .validator_index + .safe_add(1)? + .safe_rem(state.validators().len() as u64)?; + *state.next_withdrawal_validator_index_mut()? = next_validator_index; + } else { + // Advance sweep by the max length of the sweep if there was not a full set of withdrawals + let next_validator_index = state + .next_withdrawal_validator_index()? + .safe_add(spec.max_validators_per_withdrawals_sweep)? + .safe_rem(state.validators().len() as u64)?; + *state.next_withdrawal_validator_index_mut()? = next_validator_index; + } + Ok(()) +} + +pub fn apply_withdrawals( + state: &mut BeaconState, + withdrawals: &Withdrawals, +) -> Result<(), BlockProcessingError> { + for withdrawal in withdrawals { + if state.fork_name_unchecked().gloas_enabled() + && is_builder_index(withdrawal.validator_index) + { + let builder_index = + convert_validator_index_to_builder_index(withdrawal.validator_index); + let builder = state + .builders_mut()? + .get_mut(builder_index as usize) + .ok_or(BeaconStateError::UnknownBuilder(builder_index))?; + builder.balance = builder.balance.saturating_sub(withdrawal.amount); + } else { + decrease_balance( + state, + withdrawal.validator_index as usize, + withdrawal.amount, + )?; + } + } + Ok(()) +} + +pub mod capella_electra { + use super::*; + + /// Apply withdrawals to the state. + pub fn process_withdrawals>( + state: &mut BeaconState, + payload: Payload::Ref<'_>, + spec: &ChainSpec, + ) -> Result<(), BlockProcessingError> { + let expected_withdrawals = get_expected_withdrawals(state, spec)?; + + let expected_root = expected_withdrawals.withdrawals().tree_hash_root(); + let withdrawals_root = payload.withdrawals_root()?; + if expected_root != withdrawals_root { + return Err(BlockProcessingError::WithdrawalsRootMismatch { + expected: expected_root, + found: withdrawals_root, + }); + } + + // Apply expected withdrawals. + apply_withdrawals(state, expected_withdrawals.withdrawals())?; + + // [Common] Update withdrawals fields in the state + update_next_withdrawal_index(state, expected_withdrawals.withdrawals())?; + + // [New in Electra:EIP7251] + if let Ok(processed_partial_withdrawals_count) = + expected_withdrawals.processed_partial_withdrawals_count() + { + update_pending_partial_withdrawals(state, processed_partial_withdrawals_count)?; + } + + // [Common from Capella] + update_next_withdrawal_validator_index(state, expected_withdrawals.withdrawals(), spec)?; + + Ok(()) + } +} + +pub mod gloas { + use super::*; + + /// Apply withdrawals to the state. + pub fn process_withdrawals( + state: &mut BeaconState, + spec: &ChainSpec, + ) -> Result<(), BlockProcessingError> { + if !state.is_parent_block_full() { + return Ok(()); + } + + let ExpectedWithdrawals::Gloas(ExpectedWithdrawalsGloas { + withdrawals, + processed_builder_withdrawals_count, + processed_partial_withdrawals_count, + processed_builders_sweep_count, + processed_sweep_withdrawals_count: _, + }) = get_expected_withdrawals(state, spec)? + else { + return Err(BlockProcessingError::IncorrectExpectedWithdrawalsVariant); + }; + + // Apply expected withdrawals. + apply_withdrawals(state, &withdrawals)?; + + // [Common] Update withdrawals fields in the state + update_next_withdrawal_index(state, &withdrawals)?; + + // [New in Gloas:EIP7732] + update_payload_expected_withdrawals(state, &withdrawals)?; + + // [New in Gloas:EIP7732] + update_builder_pending_withdrawals(state, processed_builder_withdrawals_count)?; + + // [Common from Electra] + update_pending_partial_withdrawals(state, processed_partial_withdrawals_count)?; + + // [New in Gloas:EIP7732] + update_next_withdrawal_builder_index(state, processed_builders_sweep_count)?; + + // [Common from Capella] + update_next_withdrawal_validator_index(state, &withdrawals, spec)?; + + Ok(()) + } +} diff --git a/consensus/types/src/core/chain_spec.rs b/consensus/types/src/core/chain_spec.rs index a90a39a69d..6d25e3baf4 100644 --- a/consensus/types/src/core/chain_spec.rs +++ b/consensus/types/src/core/chain_spec.rs @@ -12,6 +12,7 @@ use ssz_types::RuntimeVariableList; use tree_hash::TreeHash; use crate::{ + consts::bellatrix::BASIS_POINTS, core::{ APPLICATION_DOMAIN_BUILDER, Address, ApplicationDomain, EnrForkId, Epoch, EthSpec, EthSpecId, ExecutionBlockHash, Hash256, MainnetEthSpec, Slot, Uint256, @@ -95,8 +96,10 @@ pub struct ChainSpec { * Time parameters */ pub genesis_delay: u64, + // TODO deprecate seconds_per_slot pub seconds_per_slot: u64, - pub slot_duration_ms: u64, + // Private so that this value can't get changed except via the `set_slot_duration_ms` function. + slot_duration_ms: u64, pub min_attestation_inclusion_delay: u64, pub min_seed_lookahead: Epoch, pub max_seed_lookahead: Epoch, @@ -109,6 +112,14 @@ pub struct ChainSpec { pub sync_message_due_bps: u64, pub contribution_due_bps: u64, + /* + * Derived time values (computed at startup via `compute_derived_values()`) + */ + pub unaggregated_attestation_due: Duration, + pub aggregate_attestation_due: Duration, + pub sync_message_due: Duration, + pub contribution_and_proof_due: Duration, + /* * Reward and penalty quotients */ @@ -851,6 +862,110 @@ impl ChainSpec { ) } + /// Get the duration into a slot in which an unaggregated attestation is due. + /// Returns the pre-computed value from `compute_derived_values()`. + pub fn get_unaggregated_attestation_due(&self) -> Duration { + self.unaggregated_attestation_due + } + + /// Get the duration into a slot in which an aggregated attestation is due. + /// Returns the pre-computed value from `compute_derived_values()`. + pub fn get_aggregate_attestation_due(&self) -> Duration { + self.aggregate_attestation_due + } + + /// Get the duration into a slot in which a `SignedContributionAndProof` is due. + /// Returns the pre-computed value from `compute_derived_values()`. + pub fn get_contribution_message_due(&self) -> Duration { + self.contribution_and_proof_due + } + + /// Get the duration into a slot in which a sync committee message is due. + /// Returns the pre-computed value from `compute_derived_values()`. + pub fn get_sync_message_due(&self) -> Duration { + self.sync_message_due + } + + /// Calculate the duration into a slot for a given slot component + fn compute_slot_component_duration( + &self, + component_basis_points: u64, + ) -> Result { + Ok(Duration::from_millis( + component_basis_points + .safe_mul(self.slot_duration_ms)? + .safe_div(BASIS_POINTS)?, + )) + } + + /// Get the duration of a slot + pub fn get_slot_duration(&self) -> Duration { + Duration::from_millis(self.slot_duration_ms) + } + + /// Set the duration of a slot (in ms). + pub fn set_slot_duration_ms(mut self, slot_duration_ms: u64) -> Self { + self.slot_duration_ms = slot_duration_ms; + self.compute_derived_values::() + } + + /// Compute values that are derived from other config values. + /// + /// Must be called after loading or modifying a ChainSpec's fields. + /// + /// Panics if any computation fails (indicates invalid config). + pub fn compute_derived_values(mut self) -> Self { + assert!( + self.attestation_due_bps <= BASIS_POINTS, + "invalid chain spec: attestation_due_bps ({}) exceeds slot duration", + self.attestation_due_bps + ); + assert!( + self.aggregate_due_bps <= BASIS_POINTS, + "invalid chain spec: aggregate_due_bps ({}) exceeds slot duration", + self.aggregate_due_bps + ); + assert!( + self.sync_message_due_bps <= BASIS_POINTS, + "invalid chain spec: sync_message_due_bps ({}) exceeds slot duration", + self.sync_message_due_bps + ); + assert!( + self.contribution_due_bps <= BASIS_POINTS, + "invalid chain spec: contribution_due_bps ({}) exceeds slot duration", + self.contribution_due_bps + ); + + self.unaggregated_attestation_due = self + .compute_slot_component_duration(self.attestation_due_bps) + .expect("invalid chain spec: cannot compute unaggregated_attestation_due"); + self.aggregate_attestation_due = self + .compute_slot_component_duration(self.aggregate_due_bps) + .expect("invalid chain spec: cannot compute aggregate_attestation_due"); + self.sync_message_due = self + .compute_slot_component_duration(self.sync_message_due_bps) + .expect("invalid chain spec: cannot compute sync_message_due"); + self.contribution_and_proof_due = self + .compute_slot_component_duration(self.contribution_due_bps) + .expect("invalid chain spec: cannot compute contribution_and_proof_due"); + + self.attestation_subnet_prefix_bits = compute_attestation_subnet_prefix_bits( + self.attestation_subnet_count, + self.attestation_subnet_extra_bits, + ); + + self.max_blocks_by_root_request = + max_blocks_by_root_request_common(self.max_request_blocks); + self.max_blocks_by_root_request_deneb = + max_blocks_by_root_request_common(self.max_request_blocks_deneb); + self.max_blobs_by_root_request = + max_blobs_by_root_request_common(self.max_request_blob_sidecars); + self.max_data_columns_by_root_request = + max_data_columns_by_root_request_common::(self.max_request_blocks_deneb); + + self + } + /// Returns the slot at which the proposer shuffling was decided. /// /// The block root at this slot can be used to key the proposer shuffling for the given epoch. @@ -956,6 +1071,14 @@ impl ChainSpec { sync_message_due_bps: 3333, contribution_due_bps: 6667, + /* + * Derived time values (set by `compute_derived_values()`) + */ + unaggregated_attestation_due: Duration::from_millis(3999), + aggregate_attestation_due: Duration::from_millis(8000), + sync_message_due: Duration::from_millis(3999), + contribution_and_proof_due: Duration::from_millis(8000), + /* * Reward and penalty quotients */ @@ -1201,6 +1324,7 @@ impl ChainSpec { shard_committee_period: 64, genesis_delay: 300, seconds_per_slot: 6, + slot_duration_ms: 6000, inactivity_penalty_quotient: u64::checked_pow(2, 25).expect("pow does not overflow"), min_slashing_penalty_quotient: 64, proportional_slashing_multiplier: 2, @@ -1245,6 +1369,16 @@ impl ChainSpec { // Gloas gloas_fork_version: [0x07, 0x00, 0x00, 0x01], gloas_fork_epoch: None, + + /* + * Derived time values (set by `compute_derived_values()`) + * Precomputed for 6000ms slot: 3333 bps = 1999ms, 6667 bps = 4000ms + */ + unaggregated_attestation_due: Duration::from_millis(1999), + aggregate_attestation_due: Duration::from_millis(4000), + sync_message_due: Duration::from_millis(1999), + contribution_and_proof_due: Duration::from_millis(4000), + // Other network_id: 2, // lighthouse testnet network id deposit_chain_id: 5, @@ -1328,8 +1462,15 @@ impl ChainSpec { proposer_reorg_cutoff_bps: 1667, attestation_due_bps: 3333, aggregate_due_bps: 6667, - sync_message_due_bps: 3333, - contribution_due_bps: 6667, + + /* + * Derived time values (set by `compute_derived_values()`) + * Precomputed for 5000ms slot: 3333 bps = 1666ms, 6667 bps = 3333ms + */ + unaggregated_attestation_due: Duration::from_millis(1666), + aggregate_attestation_due: Duration::from_millis(3333), + sync_message_due: Duration::from_millis(1666), + contribution_and_proof_due: Duration::from_millis(3333), /* * Reward and penalty quotients @@ -1398,6 +1539,8 @@ impl ChainSpec { domain_contribution_and_proof: 9, altair_fork_version: [0x01, 0x00, 0x00, 0x64], altair_fork_epoch: Some(Epoch::new(512)), + sync_message_due_bps: 3333, + contribution_due_bps: 6667, /* * Bellatrix hard fork params @@ -1754,6 +1897,9 @@ pub struct Config { #[serde(with = "serde_utils::quoted_u64")] seconds_per_slot: u64, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + slot_duration_ms: Option>, #[serde(with = "serde_utils::quoted_u64")] seconds_per_eth1_block: u64, #[serde(with = "serde_utils::quoted_u64")] @@ -1889,6 +2035,22 @@ pub struct Config { #[serde(default = "default_min_epochs_for_data_column_sidecars_requests")] #[serde(with = "serde_utils::quoted_u64")] min_epochs_for_data_column_sidecars_requests: u64, + + #[serde(default = "default_proposer_reorg_cutoff_bps")] + #[serde(with = "serde_utils::quoted_u64")] + proposer_reorg_cutoff_bps: u64, + #[serde(default = "default_attestation_due_bps")] + #[serde(with = "serde_utils::quoted_u64")] + attestation_due_bps: u64, + #[serde(default = "default_aggregate_due_bps")] + #[serde(with = "serde_utils::quoted_u64")] + aggregate_due_bps: u64, + #[serde(default = "default_sync_message_due_bps")] + #[serde(with = "serde_utils::quoted_u64")] + sync_message_due_bps: u64, + #[serde(default = "default_contribution_due_bps")] + #[serde(with = "serde_utils::quoted_u64")] + contribution_due_bps: u64, } fn default_bellatrix_fork_version() -> [u8; 4] { @@ -2094,6 +2256,26 @@ const fn default_min_epochs_for_data_column_sidecars_requests() -> u64 { 4096 } +const fn default_proposer_reorg_cutoff_bps() -> u64 { + 1667 +} + +const fn default_attestation_due_bps() -> u64 { + 3333 +} + +const fn default_aggregate_due_bps() -> u64 { + 6667 +} + +const fn default_sync_message_due_bps() -> u64 { + 3333 +} + +const fn default_contribution_due_bps() -> u64 { + 6667 +} + fn max_blocks_by_root_request_common(max_request_blocks: u64) -> usize { let max_request_blocks = max_request_blocks as usize; RuntimeVariableList::::new( @@ -2257,6 +2439,9 @@ impl Config { .map(|epoch| MaybeQuoted { value: epoch }), seconds_per_slot: spec.seconds_per_slot, + slot_duration_ms: Some(MaybeQuoted { + value: spec.slot_duration_ms, + }), seconds_per_eth1_block: spec.seconds_per_eth1_block, min_validator_withdrawability_delay: spec.min_validator_withdrawability_delay, shard_committee_period: spec.shard_committee_period, @@ -2313,6 +2498,12 @@ impl Config { balance_per_additional_custody_group: spec.balance_per_additional_custody_group, min_epochs_for_data_column_sidecars_requests: spec .min_epochs_for_data_column_sidecars_requests, + + proposer_reorg_cutoff_bps: spec.proposer_reorg_cutoff_bps, + attestation_due_bps: spec.attestation_due_bps, + aggregate_due_bps: spec.aggregate_due_bps, + sync_message_due_bps: spec.sync_message_due_bps, + contribution_due_bps: spec.contribution_due_bps, } } @@ -2350,6 +2541,7 @@ impl Config { gloas_fork_version, gloas_fork_epoch, seconds_per_slot, + slot_duration_ms, seconds_per_eth1_block, min_validator_withdrawability_delay, shard_committee_period, @@ -2398,13 +2590,18 @@ impl Config { validator_custody_requirement, balance_per_additional_custody_group, min_epochs_for_data_column_sidecars_requests, + proposer_reorg_cutoff_bps, + attestation_due_bps, + aggregate_due_bps, + sync_message_due_bps, + contribution_due_bps, } = self; if preset_base != E::spec_name().to_string().as_str() { return None; } - Some(ChainSpec { + let spec = ChainSpec { config_name: config_name.clone(), min_genesis_active_validator_count, min_genesis_time, @@ -2425,6 +2622,9 @@ impl Config { gloas_fork_version, gloas_fork_epoch: gloas_fork_epoch.map(|q| q.value), seconds_per_slot, + slot_duration_ms: slot_duration_ms + .map(|q| q.value) + .unwrap_or_else(|| seconds_per_slot.saturating_mul(1000)), seconds_per_eth1_block, min_validator_withdrawability_delay, shard_committee_period, @@ -2453,11 +2653,6 @@ impl Config { resp_timeout, message_domain_invalid_snappy, message_domain_valid_snappy, - // Compute attestation_subnet_prefix_bits dynamically - attestation_subnet_prefix_bits: compute_attestation_subnet_prefix_bits( - attestation_subnet_count, - attestation_subnet_extra_bits, - ), max_request_blocks, attestation_propagation_slot_range, maximum_gossip_clock_disparity, @@ -2474,16 +2669,6 @@ impl Config { max_request_blob_sidecars_electra, blob_sidecar_subnet_count_electra, - // We need to re-derive any values that might have changed in the config. - max_blocks_by_root_request: max_blocks_by_root_request_common(max_request_blocks), - max_blocks_by_root_request_deneb: max_blocks_by_root_request_common( - max_request_blocks_deneb, - ), - max_blobs_by_root_request: max_blobs_by_root_request_common(max_request_blob_sidecars), - max_data_columns_by_root_request: max_data_columns_by_root_request_common::( - max_request_blocks_deneb, - ), - number_of_custody_groups, data_column_sidecar_subnet_count, samples_per_slot, @@ -2493,8 +2678,15 @@ impl Config { balance_per_additional_custody_group, min_epochs_for_data_column_sidecars_requests, + proposer_reorg_cutoff_bps, + attestation_due_bps, + aggregate_due_bps, + sync_message_due_bps, + contribution_due_bps, + ..chain_spec.clone() - }) + }; + Some(spec.compute_derived_values::()) } } @@ -2698,6 +2890,7 @@ mod yaml_tests { GENESIS_FORK_VERSION: 0x10355025 GENESIS_DELAY: 60 SECONDS_PER_SLOT: 12 + SLOT_DURATION_MS: 12000 SECONDS_PER_ETH1_BLOCK: 12 MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 SHARD_COMMITTEE_PERIOD: 256 @@ -2850,6 +3043,7 @@ mod yaml_tests { GENESIS_FORK_VERSION: 0x10355025 GENESIS_DELAY: 60 SECONDS_PER_SLOT: 12 + SLOT_DURATION_MS: 12000 SECONDS_PER_ETH1_BLOCK: 12 MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 SHARD_COMMITTEE_PERIOD: 256 @@ -2965,6 +3159,7 @@ mod yaml_tests { SHARDING_FORK_VERSION: 0x03000000 SHARDING_FORK_EPOCH: 18446744073709551615 SECONDS_PER_SLOT: 12 + SLOT_DURATION_MS: 12000 SECONDS_PER_ETH1_BLOCK: 14 MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 SHARD_COMMITTEE_PERIOD: 256 @@ -3135,4 +3330,138 @@ mod yaml_tests { ); } } + + #[test] + fn test_slot_component_duration_calculations() { + let spec = ChainSpec::mainnet().compute_derived_values::(); + + // Test unaggregated attestation (3333 bps = 33.33% of 12s = 4s) + let unagg_due = spec.get_unaggregated_attestation_due(); + assert_eq!(unagg_due, Duration::from_millis(3999)); // 12000 * 3333 / 10000 + + // Test aggregate attestation (6667 bps = 66.67% of 12s = 8s) + let agg_due = spec.get_aggregate_attestation_due(); + assert_eq!(agg_due, Duration::from_millis(8000)); // 12000 * 6667 / 10000 + + // Test sync message (3333 bps = 33.33% of 12s = 4s) + let sync_msg_due = spec.get_sync_message_due(); + assert_eq!(sync_msg_due, Duration::from_millis(3999)); // 12000 * 3333 / 10000 + + // Test contribution message (6667 bps = 66.67% of 12s = 8s) + let contribution_due = spec.get_contribution_message_due(); + assert_eq!(contribution_due, Duration::from_millis(8000)); // 12000 * 6667 / 10000 + + // Test slot duration + let slot_duration = spec.get_slot_duration(); + assert_eq!(slot_duration, Duration::from_millis(12000)); + assert_eq!(slot_duration, Duration::from_secs(spec.seconds_per_slot)); + + // Test edge cases with custom spec + let mut custom_spec = spec.clone(); + + // Edge case: 0 bps should give 0 duration + custom_spec.attestation_due_bps = 0; + let custom_spec = custom_spec.compute_derived_values::(); + let zero_due = custom_spec.get_unaggregated_attestation_due(); + assert_eq!(zero_due, Duration::from_millis(0)); + + // Edge case: 10000 bps (100%) should give full slot duration + let mut custom_spec = custom_spec; + custom_spec.attestation_due_bps = 10_000; + let custom_spec = custom_spec.compute_derived_values::(); + let full_due = custom_spec.get_unaggregated_attestation_due(); + assert_eq!(full_due, Duration::from_millis(12000)); + + // Edge case: 5000 bps (50%) should give half slot duration + let mut custom_spec = custom_spec; + custom_spec.attestation_due_bps = 5_000; + let custom_spec = custom_spec.compute_derived_values::(); + let half_due = custom_spec.get_unaggregated_attestation_due(); + assert_eq!(half_due, Duration::from_millis(6000)); + + // Test with different slot duration (Gnosis: 5s slots) + let mut custom_spec = custom_spec; + custom_spec.slot_duration_ms = 5000; + custom_spec.attestation_due_bps = 3333; + let custom_spec = custom_spec.compute_derived_values::(); + let gnosis_due = custom_spec.get_unaggregated_attestation_due(); + assert_eq!(gnosis_due, Duration::from_millis(1666)); // 5000 * 3333 / 10000 + + // Test with very small slot duration + let mut custom_spec = custom_spec; + custom_spec.slot_duration_ms = 1000; // 1 second + custom_spec.attestation_due_bps = 3333; + let custom_spec = custom_spec.compute_derived_values::(); + let small_due = custom_spec.get_unaggregated_attestation_due(); + assert_eq!(small_due, Duration::from_millis(333)); // 1000 * 3333 / 10000 + + // Test rounding behavior with non-divisible values + let mut custom_spec = custom_spec; + custom_spec.slot_duration_ms = 12000; + custom_spec.attestation_due_bps = 1; // 0.01% + let custom_spec = custom_spec.compute_derived_values::(); + let tiny_due = custom_spec.get_unaggregated_attestation_due(); + assert_eq!(tiny_due, Duration::from_millis(1)); // 12000 * 1 / 10000 = 1.2 -> 1 + } + + #[test] + fn test_default_duration_values_without_compute_derived_values() { + // Verify that mainnet, minimal, and gnosis have correct pre-computed defaults + // without needing to call compute_derived_values() + let mainnet = ChainSpec::mainnet(); + assert_eq!( + mainnet.get_unaggregated_attestation_due(), + Duration::from_millis(3999) + ); + assert_eq!( + mainnet.get_aggregate_attestation_due(), + Duration::from_millis(8000) + ); + assert_eq!(mainnet.get_sync_message_due(), Duration::from_millis(3999)); + assert_eq!( + mainnet.get_contribution_message_due(), + Duration::from_millis(8000) + ); + + // Minimal spec: 6000ms slots, 3333 bps = 1999ms, 6667 bps = 4000ms + let minimal = ChainSpec::minimal(); + assert_eq!( + minimal.get_unaggregated_attestation_due(), + Duration::from_millis(1999) + ); + assert_eq!( + minimal.get_aggregate_attestation_due(), + Duration::from_millis(4000) + ); + assert_eq!(minimal.get_sync_message_due(), Duration::from_millis(1999)); + assert_eq!( + minimal.get_contribution_message_due(), + Duration::from_millis(4000) + ); + + // Gnosis spec: 5000ms slots, 3333 bps = 1666ms, 6667 bps = 3333ms + let gnosis = ChainSpec::gnosis(); + assert_eq!( + gnosis.get_unaggregated_attestation_due(), + Duration::from_millis(1666) + ); + assert_eq!( + gnosis.get_aggregate_attestation_due(), + Duration::from_millis(3333) + ); + assert_eq!(gnosis.get_sync_message_due(), Duration::from_millis(1666)); + assert_eq!( + gnosis.get_contribution_message_due(), + Duration::from_millis(3333) + ); + } + + #[test] + #[should_panic(expected = "exceeds slot duration")] + fn test_compute_derived_values_panics_on_invalid_bps_values() { + let mut spec = ChainSpec::mainnet(); + // 15000 bps = 150% of slot duration, which is invalid + spec.attestation_due_bps = 15000; + spec.compute_derived_values::(); + } } diff --git a/consensus/types/src/core/consts.rs b/consensus/types/src/core/consts.rs index 2c67657ee6..0d4c0591cb 100644 --- a/consensus/types/src/core/consts.rs +++ b/consensus/types/src/core/consts.rs @@ -20,7 +20,7 @@ pub mod altair { pub const NUM_FLAG_INDICES: usize = 3; } pub mod bellatrix { - pub const INTERVALS_PER_SLOT: u64 = 3; + pub const BASIS_POINTS: u64 = 10_000; } pub mod deneb { pub use kzg::VERSIONED_HASH_VERSION_KZG; diff --git a/consensus/types/src/data/data_column_sidecar.rs b/consensus/types/src/data/data_column_sidecar.rs index d98470297a..c8a49e346a 100644 --- a/consensus/types/src/data/data_column_sidecar.rs +++ b/consensus/types/src/data/data_column_sidecar.rs @@ -81,7 +81,9 @@ pub struct DataColumnSidecar { pub index: ColumnIndex, #[serde(with = "ssz_types::serde_utils::list_of_hex_fixed_vec")] pub column: DataColumn, - /// All the KZG commitments and proofs associated with the block, used for verifying sample cells. + /// All the KZG commitments associated with the block, used for verifying sample cells. + /// In Gloas, commitments come from `block.body.signed_execution_payload_bid.message.blob_kzg_commitments`. + #[superstruct(only(Fulu))] pub kzg_commitments: KzgCommitments, pub kzg_proofs: VariableList, #[superstruct(only(Fulu))] @@ -210,7 +212,6 @@ impl DataColumnSidecarGloas { Self { index: 0, column: VariableList::new(vec![Cell::::default()]).unwrap(), - kzg_commitments: VariableList::new(vec![KzgCommitment::empty_for_testing()]).unwrap(), kzg_proofs: VariableList::new(vec![KzgProof::empty()]).unwrap(), slot: Slot::new(0), beacon_block_root: Hash256::ZERO, @@ -223,11 +224,6 @@ impl DataColumnSidecarGloas { Self { index: 0, column: VariableList::new(vec![Cell::::default(); max_blobs_per_block]).unwrap(), - kzg_commitments: VariableList::new(vec![ - KzgCommitment::empty_for_testing(); - max_blobs_per_block - ]) - .unwrap(), kzg_proofs: VariableList::new(vec![KzgProof::empty(); max_blobs_per_block]).unwrap(), slot: Slot::new(0), beacon_block_root: Hash256::ZERO, diff --git a/consensus/types/src/state/beacon_state.rs b/consensus/types/src/state/beacon_state.rs index 2c639160a4..f661988edb 100644 --- a/consensus/types/src/state/beacon_state.rs +++ b/consensus/types/src/state/beacon_state.rs @@ -23,13 +23,13 @@ use tree_hash_derive::TreeHash; use typenum::Unsigned; use crate::{ - Builder, BuilderIndex, BuilderPendingPayment, BuilderPendingWithdrawal, ExecutionBlockHash, - ExecutionPayloadBid, Withdrawal, + ExecutionBlockHash, ExecutionPayloadBid, Withdrawal, attestation::{ AttestationData, AttestationDuty, BeaconCommittee, Checkpoint, CommitteeIndex, PTC, ParticipationFlags, PendingAttestation, }, block::{BeaconBlock, BeaconBlockHeader, SignedBeaconBlockHash}, + builder::{Builder, BuilderIndex, BuilderPendingPayment, BuilderPendingWithdrawal}, consolidation::PendingConsolidation, core::{ChainSpec, Domain, Epoch, EthSpec, Hash256, RelativeEpoch, RelativeEpochError, Slot}, deposit::PendingDeposit, @@ -68,6 +68,7 @@ pub enum BeaconStateError { EpochOutOfBounds, SlotOutOfBounds, UnknownValidator(usize), + UnknownBuilder(BuilderIndex), UnableToDetermineProducer, InvalidBitfield, EmptyCommittee, diff --git a/consensus/types/src/withdrawal/expected_withdrawals.rs b/consensus/types/src/withdrawal/expected_withdrawals.rs new file mode 100644 index 0000000000..f9809e6e73 --- /dev/null +++ b/consensus/types/src/withdrawal/expected_withdrawals.rs @@ -0,0 +1,29 @@ +use crate::{EthSpec, Withdrawals}; +use superstruct::superstruct; + +#[superstruct( + variants(Capella, Electra, Gloas), + variant_attributes(derive(Debug, PartialEq, Clone)) +)] +#[derive(Debug, PartialEq, Clone)] +pub struct ExpectedWithdrawals { + pub withdrawals: Withdrawals, + #[superstruct(only(Gloas), partial_getter(copy))] + pub processed_builder_withdrawals_count: u64, + #[superstruct(only(Electra, Gloas), partial_getter(copy))] + pub processed_partial_withdrawals_count: u64, + #[superstruct(only(Gloas), partial_getter(copy))] + pub processed_builders_sweep_count: u64, + #[superstruct(getter(copy))] + pub processed_sweep_withdrawals_count: u64, +} + +impl From> for Withdrawals { + fn from(expected_withdrawals: ExpectedWithdrawals) -> Withdrawals { + match expected_withdrawals { + ExpectedWithdrawals::Capella(ew) => ew.withdrawals, + ExpectedWithdrawals::Electra(ew) => ew.withdrawals, + ExpectedWithdrawals::Gloas(ew) => ew.withdrawals, + } + } +} diff --git a/consensus/types/src/withdrawal/mod.rs b/consensus/types/src/withdrawal/mod.rs index bac80d00be..fbe7351754 100644 --- a/consensus/types/src/withdrawal/mod.rs +++ b/consensus/types/src/withdrawal/mod.rs @@ -1,8 +1,13 @@ +mod expected_withdrawals; mod pending_partial_withdrawal; mod withdrawal; mod withdrawal_credentials; mod withdrawal_request; +pub use expected_withdrawals::{ + ExpectedWithdrawals, ExpectedWithdrawalsCapella, ExpectedWithdrawalsElectra, + ExpectedWithdrawalsGloas, +}; pub use pending_partial_withdrawal::PendingPartialWithdrawal; pub use withdrawal::{Withdrawal, Withdrawals}; pub use withdrawal_credentials::WithdrawalCredentials; diff --git a/lighthouse/environment/tests/testnet_dir/config.yaml b/lighthouse/environment/tests/testnet_dir/config.yaml index 7cfeb746f2..f155fac826 100644 --- a/lighthouse/environment/tests/testnet_dir/config.yaml +++ b/lighthouse/environment/tests/testnet_dir/config.yaml @@ -47,6 +47,8 @@ TRANSITION_TOTAL_DIFFICULTY: 4294967296 # --------------------------------------------------------------- # 12 seconds SECONDS_PER_SLOT: 12 +# 12 seconds +SLOT_DURATION_MS: 12000 # 14 (estimate from Eth1 mainnet) SECONDS_PER_ETH1_BLOCK: 14 # 2**8 (= 256) epochs ~27 hours @@ -55,6 +57,18 @@ MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 SHARD_COMMITTEE_PERIOD: 256 # 2**11 (= 2,048) Eth1 blocks ~8 hours ETH1_FOLLOW_DISTANCE: 2048 +# 1667 basis points, ~17% of SLOT_DURATION_MS +PROPOSER_REORG_CUTOFF_BPS: 1667 +# 3333 basis points, ~33% of SLOT_DURATION_MS +ATTESTATION_DUE_BPS: 3333 +# 6667 basis points, ~67% of SLOT_DURATION_MS +AGGREGATE_DUE_BPS: 6667 + +# Altair +# 3333 basis points, ~33% of SLOT_DURATION_MS +SYNC_MESSAGE_DUE_BPS: 3333 +# 6667 basis points, ~67% of SLOT_DURATION_MS +CONTRIBUTION_DUE_BPS: 6667 # Validator cycle diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index 69817e5c9d..a2fad31f65 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -2332,7 +2332,7 @@ fn enable_proposer_re_orgs_default() { DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION, ); assert_eq!( - config.chain.re_org_cutoff(12), + config.chain.re_org_cutoff(Duration::from_secs(12)), Duration::from_secs(12) / DEFAULT_RE_ORG_CUTOFF_DENOMINATOR ); }); @@ -2384,7 +2384,10 @@ fn proposer_re_org_cutoff() { .flag("proposer-reorg-cutoff", Some("500")) .run_with_zero_port() .with_config(|config| { - assert_eq!(config.chain.re_org_cutoff(12), Duration::from_millis(500)) + assert_eq!( + config.chain.re_org_cutoff(Duration::from_secs(12)), + Duration::from_millis(500) + ) }); } diff --git a/scripts/local_testnet/network_params.yaml b/scripts/local_testnet/network_params.yaml index a048674e63..0c36e5c49c 100644 --- a/scripts/local_testnet/network_params.yaml +++ b/scripts/local_testnet/network_params.yaml @@ -18,7 +18,7 @@ participants: count: 2 network_params: fulu_fork_epoch: 0 - seconds_per_slot: 6 + slot_duration_ms: 3000 snooper_enabled: false global_log_level: debug additional_services: diff --git a/scripts/tests/doppelganger_protection.sh b/scripts/tests/doppelganger_protection.sh index 9009d49d58..e5ffee494f 100755 --- a/scripts/tests/doppelganger_protection.sh +++ b/scripts/tests/doppelganger_protection.sh @@ -9,7 +9,7 @@ NETWORK_PARAMS_FILE=$SCRIPT_DIR/network_params.yaml BEHAVIOR=$1 ENCLAVE_NAME=local-testnet-$BEHAVIOR -SECONDS_PER_SLOT=$(yq eval ".network_params.seconds_per_slot" $NETWORK_PARAMS_FILE) +SLOT_DURATION_MS=$(yq eval ".network_params.slot_duration_ms" $NETWORK_PARAMS_FILE) KEYS_PER_NODE=$(yq eval ".network_params.num_validator_keys_per_node" $NETWORK_PARAMS_FILE) LH_IMAGE_NAME=$(yq eval ".participants[0].cl_image" $NETWORK_PARAMS_FILE) @@ -56,7 +56,7 @@ GENESIS_DELAY=`curl -s $BN1_HTTP_ADDRESS/eth/v1/config/spec | jq '.data.GENESIS_ CURRENT_TIME=`date +%s` # Note: doppelganger protection can only be started post epoch 0 echo "Waiting until next epoch before starting the next validator client..." -DELAY=$(( $SECONDS_PER_SLOT * 32 + $GENESIS_DELAY + $MIN_GENESIS_TIME - $CURRENT_TIME)) +DELAY=$((($SLOT_DURATION_MS / 1000) * 32 + $GENESIS_DELAY + $MIN_GENESIS_TIME - $CURRENT_TIME)) sleep $DELAY # Use BN2 for the next validator client @@ -98,7 +98,7 @@ EOF # Check if doppelganger VC has stopped and exited. Exit code 1 means the check timed out and VC is still running. check_exit_cmd="until [ \$(get_service_status $service_name) != 'RUNNING' ]; do sleep 1; done" - doppelganger_exit=$(run_command_without_exit "timeout $(( $SECONDS_PER_SLOT * 32 * 2 )) bash -c \"$check_exit_cmd\"") + doppelganger_exit=$(run_command_without_exit "timeout $((($SLOT_DURATION_MS / 1000) * 32 * 2 )) bash -c \"$check_exit_cmd\"") if [[ $doppelganger_exit -eq 1 ]]; then echo "Test failed: expected doppelganger but VC is still running. Check the logs for details." @@ -148,7 +148,7 @@ EOF # # See: https://lighthouse-book.sigmaprime.io/api_validator_inclusion.html echo "Waiting three epochs..." - sleep $(( $SECONDS_PER_SLOT * 32 * 3 )) + sleep $((($SLOT_DURATION_MS / 1000) * 32 * 3 )) # Get VC4 validator keys keys_path=$SCRIPT_DIR/$ENCLAVE_NAME/node_4/validators @@ -176,7 +176,7 @@ EOF # # See: https://lighthouse-book.sigmaprime.io/api_validator_inclusion.html echo "Waiting two more epochs..." - sleep $(( $SECONDS_PER_SLOT * 32 * 2 )) + sleep $(( $SLOT_DURATION_MS / 1000 * 32 * 2 )) for val in 0x*; do [[ -e $val ]] || continue is_attester=$(run_command_without_exit "curl -s $BN1_HTTP_ADDRESS/lighthouse/validator_inclusion/5/$val | jq | grep -q '\"is_previous_epoch_target_attester\": true'") diff --git a/scripts/tests/genesis-sync-config-electra.yaml b/scripts/tests/genesis-sync-config-electra.yaml index 1d1ed4d315..0e41a5d165 100644 --- a/scripts/tests/genesis-sync-config-electra.yaml +++ b/scripts/tests/genesis-sync-config-electra.yaml @@ -11,7 +11,7 @@ participants: cl_image: lighthouse:local validator_count: 0 network_params: - seconds_per_slot: 6 + slot_duration_ms: 6000 electra_fork_epoch: 0 fulu_fork_epoch: 100000 # a really big number so this test stays in electra preset: "minimal" diff --git a/scripts/tests/genesis-sync-config-fulu.yaml b/scripts/tests/genesis-sync-config-fulu.yaml index 6d2c2647a9..5ca76a7736 100644 --- a/scripts/tests/genesis-sync-config-fulu.yaml +++ b/scripts/tests/genesis-sync-config-fulu.yaml @@ -20,7 +20,7 @@ participants: supernode: false validator_count: 0 network_params: - seconds_per_slot: 6 + slot_duration_ms: 6000 fulu_fork_epoch: 0 preset: "minimal" additional_services: diff --git a/scripts/tests/network_params.yaml b/scripts/tests/network_params.yaml index 35916ac1e4..30654deaae 100644 --- a/scripts/tests/network_params.yaml +++ b/scripts/tests/network_params.yaml @@ -10,7 +10,7 @@ participants: count: 4 network_params: fulu_fork_epoch: 0 - seconds_per_slot: 3 + slot_duration_ms: 3000 num_validator_keys_per_node: 20 global_log_level: debug snooper_enabled: false diff --git a/testing/ef_tests/Makefile b/testing/ef_tests/Makefile index 0c6371f825..fd8a3f6da0 100644 --- a/testing/ef_tests/Makefile +++ b/testing/ef_tests/Makefile @@ -1,6 +1,6 @@ # To download/extract nightly tests, run: # CONSENSUS_SPECS_TEST_VERSION=nightly make -CONSENSUS_SPECS_TEST_VERSION ?= v1.7.0-alpha.1 +CONSENSUS_SPECS_TEST_VERSION ?= v1.7.0-alpha.2 REPO_NAME := consensus-spec-tests OUTPUT_DIR := ./$(REPO_NAME) diff --git a/testing/ef_tests/check_all_files_accessed.py b/testing/ef_tests/check_all_files_accessed.py index 7787af64f0..97c1c4f4f9 100755 --- a/testing/ef_tests/check_all_files_accessed.py +++ b/testing/ef_tests/check_all_files_accessed.py @@ -59,6 +59,8 @@ excluded_paths = [ # Ignore full epoch tests for now (just test the sub-transitions). "tests/.*/.*/epoch_processing/.*/pre_epoch.ssz_snappy", "tests/.*/.*/epoch_processing/.*/post_epoch.ssz_snappy", + # Ignore inactivity_scores tests for now (should implement soon). + "tests/.*/.*/rewards/inactivity_scores/.*", # Ignore gloas tests for now "tests/.*/gloas/.*", # Ignore KZG tests that target internal kzg library functions diff --git a/testing/ef_tests/src/cases/fork_choice.rs b/testing/ef_tests/src/cases/fork_choice.rs index ae61206102..b9f8e5ce5b 100644 --- a/testing/ef_tests/src/cases/fork_choice.rs +++ b/testing/ef_tests/src/cases/fork_choice.rs @@ -488,7 +488,7 @@ impl Tester { let since_genesis = tick .checked_sub(genesis_time) .ok_or_else(|| Error::FailedToParseTest("tick is prior to genesis".into()))?; - let slots_since_genesis = since_genesis / self.spec.seconds_per_slot; + let slots_since_genesis = since_genesis / self.spec.get_slot_duration().as_secs(); Ok(self.spec.genesis_slot + slots_since_genesis) } diff --git a/testing/ef_tests/src/cases/operations.rs b/testing/ef_tests/src/cases/operations.rs index a53bce927c..e778300879 100644 --- a/testing/ef_tests/src/cases/operations.rs +++ b/testing/ef_tests/src/cases/operations.rs @@ -19,7 +19,7 @@ use state_processing::{ altair_deneb, base, process_attester_slashings, process_bls_to_execution_changes, process_deposits, process_exits, process_proposer_slashings, }, - process_sync_aggregate, process_withdrawals, + process_sync_aggregate, withdrawals, }, }; use std::fmt::Debug; @@ -45,7 +45,7 @@ struct ExecutionMetadata { /// Newtype for testing withdrawals. #[derive(Debug, Clone, Deserialize)] pub struct WithdrawalsPayload { - payload: FullPayload, + payload: Option>, } #[derive(Debug, Clone)] @@ -405,12 +405,17 @@ impl Operation for WithdrawalsPayload { } fn decode(path: &Path, fork_name: ForkName, _spec: &ChainSpec) -> Result { - ssz_decode_file_with(path, |bytes| { - ExecutionPayload::from_ssz_bytes_by_fork(bytes, fork_name) - }) - .map(|payload| WithdrawalsPayload { - payload: payload.into(), - }) + if fork_name.gloas_enabled() { + // No payload present or required for Gloas tests. + Ok(WithdrawalsPayload { payload: None }) + } else { + ssz_decode_file_with(path, |bytes| { + ExecutionPayload::from_ssz_bytes_by_fork(bytes, fork_name) + }) + .map(|payload| WithdrawalsPayload { + payload: Some(payload), + }) + } } fn apply_to( @@ -419,8 +424,16 @@ impl Operation for WithdrawalsPayload { spec: &ChainSpec, _: &Operations, ) -> Result<(), BlockProcessingError> { - // TODO(EIP-7732): implement separate gloas and non-gloas variants of process_withdrawals - process_withdrawals::<_, FullPayload<_>>(state, self.payload.to_ref(), spec) + if state.fork_name_unchecked().gloas_enabled() { + withdrawals::gloas::process_withdrawals(state, spec) + } else { + let full_payload = FullPayload::from(self.payload.clone().unwrap()); + withdrawals::capella_electra::process_withdrawals::<_, FullPayload<_>>( + state, + full_payload.to_ref(), + spec, + ) + } } } diff --git a/testing/ef_tests/src/handler.rs b/testing/ef_tests/src/handler.rs index afa6304eae..b0fc90b169 100644 --- a/testing/ef_tests/src/handler.rs +++ b/testing/ef_tests/src/handler.rs @@ -333,6 +333,10 @@ impl SszStaticHandler { Self::for_forks(ForkName::list_all()[6..].to_vec()) } + pub fn gloas_and_later() -> Self { + Self::for_forks(ForkName::list_all()[7..].to_vec()) + } + pub fn pre_electra() -> Self { Self::for_forks(ForkName::list_all()[0..5].to_vec()) } @@ -397,10 +401,7 @@ where } fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { - // TODO(gloas): DataColumnSidecar tests are disabled until we update the DataColumnSidecar - // type. self.supported_forks.contains(&fork_name) - && !(fork_name == ForkName::Gloas && T::name() == "DataColumnSidecar") } } @@ -1117,6 +1118,17 @@ impl> Handler for OperationsHandler fn handler_name(&self) -> String { O::handler_name() } + + fn disabled_forks(&self) -> Vec { + // TODO(gloas): Can be removed once we enable Gloas on all tests + vec![] + } + + fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { + // TODO(gloas): So far only withdrawals tests are enabled for Gloas. + Self::Case::is_enabled_for_fork(fork_name) + && (!fork_name.gloas_enabled() || self.handler_name() == "withdrawals") + } } #[derive(Educe)] diff --git a/testing/ef_tests/src/type_name.rs b/testing/ef_tests/src/type_name.rs index 87d56968cc..ae00727fc3 100644 --- a/testing/ef_tests/src/type_name.rs +++ b/testing/ef_tests/src/type_name.rs @@ -55,6 +55,7 @@ type_name_generic!(BeaconBlockBodyCapella, "BeaconBlockBody"); type_name_generic!(BeaconBlockBodyDeneb, "BeaconBlockBody"); type_name_generic!(BeaconBlockBodyElectra, "BeaconBlockBody"); type_name_generic!(BeaconBlockBodyFulu, "BeaconBlockBody"); +type_name_generic!(BeaconBlockBodyGloas, "BeaconBlockBody"); type_name!(BeaconBlockHeader); type_name_generic!(BeaconState); type_name!(BlobIdentifier); @@ -78,6 +79,7 @@ type_name_generic!(ExecutionPayloadCapella, "ExecutionPayload"); type_name_generic!(ExecutionPayloadDeneb, "ExecutionPayload"); type_name_generic!(ExecutionPayloadElectra, "ExecutionPayload"); type_name_generic!(ExecutionPayloadFulu, "ExecutionPayload"); +type_name_generic!(ExecutionPayloadGloas, "ExecutionPayload"); type_name_generic!(FullPayload, "ExecutionPayload"); type_name_generic!(ExecutionPayloadHeader); type_name_generic!(ExecutionPayloadHeaderBellatrix, "ExecutionPayloadHeader"); @@ -85,6 +87,8 @@ type_name_generic!(ExecutionPayloadHeaderCapella, "ExecutionPayloadHeader"); type_name_generic!(ExecutionPayloadHeaderDeneb, "ExecutionPayloadHeader"); type_name_generic!(ExecutionPayloadHeaderElectra, "ExecutionPayloadHeader"); type_name_generic!(ExecutionPayloadHeaderFulu, "ExecutionPayloadHeader"); +type_name_generic!(ExecutionPayloadBid); +type_name_generic!(SignedExecutionPayloadBid); type_name_generic!(ExecutionRequests); type_name_generic!(BlindedPayload, "ExecutionPayloadHeader"); type_name!(Fork); diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index e5c043e27b..505693c31d 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -369,6 +369,10 @@ mod ssz_static { .run(); SszStaticHandler::, MinimalEthSpec>::fulu_only().run(); SszStaticHandler::, MainnetEthSpec>::fulu_only().run(); + SszStaticHandler::, MinimalEthSpec>::gloas_only() + .run(); + SszStaticHandler::, MainnetEthSpec>::gloas_only() + .run(); } // Altair and later @@ -596,6 +600,10 @@ mod ssz_static { .run(); SszStaticHandler::, MinimalEthSpec>::fulu_only().run(); SszStaticHandler::, MainnetEthSpec>::fulu_only().run(); + SszStaticHandler::, MainnetEthSpec>::gloas_only() + .run(); + SszStaticHandler::, MainnetEthSpec>::gloas_only() + .run(); } #[test] @@ -622,6 +630,20 @@ mod ssz_static { .run(); } + #[test] + fn execution_payload_bid() { + SszStaticHandler::, MinimalEthSpec>::gloas_and_later() + .run(); + SszStaticHandler::, MainnetEthSpec>::gloas_and_later() + .run(); + } + + #[test] + fn signed_execution_payload_bid() { + SszStaticHandler::, MinimalEthSpec>::gloas_and_later().run(); + SszStaticHandler::, MainnetEthSpec>::gloas_and_later().run(); + } + #[test] fn withdrawal() { SszStaticHandler::::capella_and_later().run(); diff --git a/testing/simulator/src/basic_sim.rs b/testing/simulator/src/basic_sim.rs index 13bfcb5fc3..a9d0a0756b 100644 --- a/testing/simulator/src/basic_sim.rs +++ b/testing/simulator/src/basic_sim.rs @@ -14,7 +14,6 @@ use rayon::prelude::*; use std::cmp::max; use std::path::PathBuf; use std::sync::Arc; -use std::time::Duration; use environment::tracing_common; use tracing_subscriber::prelude::*; @@ -175,8 +174,11 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { let latest_fork_version = spec.electra_fork_version; let latest_fork_start_epoch = ELECTRA_FORK_EPOCH; - spec.seconds_per_slot /= speed_up_factor; - spec.seconds_per_slot = max(1, spec.seconds_per_slot); + let mut slot_duration_ms = spec.get_slot_duration().as_millis() as u64; + slot_duration_ms /= speed_up_factor; + slot_duration_ms = max(1_000, slot_duration_ms); + spec = spec.set_slot_duration_ms::(slot_duration_ms); + spec.genesis_delay = genesis_delay; spec.min_genesis_time = 0; spec.min_genesis_active_validator_count = total_validator_count as u64; @@ -188,7 +190,7 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { let spec = Arc::new(spec); env.eth2_config.spec = spec.clone(); - let slot_duration = Duration::from_secs(spec.seconds_per_slot); + let slot_duration = spec.get_slot_duration(); let slots_per_epoch = MinimalEthSpec::slots_per_epoch(); let initial_validator_count = spec.min_genesis_active_validator_count as usize; diff --git a/testing/simulator/src/fallback_sim.rs b/testing/simulator/src/fallback_sim.rs index 3d9a60abc7..06f4478c5e 100644 --- a/testing/simulator/src/fallback_sim.rs +++ b/testing/simulator/src/fallback_sim.rs @@ -15,12 +15,12 @@ use rayon::prelude::*; use std::cmp::max; use std::path::PathBuf; use std::sync::Arc; -use std::time::Duration; use tokio::time::sleep; use tracing::Level; use tracing_subscriber::prelude::*; use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; use types::{Epoch, EthSpec, MinimalEthSpec}; + const END_EPOCH: u64 = 16; const GENESIS_DELAY: u64 = 38; const ALTAIR_FORK_EPOCH: u64 = 0; @@ -179,8 +179,11 @@ pub fn run_fallback_sim(matches: &ArgMatches) -> Result<(), String> { let genesis_delay = GENESIS_DELAY; - spec.seconds_per_slot /= speed_up_factor; - spec.seconds_per_slot = max(1, spec.seconds_per_slot); + let mut slot_duration_ms = spec.get_slot_duration().as_millis() as u64; + slot_duration_ms /= speed_up_factor; + slot_duration_ms = max(1_000, slot_duration_ms); + spec = spec.set_slot_duration_ms::(slot_duration_ms); + spec.genesis_delay = genesis_delay; spec.min_genesis_time = 0; spec.min_genesis_active_validator_count = total_validator_count as u64; @@ -193,7 +196,7 @@ pub fn run_fallback_sim(matches: &ArgMatches) -> Result<(), String> { let spec = Arc::new(spec); env.eth2_config.spec = spec.clone(); - let slot_duration = Duration::from_secs(spec.seconds_per_slot); + let slot_duration = spec.get_slot_duration(); let slots_per_epoch = MinimalEthSpec::slots_per_epoch(); let disconnection_epoch = 1; diff --git a/testing/simulator/src/local_network.rs b/testing/simulator/src/local_network.rs index 58d7e1372f..2beb9c0efc 100644 --- a/testing/simulator/src/local_network.rs +++ b/testing/simulator/src/local_network.rs @@ -73,23 +73,33 @@ fn default_mock_execution_config( if let Some(capella_fork_epoch) = spec.capella_fork_epoch { mock_execution_config.shanghai_time = Some( genesis_time - + spec.seconds_per_slot * E::slots_per_epoch() * capella_fork_epoch.as_u64(), + + (spec.get_slot_duration().as_secs()) + * E::slots_per_epoch() + * capella_fork_epoch.as_u64(), ) } if let Some(deneb_fork_epoch) = spec.deneb_fork_epoch { mock_execution_config.cancun_time = Some( - genesis_time + spec.seconds_per_slot * E::slots_per_epoch() * deneb_fork_epoch.as_u64(), + genesis_time + + (spec.get_slot_duration().as_secs()) + * E::slots_per_epoch() + * deneb_fork_epoch.as_u64(), ) } if let Some(electra_fork_epoch) = spec.electra_fork_epoch { mock_execution_config.prague_time = Some( genesis_time - + spec.seconds_per_slot * E::slots_per_epoch() * electra_fork_epoch.as_u64(), + + (spec.get_slot_duration().as_secs()) + * E::slots_per_epoch() + * electra_fork_epoch.as_u64(), ) } if let Some(fulu_fork_epoch) = spec.fulu_fork_epoch { mock_execution_config.osaka_time = Some( - genesis_time + spec.seconds_per_slot * E::slots_per_epoch() * fulu_fork_epoch.as_u64(), + genesis_time + + (spec.get_slot_duration().as_secs()) + * E::slots_per_epoch() + * fulu_fork_epoch.as_u64(), ) } diff --git a/validator_client/beacon_node_fallback/src/lib.rs b/validator_client/beacon_node_fallback/src/lib.rs index 2d75df2fa3..3c20e57200 100644 --- a/validator_client/beacon_node_fallback/src/lib.rs +++ b/validator_client/beacon_node_fallback/src/lib.rs @@ -476,9 +476,9 @@ impl BeaconNodeFallback { } let timeouts: Timeouts = if new_list.len() == 1 || use_long_timeouts { - Timeouts::set_all(Duration::from_secs(self.spec.seconds_per_slot)) + Timeouts::set_all(self.spec.get_slot_duration()) } else { - Timeouts::use_optimized_timeouts(Duration::from_secs(self.spec.seconds_per_slot)) + Timeouts::use_optimized_timeouts(self.spec.get_slot_duration()) }; let new_candidates: Vec = new_list diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index b3cd3425f3..2b863715d2 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -268,7 +268,7 @@ impl ProductionValidatorClient { let beacon_node_setup = |x: (usize, &SensitiveUrl)| { let i = x.0; let url = x.1; - let slot_duration = Duration::from_secs(context.eth2_config.spec.seconds_per_slot); + let slot_duration = context.eth2_config.spec.get_slot_duration(); let mut beacon_node_http_client_builder = ClientBuilder::new(); @@ -389,7 +389,7 @@ impl ProductionValidatorClient { let slot_clock = SystemTimeSlotClock::new( context.eth2_config.spec.genesis_slot, Duration::from_secs(genesis_time), - Duration::from_secs(context.eth2_config.spec.seconds_per_slot), + context.eth2_config.spec.get_slot_duration(), ); beacon_nodes.set_slot_clock(slot_clock.clone()); diff --git a/validator_client/validator_services/src/attestation_service.rs b/validator_client/validator_services/src/attestation_service.rs index 58b1acfcdf..326ec6d01e 100644 --- a/validator_client/validator_services/src/attestation_service.rs +++ b/validator_client/validator_services/src/attestation_service.rs @@ -144,7 +144,7 @@ impl AttestationService AttestationService AttestationService Result<(), String> { + fn spawn_attestation_tasks(&self) -> Result<(), String> { let slot = self.slot_clock.now().ok_or("Failed to read slot clock")?; let duration_to_next_slot = self .slot_clock @@ -247,7 +249,8 @@ impl AttestationService> = self diff --git a/validator_client/validator_services/src/notifier_service.rs b/validator_client/validator_services/src/notifier_service.rs index 7d73059f02..a8f73490c7 100644 --- a/validator_client/validator_services/src/notifier_service.rs +++ b/validator_client/validator_services/src/notifier_service.rs @@ -2,7 +2,7 @@ use crate::duties_service::DutiesService; use slot_clock::SlotClock; use std::sync::Arc; use task_executor::TaskExecutor; -use tokio::time::{Duration, sleep}; +use tokio::time::sleep; use tracing::{debug, error, info}; use types::{ChainSpec, EthSpec}; use validator_metrics::set_gauge; @@ -14,7 +14,7 @@ pub fn spawn_notifier( executor: TaskExecutor, spec: &ChainSpec, ) -> Result<(), String> { - let slot_duration = Duration::from_secs(spec.seconds_per_slot); + let slot_duration = spec.get_slot_duration(); let interval_fut = async move { loop { diff --git a/validator_client/validator_services/src/preparation_service.rs b/validator_client/validator_services/src/preparation_service.rs index 063b11512f..913b6ab4e9 100644 --- a/validator_client/validator_services/src/preparation_service.rs +++ b/validator_client/validator_services/src/preparation_service.rs @@ -8,7 +8,7 @@ use std::ops::Deref; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use task_executor::TaskExecutor; -use tokio::time::{Duration, sleep}; +use tokio::time::sleep; use tracing::{debug, error, info, warn}; use types::{ Address, ChainSpec, EthSpec, ProposerPreparationData, SignedValidatorRegistrationData, @@ -174,7 +174,7 @@ impl PreparationService Result<(), String> { - let slot_duration = Duration::from_secs(spec.seconds_per_slot); + let slot_duration = spec.get_slot_duration(); info!("Proposer preparation service started"); let executor = self.executor.clone(); @@ -214,7 +214,7 @@ impl PreparationService SyncCommitteeService SyncCommitteeService SyncCommitteeService Result<(), String> { + async fn spawn_contribution_tasks(&self) -> Result<(), String> { + let spec = &self.duties_service.spec; let slot = self.slot_clock.now().ok_or("Failed to read slot clock")?; let duration_to_next_slot = self .slot_clock @@ -151,7 +154,8 @@ impl SyncCommitteeService(genesis_time: u64, spec: &ChainSpec) -> Opt let slot_clock = SystemTimeSlotClock::new( spec.genesis_slot, Duration::from_secs(genesis_time), - Duration::from_secs(spec.seconds_per_slot), + spec.get_slot_duration(), ); slot_clock.now().map(|s| s.epoch(E::slots_per_epoch())) } diff --git a/validator_manager/src/list_validators.rs b/validator_manager/src/list_validators.rs index f7a09f8d8e..7cc31d1b7a 100644 --- a/validator_manager/src/list_validators.rs +++ b/validator_manager/src/list_validators.rs @@ -185,7 +185,9 @@ async fn run(config: ListConfig) -> Result {