@@ -5,8 +5,9 @@ use crate::attestation_verification::{
5
5
} ;
6
6
use crate :: attester_cache:: { AttesterCache , AttesterCacheKey } ;
7
7
use 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
+ } ;
10
11
use crate :: blob_verification:: { GossipBlobError , GossipVerifiedBlob } ;
11
12
use crate :: block_times_cache:: BlockTimesCache ;
12
13
use crate :: block_verification:: POS_PANDA_BANNER ;
@@ -4698,65 +4699,54 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
4698
4699
4699
4700
// Compute the proposer index.
4700
4701
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
+ ) ;
4748
4720
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)
4757
4747
}
4758
-
4759
- proposer as u64
4748
+ } , |value| Ok ( Some ( value ) ) ) ? else {
4749
+ return Ok ( None ) ;
4760
4750
} ;
4761
4751
4762
4752
// Get the `prev_randao` and parent block number.
@@ -4916,14 +4906,19 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
4916
4906
4917
4907
// Only attempt a re-org if we have a proposer registered for the re-org slot.
4918
4908
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 ;
4927
4922
let proposer_index = self
4928
4923
. beacon_proposer_cache
4929
4924
. lock ( )
@@ -6558,6 +6553,70 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
6558
6553
}
6559
6554
}
6560
6555
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
+
6561
6620
/// Runs the `map_fn` with the committee cache for `shuffling_epoch` from the chain with head
6562
6621
/// `head_block_root`. The `map_fn` will be supplied two values:
6563
6622
///
0 commit comments