Block proposal optimisations (#8156)

Closes:

- https://github.com/sigp/lighthouse/issues/4412

This should reduce Lighthouse's block proposal times on Holesky and prevent us getting reorged.


  - [x] Allow the head state to be advanced further than 1 slot. This lets us avoid epoch processing on hot paths including block production, by having new epoch boundaries pre-computed and available in the state cache.
- [x] Use the finalized state to prune the op pool. We were previously using the head state and trying to infer slashing/exit relevance based on `exit_epoch`. However some exit epochs are far in the future, despite occurring recently.


Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
Michael Sproul
2025-10-08 17:09:12 +11:00
committed by GitHub
parent 2a433bc406
commit 13dfa9200f
4 changed files with 53 additions and 72 deletions

View File

@@ -937,13 +937,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.execution_status
.is_optimistic_or_invalid();
self.op_pool.prune_all(
&new_snapshot.beacon_block,
&new_snapshot.beacon_state,
self.epoch()?,
&self.spec,
);
self.observed_block_producers.write().prune(
new_view
.finalized_checkpoint
@@ -982,9 +975,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}));
}
// The store migration task requires the *state at the slot of the finalized epoch*,
// rather than the state of the latest finalized block. These two values will only
// differ when the first slot of the finalized epoch is a skip slot.
// The store migration task and op pool pruning require the *state at the first slot of the
// finalized epoch*, rather than the state of the latest finalized block. These two values
// will only differ when the first slot of the finalized epoch is a skip slot.
//
// Use the `StateRootsIterator` directly rather than `BeaconChain::state_root_at_slot`
// to ensure we use the same state that we just set as the head.
@@ -1006,6 +999,23 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
)?
.ok_or(Error::MissingFinalizedStateRoot(new_finalized_slot))?;
let update_cache = true;
let new_finalized_state = self
.store
.get_hot_state(&new_finalized_state_root, update_cache)?
.ok_or(Error::MissingBeaconState(new_finalized_state_root))?;
self.op_pool.prune_all(
&new_snapshot.beacon_block,
&new_snapshot.beacon_state,
&new_finalized_state,
self.epoch()?,
&self.spec,
);
// We just pass the state root to the finalization thread. It should be able to reload the
// state from the state_cache near instantly anyway. We could experiment with sending the
// state over a channel in future, but it's probably no quicker.
self.store_migrator.process_finalization(
new_finalized_state_root.into(),
new_view.finalized_checkpoint,