More proposer shuffling cleanup (#8130)

Addressing more review comments from:

- https://github.com/sigp/lighthouse/pull/8101

I've also tweaked a few more things that I think are minor bugs.


  - Instrument `ensure_state_can_determine_proposers_for_epoch`
- Fix `block_root` usage in `compute_proposer_duties_from_head`. This was a regression introduced in 8101 😬 .
- Update the `state_advance_timer` to prime the next-epoch proposer cache post-Fulu.


Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
Michael Sproul
2025-10-20 14:14:14 +11:00
committed by GitHub
parent 79716f6ec1
commit 2f8587301d
8 changed files with 359 additions and 59 deletions

View File

@@ -4726,6 +4726,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// efficient packing of execution blocks.
Err(Error::SkipProposerPreparation)
} else {
debug!(
?shuffling_decision_root,
epoch = %proposal_epoch,
"Proposer shuffling cache miss for proposer prep"
);
let head = self.canonical_head.cached_head();
Ok((
head.head_state_root(),
@@ -6557,6 +6562,26 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
}
/// This function provides safe and efficient multi-threaded access to the beacon proposer cache.
///
/// The arguments are:
///
/// - `shuffling_decision_block`: The block root of the decision block for the desired proposer
/// shuffling. This should be computed using one of the methods for computing proposer
/// shuffling decision roots, e.g. `BeaconState::proposer_shuffling_decision_root_at_epoch`.
/// - `proposal_epoch`: The epoch at which the proposer shuffling is required.
/// - `accessor`: A closure to run against the proposers for the selected epoch. Usually this
/// closure just grabs a single proposer, or takes the vec of proposers for the epoch.
/// - `state_provider`: A closure to compute a state suitable for determining the shuffling.
/// This closure is evaluated lazily ONLY in the case that a cache miss occurs. It is
/// recommended for code that wants to keep track of cache misses to produce a log and/or
/// increment a metric inside this closure .
///
/// This function makes use of closures in order to efficiently handle concurrent accesses to
/// the cache.
///
/// The error type is polymorphic, if in doubt you can use `BeaconChainError`. You might need
/// to use a turbofish if type inference can't work it out.
pub fn with_proposer_cache<V, E: From<BeaconChainError> + From<BeaconStateError>>(
&self,
shuffling_decision_block: Hash256,
@@ -6575,12 +6600,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// If it is already initialised, then `get_or_try_init` will return immediately without
// executing the initialisation code at all.
let epoch_block_proposers = cache_entry.get_or_try_init(|| {
debug!(
?shuffling_decision_block,
%proposal_epoch,
"Proposer shuffling cache miss"
);
// Fetch the state on-demand if the required epoch was missing from the cache.
// If the caller wants to not compute the state they must return an error here and then
// catch it at the call site.
@@ -6610,11 +6629,18 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
let proposers = state.get_beacon_proposer_indices(proposal_epoch, &self.spec)?;
Ok::<_, E>(EpochBlockProposers::new(
proposal_epoch,
state.fork(),
proposers,
))
// Use fork_at_epoch rather than the state's fork, because post-Fulu we may not have
// advanced the state completely into the new epoch.
let fork = self.spec.fork_at_epoch(proposal_epoch);
debug!(
?shuffling_decision_block,
epoch = %proposal_epoch,
"Priming proposer shuffling cache"
);
Ok::<_, E>(EpochBlockProposers::new(proposal_epoch, fork, proposers))
})?;
// Run the accessor function on the computed epoch proposers.