5306 implemented

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Eitan Seri-Levi
2026-06-02 11:12:19 +03:00
parent aed0ddd4c0
commit 2b729482b3
6 changed files with 45 additions and 64 deletions

View File

@@ -4218,12 +4218,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let cached_head = self.canonical_head.cached_head();
let old_head_slot = cached_head.head_slot();
// Compute the expected proposer for `current_slot` on the canonical chain. This is used by
// `on_block` to gate proposer boost on the block's proposer matching the canonical proposer
// (per spec `update_proposer_boost_root` added in v1.7.0-alpha.5).
let canonical_head_proposer_index =
self.canonical_head_proposer_index(current_slot, &cached_head)?;
// Take an upgradable read lock on fork choice so we can check if this block has already
// been imported. We don't want to repeat work importing a block that is already imported.
let fork_choice_reader = self.canonical_head.fork_choice_upgradable_read_lock();
@@ -4255,7 +4249,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
block_delay,
&state,
payload_verification_status,
canonical_head_proposer_index,
&self.spec,
)
.map_err(|e| BlockError::BeaconChainError(Box::new(e.into())))?;
@@ -5003,42 +4996,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}))
}
/// Compute the expected beacon proposer for `slot` on the canonical chain extending `cached_head`.
///
/// Uses the beacon proposer cache to avoid recomputing the shuffling on every block import.
///
/// This is used by `update_proposer_boost_root` to gate proposer boost on the block's proposer
/// matching the canonical proposer, per consensus-specs v1.7.0-alpha.5.
///
/// This function should never error unless there is some corruption of the head state. If a
/// state advance is needed, it will be handled by the proposer cache.
pub fn canonical_head_proposer_index(
&self,
slot: Slot,
cached_head: &CachedHead<T::EthSpec>,
) -> Result<u64, Error> {
let proposal_epoch = slot.epoch(T::EthSpec::slots_per_epoch());
let head_block_root = cached_head.head_block_root();
let head_state = &cached_head.snapshot.beacon_state;
let shuffling_decision_root = head_state.proposer_shuffling_decision_root_at_epoch(
proposal_epoch,
head_block_root,
&self.spec,
)?;
self.with_proposer_cache::<_, Error>(
shuffling_decision_root,
proposal_epoch,
|proposers| {
proposers
.get_slot::<T::EthSpec>(slot)
.map(|p| p.index as u64)
},
|| Ok((cached_head.head_state_root(), head_state.clone())),
)
}
pub fn get_expected_withdrawals(
&self,
forkchoice_update_params: &ForkchoiceUpdateParameters,

View File

@@ -1077,7 +1077,6 @@ async fn invalid_parent() {
Duration::from_secs(0),
&state,
PayloadVerificationStatus::Optimistic,
block.message().proposer_index(),
&rig.harness.chain.spec,
),
Err(ForkChoiceError::ProtoArrayStringError(message))

View File

@@ -621,10 +621,6 @@ mod tests {
.into_available_block();
let current_slot = harness.get_current_slot();
let cached_head = chain.canonical_head.cached_head();
let canonical_head_proposer_index = chain
.canonical_head_proposer_index(current_slot, &cached_head)
.unwrap();
chain
.canonical_head
@@ -636,7 +632,6 @@ mod tests {
Duration::ZERO,
&post_state,
PayloadVerificationStatus::Verified,
canonical_head_proposer_index,
&chain.spec,
)
.unwrap();

View File

@@ -530,6 +530,30 @@ where
}
}
/// Returns the dependent root for `block_root`, per the spec `get_dependent_root` helper.
///
/// The dependent root is the block root at the slot immediately preceding the start of epoch
/// `current_epoch - MIN_SEED_LOOKAHEAD`. Two blocks that share a dependent root descend from
/// the same shuffling, which is the condition used to gate proposer boost (see the spec
/// `update_proposer_boost_root`).
fn get_dependent_root(
&self,
block_root: Hash256,
current_slot: Slot,
spec: &ChainSpec,
) -> Result<Option<Hash256>, Error<T::Error>> {
let epoch = current_slot.epoch(E::slots_per_epoch());
if epoch <= spec.min_seed_lookahead {
// Genesis block parent.
return Ok(Some(Hash256::zero()));
}
let dependent_slot = epoch
.saturating_sub(spec.min_seed_lookahead)
.start_slot(E::slots_per_epoch())
.saturating_sub(1_u64);
self.get_ancestor(block_root, dependent_slot)
}
/// Run the fork choice rule to determine the head.
///
/// ## Specification
@@ -760,7 +784,6 @@ where
block_delay: Duration,
state: &BeaconState<E>,
payload_verification_status: PayloadVerificationStatus,
canonical_head_proposer_index: u64,
spec: &ChainSpec,
) -> Result<(), Error<T::Error>> {
let _timer = metrics::start_timer(&metrics::FORK_CHOICE_ON_BLOCK_TIMES);
@@ -825,19 +848,29 @@ where
let attestation_threshold = spec.get_attestation_due::<E>(block.slot());
// Add proposer score boost if the block is the first timely block for this slot and its
// proposer matches the expected proposer on the canonical chain (per spec
// `update_proposer_boost_root`, introduced in v1.7.0-alpha.5).
// Add proposer score boost if the block is the first timely block for this slot and it
// shares the same dependent root as the canonical chain head (per spec
// `update_proposer_boost_root`).
let is_before_attesting_interval = block_delay < attestation_threshold;
let is_timely = current_slot == block.slot() && is_before_attesting_interval;
let is_first_block = self.fc_store.proposer_boost_root().is_zero();
let is_canonical_proposer = block.proposer_index() == canonical_head_proposer_index;
if current_slot == block.slot()
&& is_before_attesting_interval
&& is_first_block
&& is_canonical_proposer
{
self.fc_store.set_proposer_boost_root(block_root);
if is_timely && is_first_block {
// Compute the head *before* this block is added to fork choice, matching the spec's
// `head = get_head(store)` in `on_block`. This is gated on `is_timely && is_first_block`
// so `get_head` runs at most once per slot.
let (head_root, _) = self.get_head(system_time_current_slot, spec)?;
// The block has not yet been added to proto-array, so resolve its dependent root via
// its parent. The ancestor at the dependent slot (an earlier epoch boundary) is
// identical whether resolved from the block or its parent.
let block_dependent_root =
self.get_dependent_root(block.parent_root(), current_slot, spec)?;
let head_dependent_root = self.get_dependent_root(head_root, current_slot, spec)?;
if block_dependent_root.is_some() && block_dependent_root == head_dependent_root {
self.fc_store.set_proposer_boost_root(block_root);
}
}
// Update store with checkpoints if necessary

View File

@@ -316,7 +316,6 @@ impl ForkChoiceTest {
Duration::from_secs(0),
&state,
PayloadVerificationStatus::Verified,
block.message().proposer_index(),
&self.harness.chain.spec,
)
.unwrap();
@@ -360,7 +359,6 @@ impl ForkChoiceTest {
Duration::from_secs(0),
&state,
PayloadVerificationStatus::Verified,
block.message().proposer_index(),
&self.harness.chain.spec,
)
.expect_err("on_block did not return an error");

View File

@@ -849,7 +849,6 @@ impl<E: EthSpec> Tester<E> {
block_delay,
&state,
PayloadVerificationStatus::Irrelevant,
block.message().proposer_index(),
&self.harness.chain.spec,
);