Skip to content

Commit 4908687

Browse files
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 <[email protected]>
1 parent 25832e5 commit 4908687

File tree

6 files changed

+64
-16
lines changed

6 files changed

+64
-16
lines changed

beacon_node/beacon_chain/src/beacon_proposer_cache.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,17 @@ impl BeaconProposerCache {
166166
}
167167

168168
/// Compute the proposer duties using the head state without cache.
169+
///
170+
/// Return:
171+
/// - Proposer indices.
172+
/// - True dependent root.
173+
/// - Legacy dependent root (last block of epoch `N - 1`).
174+
/// - Head execution status.
175+
/// - Fork at `request_epoch`.
169176
pub fn compute_proposer_duties_from_head<T: BeaconChainTypes>(
170177
request_epoch: Epoch,
171178
chain: &BeaconChain<T>,
172-
) -> Result<(Vec<usize>, Hash256, ExecutionStatus, Fork), BeaconChainError> {
179+
) -> Result<(Vec<usize>, Hash256, Hash256, ExecutionStatus, Fork), BeaconChainError> {
173180
// Atomically collect information about the head whilst holding the canonical head `Arc` as
174181
// short as possible.
175182
let (mut state, head_state_root, head_block_root) = {
@@ -203,11 +210,23 @@ pub fn compute_proposer_duties_from_head<T: BeaconChainTypes>(
203210
.proposer_shuffling_decision_root_at_epoch(request_epoch, head_block_root, &chain.spec)
204211
.map_err(BeaconChainError::from)?;
205212

213+
// This is only required because the V1 proposer duties endpoint spec wasn't updated for Fulu. We
214+
// can delete this once the V1 endpoint is deprecated at the Glamsterdam fork.
215+
let legacy_dependent_root = state
216+
.legacy_proposer_shuffling_decision_root_at_epoch(request_epoch, head_block_root)
217+
.map_err(BeaconChainError::from)?;
218+
206219
// Use fork_at_epoch rather than the state's fork, because post-Fulu we may not have advanced
207220
// the state completely into the new epoch.
208221
let fork = chain.spec.fork_at_epoch(request_epoch);
209222

210-
Ok((indices, dependent_root, execution_status, fork))
223+
Ok((
224+
indices,
225+
dependent_root,
226+
legacy_dependent_root,
227+
execution_status,
228+
fork,
229+
))
211230
}
212231

213232
/// If required, advance `state` to the epoch required to determine proposer indices in `target_epoch`.

beacon_node/beacon_chain/tests/store_tests.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1561,7 +1561,7 @@ async fn proposer_duties_from_head_fulu() {
15611561

15621562
// Compute the proposer duties at the next epoch from the head
15631563
let next_epoch = head_state.next_epoch().unwrap();
1564-
let (_indices, dependent_root, _, fork) =
1564+
let (_indices, dependent_root, legacy_dependent_root, _, fork) =
15651565
compute_proposer_duties_from_head(next_epoch, &harness.chain).unwrap();
15661566

15671567
assert_eq!(
@@ -1570,6 +1570,8 @@ async fn proposer_duties_from_head_fulu() {
15701570
.proposer_shuffling_decision_root_at_epoch(next_epoch, head_block_root.into(), spec)
15711571
.unwrap()
15721572
);
1573+
assert_ne!(dependent_root, legacy_dependent_root);
1574+
assert_eq!(legacy_dependent_root, Hash256::from(head_block_root));
15731575
assert_eq!(fork, head_state.fork());
15741576
}
15751577

@@ -1617,7 +1619,7 @@ async fn proposer_lookahead_gloas_fork_epoch() {
16171619
assert_eq!(head_state.current_epoch(), gloas_fork_epoch - 1);
16181620

16191621
// Compute the proposer duties at the fork epoch from the head.
1620-
let (indices, dependent_root, _, fork) =
1622+
let (indices, dependent_root, legacy_dependent_root, _, fork) =
16211623
compute_proposer_duties_from_head(gloas_fork_epoch, &harness.chain).unwrap();
16221624

16231625
assert_eq!(
@@ -1630,6 +1632,7 @@ async fn proposer_lookahead_gloas_fork_epoch() {
16301632
)
16311633
.unwrap()
16321634
);
1635+
assert_ne!(dependent_root, legacy_dependent_root);
16331636
assert_ne!(fork, head_state.fork());
16341637
assert_eq!(fork, spec.fork_at_epoch(gloas_fork_epoch));
16351638

@@ -1639,7 +1642,7 @@ async fn proposer_lookahead_gloas_fork_epoch() {
16391642
.add_attested_blocks_at_slots(head_state, head_state_root, &gloas_slots, &all_validators)
16401643
.await;
16411644

1642-
let (no_lookahead_indices, no_lookahead_dependent_root, _, no_lookahead_fork) =
1645+
let (no_lookahead_indices, no_lookahead_dependent_root, _, _, no_lookahead_fork) =
16431646
compute_proposer_duties_from_head(gloas_fork_epoch, &harness.chain).unwrap();
16441647

16451648
assert_eq!(no_lookahead_indices, indices);

beacon_node/http_api/src/proposer_duties.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ pub fn proposer_duties<T: BeaconChainTypes>(
6060
.safe_add(1)
6161
.map_err(warp_utils::reject::arith_error)?
6262
{
63-
let (proposers, dependent_root, execution_status, _fork) =
63+
let (proposers, _dependent_root, legacy_dependent_root, execution_status, _fork) =
6464
compute_proposer_duties_from_head(request_epoch, chain)
6565
.map_err(warp_utils::reject::unhandled_error)?;
6666
convert_to_api_response(
6767
chain,
6868
request_epoch,
69-
dependent_root,
69+
legacy_dependent_root,
7070
execution_status.is_optimistic_or_invalid(),
7171
proposers,
7272
)
@@ -116,6 +116,11 @@ fn try_proposer_duties_from_cache<T: BeaconChainTypes>(
116116
.beacon_state
117117
.proposer_shuffling_decision_root_at_epoch(request_epoch, head_block_root, &chain.spec)
118118
.map_err(warp_utils::reject::beacon_state_error)?;
119+
let legacy_dependent_root = head
120+
.snapshot
121+
.beacon_state
122+
.legacy_proposer_shuffling_decision_root_at_epoch(request_epoch, head_block_root)
123+
.map_err(warp_utils::reject::beacon_state_error)?;
119124
let execution_optimistic = chain
120125
.is_optimistic_or_invalid_head_block(head_block)
121126
.map_err(warp_utils::reject::unhandled_error)?;
@@ -129,7 +134,7 @@ fn try_proposer_duties_from_cache<T: BeaconChainTypes>(
129134
convert_to_api_response(
130135
chain,
131136
request_epoch,
132-
head_decision_root,
137+
legacy_dependent_root,
133138
execution_optimistic,
134139
indices.to_vec(),
135140
)
@@ -151,7 +156,7 @@ fn compute_and_cache_proposer_duties<T: BeaconChainTypes>(
151156
current_epoch: Epoch,
152157
chain: &BeaconChain<T>,
153158
) -> Result<ApiDuties, warp::reject::Rejection> {
154-
let (indices, dependent_root, execution_status, fork) =
159+
let (indices, dependent_root, legacy_dependent_root, execution_status, fork) =
155160
compute_proposer_duties_from_head(current_epoch, chain)
156161
.map_err(warp_utils::reject::unhandled_error)?;
157162

@@ -166,7 +171,7 @@ fn compute_and_cache_proposer_duties<T: BeaconChainTypes>(
166171
convert_to_api_response(
167172
chain,
168173
current_epoch,
169-
dependent_root,
174+
legacy_dependent_root,
170175
execution_status.is_optimistic_or_invalid(),
171176
indices,
172177
)
@@ -229,12 +234,18 @@ fn compute_historic_proposer_duties<T: BeaconChainTypes>(
229234

230235
// We can supply the genesis block root as the block root since we know that the only block that
231236
// decides its own root is the genesis block.
232-
let dependent_root = state
233-
.proposer_shuffling_decision_root(chain.genesis_block_root, &chain.spec)
237+
let legacy_dependent_root = state
238+
.legacy_proposer_shuffling_decision_root_at_epoch(epoch, chain.genesis_block_root)
234239
.map_err(BeaconChainError::from)
235240
.map_err(warp_utils::reject::unhandled_error)?;
236241

237-
convert_to_api_response(chain, epoch, dependent_root, execution_optimistic, indices)
242+
convert_to_api_response(
243+
chain,
244+
epoch,
245+
legacy_dependent_root,
246+
execution_optimistic,
247+
indices,
248+
)
238249
}
239250

240251
/// Converts the internal representation of proposer duties into one that is compatible with the

beacon_node/http_api/tests/interactive_tests.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,10 +1017,9 @@ async fn proposer_duties_with_gossip_tolerance() {
10171017
assert_eq!(
10181018
proposer_duties_tolerant_current_epoch.dependent_root,
10191019
head_state
1020-
.proposer_shuffling_decision_root_at_epoch(
1020+
.legacy_proposer_shuffling_decision_root_at_epoch(
10211021
tolerant_current_epoch,
10221022
head_block_root,
1023-
spec
10241023
)
10251024
.unwrap()
10261025
);

consensus/types/src/beacon_state.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,22 @@ impl<E: EthSpec> BeaconState<E> {
911911
}
912912
}
913913

914+
/// Returns the block root at the last slot of `epoch - 1`.
915+
///
916+
/// This can be deleted after Glamsterdam and the removal of the v1 proposer duties endpoint.
917+
pub fn legacy_proposer_shuffling_decision_root_at_epoch(
918+
&self,
919+
epoch: Epoch,
920+
head_block_root: Hash256,
921+
) -> Result<Hash256, Error> {
922+
let decision_slot = epoch.saturating_sub(1u64).end_slot(E::slots_per_epoch());
923+
if self.slot() <= decision_slot {
924+
Ok(head_block_root)
925+
} else {
926+
self.get_block_root(decision_slot).copied()
927+
}
928+
}
929+
914930
/// Returns the block root which decided the proposer shuffling for the current epoch. This root
915931
/// can be used to key this proposer shuffling.
916932
///

testing/ef_tests/src/cases/fork_choice.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -920,7 +920,7 @@ impl<E: EthSpec> Tester<E> {
920920
let cached_head = self.harness.chain.canonical_head.cached_head();
921921
let next_slot = cached_head.snapshot.beacon_block.slot() + 1;
922922
let next_slot_epoch = next_slot.epoch(E::slots_per_epoch());
923-
let (proposer_indices, decision_root, _, fork) =
923+
let (proposer_indices, decision_root, _, _, fork) =
924924
compute_proposer_duties_from_head(next_slot_epoch, &self.harness.chain).unwrap();
925925
let proposer_index = proposer_indices[next_slot.as_usize() % E::slots_per_epoch() as usize];
926926

0 commit comments

Comments
 (0)