diff --git a/beacon_node/store/src/chunked_vector.rs b/beacon_node/store/src/chunked_vector.rs index 8c64d4bcc0..73edfbb074 100644 --- a/beacon_node/store/src/chunked_vector.rs +++ b/beacon_node/store/src/chunked_vector.rs @@ -18,6 +18,7 @@ use self::UpdatePattern::*; use crate::*; use ssz::{Decode, Encode}; use typenum::Unsigned; +use types::historical_summary::HistoricalSummary; /// Description of how a `BeaconState` field is updated during state processing. /// @@ -26,7 +27,18 @@ use typenum::Unsigned; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum UpdatePattern { /// The value is updated once per `n` slots. - OncePerNSlots { n: u64 }, + OncePerNSlots { + n: u64, + /// The slot at which the field begins to accumulate values. + /// + /// The field should not be read or written until `activation_slot` is reached, and the + /// activation slot should act as an offset when converting slots to vector indices. + activation_slot: Option, + /// The slot at which the field ceases to accumulate values. + /// + /// If this is `None` then the field is continually updated. + deactivation_slot: Option, + }, /// The value is updated once per epoch, for the epoch `current_epoch - lag`. OncePerEpoch { lag: u64 }, } @@ -98,12 +110,30 @@ pub trait Field: Copy { fn start_and_end_vindex(current_slot: Slot, spec: &ChainSpec) -> (usize, usize) { // We take advantage of saturating subtraction on slots and epochs match Self::update_pattern(spec) { - OncePerNSlots { n } => { + OncePerNSlots { + n, + activation_slot, + deactivation_slot, + } => { // Per-slot changes exclude the index for the current slot, because // it won't be set until the slot completes (think of `state_roots`, `block_roots`). // This also works for the `historical_roots` because at the `n`th slot, the 0th // entry of the list is created, and before that the list is empty. - let end_vindex = current_slot / n; + // + // To account for the switch from historical roots to historical summaries at + // Capella we also modify the current slot by the activation and deactivation slots. + // The activation slot acts as an offset (subtraction) while the deactivation slot + // acts as a clamp (min). + let slot_with_clamp = deactivation_slot.map_or(current_slot, |deactivation_slot| { + std::cmp::min(current_slot, deactivation_slot) + }); + let slot_with_clamp_and_offset = if let Some(activation_slot) = activation_slot { + slot_with_clamp - activation_slot + } else { + // Return (0, 0) to indicate that the field should not be read/written. + return (0, 0); + }; + let end_vindex = slot_with_clamp_and_offset / n; let start_vindex = end_vindex - Self::Length::to_u64(); (start_vindex.as_usize(), end_vindex.as_usize()) } @@ -295,7 +325,11 @@ field!( Hash256, T::SlotsPerHistoricalRoot, DBColumn::BeaconBlockRoots, - |_| OncePerNSlots { n: 1 }, + |_| OncePerNSlots { + n: 1, + activation_slot: Some(Slot::new(0)), + deactivation_slot: None + }, |state: &BeaconState<_>, index, _| safe_modulo_index(state.block_roots(), index) ); @@ -305,7 +339,11 @@ field!( Hash256, T::SlotsPerHistoricalRoot, DBColumn::BeaconStateRoots, - |_| OncePerNSlots { n: 1 }, + |_| OncePerNSlots { + n: 1, + activation_slot: Some(Slot::new(0)), + deactivation_slot: None, + }, |state: &BeaconState<_>, index, _| safe_modulo_index(state.state_roots(), index) ); @@ -315,8 +353,12 @@ field!( Hash256, T::HistoricalRootsLimit, DBColumn::BeaconHistoricalRoots, - |_| OncePerNSlots { - n: T::SlotsPerHistoricalRoot::to_u64() + |spec: &ChainSpec| OncePerNSlots { + n: T::SlotsPerHistoricalRoot::to_u64(), + activation_slot: Some(Slot::new(0)), + deactivation_slot: spec + .capella_fork_epoch + .map(|fork_epoch| fork_epoch.start_slot(T::slots_per_epoch())), }, |state: &BeaconState<_>, index, _| safe_modulo_index(state.historical_roots(), index) ); @@ -331,6 +373,27 @@ field!( |state: &BeaconState<_>, index, _| safe_modulo_index(state.randao_mixes(), index) ); +field!( + HistoricalSummaries, + VariableLengthField, + HistoricalSummary, + T::HistoricalRootsLimit, + DBColumn::BeaconHistoricalSummaries, + |spec: &ChainSpec| OncePerNSlots { + n: T::SlotsPerHistoricalRoot::to_u64(), + activation_slot: spec + .capella_fork_epoch + .map(|fork_epoch| fork_epoch.start_slot(T::slots_per_epoch())), + deactivation_slot: None, + }, + |state: &BeaconState<_>, index, _| safe_modulo_index( + state + .historical_summaries() + .map_err(|_| ChunkError::InvalidFork)?, + index + ) +); + pub fn store_updated_vector, E: EthSpec, S: KeyValueStore>( field: F, store: &S, @@ -679,6 +742,7 @@ pub enum ChunkError { end_vindex: usize, length: usize, }, + InvalidFork, } #[cfg(test)] diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 4600e5e826..93af0ca184 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -1,5 +1,5 @@ use crate::chunked_vector::{ - store_updated_vector, BlockRoots, HistoricalRoots, RandaoMixes, StateRoots, + store_updated_vector, BlockRoots, HistoricalRoots, HistoricalSummaries, RandaoMixes, StateRoots, }; use crate::config::{ OnDiskStoreConfig, StoreConfig, DEFAULT_SLOTS_PER_RESTORE_POINT, @@ -948,6 +948,7 @@ impl, Cold: ItemStore> HotColdDB store_updated_vector(StateRoots, db, state, &self.spec, ops)?; store_updated_vector(HistoricalRoots, db, state, &self.spec, ops)?; store_updated_vector(RandaoMixes, db, state, &self.spec, ops)?; + store_updated_vector(HistoricalSummaries, db, state, &self.spec, ops)?; // 3. Store restore point. let restore_point_index = state.slot().as_u64() / self.config.slots_per_restore_point; @@ -1002,6 +1003,7 @@ impl, Cold: ItemStore> HotColdDB partial_state.load_state_roots(&self.cold_db, &self.spec)?; partial_state.load_historical_roots(&self.cold_db, &self.spec)?; partial_state.load_randao_mixes(&self.cold_db, &self.spec)?; + partial_state.load_historical_summaries(&self.cold_db, &self.spec)?; partial_state.try_into() } diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index d9041dd636..9db5f45ece 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -214,6 +214,8 @@ pub enum DBColumn { /// For Optimistically Imported Merge Transition Blocks #[strum(serialize = "otb")] OptimisticTransitionBlock, + #[strum(serialize = "bhs")] + BeaconHistoricalSummaries, } /// A block from the database, which might have an execution payload or not. diff --git a/beacon_node/store/src/partial_beacon_state.rs b/beacon_node/store/src/partial_beacon_state.rs index ca35bc0b22..55697bd316 100644 --- a/beacon_node/store/src/partial_beacon_state.rs +++ b/beacon_node/store/src/partial_beacon_state.rs @@ -1,12 +1,13 @@ use crate::chunked_vector::{ - load_variable_list_from_db, load_vector_from_db, BlockRoots, HistoricalRoots, RandaoMixes, - StateRoots, + load_variable_list_from_db, load_vector_from_db, BlockRoots, HistoricalRoots, + HistoricalSummaries, RandaoMixes, StateRoots, }; use crate::{get_key_for_col, DBColumn, Error, KeyValueStore, KeyValueStoreOp}; use ssz::{Decode, DecodeError, Encode}; use ssz_derive::{Decode, Encode}; use std::convert::TryInto; use std::sync::Arc; +use types::historical_summary::HistoricalSummary; use types::superstruct; use types::*; @@ -104,16 +105,20 @@ where )] pub latest_execution_payload_header: ExecutionPayloadHeaderEip4844, - // Withdrawals + // Capella #[superstruct(only(Capella, Eip4844))] pub next_withdrawal_index: u64, #[superstruct(only(Capella, Eip4844))] pub next_withdrawal_validator_index: u64, + + #[ssz(skip_serializing, skip_deserializing)] + #[superstruct(only(Capella, Eip4844))] + pub historical_summaries: Option>, } /// Implement the conversion function from BeaconState -> PartialBeaconState. macro_rules! impl_from_state_forgetful { - ($s:ident, $outer:ident, $variant_name:ident, $struct_name:ident, [$($extra_fields:ident),*]) => { + ($s:ident, $outer:ident, $variant_name:ident, $struct_name:ident, [$($extra_fields:ident),*], [$($extra_fields_opt:ident),*]) => { PartialBeaconState::$variant_name($struct_name { // Versioning genesis_time: $s.genesis_time, @@ -154,6 +159,11 @@ macro_rules! impl_from_state_forgetful { // Variant-specific fields $( $extra_fields: $s.$extra_fields.clone() + ),*, + + // Variant-specific optional + $( + $extra_fields_opt: None ),* }) } @@ -168,7 +178,8 @@ impl PartialBeaconState { outer, Base, PartialBeaconStateBase, - [previous_epoch_attestations, current_epoch_attestations] + [previous_epoch_attestations, current_epoch_attestations], + [] ), BeaconState::Altair(s) => impl_from_state_forgetful!( s, @@ -181,7 +192,8 @@ impl PartialBeaconState { current_sync_committee, next_sync_committee, inactivity_scores - ] + ], + [] ), BeaconState::Merge(s) => impl_from_state_forgetful!( s, @@ -195,7 +207,8 @@ impl PartialBeaconState { next_sync_committee, inactivity_scores, latest_execution_payload_header - ] + ], + [] ), BeaconState::Capella(s) => impl_from_state_forgetful!( s, @@ -211,7 +224,8 @@ impl PartialBeaconState { latest_execution_payload_header, next_withdrawal_index, next_withdrawal_validator_index - ] + ], + [historical_summaries] ), BeaconState::Eip4844(s) => impl_from_state_forgetful!( s, @@ -227,7 +241,8 @@ impl PartialBeaconState { latest_execution_payload_header, next_withdrawal_index, next_withdrawal_validator_index - ] + ], + [historical_summaries] ), } } @@ -303,6 +318,23 @@ impl PartialBeaconState { Ok(()) } + pub fn load_historical_summaries>( + &mut self, + store: &S, + spec: &ChainSpec, + ) -> Result<(), Error> { + let slot = self.slot(); + if let Ok(historical_summaries) = self.historical_summaries_mut() { + if historical_summaries.is_none() { + *historical_summaries = + Some(load_variable_list_from_db::( + store, slot, spec, + )?); + } + } + Ok(()) + } + pub fn load_randao_mixes>( &mut self, store: &S, @@ -326,7 +358,7 @@ impl PartialBeaconState { /// Implement the conversion from PartialBeaconState -> BeaconState. macro_rules! impl_try_into_beacon_state { - ($inner:ident, $variant_name:ident, $struct_name:ident, [$($extra_fields:ident),*]) => { + ($inner:ident, $variant_name:ident, $struct_name:ident, [$($extra_fields:ident),*], [$($extra_opt_fields:ident),*]) => { BeaconState::$variant_name($struct_name { // Versioning genesis_time: $inner.genesis_time, @@ -371,6 +403,11 @@ macro_rules! impl_try_into_beacon_state { // Variant-specific fields $( $extra_fields: $inner.$extra_fields + ),*, + + // Variant-specific optional fields + $( + $extra_opt_fields: unpack_field($inner.$extra_opt_fields)? ),* }) } @@ -389,7 +426,8 @@ impl TryInto> for PartialBeaconState { inner, Base, BeaconStateBase, - [previous_epoch_attestations, current_epoch_attestations] + [previous_epoch_attestations, current_epoch_attestations], + [] ), PartialBeaconState::Altair(inner) => impl_try_into_beacon_state!( inner, @@ -401,7 +439,8 @@ impl TryInto> for PartialBeaconState { current_sync_committee, next_sync_committee, inactivity_scores - ] + ], + [] ), PartialBeaconState::Merge(inner) => impl_try_into_beacon_state!( inner, @@ -414,7 +453,8 @@ impl TryInto> for PartialBeaconState { next_sync_committee, inactivity_scores, latest_execution_payload_header - ] + ], + [] ), PartialBeaconState::Capella(inner) => impl_try_into_beacon_state!( inner, @@ -429,7 +469,8 @@ impl TryInto> for PartialBeaconState { latest_execution_payload_header, next_withdrawal_index, next_withdrawal_validator_index - ] + ], + [historical_summaries] ), PartialBeaconState::Eip4844(inner) => impl_try_into_beacon_state!( inner, @@ -444,7 +485,8 @@ impl TryInto> for PartialBeaconState { latest_execution_payload_header, next_withdrawal_index, next_withdrawal_validator_index - ] + ], + [historical_summaries] ), }; Ok(state) diff --git a/consensus/state_processing/src/per_epoch_processing.rs b/consensus/state_processing/src/per_epoch_processing.rs index f227b82863..996e39c27f 100644 --- a/consensus/state_processing/src/per_epoch_processing.rs +++ b/consensus/state_processing/src/per_epoch_processing.rs @@ -3,14 +3,16 @@ pub use epoch_processing_summary::EpochProcessingSummary; use errors::EpochProcessingError as Error; pub use justification_and_finalization_state::JustificationAndFinalizationState; -pub use registry_updates::process_registry_updates; use safe_arith::SafeArith; -pub use slashings::process_slashings; use types::{BeaconState, ChainSpec, EthSpec}; + +pub use registry_updates::process_registry_updates; +pub use slashings::process_slashings; pub use weigh_justification_and_finalization::weigh_justification_and_finalization; pub mod altair; pub mod base; +pub mod capella; pub mod effective_balance_updates; pub mod epoch_processing_summary; pub mod errors; @@ -37,10 +39,8 @@ pub fn process_epoch( match state { BeaconState::Base(_) => base::process_epoch(state, spec), - BeaconState::Altair(_) - | BeaconState::Merge(_) - | BeaconState::Capella(_) - | BeaconState::Eip4844(_) => altair::process_epoch(state, spec), + BeaconState::Altair(_) | BeaconState::Merge(_) => altair::process_epoch(state, spec), + BeaconState::Capella(_) | BeaconState::Eip4844(_) => capella::process_epoch(state, spec), } } diff --git a/consensus/state_processing/src/per_epoch_processing/capella.rs b/consensus/state_processing/src/per_epoch_processing/capella.rs new file mode 100644 index 0000000000..aaf301f29e --- /dev/null +++ b/consensus/state_processing/src/per_epoch_processing/capella.rs @@ -0,0 +1,78 @@ +use super::altair::inactivity_updates::process_inactivity_updates; +use super::altair::justification_and_finalization::process_justification_and_finalization; +use super::altair::participation_cache::ParticipationCache; +use super::altair::participation_flag_updates::process_participation_flag_updates; +use super::altair::rewards_and_penalties::process_rewards_and_penalties; +use super::altair::sync_committee_updates::process_sync_committee_updates; +use super::{process_registry_updates, process_slashings, EpochProcessingSummary, Error}; +use crate::per_epoch_processing::{ + effective_balance_updates::process_effective_balance_updates, + resets::{process_eth1_data_reset, process_randao_mixes_reset, process_slashings_reset}, +}; +use types::{BeaconState, ChainSpec, EthSpec, RelativeEpoch}; + +pub use historical_summaries_update::process_historical_summaries_update; + +mod historical_summaries_update; + +pub fn process_epoch( + state: &mut BeaconState, + spec: &ChainSpec, +) -> Result, Error> { + // Ensure the committee caches are built. + state.build_committee_cache(RelativeEpoch::Previous, spec)?; + state.build_committee_cache(RelativeEpoch::Current, spec)?; + state.build_committee_cache(RelativeEpoch::Next, spec)?; + + // Pre-compute participating indices and total balances. + let participation_cache = ParticipationCache::new(state, spec)?; + let sync_committee = state.current_sync_committee()?.clone(); + + // Justification and finalization. + let justification_and_finalization_state = + process_justification_and_finalization(state, &participation_cache)?; + justification_and_finalization_state.apply_changes_to_state(state); + + process_inactivity_updates(state, &participation_cache, spec)?; + + // Rewards and Penalties. + process_rewards_and_penalties(state, &participation_cache, spec)?; + + // Registry Updates. + process_registry_updates(state, spec)?; + + // Slashings. + process_slashings( + state, + participation_cache.current_epoch_total_active_balance(), + spec, + )?; + + // Reset eth1 data votes. + process_eth1_data_reset(state)?; + + // Update effective balances with hysteresis (lag). + process_effective_balance_updates(state, spec)?; + + // Reset slashings + process_slashings_reset(state)?; + + // Set randao mix + process_randao_mixes_reset(state)?; + + // Set historical summaries accumulator + process_historical_summaries_update(state)?; + + // Rotate current/previous epoch participation + process_participation_flag_updates(state)?; + + process_sync_committee_updates(state, spec)?; + + // Rotate the epoch caches to suit the epoch transition. + state.advance_caches(spec)?; + + Ok(EpochProcessingSummary::Altair { + participation_cache, + sync_committee, + }) +} diff --git a/consensus/state_processing/src/per_epoch_processing/capella/historical_summaries_update.rs b/consensus/state_processing/src/per_epoch_processing/capella/historical_summaries_update.rs new file mode 100644 index 0000000000..9a87ceb605 --- /dev/null +++ b/consensus/state_processing/src/per_epoch_processing/capella/historical_summaries_update.rs @@ -0,0 +1,23 @@ +use crate::EpochProcessingError; +use safe_arith::SafeArith; +use types::historical_summary::HistoricalSummary; +use types::{BeaconState, EthSpec}; + +pub fn process_historical_summaries_update( + state: &mut BeaconState, +) -> Result<(), EpochProcessingError> { + // Set historical block root accumulator. + let next_epoch = state.next_epoch()?; + if next_epoch + .as_u64() + .safe_rem((T::slots_per_historical_root() as u64).safe_div(T::slots_per_epoch())?)? + == 0 + { + let summary = HistoricalSummary::new(state); + return state + .historical_summaries_mut()? + .push(summary) + .map_err(Into::into); + } + Ok(()) +} diff --git a/consensus/state_processing/src/upgrade/capella.rs b/consensus/state_processing/src/upgrade/capella.rs index dc759b384d..3b933fac37 100644 --- a/consensus/state_processing/src/upgrade/capella.rs +++ b/consensus/state_processing/src/upgrade/capella.rs @@ -1,3 +1,4 @@ +use ssz_types::VariableList; use std::mem; use types::{BeaconState, BeaconStateCapella, BeaconStateError as Error, ChainSpec, EthSpec, Fork}; @@ -55,9 +56,10 @@ pub fn upgrade_to_capella( next_sync_committee: pre.next_sync_committee.clone(), // Execution latest_execution_payload_header: pre.latest_execution_payload_header.upgrade_to_capella(), - // Withdrawals + // Capella next_withdrawal_index: 0, next_withdrawal_validator_index: 0, + historical_summaries: VariableList::default(), // Caches total_active_balance: pre.total_active_balance, committee_caches: mem::take(&mut pre.committee_caches), diff --git a/consensus/state_processing/src/upgrade/eip4844.rs b/consensus/state_processing/src/upgrade/eip4844.rs index e829c01e7e..4f6ff9d194 100644 --- a/consensus/state_processing/src/upgrade/eip4844.rs +++ b/consensus/state_processing/src/upgrade/eip4844.rs @@ -57,9 +57,10 @@ pub fn upgrade_to_eip4844( next_sync_committee: pre.next_sync_committee.clone(), // Execution latest_execution_payload_header: pre.latest_execution_payload_header.upgrade_to_eip4844(), - // Withdrawals + // Capella next_withdrawal_index: pre.next_withdrawal_index, next_withdrawal_validator_index: pre.next_withdrawal_validator_index, + historical_summaries: pre.historical_summaries.clone(), // Caches total_active_balance: pre.total_active_balance, committee_caches: mem::take(&mut pre.committee_caches), diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 625ff3d174..f51a7bf9fb 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -14,6 +14,7 @@ use ssz::{ssz_encode, Decode, DecodeError, Encode}; use ssz_derive::{Decode, Encode}; use ssz_types::{typenum::Unsigned, BitVector, FixedVector}; use std::convert::TryInto; +use std::hash::Hash; use std::{fmt, mem, sync::Arc}; use superstruct::superstruct; use swap_or_not_shuffle::compute_shuffled_index; @@ -25,6 +26,7 @@ pub use self::committee_cache::{ compute_committee_index_in_epoch, compute_committee_range_in_epoch, epoch_committee_count, CommitteeCache, }; +use crate::historical_summary::HistoricalSummary; pub use clone_config::CloneConfig; pub use eth_spec::*; pub use iter::BlockRootsIter; @@ -223,6 +225,7 @@ where pub block_roots: FixedVector, #[compare_fields(as_slice)] pub state_roots: FixedVector, + // Frozen in Capella, replaced by historical_summaries pub historical_roots: VariableList, // Ethereum 1.0 chain data @@ -296,11 +299,14 @@ where )] pub latest_execution_payload_header: ExecutionPayloadHeaderEip4844, - // Withdrawals + // Capella #[superstruct(only(Capella, Eip4844), partial_getter(copy))] pub next_withdrawal_index: u64, #[superstruct(only(Capella, Eip4844), partial_getter(copy))] pub next_withdrawal_validator_index: u64, + // Deep history valid from Capella onwards. + #[superstruct(only(Capella, Eip4844))] + pub historical_summaries: VariableList, // Caching (not in the spec) #[serde(skip_serializing, skip_deserializing)] diff --git a/consensus/types/src/beacon_state/tree_hash_cache.rs b/consensus/types/src/beacon_state/tree_hash_cache.rs index 4cfc684f4d..5515fb753a 100644 --- a/consensus/types/src/beacon_state/tree_hash_cache.rs +++ b/consensus/types/src/beacon_state/tree_hash_cache.rs @@ -3,6 +3,7 @@ #![allow(clippy::indexing_slicing)] use super::Error; +use crate::historical_summary::HistoricalSummaryCache; use crate::{BeaconState, EthSpec, Hash256, ParticipationList, Slot, Unsigned, Validator}; use cached_tree_hash::{int_log, CacheArena, CachedTreeHash, TreeHashCache}; use rayon::prelude::*; @@ -142,6 +143,7 @@ pub struct BeaconTreeHashCacheInner { block_roots: TreeHashCache, state_roots: TreeHashCache, historical_roots: TreeHashCache, + historical_summaries: OptionalTreeHashCache, balances: TreeHashCache, randao_mixes: TreeHashCache, slashings: TreeHashCache, @@ -164,6 +166,14 @@ impl BeaconTreeHashCacheInner { let historical_roots = state .historical_roots() .new_tree_hash_cache(&mut fixed_arena); + let historical_summaries = OptionalTreeHashCache::new( + state + .historical_summaries() + .ok() + .map(HistoricalSummaryCache::new) + .as_ref(), + ); + let randao_mixes = state.randao_mixes().new_tree_hash_cache(&mut fixed_arena); let validators = ValidatorsListTreeHashCache::new::(state.validators()); @@ -200,6 +210,7 @@ impl BeaconTreeHashCacheInner { block_roots, state_roots, historical_roots, + historical_summaries, balances, randao_mixes, slashings, @@ -249,6 +260,7 @@ impl BeaconTreeHashCacheInner { .slashings() .recalculate_tree_hash_root(&mut self.slashings_arena, &mut self.slashings)?, ]; + // Participation if let BeaconState::Base(state) = state { leaves.push(state.previous_epoch_attestations.tree_hash_root()); @@ -291,6 +303,24 @@ impl BeaconTreeHashCacheInner { if let Ok(payload_header) = state.latest_execution_payload_header() { leaves.push(payload_header.tree_hash_root()); } + + // Withdrawal indices (Capella and later). + if let Ok(next_withdrawal_index) = state.next_withdrawal_index() { + leaves.push(next_withdrawal_index.tree_hash_root()); + } + if let Ok(next_withdrawal_validator_index) = state.next_withdrawal_validator_index() { + leaves.push(next_withdrawal_validator_index.tree_hash_root()); + } + + // Historical roots/summaries (Capella and later). + if let Ok(historical_summaries) = state.historical_summaries() { + leaves.push( + self.historical_summaries.recalculate_tree_hash_root( + &HistoricalSummaryCache::new(historical_summaries), + )?, + ); + } + Ok(leaves) } @@ -335,14 +365,6 @@ impl BeaconTreeHashCacheInner { hasher.write(leaf.as_bytes())?; } - // Withdrawal indices (Capella and later). - if let Ok(next_withdrawal_index) = state.next_withdrawal_index() { - hasher.write(next_withdrawal_index.tree_hash_root().as_bytes())?; - } - if let Ok(next_withdrawal_validator_index) = state.next_withdrawal_validator_index() { - hasher.write(next_withdrawal_validator_index.tree_hash_root().as_bytes())?; - } - let root = hasher.finish()?; self.previous_state = Some((root, state.slot())); diff --git a/consensus/types/src/historical_summary.rs b/consensus/types/src/historical_summary.rs new file mode 100644 index 0000000000..1f79ee49fa --- /dev/null +++ b/consensus/types/src/historical_summary.rs @@ -0,0 +1,88 @@ +use crate::test_utils::TestRandom; +use crate::Unsigned; +use crate::{BeaconState, EthSpec, Hash256}; +use cached_tree_hash::Error; +use cached_tree_hash::{int_log, CacheArena, CachedTreeHash, TreeHashCache}; +use compare_fields_derive::CompareFields; +use serde_derive::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use ssz_types::VariableList; +use test_random_derive::TestRandom; +use tree_hash::{mix_in_length, TreeHash, BYTES_PER_CHUNK}; +use tree_hash_derive::TreeHash; + +/// `HistoricalSummary` matches the components of the phase0 `HistoricalBatch` +/// making the two hash_tree_root-compatible. This struct is introduced into the beacon state +/// in the Capella hard fork. +/// +/// https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#historicalsummary +#[derive( + Debug, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, + CompareFields, + Clone, + Copy, + Default, +)] +pub struct HistoricalSummary { + block_summary_root: Hash256, + state_summary_root: Hash256, +} + +impl HistoricalSummary { + pub fn new(state: &BeaconState) -> Self { + Self { + block_summary_root: state.block_roots().tree_hash_root(), + state_summary_root: state.state_roots().tree_hash_root(), + } + } +} + +/// Wrapper type allowing the implementation of `CachedTreeHash`. +#[derive(Debug)] +pub struct HistoricalSummaryCache<'a, N: Unsigned> { + pub inner: &'a VariableList, +} + +impl<'a, N: Unsigned> HistoricalSummaryCache<'a, N> { + pub fn new(inner: &'a VariableList) -> Self { + Self { inner } + } + + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.inner.len() + } +} + +impl<'a, N: Unsigned> CachedTreeHash for HistoricalSummaryCache<'a, N> { + fn new_tree_hash_cache(&self, arena: &mut CacheArena) -> TreeHashCache { + TreeHashCache::new(arena, int_log(N::to_usize()), self.len()) + } + + fn recalculate_tree_hash_root( + &self, + arena: &mut CacheArena, + cache: &mut TreeHashCache, + ) -> Result { + Ok(mix_in_length( + &cache.recalculate_merkle_root(arena, leaf_iter(self.inner))?, + self.len(), + )) + } +} + +pub fn leaf_iter( + values: &[HistoricalSummary], +) -> impl Iterator + ExactSizeIterator + '_ { + values + .iter() + .map(|value| value.tree_hash_root()) + .map(Hash256::to_fixed_bytes) +} diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 6cbb9568da..f0806e35ad 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -49,6 +49,7 @@ pub mod fork_name; pub mod free_attestation; pub mod graffiti; pub mod historical_batch; +pub mod historical_summary; pub mod indexed_attestation; pub mod light_client_bootstrap; pub mod light_client_finality_update; diff --git a/lcli/src/main.rs b/lcli/src/main.rs index 238c7e9f16..3af485be10 100644 --- a/lcli/src/main.rs +++ b/lcli/src/main.rs @@ -741,7 +741,6 @@ fn main() { .value_name("PATH") .takes_value(true) .conflicts_with("beacon-url") - .requires("pre-state-path") .help("Path to load a SignedBeaconBlock from file as SSZ."), ) .arg( diff --git a/testing/ef_tests/Makefile b/testing/ef_tests/Makefile index 69b18ee747..354631e5c3 100644 --- a/testing/ef_tests/Makefile +++ b/testing/ef_tests/Makefile @@ -1,4 +1,4 @@ -TESTS_TAG := v1.3.0-alpha.2 +TESTS_TAG := v1.3.0-rc.0 TESTS = general minimal mainnet TARBALLS = $(patsubst %,%-$(TESTS_TAG).tar.gz,$(TESTS)) diff --git a/testing/ef_tests/check_all_files_accessed.py b/testing/ef_tests/check_all_files_accessed.py index 04f45ac5da..b098241366 100755 --- a/testing/ef_tests/check_all_files_accessed.py +++ b/testing/ef_tests/check_all_files_accessed.py @@ -41,8 +41,6 @@ excluded_paths = [ "tests/.*/.*/ssz_static/LightClientFinalityUpdate", # Eip4844 tests are disabled for now. "tests/.*/eip4844", - # Capella tests are disabled for now. - "tests/.*/capella", # One of the EF researchers likes to pack the tarballs on a Mac ".*\.DS_Store.*", # More Mac weirdness. diff --git a/testing/ef_tests/src/cases/epoch_processing.rs b/testing/ef_tests/src/cases/epoch_processing.rs index b0e16e12c7..59a8ebd41c 100644 --- a/testing/ef_tests/src/cases/epoch_processing.rs +++ b/testing/ef_tests/src/cases/epoch_processing.rs @@ -5,6 +5,7 @@ use crate::decode::{ssz_decode_state, yaml_decode_file}; use crate::type_name; use crate::type_name::TypeName; use serde_derive::Deserialize; +use state_processing::per_epoch_processing::capella::process_historical_summaries_update; use state_processing::per_epoch_processing::{ altair, base, effective_balance_updates::process_effective_balance_updates, @@ -57,6 +58,8 @@ pub struct RandaoMixesReset; #[derive(Debug)] pub struct HistoricalRootsUpdate; #[derive(Debug)] +pub struct HistoricalSummariesUpdate; +#[derive(Debug)] pub struct ParticipationRecordUpdates; #[derive(Debug)] pub struct SyncCommitteeUpdates; @@ -77,6 +80,7 @@ type_name!(EffectiveBalanceUpdates, "effective_balance_updates"); type_name!(SlashingsReset, "slashings_reset"); type_name!(RandaoMixesReset, "randao_mixes_reset"); type_name!(HistoricalRootsUpdate, "historical_roots_update"); +type_name!(HistoricalSummariesUpdate, "historical_summaries_update"); type_name!(ParticipationRecordUpdates, "participation_record_updates"); type_name!(SyncCommitteeUpdates, "sync_committee_updates"); type_name!(InactivityUpdates, "inactivity_updates"); @@ -194,7 +198,23 @@ impl EpochTransition for RandaoMixesReset { impl EpochTransition for HistoricalRootsUpdate { fn run(state: &mut BeaconState, _spec: &ChainSpec) -> Result<(), EpochProcessingError> { - process_historical_roots_update(state) + match state { + BeaconState::Base(_) | BeaconState::Altair(_) | BeaconState::Merge(_) => { + process_historical_roots_update(state) + } + _ => Ok(()), + } + } +} + +impl EpochTransition for HistoricalSummariesUpdate { + fn run(state: &mut BeaconState, _spec: &ChainSpec) -> Result<(), EpochProcessingError> { + match state { + BeaconState::Capella(_) | BeaconState::Eip4844(_) => { + process_historical_summaries_update(state) + } + _ => Ok(()), + } } } @@ -287,10 +307,16 @@ impl> Case for EpochProcessing { T::name() != "sync_committee_updates" && T::name() != "inactivity_updates" && T::name() != "participation_flag_updates" + && T::name() != "historical_summaries_update" } // No phase0 tests for Altair and later. - ForkName::Altair | ForkName::Merge | ForkName::Capella => { + ForkName::Altair | ForkName::Merge => { T::name() != "participation_record_updates" + && T::name() != "historical_summaries_update" + } + ForkName::Capella => { + T::name() != "participation_record_updates" + && T::name() != "historical_roots_update" } ForkName::Eip4844 => false, // TODO: revisit when tests are out } diff --git a/testing/ef_tests/src/handler.rs b/testing/ef_tests/src/handler.rs index 66f81616b7..07db7cd2a1 100644 --- a/testing/ef_tests/src/handler.rs +++ b/testing/ef_tests/src/handler.rs @@ -374,6 +374,11 @@ impl Handler for SanitySlotsHandler { fn handler_name(&self) -> String { "slots".into() } + + fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { + // Some sanity tests compute sync committees, which requires real crypto. + fork_name == ForkName::Base || cfg!(not(feature = "fake_crypto")) + } } #[derive(Derivative)] diff --git a/testing/ef_tests/src/lib.rs b/testing/ef_tests/src/lib.rs index f52b7bca17..5ab2b4b7b4 100644 --- a/testing/ef_tests/src/lib.rs +++ b/testing/ef_tests/src/lib.rs @@ -1,10 +1,10 @@ pub use case_result::CaseResult; pub use cases::WithdrawalsPayload; pub use cases::{ - Case, EffectiveBalanceUpdates, Eth1DataReset, HistoricalRootsUpdate, InactivityUpdates, - JustificationAndFinalization, ParticipationFlagUpdates, ParticipationRecordUpdates, - RandaoMixesReset, RegistryUpdates, RewardsAndPenalties, Slashings, SlashingsReset, - SyncCommitteeUpdates, + Case, EffectiveBalanceUpdates, Eth1DataReset, HistoricalRootsUpdate, HistoricalSummariesUpdate, + InactivityUpdates, JustificationAndFinalization, ParticipationFlagUpdates, + ParticipationRecordUpdates, RandaoMixesReset, RegistryUpdates, RewardsAndPenalties, Slashings, + SlashingsReset, SyncCommitteeUpdates, }; pub use decode::log_file_access; pub use error::Error; diff --git a/testing/ef_tests/src/type_name.rs b/testing/ef_tests/src/type_name.rs index bee2d9b03d..0239293e09 100644 --- a/testing/ef_tests/src/type_name.rs +++ b/testing/ef_tests/src/type_name.rs @@ -1,4 +1,5 @@ //! Mapping from types to canonical string identifiers used in testing. +use types::historical_summary::HistoricalSummary; use types::*; pub trait TypeName { @@ -87,3 +88,4 @@ type_name!(VoluntaryExit); type_name!(Withdrawal); type_name!(BlsToExecutionChange, "BLSToExecutionChange"); type_name!(SignedBlsToExecutionChange, "SignedBLSToExecutionChange"); +type_name!(HistoricalSummary); diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index f84be64dad..8a7209b89b 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -215,6 +215,7 @@ macro_rules! ssz_static_test_no_run { #[cfg(feature = "fake_crypto")] mod ssz_static { use ef_tests::{Handler, SszStaticHandler, SszStaticTHCHandler, SszStaticWithSpecHandler}; + use types::historical_summary::HistoricalSummary; use types::*; ssz_static_test!(aggregate_and_proof, AggregateAndProof<_>); @@ -357,6 +358,12 @@ mod ssz_static { SszStaticHandler::::capella_only().run(); SszStaticHandler::::capella_only().run(); } + + #[test] + fn historical_summary() { + SszStaticHandler::::capella_only().run(); + SszStaticHandler::::capella_only().run(); + } } #[test] @@ -423,6 +430,12 @@ fn epoch_processing_historical_roots_update() { EpochProcessingHandler::::default().run(); } +#[test] +fn epoch_processing_historical_summaries_update() { + EpochProcessingHandler::::default().run(); + EpochProcessingHandler::::default().run(); +} + #[test] fn epoch_processing_participation_record_updates() { EpochProcessingHandler::::default().run();