diff --git a/state-transition/core/validation_deposits.go b/state-transition/core/validation_deposits.go index 1838450efe..63a5da7686 100644 --- a/state-transition/core/validation_deposits.go +++ b/state-transition/core/validation_deposits.go @@ -109,7 +109,7 @@ func ValidateNonGenesisDeposits( //#nosec:G115 // won't overflow in practice. if blkDepositIndex != depositIndex+uint64(i) { return errors.Wrapf(ErrDepositIndexOutOfOrder, - "deposit index: %d, expected index: %d", blkDepositIndex, i, + "deposit index: %d, expected index: %d", blkDepositIndex, depositIndex+uint64(i), ) } diff --git a/state-transition/core/validation_deposits_test.go b/state-transition/core/validation_deposits_test.go index 261113d85d..8e42a78a1e 100644 --- a/state-transition/core/validation_deposits_test.go +++ b/state-transition/core/validation_deposits_test.go @@ -23,6 +23,7 @@ package core_test import ( + "fmt" "testing" "github.com/berachain/beacon-kit/chain" @@ -30,6 +31,7 @@ import ( "github.com/berachain/beacon-kit/consensus-types/types" "github.com/berachain/beacon-kit/primitives/common" "github.com/berachain/beacon-kit/primitives/constants" + "github.com/berachain/beacon-kit/state-transition/core" statetransition "github.com/berachain/beacon-kit/testing/state-transition" "github.com/stretchr/testify/require" ) @@ -253,6 +255,59 @@ func TestLocalDepositsExceedBlockDeposits(t *testing.T) { require.NoError(t, err) } +// TestDepositIndexOutOfOrderErrorMessage verifies that the error message produced by +// ErrDepositIndexOutOfOrder reports the absolute expected index (depositIndex+i), +// not just the loop counter i. This is a regression test for a bug where a chain +// 100 deposits in would report "expected index: 0" instead of "expected index: 100". +// +//nolint:paralleltest // uses envars +func TestDepositIndexOutOfOrderErrorMessage(t *testing.T) { + cs := setupChain(t) + _, st, ds, ctx, _, _ := statetransition.SetupTestState(t, cs) + + credentials0 := types.NewCredentialsFromExecutionAddress(common.ExecutionAddress{}) + + // Simulate a chain that has already processed 5 deposits (indices 0–4). + const processedDeposits = uint64(5) + + // Seed the deposit store with processedDeposits+1 entries so that the length + // check passes when we submit a single-deposit block. + allDeposits := make(types.Deposits, processedDeposits+1) + for i := range allDeposits { + allDeposits[i] = &types.Deposit{ + Pubkey: [48]byte{byte(i)}, + Credentials: credentials0, + Amount: cs.MinActivationBalance(), + Index: uint64(i), + } + } + require.NoError(t, ds.EnqueueDeposits(ctx.ConsensusCtx(), allDeposits)) + + // Advance the state's eth1 deposit index to reflect the already-processed deposits. + require.NoError(t, st.SetEth1DepositIndex(processedDeposits)) + + // Build a deposit whose index is wrong (0 instead of the expected processedDeposits). + wrongDeposit := &types.Deposit{ + Pubkey: [48]byte{0xff}, + Credentials: credentials0, + Amount: cs.MinActivationBalance(), + Index: 0, // should be processedDeposits + } + + err := core.ValidateNonGenesisDeposits( + ctx.ConsensusCtx(), + st, + ds, + cs.MaxDepositsPerBlock(), + types.Deposits{wrongDeposit}, + common.Root{}, // root check is never reached; index check fires first + ) + require.Error(t, err) + require.ErrorIs(t, err, core.ErrDepositIndexOutOfOrder) + // The error must report the absolute expected index, not just the loop counter (0). + require.ErrorContains(t, err, fmt.Sprintf("expected index: %d", processedDeposits)) +} + func TestLocalDepositsExceedBlockDepositsBadRoot(t *testing.T) { t.Parallel() csData := spec.DevnetChainSpecData()