From ee3ad6e9931216bae2a753e70cb3e08d1c3b7415 Mon Sep 17 00:00:00 2001 From: Preston Van Loon Date: Wed, 8 May 2024 11:13:20 -0500 Subject: [PATCH] Consolidate unit tests + spectests TODO: Add godocs --- beacon-chain/core/electra/BUILD.bazel | 6 + beacon-chain/core/electra/consolidations.go | 48 +- .../core/electra/consolidations_test.go | 444 ++++++++++++++++++ .../electra/epoch_processing/BUILD.bazel | 1 + .../pending_consolidations_test.go | 11 + .../mainnet/electra/operations/BUILD.bazel | 1 + .../electra/operations/consolidation_test.go | 11 + .../electra/epoch_processing/BUILD.bazel | 1 + .../pending_consolidations_test.go | 11 + .../minimal/electra/operations/BUILD.bazel | 1 + .../electra/operations/consolidation_test.go | 11 + .../electra/epoch_processing/BUILD.bazel | 1 + .../pending_consolidations.go | 28 ++ .../shared/electra/operations/BUILD.bazel | 1 + .../electra/operations/consolidations.go | 48 ++ 15 files changed, 606 insertions(+), 18 deletions(-) create mode 100644 beacon-chain/core/electra/consolidations_test.go create mode 100644 testing/spectest/mainnet/electra/epoch_processing/pending_consolidations_test.go create mode 100644 testing/spectest/mainnet/electra/operations/consolidation_test.go create mode 100644 testing/spectest/minimal/electra/epoch_processing/pending_consolidations_test.go create mode 100644 testing/spectest/minimal/electra/operations/consolidation_test.go create mode 100644 testing/spectest/shared/electra/epoch_processing/pending_consolidations.go create mode 100644 testing/spectest/shared/electra/operations/consolidations.go diff --git a/beacon-chain/core/electra/BUILD.bazel b/beacon-chain/core/electra/BUILD.bazel index 7c3c9e04dc67..acb1f7dceded 100644 --- a/beacon-chain/core/electra/BUILD.bazel +++ b/beacon-chain/core/electra/BUILD.bazel @@ -36,21 +36,27 @@ go_test( name = "go_default_test", srcs = [ "churn_test.go", + "consolidations_test.go", "upgrade_test.go", "validator_test.go", ], deps = [ ":go_default_library", "//beacon-chain/core/helpers:go_default_library", + "//beacon-chain/core/signing:go_default_library", "//beacon-chain/core/time:go_default_library", "//beacon-chain/state:go_default_library", "//beacon-chain/state/state-native:go_default_library", + "//config/fieldparams:go_default_library", "//config/params:go_default_library", "//consensus-types/primitives:go_default_library", + "//crypto/bls/blst:go_default_library", + "//crypto/bls/common:go_default_library", "//encoding/bytesutil:go_default_library", "//math:go_default_library", "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//runtime/interop:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", "//time/slots:go_default_library", diff --git a/beacon-chain/core/electra/consolidations.go b/beacon-chain/core/electra/consolidations.go index 98fe0e968175..9808982bb746 100644 --- a/beacon-chain/core/electra/consolidations.go +++ b/beacon-chain/core/electra/consolidations.go @@ -15,9 +15,8 @@ import ( "go.opencensus.io/trace" ) -var ErrNilConsolidations = errors.New("nil consolidations") - -// ProcessPendingConsolidations -- +// ProcessPendingConsolidations implements the spec definition below. This method makes mutating +// calls to the beacon state. // // Spec definition: // @@ -40,7 +39,7 @@ var ErrNilConsolidations = errors.New("nil consolidations") // next_pending_consolidation += 1 // // state.pending_consolidations = state.pending_consolidations[next_pending_consolidation:] -func ProcessPendingConsolidations(ctx context.Context, st state.BeaconState, activeBalance uint64) (state.BeaconState, error) { +func ProcessPendingConsolidations(ctx context.Context, st state.BeaconState) (state.BeaconState, error) { ctx, span := trace.StartSpan(ctx, "electra.ProcessPendingConsolidations") defer span.End() @@ -48,6 +47,8 @@ func ProcessPendingConsolidations(ctx context.Context, st state.BeaconState, act return nil, errors.New("nil state") } + currentEpoch := slots.ToEpoch(st.Slot()) + var nextPendingConsolidation uint64 pendingConsolidations, err := st.PendingConsolidations() if err != nil { @@ -62,7 +63,7 @@ func ProcessPendingConsolidations(ctx context.Context, st state.BeaconState, act nextPendingConsolidation++ continue } - if sourceValidator.WithdrawableEpoch > slots.ToEpoch(st.Slot()) { + if sourceValidator.WithdrawableEpoch > currentEpoch { break } @@ -70,6 +71,10 @@ func ProcessPendingConsolidations(ctx context.Context, st state.BeaconState, act return nil, err } + activeBalance, err := st.ActiveBalanceAtIndex(pc.SourceIndex) + if err != nil { + return nil, err + } if err := helpers.DecreaseBalance(st, pc.SourceIndex, activeBalance); err != nil { return nil, err } @@ -79,15 +84,18 @@ func ProcessPendingConsolidations(ctx context.Context, st state.BeaconState, act nextPendingConsolidation++ } - // TODO: Check OOB - if err := st.SetPendingConsolidations(pendingConsolidations[nextPendingConsolidation:]); err != nil { - return nil, err + if nextPendingConsolidation > 0 { + // TODO: Check OOB + if err := st.SetPendingConsolidations(pendingConsolidations[nextPendingConsolidation:]); err != nil { + return nil, err + } } return st, nil } -// ProcessConsolidations -- +// ProcessConsolidations implements the spec definition below. This method makes mutating calls to +// the beacon state. // // Spec definition: // @@ -141,11 +149,21 @@ func ProcessConsolidations(ctx context.Context, st state.BeaconState, cs []*ethp if st == nil || st.IsNil() { return nil, errors.New("nil state") } - if cs == nil { - return nil, ErrNilConsolidations + + if len(cs) == 0 { + return st, nil // Nothing to process. + } + + domain, err := signing.ComputeDomain( + params.BeaconConfig().DomainConsolidation, + st.Fork().CurrentVersion, + st.GenesisValidatorsRoot(), + ) + if err != nil { + return nil, err } - domain, err := signing.ComputeDomain(params.BeaconConfig().DomainConsolidation, st.Fork().CurrentVersion, st.GenesisValidatorsRoot()) + totalBalance, err := helpers.TotalActiveBalance(st) if err != nil { return nil, err } @@ -155,22 +173,16 @@ func ProcessConsolidations(ctx context.Context, st state.BeaconState, cs []*ethp return nil, errors.New("nil consolidation") } - // TODO(preston): can these be moved outside of the loop? if n, err := st.NumPendingConsolidations(); err != nil { return nil, err } else if n >= params.BeaconConfig().PendingConsolidationsLimit { return nil, errors.New("pending consolidations queue is full") } - totalBalance, err := helpers.TotalActiveBalance(st) - if err != nil { - return nil, err - } if helpers.ConsolidationChurnLimit(math.Gwei(totalBalance)) <= math.Gwei(params.BeaconConfig().MinActivationBalance) { return nil, errors.New("too little available consolidation churn limit") } currentEpoch := slots.ToEpoch(st.Slot()) - // END TODO if c.Message.SourceIndex == c.Message.TargetIndex { return nil, errors.New("source and target index are the same") diff --git a/beacon-chain/core/electra/consolidations_test.go b/beacon-chain/core/electra/consolidations_test.go new file mode 100644 index 000000000000..9632cf5333bd --- /dev/null +++ b/beacon-chain/core/electra/consolidations_test.go @@ -0,0 +1,444 @@ +package electra_test + +import ( + "context" + "testing" + + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" + state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" + fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/crypto/bls/blst" + "github.com/prysmaticlabs/prysm/v5/crypto/bls/common" + "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" + eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/interop" + "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/util" +) + +func TestProcessPendingConsolidations(t *testing.T) { + tests := []struct { + name string + state state.BeaconState + check func(*testing.T, state.BeaconState) + wantErr bool + }{ + { + name: "nil state", + state: nil, + wantErr: true, + }, + { + name: "no pending consolidations", + state: func() state.BeaconState { + pb := ð.BeaconStateElectra{} + + st, err := state_native.InitializeFromProtoUnsafeElectra(pb) + require.NoError(t, err) + return st + }(), + wantErr: false, + }, + { + name: "processes pending consolidation successfully", + state: func() state.BeaconState { + pb := ð.BeaconStateElectra{ + Validators: []*eth.Validator{ + { + WithdrawalCredentials: []byte{0x01, 0xFF}, + }, + { + WithdrawalCredentials: []byte{0x01, 0xAB}, + }, + }, + Balances: []uint64{ + params.BeaconConfig().MinActivationBalance, + params.BeaconConfig().MinActivationBalance, + }, + PendingConsolidations: []*eth.PendingConsolidation{ + { + SourceIndex: 0, + TargetIndex: 1, + }, + }, + } + + st, err := state_native.InitializeFromProtoUnsafeElectra(pb) + require.NoError(t, err) + return st + }(), + check: func(t *testing.T, st state.BeaconState) { + // Balances are transferred from v0 to v1. + bal0, err := st.BalanceAtIndex(0) + require.NoError(t, err) + require.Equal(t, uint64(0), bal0) + bal1, err := st.BalanceAtIndex(1) + require.NoError(t, err) + require.Equal(t, 2*params.BeaconConfig().MinActivationBalance, bal1) + + // The pending consolidation is removed from the list. + num, err := st.NumPendingConsolidations() + require.NoError(t, err) + require.Equal(t, uint64(0), num) + + // v1 is switched to compounding validator. + v1, err := st.ValidatorAtIndex(1) + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().CompoundingWithdrawalPrefixByte, v1.WithdrawalCredentials[0]) + }, + wantErr: false, + }, + { + name: "stop processing when a source val withdrawable epoch is in the future", + state: func() state.BeaconState { + pb := ð.BeaconStateElectra{ + Validators: []*eth.Validator{ + { + WithdrawalCredentials: []byte{0x01, 0xFF}, + WithdrawableEpoch: 100, + }, + { + WithdrawalCredentials: []byte{0x01, 0xAB}, + }, + }, + Balances: []uint64{ + params.BeaconConfig().MinActivationBalance, + params.BeaconConfig().MinActivationBalance, + }, + PendingConsolidations: []*eth.PendingConsolidation{ + { + SourceIndex: 0, + TargetIndex: 1, + }, + }, + } + + st, err := state_native.InitializeFromProtoUnsafeElectra(pb) + require.NoError(t, err) + return st + }(), + check: func(t *testing.T, st state.BeaconState) { + // No balances are transferred from v0 to v1. + bal0, err := st.BalanceAtIndex(0) + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MinActivationBalance, bal0) + bal1, err := st.BalanceAtIndex(1) + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MinActivationBalance, bal1) + + // The pending consolidation is still in the list. + num, err := st.NumPendingConsolidations() + require.NoError(t, err) + require.Equal(t, uint64(1), num) + }, + wantErr: false, + }, + { + name: "slashed validator is not consolidated", + state: func() state.BeaconState { + pb := ð.BeaconStateElectra{ + Validators: []*eth.Validator{ + { + WithdrawalCredentials: []byte{0x01, 0xFF}, + }, + { + WithdrawalCredentials: []byte{0x01, 0xAB}, + }, + { + Slashed: true, + }, + { + WithdrawalCredentials: []byte{0x01, 0xCC}, + }, + }, + Balances: []uint64{ + params.BeaconConfig().MinActivationBalance, + params.BeaconConfig().MinActivationBalance, + params.BeaconConfig().MinActivationBalance, + params.BeaconConfig().MinActivationBalance, + }, + PendingConsolidations: []*eth.PendingConsolidation{ + { + SourceIndex: 2, + TargetIndex: 3, + }, + { + SourceIndex: 0, + TargetIndex: 1, + }, + }, + } + + st, err := state_native.InitializeFromProtoUnsafeElectra(pb) + require.NoError(t, err) + return st + }(), + check: func(t *testing.T, st state.BeaconState) { + // No balances are transferred from v2 to v3. + bal0, err := st.BalanceAtIndex(2) + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MinActivationBalance, bal0) + bal1, err := st.BalanceAtIndex(3) + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MinActivationBalance, bal1) + + // No pending consolidation remaining. + num, err := st.NumPendingConsolidations() + require.NoError(t, err) + require.Equal(t, uint64(0), num) + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, err := electra.ProcessPendingConsolidations(context.TODO(), tt.state) + require.Equal(t, tt.wantErr, err != nil) + if tt.check != nil { + tt.check(t, res) + } + }) + } +} + +func stateWithActiveBalanceETH(t *testing.T, balETH uint64) state.BeaconState { + gwei := balETH * 1_000_000_000 + balPerVal := params.BeaconConfig().MinActivationBalance + numVals := gwei / balPerVal + + vals := make([]*eth.Validator, numVals) + bals := make([]uint64, numVals) + for i := uint64(0); i < numVals; i++ { + wc := make([]byte, 32) + wc[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + wc[31] = byte(i) + vals[i] = ð.Validator{ + ActivationEpoch: 0, + ExitEpoch: params.BeaconConfig().FarFutureEpoch, + EffectiveBalance: balPerVal, + WithdrawalCredentials: wc, + } + bals[i] = balPerVal + } + st, err := state_native.InitializeFromProtoUnsafeElectra(ð.BeaconStateElectra{ + Slot: 10 * params.BeaconConfig().SlotsPerEpoch, + Validators: vals, + Balances: bals, + Fork: ð.Fork{ + CurrentVersion: params.BeaconConfig().ElectraForkVersion, + }, + }) + require.NoError(t, err) + + return st +} + +func TestProcessConsolidations(t *testing.T) { + secretKeys, publicKeys, err := interop.DeterministicallyGenerateKeys(0, 2) + require.NoError(t, err) + + genesisValidatorRoot := bytesutil.PadTo([]byte("genesisValidatorRoot"), fieldparams.RootLength) + + _ = secretKeys + + tests := []struct { + name string + state state.BeaconState + scs []*eth.SignedConsolidation + check func(*testing.T, state.BeaconState) + wantErr string + }{ + { + name: "nil state", + scs: make([]*eth.SignedConsolidation, 10), + wantErr: "nil state", + }, + { + name: "nil consolidation in slice", + state: func() state.BeaconState { + st, _ := util.DeterministicGenesisStateElectra(t, 1) + return st + }(), + scs: []*eth.SignedConsolidation{nil, nil}, + wantErr: "nil consolidation", + }, + { + name: "state is 100% full of pending consolidations", + state: func() state.BeaconState { + st, _ := util.DeterministicGenesisStateElectra(t, 1) + pc := make([]*eth.PendingConsolidation, params.BeaconConfig().PendingConsolidationsLimit) + require.NoError(t, st.SetPendingConsolidations(pc)) + return st + }(), + scs: []*eth.SignedConsolidation{{Message: ð.Consolidation{}}}, + wantErr: "pending consolidations queue is full", + }, + { + name: "state has too little consolidation churn limit available to process a consolidation", + state: func() state.BeaconState { + st, _ := util.DeterministicGenesisStateElectra(t, 1) + return st + }(), + scs: []*eth.SignedConsolidation{{Message: ð.Consolidation{}}}, + wantErr: "too little available consolidation churn limit", + }, + { + name: "consolidation with source and target as the same index is rejected", + state: stateWithActiveBalanceETH(t, 19_000_000), + scs: []*eth.SignedConsolidation{{Message: ð.Consolidation{SourceIndex: 100, TargetIndex: 100}}}, + wantErr: "source and target index are the same", + }, + { + name: "consolidation with inactive source is rejected", + state: func() state.BeaconState { + st := stateWithActiveBalanceETH(t, 19_000_000) + val, err := st.ValidatorAtIndex(25) + require.NoError(t, err) + val.ActivationEpoch = params.BeaconConfig().FarFutureEpoch + require.NoError(t, st.UpdateValidatorAtIndex(25, val)) + return st + }(), + scs: []*eth.SignedConsolidation{{Message: ð.Consolidation{SourceIndex: 25, TargetIndex: 100}}}, + wantErr: "source is not active", + }, + { + name: "consolidation with inactive target is rejected", + state: func() state.BeaconState { + st := stateWithActiveBalanceETH(t, 19_000_000) + val, err := st.ValidatorAtIndex(25) + require.NoError(t, err) + val.ActivationEpoch = params.BeaconConfig().FarFutureEpoch + require.NoError(t, st.UpdateValidatorAtIndex(25, val)) + return st + }(), + scs: []*eth.SignedConsolidation{{Message: ð.Consolidation{SourceIndex: 100, TargetIndex: 25}}}, + wantErr: "target is not active", + }, + { + name: "consolidation with exiting source", + state: func() state.BeaconState { + st := stateWithActiveBalanceETH(t, 19_000_000) + val, err := st.ValidatorAtIndex(25) + require.NoError(t, err) + val.ExitEpoch = 256 + require.NoError(t, st.UpdateValidatorAtIndex(25, val)) + return st + }(), + scs: []*eth.SignedConsolidation{{Message: ð.Consolidation{SourceIndex: 25, TargetIndex: 100}}}, + wantErr: "source exit epoch has been initiated", + }, + { + name: "consolidation with exiting target", + state: func() state.BeaconState { + st := stateWithActiveBalanceETH(t, 19_000_000) + val, err := st.ValidatorAtIndex(25) + require.NoError(t, err) + val.ExitEpoch = 256 + require.NoError(t, st.UpdateValidatorAtIndex(25, val)) + return st + }(), + scs: []*eth.SignedConsolidation{{Message: ð.Consolidation{SourceIndex: 100, TargetIndex: 25}}}, + wantErr: "target exit epoch has been initiated", + }, + { + name: "consolidation with future epoch is rejected", + state: stateWithActiveBalanceETH(t, 19_000_000), + scs: []*eth.SignedConsolidation{{Message: ð.Consolidation{SourceIndex: 100, TargetIndex: 25, Epoch: 55}}}, + wantErr: "consolidation is not valid yet", + }, + { + name: "source validator without withdrawal credentials is rejected", + state: func() state.BeaconState { + st := stateWithActiveBalanceETH(t, 19_000_000) + val, err := st.ValidatorAtIndex(25) + require.NoError(t, err) + val.WithdrawalCredentials = []byte{} + require.NoError(t, st.UpdateValidatorAtIndex(25, val)) + return st + }(), + scs: []*eth.SignedConsolidation{{Message: ð.Consolidation{SourceIndex: 25, TargetIndex: 100}}}, + wantErr: "source does not have execution withdrawal credentials", + }, + { + name: "target validator without withdrawal credentials is rejected", + state: func() state.BeaconState { + st := stateWithActiveBalanceETH(t, 19_000_000) + val, err := st.ValidatorAtIndex(25) + require.NoError(t, err) + val.WithdrawalCredentials = []byte{} + require.NoError(t, st.UpdateValidatorAtIndex(25, val)) + return st + }(), + scs: []*eth.SignedConsolidation{{Message: ð.Consolidation{SourceIndex: 100, TargetIndex: 25}}}, + wantErr: "target does not have execution withdrawal credentials", + }, + { + name: "source and target with different withdrawal credentials is rejected", + state: stateWithActiveBalanceETH(t, 19_000_000), + scs: []*eth.SignedConsolidation{{Message: ð.Consolidation{SourceIndex: 100, TargetIndex: 25}}}, + wantErr: "source and target have different withdrawal credentials", + }, + { + name: "consolidation with valid signatures is OK", + state: func() state.BeaconState { + st := stateWithActiveBalanceETH(t, 19_000_000) + require.NoError(t, st.SetGenesisValidatorsRoot(genesisValidatorRoot)) + source, err := st.ValidatorAtIndex(100) + require.NoError(t, err) + target, err := st.ValidatorAtIndex(25) + require.NoError(t, err) + source.PublicKey = publicKeys[0].Marshal() + source.WithdrawalCredentials = target.WithdrawalCredentials + require.NoError(t, st.UpdateValidatorAtIndex(100, source)) + target.PublicKey = publicKeys[1].Marshal() + require.NoError(t, st.UpdateValidatorAtIndex(25, target)) + return st + }(), + scs: func() []*eth.SignedConsolidation { + sc := ð.SignedConsolidation{Message: ð.Consolidation{SourceIndex: 100, TargetIndex: 25, Epoch: 8}} + + domain, err := signing.ComputeDomain( + params.BeaconConfig().DomainConsolidation, + params.BeaconConfig().ElectraForkVersion, + genesisValidatorRoot, + ) + require.NoError(t, err) + sr, err := signing.ComputeSigningRoot(sc.Message, domain) + require.NoError(t, err) + + sig0 := secretKeys[0].Sign(sr[:]) + sig1 := secretKeys[1].Sign(sr[:]) + + sc.Signature = blst.AggregateSignatures([]common.Signature{sig0, sig1}).Marshal() + + return []*eth.SignedConsolidation{sc} + }(), + check: func(t *testing.T, st state.BeaconState) { + source, err := st.ValidatorAtIndex(100) + require.NoError(t, err) + // The consolidated validator is exiting. + require.Equal(t, primitives.Epoch(15), source.ExitEpoch) // 15 = state.Epoch(10) + MIN_SEED_LOOKAHEAD(4) + 1 + require.Equal(t, primitives.Epoch(15+params.BeaconConfig().MinValidatorWithdrawabilityDelay), source.WithdrawableEpoch) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, err := electra.ProcessConsolidations(context.TODO(), tt.state, tt.scs) + if len(tt.wantErr) > 0 { + require.ErrorContains(t, tt.wantErr, err) + } else { + require.NoError(t, err) + } + if tt.check != nil { + tt.check(t, res) + } + }) + } +} diff --git a/testing/spectest/mainnet/electra/epoch_processing/BUILD.bazel b/testing/spectest/mainnet/electra/epoch_processing/BUILD.bazel index 9bf424ef6062..c256d5da8516 100644 --- a/testing/spectest/mainnet/electra/epoch_processing/BUILD.bazel +++ b/testing/spectest/mainnet/electra/epoch_processing/BUILD.bazel @@ -8,6 +8,7 @@ go_test( "inactivity_updates_test.go", "justification_and_finalization_test.go", "participation_flag_updates_test.go", + "pending_consolidations_test.go", "randao_mixes_reset_test.go", "rewards_and_penalties_test.go", "slashings_reset_test.go", diff --git a/testing/spectest/mainnet/electra/epoch_processing/pending_consolidations_test.go b/testing/spectest/mainnet/electra/epoch_processing/pending_consolidations_test.go new file mode 100644 index 000000000000..9dbc628d062f --- /dev/null +++ b/testing/spectest/mainnet/electra/epoch_processing/pending_consolidations_test.go @@ -0,0 +1,11 @@ +package epoch_processing + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/v5/testing/spectest/shared/electra/epoch_processing" +) + +func TestMainnet_Electra_EpochProcessing_PendingConsolidations(t *testing.T) { + epoch_processing.RunPendingConsolidationsTests(t, "mainnet") +} diff --git a/testing/spectest/mainnet/electra/operations/BUILD.bazel b/testing/spectest/mainnet/electra/operations/BUILD.bazel index 48fd331379dd..a8a3ba231ec0 100644 --- a/testing/spectest/mainnet/electra/operations/BUILD.bazel +++ b/testing/spectest/mainnet/electra/operations/BUILD.bazel @@ -6,6 +6,7 @@ go_test( "attester_slashing_test.go", "block_header_test.go", "bls_to_execution_change_test.go", + "consolidation_test.go", "execution_payload_test.go", "proposer_slashing_test.go", "sync_committee_test.go", diff --git a/testing/spectest/mainnet/electra/operations/consolidation_test.go b/testing/spectest/mainnet/electra/operations/consolidation_test.go new file mode 100644 index 000000000000..3afe9874ec60 --- /dev/null +++ b/testing/spectest/mainnet/electra/operations/consolidation_test.go @@ -0,0 +1,11 @@ +package operations + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/v5/testing/spectest/shared/electra/operations" +) + +func TestMainnet_Electra_Operations_Consolidation(t *testing.T) { + operations.RunConsolidationTest(t, "mainnet") +} diff --git a/testing/spectest/minimal/electra/epoch_processing/BUILD.bazel b/testing/spectest/minimal/electra/epoch_processing/BUILD.bazel index 0f4b31514b71..02bf1f72aba4 100644 --- a/testing/spectest/minimal/electra/epoch_processing/BUILD.bazel +++ b/testing/spectest/minimal/electra/epoch_processing/BUILD.bazel @@ -8,6 +8,7 @@ go_test( "inactivity_updates_test.go", "justification_and_finalization_test.go", "participation_flag_updates_test.go", + "pending_consolidations_test.go", "randao_mixes_reset_test.go", "rewards_and_penalties_test.go", "slashings_reset_test.go", diff --git a/testing/spectest/minimal/electra/epoch_processing/pending_consolidations_test.go b/testing/spectest/minimal/electra/epoch_processing/pending_consolidations_test.go new file mode 100644 index 000000000000..0fcbb76608d0 --- /dev/null +++ b/testing/spectest/minimal/electra/epoch_processing/pending_consolidations_test.go @@ -0,0 +1,11 @@ +package epoch_processing + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/v5/testing/spectest/shared/electra/epoch_processing" +) + +func TestMinimal_Electra_EpochProcessing_PendingConsolidations(t *testing.T) { + epoch_processing.RunPendingConsolidationsTests(t, "minimal") +} diff --git a/testing/spectest/minimal/electra/operations/BUILD.bazel b/testing/spectest/minimal/electra/operations/BUILD.bazel index b54d95de73d0..d95b1cb0eb0a 100644 --- a/testing/spectest/minimal/electra/operations/BUILD.bazel +++ b/testing/spectest/minimal/electra/operations/BUILD.bazel @@ -6,6 +6,7 @@ go_test( "attester_slashing_test.go", "block_header_test.go", "bls_to_execution_change_test.go", + "consolidation_test.go", "execution_payload_test.go", "proposer_slashing_test.go", "sync_committee_test.go", diff --git a/testing/spectest/minimal/electra/operations/consolidation_test.go b/testing/spectest/minimal/electra/operations/consolidation_test.go new file mode 100644 index 000000000000..cc46d13998d2 --- /dev/null +++ b/testing/spectest/minimal/electra/operations/consolidation_test.go @@ -0,0 +1,11 @@ +package operations + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/v5/testing/spectest/shared/electra/operations" +) + +func TestMinimal_Electra_Operations_Consolidation(t *testing.T) { + operations.RunConsolidationTest(t, "minimal") +} diff --git a/testing/spectest/shared/electra/epoch_processing/BUILD.bazel b/testing/spectest/shared/electra/epoch_processing/BUILD.bazel index 90d8ef842817..7140a280dab4 100644 --- a/testing/spectest/shared/electra/epoch_processing/BUILD.bazel +++ b/testing/spectest/shared/electra/epoch_processing/BUILD.bazel @@ -10,6 +10,7 @@ go_library( "inactivity_updates.go", "justification_and_finalization.go", "participation_flag_updates.go", + "pending_consolidations.go", "randao_mixes_reset.go", "rewards_and_penalties.go", "slashings.go", diff --git a/testing/spectest/shared/electra/epoch_processing/pending_consolidations.go b/testing/spectest/shared/electra/epoch_processing/pending_consolidations.go new file mode 100644 index 000000000000..674fa40187bd --- /dev/null +++ b/testing/spectest/shared/electra/epoch_processing/pending_consolidations.go @@ -0,0 +1,28 @@ +package epoch_processing + +import ( + "context" + "path" + "testing" + + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" + "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/spectest/utils" +) + +func RunPendingConsolidationsTests(t *testing.T, config string) { + require.NoError(t, utils.SetConfig(t, config)) + + testFolders, testsFolderPath := utils.TestFolders(t, config, "electra", "epoch_processing/pending_consolidations/pyspec_tests") + for _, folder := range testFolders { + t.Run(folder.Name(), func(t *testing.T) { + folderPath := path.Join(testsFolderPath, folder.Name()) + RunEpochOperationTest(t, folderPath, processPendingConsolidations) + }) + } +} + +func processPendingConsolidations(t *testing.T, st state.BeaconState) (state.BeaconState, error) { + return electra.ProcessPendingConsolidations(context.TODO(), st) +} diff --git a/testing/spectest/shared/electra/operations/BUILD.bazel b/testing/spectest/shared/electra/operations/BUILD.bazel index 7da4295b226b..9596456ef58d 100644 --- a/testing/spectest/shared/electra/operations/BUILD.bazel +++ b/testing/spectest/shared/electra/operations/BUILD.bazel @@ -7,6 +7,7 @@ go_library( "attester_slashing.go", "block_header.go", "bls_to_execution_changes.go", + "consolidations.go", "deposit_receipt.go", "execution_layer_withdrawal_request.go", "execution_payload.go", diff --git a/testing/spectest/shared/electra/operations/consolidations.go b/testing/spectest/shared/electra/operations/consolidations.go new file mode 100644 index 000000000000..622682eb5fd7 --- /dev/null +++ b/testing/spectest/shared/electra/operations/consolidations.go @@ -0,0 +1,48 @@ +package operations + +import ( + "context" + "path" + "testing" + + "github.com/golang/snappy" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" + "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/spectest/utils" + "github.com/prysmaticlabs/prysm/v5/testing/util" +) + +func RunConsolidationTest(t *testing.T, config string) { + t.Skip("These tests were temporarily deleted in v1.5.0-alpha.2. See https://github.com/ethereum/consensus-specs/pull/3736") + require.NoError(t, utils.SetConfig(t, config)) + testFolders, testsFolderPath := utils.TestFolders(t, config, "electra", "operations/consolidation/pyspec_tests") + require.NotEqual(t, 0, len(testFolders), "missing tests for consolidation operation in folder") + for _, folder := range testFolders { + t.Run(folder.Name(), func(t *testing.T) { + folderPath := path.Join(testsFolderPath, folder.Name()) + consolidationFile, err := util.BazelFileBytes(folderPath, "consolidation.ssz_snappy") + require.NoError(t, err) + consolidationSSZ, err := snappy.Decode(nil /* dst */, consolidationFile) + require.NoError(t, err, "Failed to decompress") + consolidation := ðpb.SignedConsolidation{} + require.NoError(t, consolidation.UnmarshalSSZ(consolidationSSZ), "Failed to unmarshal") + + body := ðpb.BeaconBlockBodyElectra{Consolidations: []*ethpb.SignedConsolidation{consolidation}} + processConsolidationFunc := func(ctx context.Context, s state.BeaconState, b interfaces.SignedBeaconBlock) (state.BeaconState, error) { + body, ok := b.Block().Body().(interfaces.ROBlockBodyElectra) + if !ok { + t.Error("block body is not electra") + } + cs := body.Consolidations() + if len(cs) == 0 { + t.Error("no consolidations to test") + } + return electra.ProcessConsolidations(ctx, s, cs) + } + RunBlockOperationTest(t, folderPath, body, processConsolidationFunc) + }) + } +}