Proposer duties backwards compat (#8335)

The beacon API spec wasn't updated to use the Fulu definition of `dependent_root` for the proposer duties endpoint. No other client updated their logic, so to retain backwards compatibility the decision has been made to continue using the block root at the end of epoch `N - 1`, and introduce a new v2 endpoint down the track to use the correct dependent root.

Eth R&D discussion: https://discord.com/channels/595666850260713488/598292067260825641/1433036715848765562


  Change the behaviour of the v1 endpoint back to using the last slot of `N - 1` rather than the last slot of `N - 2`. This introduces the possibility of dependent root false positives (the root can change without changing the shuffling), but causes the least compatibility issues with other clients.


Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
Michael Sproul
2025-11-03 19:06:03 +11:00
committed by GitHub
parent 25832e5862
commit 4908687e7d
6 changed files with 64 additions and 16 deletions

View File

@@ -166,10 +166,17 @@ impl BeaconProposerCache {
}
/// Compute the proposer duties using the head state without cache.
///
/// Return:
/// - Proposer indices.
/// - True dependent root.
/// - Legacy dependent root (last block of epoch `N - 1`).
/// - Head execution status.
/// - Fork at `request_epoch`.
pub fn compute_proposer_duties_from_head<T: BeaconChainTypes>(
request_epoch: Epoch,
chain: &BeaconChain<T>,
) -> Result<(Vec<usize>, Hash256, ExecutionStatus, Fork), BeaconChainError> {
) -> Result<(Vec<usize>, Hash256, Hash256, ExecutionStatus, Fork), BeaconChainError> {
// Atomically collect information about the head whilst holding the canonical head `Arc` as
// short as possible.
let (mut state, head_state_root, head_block_root) = {
@@ -203,11 +210,23 @@ pub fn compute_proposer_duties_from_head<T: BeaconChainTypes>(
.proposer_shuffling_decision_root_at_epoch(request_epoch, head_block_root, &chain.spec)
.map_err(BeaconChainError::from)?;
// This is only required because the V1 proposer duties endpoint spec wasn't updated for Fulu. We
// can delete this once the V1 endpoint is deprecated at the Glamsterdam fork.
let legacy_dependent_root = state
.legacy_proposer_shuffling_decision_root_at_epoch(request_epoch, head_block_root)
.map_err(BeaconChainError::from)?;
// Use fork_at_epoch rather than the state's fork, because post-Fulu we may not have advanced
// the state completely into the new epoch.
let fork = chain.spec.fork_at_epoch(request_epoch);
Ok((indices, dependent_root, execution_status, fork))
Ok((
indices,
dependent_root,
legacy_dependent_root,
execution_status,
fork,
))
}
/// If required, advance `state` to the epoch required to determine proposer indices in `target_epoch`.

View File

@@ -1561,7 +1561,7 @@ async fn proposer_duties_from_head_fulu() {
// Compute the proposer duties at the next epoch from the head
let next_epoch = head_state.next_epoch().unwrap();
let (_indices, dependent_root, _, fork) =
let (_indices, dependent_root, legacy_dependent_root, _, fork) =
compute_proposer_duties_from_head(next_epoch, &harness.chain).unwrap();
assert_eq!(
@@ -1570,6 +1570,8 @@ async fn proposer_duties_from_head_fulu() {
.proposer_shuffling_decision_root_at_epoch(next_epoch, head_block_root.into(), spec)
.unwrap()
);
assert_ne!(dependent_root, legacy_dependent_root);
assert_eq!(legacy_dependent_root, Hash256::from(head_block_root));
assert_eq!(fork, head_state.fork());
}
@@ -1617,7 +1619,7 @@ async fn proposer_lookahead_gloas_fork_epoch() {
assert_eq!(head_state.current_epoch(), gloas_fork_epoch - 1);
// Compute the proposer duties at the fork epoch from the head.
let (indices, dependent_root, _, fork) =
let (indices, dependent_root, legacy_dependent_root, _, fork) =
compute_proposer_duties_from_head(gloas_fork_epoch, &harness.chain).unwrap();
assert_eq!(
@@ -1630,6 +1632,7 @@ async fn proposer_lookahead_gloas_fork_epoch() {
)
.unwrap()
);
assert_ne!(dependent_root, legacy_dependent_root);
assert_ne!(fork, head_state.fork());
assert_eq!(fork, spec.fork_at_epoch(gloas_fork_epoch));
@@ -1639,7 +1642,7 @@ async fn proposer_lookahead_gloas_fork_epoch() {
.add_attested_blocks_at_slots(head_state, head_state_root, &gloas_slots, &all_validators)
.await;
let (no_lookahead_indices, no_lookahead_dependent_root, _, no_lookahead_fork) =
let (no_lookahead_indices, no_lookahead_dependent_root, _, _, no_lookahead_fork) =
compute_proposer_duties_from_head(gloas_fork_epoch, &harness.chain).unwrap();
assert_eq!(no_lookahead_indices, indices);