Skip to content

Commit f84abef

Browse files
potuzclaudejames-prysm
authored
Implement EIP-8045: exclude slashed validators from proposer lookahead. (#16857)
Adds a gloas-specific ProcessProposerLookahead that filters out slashed validators from the candidate set when populating the EIP-7917 proposer lookahead, leaving the fulu path unchanged. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
1 parent 81419e2 commit f84abef

27 files changed

Lines changed: 1203 additions & 628 deletions

.ethspecify.yml

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version: v1.7.0-alpha.8
1+
version: v1.7.0-alpha.10
22
style: full
33

44
specrefs:
@@ -35,6 +35,7 @@ exceptions:
3535
# altair
3636
- PARTICIPATION_FLAG_WEIGHTS#altair
3737
# bellatrix
38+
- EMPTY_BLOCK_HASH#bellatrix
3839
- PAYLOAD_STATUS_INVALIDATED#bellatrix
3940
- PAYLOAD_STATUS_NOT_VALIDATED#bellatrix
4041
- PAYLOAD_STATUS_VALID#bellatrix
@@ -122,7 +123,6 @@ exceptions:
122123
- ExecutionPayload#gloas
123124
- ExecutionPayloadBid#gloas
124125
- ExecutionPayloadEnvelope#gloas
125-
- ForkChoiceNode#gloas
126126
- IndexedPayloadAttestation#gloas
127127
- LightClientHeader#gloas
128128
- PartialDataColumnGroupID#gloas
@@ -138,6 +138,7 @@ exceptions:
138138
dataclasses:
139139
# phase0
140140
- FastConfirmationStore#phase0
141+
- ForkChoiceNode#phase0
141142
- LatestMessage#phase0
142143
- Seen#phase0
143144
- Store#phase0
@@ -157,10 +158,13 @@ exceptions:
157158
- Seen#electra
158159
# fulu
159160
- BlobParameters#fulu
161+
- Seen#fulu
160162
# gloas
161163
- ExpectedWithdrawals#gloas
164+
- ForkChoiceNode#gloas
162165
- LatestMessage#gloas
163166
- PayloadAttributes#gloas
167+
- Seen#gloas
164168
- Store#gloas
165169
# heze
166170
- GetInclusionListResponse#heze
@@ -240,14 +244,18 @@ exceptions:
240244
- get_current_balance_source#phase0
241245
- get_current_target#phase0
242246
- get_current_target_score#phase0
247+
- get_dependent_root#phase0
243248
- get_equivocation_score#phase0
244249
- get_fast_confirmation_store#phase0
245250
- get_latest_confirmed#phase0
246251
- get_latest_message_epoch#phase0
252+
- get_node_children#phase0
253+
- get_node_for_root#phase0
247254
- get_previous_balance_source#phase0
248255
- get_pulled_up_head_state#phase0
249256
- get_slot_committee#phase0
250257
- get_support_discount#phase0
258+
- get_supported_node#phase0
251259
- is_ancestor#phase0
252260
- is_confirmed_chain_safe#phase0
253261
- is_full_validator_set_covered#phase0
@@ -347,6 +355,7 @@ exceptions:
347355
- validate_sync_committee_message_gossip#altair
348356
# bellatrix
349357
- get_execution_payload#bellatrix
358+
- get_safe_execution_block_hash#bellatrix
350359
- is_merge_transition_block#bellatrix
351360
- is_optimistic_candidate_block#bellatrix
352361
- latest_verified_ancestor#bellatrix
@@ -421,6 +430,13 @@ exceptions:
421430
- get_data_column_sidecars_from_block#fulu
422431
- get_data_column_sidecars_from_column_sidecar#fulu
423432
- recover_matrix#fulu
433+
- process_block#fulu
434+
- process_deposit_request#fulu
435+
- process_operations#fulu
436+
- process_pending_deposits#fulu
437+
- validate_beacon_block_gossip#fulu
438+
- validate_data_column_sidecar_gossip#fulu
439+
- validate_partial_data_column_sidecar_gossip#fulu
424440
# gloas
425441
- get_latest_message_epoch#gloas
426442
- get_payload_due_ms#gloas
@@ -454,9 +470,15 @@ exceptions:
454470
- get_forkchoice_store#gloas
455471
- get_head#gloas
456472
- get_indexed_payload_attestation#gloas
473+
- get_dependent_root#gloas
457474
- get_next_sync_committee_indices#gloas
458475
- get_node_children#gloas
476+
- get_node_for_root#gloas
459477
- get_parent_payload_status#gloas
478+
- get_safe_execution_block_hash#gloas
479+
- get_supported_node#gloas
480+
- is_ancestor#gloas
481+
- is_previous_slot_payload_decision#gloas
460482
- get_payload_attestation_due_ms#gloas
461483
- get_payload_attestation_message_signature#gloas
462484
- get_payload_status_tiebreaker#gloas
@@ -470,7 +492,6 @@ exceptions:
470492
- is_builder_withdrawal_credential#gloas
471493
- is_merge_transition_complete#gloas
472494
- is_parent_node_full#gloas
473-
- is_supporting_vote#gloas
474495
- is_valid_indexed_payload_attestation#gloas
475496
- notify_ptc_messages#gloas
476497
- on_block#gloas

WORKSPACE

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,16 +273,16 @@ filegroup(
273273
url = "https://github.com/ethereum/EIPs/archive/5480440fe51742ed23342b68cf106cefd427e39d.tar.gz",
274274
)
275275

276-
consensus_spec_version = "v1.7.0-alpha.8"
276+
consensus_spec_version = "v1.7.0-alpha.10"
277277

278278
load("@prysm//tools:download_spectests.bzl", "consensus_spec_tests")
279279

280280
consensus_spec_tests(
281281
name = "consensus_spec_tests",
282282
flavors = {
283283
"general": "sha256-szDpBVO2Ebi8/bwbiWFpW6H4c5gxnpU3hAUS31AF02E=",
284-
"minimal": "sha256-SBEdtQ+HwaxFCuPwzcvkJazRuur6LlMol3egANCwH4Y=",
285-
"mainnet": "sha256-alrKgbLxWFRNb8/jLInQ0eJru5ScAWnxM0rEOzdm/YE=",
284+
"minimal": "sha256-WUEeO8e2eyl8vvN/oFvmr3gnBbLeJHtcctfxqtH0DZg=",
285+
"mainnet": "sha256-F8jPmN/5cnKlCJvrGa90yWFE7NlQNl8dViehMcLc7GY=",
286286
},
287287
version = consensus_spec_version,
288288
)
@@ -298,7 +298,7 @@ filegroup(
298298
visibility = ["//visibility:public"],
299299
)
300300
""",
301-
integrity = "sha256-x0OkYCK+MJfPoEAnEmpftgl60ervC4W3zCg0KA9XiXU=",
301+
integrity = "sha256-a3naXiY2eXKGLBoAPetHfgKq98/vO6SI1xueoNCZnYQ=",
302302
strip_prefix = "consensus-specs-" + consensus_spec_version[1:],
303303
url = "https://github.com/ethereum/consensus-specs/archive/refs/tags/%s.tar.gz" % consensus_spec_version,
304304
)

beacon-chain/core/gloas/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ go_library(
1313
"payload.go",
1414
"payload_attestation.go",
1515
"pending_payment.go",
16+
"proposer_lookahead.go",
1617
"proposer_slashing.go",
1718
"upgrade.go",
1819
"withdrawals.go",
@@ -57,6 +58,7 @@ go_test(
5758
"payload_attestation_test.go",
5859
"payload_test.go",
5960
"pending_payment_test.go",
61+
"proposer_lookahead_test.go",
6062
"proposer_slashing_test.go",
6163
"upgrade_test.go",
6264
"withdrawals_test.go",

beacon-chain/core/gloas/payload_attestation.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ func selectByBalanceFill(
280280

281281
// validIndexedPayloadAttestation verifies the signature of an indexed payload attestation.
282282
//
283-
// <spec fn="is_valid_indexed_payload_attestation" fork="gloas" hash="d76e0f89">
283+
// <spec fn="is_valid_indexed_payload_attestation" fork="gloas" hash="745c04a1">
284284
// def is_valid_indexed_payload_attestation(
285285
// state: BeaconState, attestation: IndexedPayloadAttestation
286286
// ) -> bool:
@@ -290,7 +290,7 @@ func selectByBalanceFill(
290290
// """
291291
// # Verify indices are non-empty and sorted
292292
// indices = attestation.attesting_indices
293-
// if len(indices) == 0 or not indices == sorted(indices):
293+
// if len(indices) == 0 or indices != sorted(indices):
294294
// return False
295295
//
296296
// # Verify aggregate signature
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package gloas
2+
3+
import (
4+
"context"
5+
6+
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
7+
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
8+
"github.com/OffchainLabs/prysm/v7/config/params"
9+
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
10+
"github.com/OffchainLabs/prysm/v7/time/slots"
11+
"github.com/pkg/errors"
12+
)
13+
14+
// ProcessProposerLookahead advances the cached proposer lookahead by one epoch
15+
// using EIP-8045 semantics: slashed validators are excluded from the candidate
16+
// pool used to derive the new last-epoch proposer indices.
17+
func ProcessProposerLookahead(ctx context.Context, state state.BeaconState) error {
18+
_, span := trace.StartSpan(ctx, "gloas.processProposerLookahead")
19+
defer span.End()
20+
21+
if state == nil || state.IsNil() {
22+
return errors.New("nil state")
23+
}
24+
25+
lookAhead, err := state.ProposerLookahead()
26+
if err != nil {
27+
return errors.Wrap(err, "could not get proposer lookahead")
28+
}
29+
lastEpochStart := len(lookAhead) - int(params.BeaconConfig().SlotsPerEpoch)
30+
copy(lookAhead[:lastEpochStart], lookAhead[params.BeaconConfig().SlotsPerEpoch:])
31+
lastEpoch := slots.ToEpoch(state.Slot()).AddEpoch(params.BeaconConfig().MinSeedLookahead).Add(1)
32+
indices, err := helpers.ActiveNonSlashedValidatorIndices(ctx, state, lastEpoch)
33+
if err != nil {
34+
return err
35+
}
36+
lastEpochProposers, err := helpers.PrecomputeProposerIndices(state, indices, lastEpoch)
37+
if err != nil {
38+
return errors.Wrap(err, "could not precompute proposer indices")
39+
}
40+
copy(lookAhead[lastEpochStart:], lastEpochProposers)
41+
return state.SetProposerLookahead(lookAhead)
42+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package gloas_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
7+
"github.com/OffchainLabs/prysm/v7/config/params"
8+
"github.com/OffchainLabs/prysm/v7/testing/require"
9+
"github.com/OffchainLabs/prysm/v7/testing/util"
10+
)
11+
12+
// Verifies the EIP-8045 filter: any validator marked slashed when the last
13+
// lookahead epoch is computed must not appear in that epoch's proposer slots.
14+
func TestProcessProposerLookahead_ExcludesSlashedValidators(t *testing.T) {
15+
ctx := t.Context()
16+
17+
st, _ := util.DeterministicGenesisStateGloas(t, 256)
18+
19+
slotsPerEpoch := uint64(params.BeaconConfig().SlotsPerEpoch)
20+
lookaheadSize := (uint64(params.BeaconConfig().MinSeedLookahead) + 1) * slotsPerEpoch
21+
lastEpochStart := lookaheadSize - slotsPerEpoch
22+
23+
// Baseline: take the unfiltered last-epoch lookahead so we can pick a
24+
// validator that is guaranteed to be selected, then verify the filtered
25+
// version drops it.
26+
baseline := st.Copy()
27+
require.NoError(t, gloas.ProcessProposerLookahead(ctx, baseline))
28+
baseLookahead, err := baseline.ProposerLookahead()
29+
require.NoError(t, err)
30+
31+
target := baseLookahead[lastEpochStart]
32+
validators := st.Validators()
33+
validators[target].Slashed = true
34+
require.NoError(t, st.SetValidators(validators))
35+
36+
require.NoError(t, gloas.ProcessProposerLookahead(ctx, st))
37+
filtered, err := st.ProposerLookahead()
38+
require.NoError(t, err)
39+
40+
for i := lastEpochStart; i < lookaheadSize; i++ {
41+
require.NotEqual(t, target, filtered[i],
42+
"slashed validator %d still selected at lookahead slot %d", target, i)
43+
}
44+
45+
// And confirm the filter actually changed something at the target slot.
46+
require.NotEqual(t, baseLookahead[lastEpochStart], filtered[lastEpochStart])
47+
}

beacon-chain/core/helpers/validators.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,27 @@ func ActiveValidatorIndices(ctx context.Context, s state.ReadOnlyBeaconState, ep
126126
return indices, nil
127127
}
128128

129+
// ActiveNonSlashedValidatorIndices returns the indices of validators that are
130+
// both active at “epoch“ and not slashed. It is used to build the
131+
// EIP-8045 proposer lookahead.
132+
//
133+
// Spec pseudocode definition (EIP-8045):
134+
//
135+
// indices = [i for i in get_active_validator_indices(state, epoch)
136+
// if not state.validators[i].slashed]
137+
func ActiveNonSlashedValidatorIndices(ctx context.Context, s state.ReadOnlyBeaconState, epoch primitives.Epoch) ([]primitives.ValidatorIndex, error) {
138+
_, span := trace.StartSpan(ctx, "helpers.ActiveNonSlashedValidatorIndices")
139+
defer span.End()
140+
141+
var indices []primitives.ValidatorIndex
142+
for idx, val := range s.ValidatorsReadOnlySeq() {
143+
if IsActiveNonSlashedValidatorUsingTrie(val, epoch) {
144+
indices = append(indices, idx)
145+
}
146+
}
147+
return indices, nil
148+
}
149+
129150
// ActiveValidatorCount returns the number of active validators in the state
130151
// at the given epoch.
131152
func ActiveValidatorCount(ctx context.Context, s state.ReadOnlyBeaconState, epoch primitives.Epoch) (uint64, error) {

beacon-chain/core/requests/deposits.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
1010
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
1111
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
12+
"github.com/OffchainLabs/prysm/v7/runtime/version"
1213
"github.com/pkg/errors"
1314
)
1415

@@ -48,16 +49,20 @@ func ProcessDepositRequests(ctx context.Context, beaconState state.BeaconState,
4849
// slot=state.slot,
4950
// ))
5051
func processDepositRequest(beaconState state.BeaconState, req *enginev1.DepositRequest) (state.BeaconState, error) {
51-
requestsStartIndex, err := beaconState.DepositRequestsStartIndex()
52-
if err != nil {
53-
return nil, errors.Wrap(err, "could not get deposit requests start index")
54-
}
5552
if req == nil {
5653
return nil, errors.New("nil deposit request")
5754
}
58-
if requestsStartIndex == params.BeaconConfig().UnsetDepositRequestsStartIndex {
59-
if err := beaconState.SetDepositRequestsStartIndex(req.Index); err != nil {
60-
return nil, errors.Wrap(err, "could not set deposit requests start index")
55+
// [Modified in Fulu] The former deposit mechanism is removed, so the deposit
56+
// requests start index is no longer set from Fulu onward.
57+
if beaconState.Version() < version.Fulu {
58+
requestsStartIndex, err := beaconState.DepositRequestsStartIndex()
59+
if err != nil {
60+
return nil, errors.Wrap(err, "could not get deposit requests start index")
61+
}
62+
if requestsStartIndex == params.BeaconConfig().UnsetDepositRequestsStartIndex {
63+
if err := beaconState.SetDepositRequestsStartIndex(req.Index); err != nil {
64+
return nil, errors.Wrap(err, "could not set deposit requests start index")
65+
}
6166
}
6267
}
6368
if err := beaconState.AppendPendingDeposit(&ethpb.PendingDeposit{

beacon-chain/core/transition/gloas.go

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
77
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/electra"
88
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/epoch/precompute"
9-
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/fulu"
109
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
1110
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
1211
v "github.com/OffchainLabs/prysm/v7/beacon-chain/core/validators"
@@ -21,18 +20,9 @@ import (
2120
//
2221
// Spec definition:
2322
//
24-
// <spec fn="process_operations" fork="gloas" hash="05a7a4ea">
23+
// <spec fn="process_operations" fork="gloas" hash="e0633745">
2524
// def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
26-
// # Disable former deposit mechanism once all prior deposits are processed
27-
// eth1_deposit_index_limit = min(
28-
// state.eth1_data.deposit_count, state.deposit_requests_start_index
29-
// )
30-
// if state.eth1_deposit_index < eth1_deposit_index_limit:
31-
// assert len(body.deposits) == min(
32-
// MAX_DEPOSITS, eth1_deposit_index_limit - state.eth1_deposit_index
33-
// )
34-
// else:
35-
// assert len(body.deposits) == 0
25+
// assert len(body.deposits) == 0
3626
//
3727
// def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
3828
// for operation in operations:
@@ -43,7 +33,6 @@ import (
4333
// for_ops(body.attester_slashings, process_attester_slashing)
4434
// # [Modified in Gloas:EIP7732]
4535
// for_ops(body.attestations, process_attestation)
46-
// for_ops(body.deposits, process_deposit)
4736
// # [Modified in Gloas:EIP7732]
4837
// for_ops(body.voluntary_exits, process_voluntary_exit)
4938
// for_ops(body.bls_to_execution_changes, process_bls_to_execution_change)
@@ -200,7 +189,7 @@ func processEpochGloas(ctx context.Context, state state.BeaconState) error {
200189
if err != nil {
201190
return err
202191
}
203-
if err := fulu.ProcessProposerLookahead(ctx, state); err != nil {
192+
if err := gloas.ProcessProposerLookahead(ctx, state); err != nil {
204193
return err
205194
}
206195
return gloas.ProcessPTCWindow(ctx, state)

beacon-chain/verification/execution_payload_bid.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,14 @@ func (v *BidVerifier) VerifyGasLimitTargetCompatible(parentGasLimit, targetGasLi
132132
// isGasLimitTargetCompatible reports whether gasLimit is compatible with
133133
// targetGasLimit under the EIP-1559 transition rule from parentGasLimit.
134134
//
135-
// <spec fn="is_gas_limit_target_compatible" fork="gloas">
135+
// <spec fn="is_gas_limit_target_compatible" fork="gloas" hash="3fa22023">
136136
// def is_gas_limit_target_compatible(
137137
// parent_gas_limit: uint64, gas_limit: uint64, target_gas_limit: uint64
138138
// ) -> bool:
139+
// """
140+
// Check if ``gas_limit`` is compatible with ``target_gas_limit`` under the
141+
// EIP-1559 transition rule from ``parent_gas_limit``.
142+
// """
139143
// max_gas_limit_difference = max(parent_gas_limit // 1024, 1) - 1
140144
// min_gas_limit = parent_gas_limit - max_gas_limit_difference
141145
// max_gas_limit = parent_gas_limit + max_gas_limit_difference

0 commit comments

Comments
 (0)