Update HotStateSummary construction

This commit is contained in:
Michael Sproul
2026-02-24 18:16:53 +11:00
parent e2b3971cbd
commit 28eb5adf0a
3 changed files with 44 additions and 7 deletions

View File

@@ -656,7 +656,8 @@ impl HierarchyModuli {
/// layer 2 diff will point to the start snapshot instead of the layer 1 diff at /// layer 2 diff will point to the start snapshot instead of the layer 1 diff at
/// 2998272. /// 2998272.
/// * `payload_status` - whether the state is `Full` (came from processing a payload), or /// * `payload_status` - whether the state is `Full` (came from processing a payload), or
/// `Pending` (came from processing a block). Prior to Gloas all states are Pending. /// `Pending` (came from processing a block). Prior to Gloas all states are `Pending`.
/// Skipped slots post-Gloas should also use a `Pending` status.
pub fn storage_strategy( pub fn storage_strategy(
&self, &self,
slot: Slot, slot: Slot,

View File

@@ -1657,7 +1657,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
state: &BeaconState<E>, state: &BeaconState<E>,
ops: &mut Vec<KeyValueStoreOp>, ops: &mut Vec<KeyValueStoreOp>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let payload_status = state.payload_status(); let payload_status = state.payload_status_with_skipped_pending();
match self.state_cache.lock().put_state( match self.state_cache.lock().put_state(
*state_root, *state_root,
@@ -1727,7 +1727,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
self, self,
*state_root, *state_root,
state, state,
self.hot_storage_strategy(state.slot(), state.payload_status())?, self.hot_storage_strategy(state.slot(), state.payload_status_with_skipped_pending())?,
)?; )?;
ops.push(hot_state_summary.as_kv_store_op(*state_root)); ops.push(hot_state_summary.as_kv_store_op(*state_root));
Ok(hot_state_summary) Ok(hot_state_summary)
@@ -1740,7 +1740,8 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
ops: &mut Vec<KeyValueStoreOp>, ops: &mut Vec<KeyValueStoreOp>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let slot = state.slot(); let slot = state.slot();
let storage_strategy = self.hot_storage_strategy(slot, state.payload_status())?; let storage_strategy =
self.hot_storage_strategy(slot, state.payload_status_with_skipped_pending())?;
match storage_strategy { match storage_strategy {
StorageStrategy::ReplayFrom(_) => { StorageStrategy::ReplayFrom(_) => {
// Already have persisted the state summary, don't persist anything else // Already have persisted the state summary, don't persist anything else
@@ -1880,6 +1881,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
// summary then we know this summary is for a `Full` block (payload state). // summary then we know this summary is for a `Full` block (payload state).
// NOTE: We treat any and all skipped-slot states as `Pending` by this definition, which is // NOTE: We treat any and all skipped-slot states as `Pending` by this definition, which is
// perhaps a bit strange (they could have a payload most-recently applied). // perhaps a bit strange (they could have a payload most-recently applied).
// TODO(gloas): could maybe simplify this by checking diff_base_slot == slot?
let previous_state_summary = self let previous_state_summary = self
.load_hot_state_summary(&previous_state_root)? .load_hot_state_summary(&previous_state_root)?
.ok_or(Error::MissingHotStateSummary(previous_state_root))?; .ok_or(Error::MissingHotStateSummary(previous_state_root))?;
@@ -2072,7 +2074,9 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
desired_payload_status: StatePayloadStatus, desired_payload_status: StatePayloadStatus,
update_cache: bool, update_cache: bool,
) -> Result<BeaconState<E>, Error> { ) -> Result<BeaconState<E>, Error> {
if base_state.slot() == slot && base_state.payload_status() == desired_payload_status { if base_state.slot() == slot
&& base_state.payload_status_with_skipped_pending() == desired_payload_status
{
return Ok(base_state); return Ok(base_state);
} }
@@ -4163,9 +4167,20 @@ impl HotStateSummary {
// slots where there isn't a skip). // slots where there isn't a skip).
let latest_block_root = state.get_latest_block_root(state_root); let latest_block_root = state.get_latest_block_root(state_root);
// Payload status of the state determines a lot about how it is stored.
let payload_status = state.payload_status_with_skipped_pending();
let get_state_root = |slot| { let get_state_root = |slot| {
if slot == state.slot() { if slot == state.slot() {
// In the case where this state is a `Full` state, use the `state_root` of its
// prior `Pending` state.
if let StatePayloadStatus::Full = payload_status {
// TODO(gloas): change this assert to debug_assert_eq
assert_eq!(state.latest_block_header().slot, state.slot());
Ok(state.latest_block_header().state_root)
} else {
Ok::<_, Error>(state_root) Ok::<_, Error>(state_root)
}
} else { } else {
Ok(get_ancestor_state_root(store, state, slot).map_err(|e| { Ok(get_ancestor_state_root(store, state, slot).map_err(|e| {
Error::StateSummaryIteratorError { Error::StateSummaryIteratorError {
@@ -4184,7 +4199,9 @@ impl HotStateSummary {
OptionalDiffBaseState::Snapshot(0) OptionalDiffBaseState::Snapshot(0)
}; };
let previous_state_root = if state.slot() == 0 { let previous_state_root = if state.slot() == 0
&& let StatePayloadStatus::Pending = payload_status
{
// Set to 0x0 for genesis state to prevent any sort of circular reference. // Set to 0x0 for genesis state to prevent any sort of circular reference.
Hash256::zero() Hash256::zero()
} else { } else {

View File

@@ -1284,6 +1284,25 @@ impl<E: EthSpec> BeaconState<E> {
} }
} }
/// Determine the payload status of this state with all skipped slots considered pending.
///
/// Prior to Gloas this is always `Pending`.
///
/// Post-Gloas, the definition of the `StatePayloadStatus` is:
///
/// - `Full` if this state is the IMMEDIATE result of envelope processing (no skipped slots)
/// - `Pending` if this state is the result of block processing, or slot processing (skipped
/// slot).
pub fn payload_status_with_skipped_pending(&self) -> StatePayloadStatus {
if !self.fork_name_unchecked().gloas_enabled() {
StatePayloadStatus::Pending
} else if self.is_parent_block_full() && self.latest_block_header().slot == self.slot() {
StatePayloadStatus::Full
} else {
StatePayloadStatus::Pending
}
}
/// Return `true` if the validator who produced `slot_signature` is eligible to aggregate. /// Return `true` if the validator who produced `slot_signature` is eligible to aggregate.
/// ///
/// Spec v0.12.1 /// Spec v0.12.1