Update proposer boost calculation (#9215)

Closes:

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


  - Calculate the proposer index on the canonical chain (from canonical head) at `slot` and plumb it through to fork choice so it can be used to determine whether or not to apply the proposer boost. We use the proposer cache to handle state advances and avoid duplicate work.
- Update our FC tests to use `block.message().proposer_index()` (always pass), we are not attempting to test this feature in those tests. The EF tests use the correct canonical proposer idnex via `on_block`, except for invalid blocks which just auto-pass this check (these blocks get rejected by other checks in `on_block` anyway).


Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
Michael Sproul
2026-04-29 22:19:44 +10:00
committed by GitHub
parent 0e427ab77b
commit f406e9c3fb
6 changed files with 79 additions and 33 deletions

View File

@@ -756,6 +756,7 @@ 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);
@@ -820,16 +821,18 @@ where
let attestation_threshold = spec.get_attestation_due::<E>(block.slot());
// Add proposer score boost if the block is timely.
// TODO(gloas): the spec's `update_proposer_boost_root` additionally checks that
// `block.proposer_index == get_beacon_proposer_index(head_state)` — i.e. that
// the block's proposer matches the expected proposer on the canonical chain.
// This requires calling `get_head` and advancing the head state to the current
// slot, which is expensive. Implement once we have a cached proposer index.
// 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).
let is_before_attesting_interval = block_delay < attestation_threshold;
let is_first_block = self.fc_store.proposer_boost_root().is_zero();
if current_slot == block.slot() && is_before_attesting_interval && is_first_block {
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);
}

View File

@@ -316,6 +316,7 @@ impl ForkChoiceTest {
Duration::from_secs(0),
&state,
PayloadVerificationStatus::Verified,
block.message().proposer_index(),
&self.harness.chain.spec,
)
.unwrap();
@@ -359,6 +360,7 @@ 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");