@@ -5,8 +5,9 @@ use crate::attestation_verification::{
55} ;
66use crate :: attester_cache:: { AttesterCache , AttesterCacheKey } ;
77use crate :: beacon_block_streamer:: { BeaconBlockStreamer , CheckCaches } ;
8- use crate :: beacon_proposer_cache:: BeaconProposerCache ;
9- use crate :: beacon_proposer_cache:: compute_proposer_duties_from_head;
8+ use crate :: beacon_proposer_cache:: {
9+ BeaconProposerCache , EpochBlockProposers , ensure_state_can_determine_proposers_for_epoch,
10+ } ;
1011use crate :: blob_verification:: { GossipBlobError , GossipVerifiedBlob } ;
1112use crate :: block_times_cache:: BlockTimesCache ;
1213use crate :: block_verification:: POS_PANDA_BANNER ;
@@ -4698,65 +4699,54 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
46984699
46994700 // Compute the proposer index.
47004701 let head_epoch = cached_head. head_slot ( ) . epoch ( T :: EthSpec :: slots_per_epoch ( ) ) ;
4701- let shuffling_decision_root = if head_epoch == proposal_epoch {
4702- cached_head
4703- . snapshot
4704- . beacon_state
4705- . proposer_shuffling_decision_root ( proposer_head) ?
4706- } else {
4707- proposer_head
4708- } ;
4709- let cached_proposer = self
4710- . beacon_proposer_cache
4711- . lock ( )
4712- . get_slot :: < T :: EthSpec > ( shuffling_decision_root, proposal_slot) ;
4713- let proposer_index = if let Some ( proposer) = cached_proposer {
4714- proposer. index as u64
4715- } else {
4716- if head_epoch + self . config . sync_tolerance_epochs < proposal_epoch {
4717- warn ! (
4718- msg = "this is a non-critical issue that can happen on unhealthy nodes or \
4719- networks.",
4720- %proposal_epoch,
4721- %head_epoch,
4722- "Skipping proposer preparation"
4723- ) ;
4724-
4725- // Don't skip the head forward more than two epochs. This avoids burdening an
4726- // unhealthy node.
4727- //
4728- // Although this node might miss out on preparing for a proposal, they should still
4729- // be able to propose. This will prioritise beacon chain health over efficient
4730- // packing of execution blocks.
4731- return Ok ( None ) ;
4732- }
4733-
4734- let ( proposers, decision_root, _, fork) =
4735- compute_proposer_duties_from_head ( proposal_epoch, self ) ?;
4736-
4737- let proposer_offset = ( proposal_slot % T :: EthSpec :: slots_per_epoch ( ) ) . as_usize ( ) ;
4738- let proposer = * proposers
4739- . get ( proposer_offset)
4740- . ok_or ( BeaconChainError :: NoProposerForSlot ( proposal_slot) ) ?;
4741-
4742- self . beacon_proposer_cache . lock ( ) . insert (
4743- proposal_epoch,
4744- decision_root,
4745- proposers,
4746- fork,
4747- ) ?;
4702+ let shuffling_decision_root = cached_head
4703+ . snapshot
4704+ . beacon_state
4705+ . proposer_shuffling_decision_root_at_epoch ( proposal_epoch, proposer_head, & self . spec ) ?;
4706+
4707+ let Some ( proposer_index) = self . with_proposer_cache (
4708+ shuffling_decision_root,
4709+ proposal_epoch,
4710+ |proposers| proposers. get_slot :: < T :: EthSpec > ( proposal_slot) . map ( |p| p. index as u64 ) ,
4711+ || {
4712+ if head_epoch + self . config . sync_tolerance_epochs < proposal_epoch {
4713+ warn ! (
4714+ msg = "this is a non-critical issue that can happen on unhealthy nodes or \
4715+ networks",
4716+ %proposal_epoch,
4717+ %head_epoch,
4718+ "Skipping proposer preparation"
4719+ ) ;
47484720
4749- // It's possible that the head changes whilst computing these duties. If so, abandon
4750- // this routine since the change of head would have also spawned another instance of
4751- // this routine.
4752- //
4753- // Exit now, after updating the cache.
4754- if decision_root != shuffling_decision_root {
4755- warn ! ( "Head changed during proposer preparation" ) ;
4756- return Ok ( None ) ;
4721+ // Don't skip the head forward too many epochs. This avoids burdening an
4722+ // unhealthy node.
4723+ //
4724+ // Although this node might miss out on preparing for a proposal, they should
4725+ // still be able to propose. This will prioritise beacon chain health over
4726+ // efficient packing of execution blocks.
4727+ Err ( Error :: SkipProposerPreparation )
4728+ } else {
4729+ let head = self . canonical_head . cached_head ( ) ;
4730+ Ok ( (
4731+ head. head_state_root ( ) ,
4732+ head. snapshot . beacon_state . clone ( ) ,
4733+ ) )
4734+ }
4735+ } ,
4736+ ) . map_or_else ( |e| {
4737+ match e {
4738+ Error :: ProposerCacheIncorrectState { .. } => {
4739+ warn ! ( "Head changed during proposer preparation" ) ;
4740+ Ok ( None )
4741+ }
4742+ Error :: SkipProposerPreparation => {
4743+ // Warning logged for this above.
4744+ Ok ( None )
4745+ }
4746+ e => Err ( e)
47574747 }
4758-
4759- proposer as u64
4748+ } , |value| Ok ( Some ( value ) ) ) ? else {
4749+ return Ok ( None ) ;
47604750 } ;
47614751
47624752 // Get the `prev_randao` and parent block number.
@@ -4916,14 +4906,19 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
49164906
49174907 // Only attempt a re-org if we have a proposer registered for the re-org slot.
49184908 let proposing_at_re_org_slot = {
4919- // The proposer shuffling has the same decision root as the next epoch attestation
4920- // shuffling. We know our re-org block is not on the epoch boundary, so it has the
4921- // same proposer shuffling as the head (but not necessarily the parent which may lie
4922- // in the previous epoch).
4923- let shuffling_decision_root = info
4924- . head_node
4925- . next_epoch_shuffling_id
4926- . shuffling_decision_block ;
4909+ // We know our re-org block is not on the epoch boundary, so it has the same proposer
4910+ // shuffling as the head (but not necessarily the parent which may lie in the previous
4911+ // epoch).
4912+ let shuffling_decision_root = if self
4913+ . spec
4914+ . fork_name_at_slot :: < T :: EthSpec > ( re_org_block_slot)
4915+ . fulu_enabled ( )
4916+ {
4917+ info. head_node . current_epoch_shuffling_id
4918+ } else {
4919+ info. head_node . next_epoch_shuffling_id
4920+ }
4921+ . shuffling_decision_block ;
49274922 let proposer_index = self
49284923 . beacon_proposer_cache
49294924 . lock ( )
@@ -6558,6 +6553,70 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
65586553 }
65596554 }
65606555
6556+ pub fn with_proposer_cache < V , E : From < BeaconChainError > + From < BeaconStateError > > (
6557+ & self ,
6558+ shuffling_decision_block : Hash256 ,
6559+ proposal_epoch : Epoch ,
6560+ accessor : impl Fn ( & EpochBlockProposers ) -> Result < V , BeaconChainError > ,
6561+ state_provider : impl FnOnce ( ) -> Result < ( Hash256 , BeaconState < T :: EthSpec > ) , E > ,
6562+ ) -> Result < V , E > {
6563+ let cache_entry = self
6564+ . beacon_proposer_cache
6565+ . lock ( )
6566+ . get_or_insert_key ( proposal_epoch, shuffling_decision_block) ;
6567+
6568+ // If the cache entry is not initialised, run the code to initialise it inside a OnceCell.
6569+ // This prevents duplication of work across multiple threads.
6570+ //
6571+ // If it is already initialised, then `get_or_try_init` will return immediately without
6572+ // executing the initialisation code at all.
6573+ let epoch_block_proposers = cache_entry. get_or_try_init ( || {
6574+ debug ! (
6575+ ?shuffling_decision_block,
6576+ %proposal_epoch,
6577+ "Proposer shuffling cache miss"
6578+ ) ;
6579+
6580+ // Fetch the state on-demand if the required epoch was missing from the cache.
6581+ // If the caller wants to not compute the state they must return an error here and then
6582+ // catch it at the call site.
6583+ let ( state_root, mut state) = state_provider ( ) ?;
6584+
6585+ // Ensure the state can compute proposer duties for `epoch`.
6586+ ensure_state_can_determine_proposers_for_epoch (
6587+ & mut state,
6588+ state_root,
6589+ proposal_epoch,
6590+ & self . spec ,
6591+ ) ?;
6592+
6593+ // Sanity check the state.
6594+ let latest_block_root = state. get_latest_block_root ( state_root) ;
6595+ let state_decision_block_root = state. proposer_shuffling_decision_root_at_epoch (
6596+ proposal_epoch,
6597+ latest_block_root,
6598+ & self . spec ,
6599+ ) ?;
6600+ if state_decision_block_root != shuffling_decision_block {
6601+ return Err ( Error :: ProposerCacheIncorrectState {
6602+ state_decision_block_root,
6603+ requested_decision_block_root : shuffling_decision_block,
6604+ }
6605+ . into ( ) ) ;
6606+ }
6607+
6608+ let proposers = state. get_beacon_proposer_indices ( proposal_epoch, & self . spec ) ?;
6609+ Ok :: < _ , E > ( EpochBlockProposers :: new (
6610+ proposal_epoch,
6611+ state. fork ( ) ,
6612+ proposers,
6613+ ) )
6614+ } ) ?;
6615+
6616+ // Run the accessor function on the computed epoch proposers.
6617+ accessor ( epoch_block_proposers) . map_err ( Into :: into)
6618+ }
6619+
65616620 /// Runs the `map_fn` with the committee cache for `shuffling_epoch` from the chain with head
65626621 /// `head_block_root`. The `map_fn` will be supplied two values:
65636622 ///
0 commit comments