Files
lighthouse/consensus/state_processing/src/upgrade/fulu.rs
Michael Sproul c754234b2c Fix bugs in proposer calculation post-Fulu (#8101)
As identified by a researcher during the Fusaka security competition, we were computing the proposer index incorrectly in some places by computing without lookahead.


  - [x] Add "low level" checks to computation functions in `consensus/types` to ensure they error cleanly
- [x] Re-work the determination of proposer shuffling decision roots, which are now fork aware.
- [x] Re-work and simplify the beacon proposer cache to be fork-aware.
- [x] Optimise `with_proposer_cache` to use `OnceCell`.
- [x] All tests passing.
- [x] Resolve all remaining `FIXME(sproul)`s.
- [x] Unit tests for `ProtoBlock::proposer_shuffling_root_for_child_block`.
- [x] End-to-end regression test.
- [x] Test on pre-Fulu network.
- [x] Test on post-Fulu network.


Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
2025-09-26 14:44:50 +00:00

118 lines
4.8 KiB
Rust

use safe_arith::SafeArith;
use std::mem;
use types::{
BeaconState, BeaconStateError as Error, BeaconStateFulu, ChainSpec, EthSpec, Fork, Vector,
};
/// Transform a `Electra` state into an `Fulu` state.
pub fn upgrade_to_fulu<E: EthSpec>(
pre_state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), Error> {
let _epoch = pre_state.current_epoch();
let post = upgrade_state_to_fulu(pre_state, spec)?;
*pre_state = post;
Ok(())
}
fn initialize_proposer_lookahead<E: EthSpec>(
state: &BeaconState<E>,
spec: &ChainSpec,
) -> Result<Vector<u64, E::ProposerLookaheadSlots>, Error> {
let current_epoch = state.current_epoch();
let mut lookahead = Vec::with_capacity(E::proposer_lookahead_slots());
for i in 0..(spec.min_seed_lookahead.safe_add(1)?.as_u64()) {
let target_epoch = current_epoch.safe_add(i)?;
lookahead.extend(
state
.get_beacon_proposer_indices(target_epoch, spec)
.map(|vec| vec.into_iter().map(|x| x as u64))?,
);
}
Vector::new(lookahead).map_err(|e| e.into())
}
pub fn upgrade_state_to_fulu<E: EthSpec>(
pre_state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<BeaconState<E>, Error> {
let epoch = pre_state.current_epoch();
let proposer_lookahead = initialize_proposer_lookahead(pre_state, spec)?;
let pre = pre_state.as_electra_mut()?;
// Where possible, use something like `mem::take` to move fields from behind the &mut
// reference. For other fields that don't have a good default value, use `clone`.
//
// Fixed size vectors get cloned because replacing them would require the same size
// allocation as cloning.
let post = BeaconState::Fulu(BeaconStateFulu {
// Versioning
genesis_time: pre.genesis_time,
genesis_validators_root: pre.genesis_validators_root,
slot: pre.slot,
fork: Fork {
previous_version: pre.fork.current_version,
current_version: spec.fulu_fork_version,
epoch,
},
// History
latest_block_header: pre.latest_block_header.clone(),
block_roots: pre.block_roots.clone(),
state_roots: pre.state_roots.clone(),
historical_roots: mem::take(&mut pre.historical_roots),
// Eth1
eth1_data: pre.eth1_data.clone(),
eth1_data_votes: mem::take(&mut pre.eth1_data_votes),
eth1_deposit_index: pre.eth1_deposit_index,
// Registry
validators: mem::take(&mut pre.validators),
balances: mem::take(&mut pre.balances),
// Randomness
randao_mixes: pre.randao_mixes.clone(),
// Slashings
slashings: pre.slashings.clone(),
// `Participation
previous_epoch_participation: mem::take(&mut pre.previous_epoch_participation),
current_epoch_participation: mem::take(&mut pre.current_epoch_participation),
// Finality
justification_bits: pre.justification_bits.clone(),
previous_justified_checkpoint: pre.previous_justified_checkpoint,
current_justified_checkpoint: pre.current_justified_checkpoint,
finalized_checkpoint: pre.finalized_checkpoint,
// Inactivity
inactivity_scores: mem::take(&mut pre.inactivity_scores),
// Sync committees
current_sync_committee: pre.current_sync_committee.clone(),
next_sync_committee: pre.next_sync_committee.clone(),
// Execution
latest_execution_payload_header: pre.latest_execution_payload_header.upgrade_to_fulu(),
// Capella
next_withdrawal_index: pre.next_withdrawal_index,
next_withdrawal_validator_index: pre.next_withdrawal_validator_index,
historical_summaries: pre.historical_summaries.clone(),
// Electra
deposit_requests_start_index: pre.deposit_requests_start_index,
deposit_balance_to_consume: pre.deposit_balance_to_consume,
exit_balance_to_consume: pre.exit_balance_to_consume,
earliest_exit_epoch: pre.earliest_exit_epoch,
consolidation_balance_to_consume: pre.consolidation_balance_to_consume,
earliest_consolidation_epoch: pre.earliest_consolidation_epoch,
pending_deposits: pre.pending_deposits.clone(),
pending_partial_withdrawals: pre.pending_partial_withdrawals.clone(),
pending_consolidations: pre.pending_consolidations.clone(),
// Caches
total_active_balance: pre.total_active_balance,
progressive_balances_cache: mem::take(&mut pre.progressive_balances_cache),
committee_caches: mem::take(&mut pre.committee_caches),
pubkey_cache: mem::take(&mut pre.pubkey_cache),
exit_cache: mem::take(&mut pre.exit_cache),
slashings_cache: mem::take(&mut pre.slashings_cache),
epoch_cache: mem::take(&mut pre.epoch_cache),
proposer_lookahead,
});
Ok(post)
}