From 3def16caaa6063eb6b026effdb6a9a976aeb666e Mon Sep 17 00:00:00 2001 From: Potuz Date: Wed, 16 Jul 2025 00:16:11 -0300 Subject: [PATCH] Fix InitializeProposerLookahead (#15450) * Fix InitializeProposerLookahead Get the right Active validator indices for each epoch after the fork transition. Co-Authored-By: Claude * Add test Co-Authored-By: Claude --------- Co-authored-by: Claude Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com> --- beacon-chain/core/helpers/beacon_committee.go | 8 ++-- .../core/helpers/beacon_committee_test.go | 43 +++++++++++++++++++ changelog/potuz_fix_initialize_lookahead.md | 2 + 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 changelog/potuz_fix_initialize_lookahead.md diff --git a/beacon-chain/core/helpers/beacon_committee.go b/beacon-chain/core/helpers/beacon_committee.go index a7f282811d..16f734bbd8 100644 --- a/beacon-chain/core/helpers/beacon_committee.go +++ b/beacon-chain/core/helpers/beacon_committee.go @@ -669,11 +669,11 @@ func ComputeCommittee( // InitializeProposerLookahead computes the list of the proposer indices for the next MIN_SEED_LOOKAHEAD + 1 epochs. func InitializeProposerLookahead(ctx context.Context, state state.ReadOnlyBeaconState, epoch primitives.Epoch) ([]uint64, error) { lookAhead := make([]uint64, 0, uint64(params.BeaconConfig().MinSeedLookahead+1)*uint64(params.BeaconConfig().SlotsPerEpoch)) - indices, err := ActiveValidatorIndices(ctx, state, epoch) - if err != nil { - return nil, errors.Wrap(err, "could not get active indices") - } for i := range params.BeaconConfig().MinSeedLookahead + 1 { + indices, err := ActiveValidatorIndices(ctx, state, epoch+i) + if err != nil { + return nil, errors.Wrap(err, "could not get active indices") + } proposerIndices, err := PrecomputeProposerIndices(state, indices, epoch+i) if err != nil { return nil, errors.Wrap(err, "could not compute proposer indices") diff --git a/beacon-chain/core/helpers/beacon_committee_test.go b/beacon-chain/core/helpers/beacon_committee_test.go index 86ede9fcb0..d168e8e8ab 100644 --- a/beacon-chain/core/helpers/beacon_committee_test.go +++ b/beacon-chain/core/helpers/beacon_committee_test.go @@ -916,3 +916,46 @@ func TestAssignmentForValidator(t *testing.T) { require.DeepEqual(t, &helpers.LiteAssignment{}, got) }) } + +// Regression for #15450 +func TestInitializeProposerLookahead_RegressionTest(t *testing.T) { + ctx := t.Context() + + state, _ := util.DeterministicGenesisState(t, 128) + // Set some validators to activate in epoch 3 instead of 0 + validators := state.Validators() + for i := 64; i < 128; i++ { + validators[i].ActivationEpoch = 3 + } + require.NoError(t, state.SetValidators(validators)) + require.NoError(t, state.SetSlot(64)) // epoch 2 + epoch := slots.ToEpoch(state.Slot()) + + proposerLookahead, err := helpers.InitializeProposerLookahead(ctx, state, epoch) + require.NoError(t, err) + slotsPerEpoch := int(params.BeaconConfig().SlotsPerEpoch) + for epochOffset := primitives.Epoch(0); epochOffset < 2; epochOffset++ { + targetEpoch := epoch + epochOffset + + activeIndices, err := helpers.ActiveValidatorIndices(ctx, state, targetEpoch) + require.NoError(t, err) + + expectedProposers, err := helpers.PrecomputeProposerIndices(state, activeIndices, targetEpoch) + require.NoError(t, err) + + startIdx := int(epochOffset) * slotsPerEpoch + endIdx := startIdx + slotsPerEpoch + actualProposers := proposerLookahead[startIdx:endIdx] + + expectedUint64 := make([]uint64, len(expectedProposers)) + for i, proposer := range expectedProposers { + expectedUint64[i] = uint64(proposer) + } + + // This assertion would fail with the original bug: + for i, expected := range expectedUint64 { + require.Equal(t, expected, actualProposers[i], + "Proposer index mismatch at slot %d in epoch %d", i, targetEpoch) + } + } +} diff --git a/changelog/potuz_fix_initialize_lookahead.md b/changelog/potuz_fix_initialize_lookahead.md new file mode 100644 index 0000000000..17e67b7ada --- /dev/null +++ b/changelog/potuz_fix_initialize_lookahead.md @@ -0,0 +1,2 @@ +### Fixed +- Fixed lookahead initialization at the fulu fork.