From 7129af5ccf7c07797eae789cd7308ae746bd2d03 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Tue, 27 Jul 2021 12:10:08 -0700 Subject: [PATCH] Core/deposit: altair processing methods and tests (#9227) --- beacon-chain/core/altair/BUILD.bazel | 39 +++ beacon-chain/core/altair/deposit.go | 68 +++++ beacon-chain/core/altair/deposit_fuzz_test.go | 49 ++++ beacon-chain/core/altair/deposit_test.go | 246 ++++++++++++++++++ beacon-chain/state/v2/BUILD.bazel | 2 - 5 files changed, 402 insertions(+), 2 deletions(-) create mode 100644 beacon-chain/core/altair/BUILD.bazel create mode 100644 beacon-chain/core/altair/deposit.go create mode 100644 beacon-chain/core/altair/deposit_fuzz_test.go create mode 100644 beacon-chain/core/altair/deposit_test.go diff --git a/beacon-chain/core/altair/BUILD.bazel b/beacon-chain/core/altair/BUILD.bazel new file mode 100644 index 0000000000..8034f776ba --- /dev/null +++ b/beacon-chain/core/altair/BUILD.bazel @@ -0,0 +1,39 @@ +load("@prysm//tools/go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["deposit.go"], + importpath = "github.com/prysmaticlabs/prysm/beacon-chain/core/altair", + visibility = ["//beacon-chain:__subpackages__"], + deps = [ + "//beacon-chain/core/blocks:go_default_library", + "//beacon-chain/state:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//shared/bytesutil:go_default_library", + "//shared/params:go_default_library", + "@com_github_pkg_errors//:go_default_library", + "@com_github_prysmaticlabs_eth2_types//:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "deposit_fuzz_test.go", + "deposit_test.go", + ], + deps = [ + ":go_default_library", + "//beacon-chain/core/helpers:go_default_library", + "//beacon-chain/state/v2:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//proto/prysm/v2/state:go_default_library", + "//shared/bls:go_default_library", + "//shared/bytesutil:go_default_library", + "//shared/params:go_default_library", + "//shared/testutil:go_default_library", + "//shared/testutil/require:go_default_library", + "//shared/trieutil:go_default_library", + "@com_github_google_gofuzz//:go_default_library", + ], +) diff --git a/beacon-chain/core/altair/deposit.go b/beacon-chain/core/altair/deposit.go new file mode 100644 index 0000000000..58d97d5bc0 --- /dev/null +++ b/beacon-chain/core/altair/deposit.go @@ -0,0 +1,68 @@ +package altair + +import ( + "context" + + "github.com/pkg/errors" + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks" + "github.com/prysmaticlabs/prysm/beacon-chain/state" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/shared/bytesutil" + "github.com/prysmaticlabs/prysm/shared/params" +) + +// ProcessDeposits processes validator deposits for beacon state Altair. +func ProcessDeposits( + ctx context.Context, + beaconState state.BeaconStateAltair, + deposits []*ethpb.Deposit, +) (state.BeaconStateAltair, error) { + batchVerified, err := blocks.BatchVerifyDepositsSignatures(ctx, deposits) + if err != nil { + return nil, err + } + + for _, deposit := range deposits { + if deposit == nil || deposit.Data == nil { + return nil, errors.New("got a nil deposit in block") + } + beaconState, err = ProcessDeposit(ctx, beaconState, deposit, batchVerified) + if err != nil { + return nil, errors.Wrapf(err, "could not process deposit from %#x", bytesutil.Trunc(deposit.Data.PublicKey)) + } + } + return beaconState, nil +} + +// ProcessDeposit processes validator deposit for beacon state Altair. +func ProcessDeposit(ctx context.Context, beaconState state.BeaconStateAltair, deposit *ethpb.Deposit, verifySignature bool) (state.BeaconStateAltair, error) { + beaconState, err := blocks.ProcessDeposit(beaconState, deposit, verifySignature) + if err != nil { + return nil, err + } + + // The last validator in the beacon state validator registry. + v, err := beaconState.ValidatorAtIndexReadOnly(types.ValidatorIndex(beaconState.NumValidators() - 1)) + if err != nil { + return nil, err + } + // We know a validator is brand new when its status epochs are all far future epoch. + // In this case, we append 0 to inactivity score and participation bits. + if v.ActivationEligibilityEpoch() == v.ActivationEpoch() && + v.ActivationEpoch() == v.ExitEpoch() && + v.ExitEpoch() == v.WithdrawableEpoch() && + v.WithdrawableEpoch() == params.BeaconConfig().FarFutureEpoch { + if err := beaconState.AppendInactivityScore(0); err != nil { + return nil, err + } + if err := beaconState.AppendPreviousParticipationBits(0); err != nil { + return nil, err + } + if err := beaconState.AppendCurrentParticipationBits(0); err != nil { + return nil, err + } + } + + return beaconState, nil +} diff --git a/beacon-chain/core/altair/deposit_fuzz_test.go b/beacon-chain/core/altair/deposit_fuzz_test.go new file mode 100644 index 0000000000..2d477e7804 --- /dev/null +++ b/beacon-chain/core/altair/deposit_fuzz_test.go @@ -0,0 +1,49 @@ +package altair_test + +import ( + "context" + "testing" + + fuzz "github.com/google/gofuzz" + "github.com/prysmaticlabs/prysm/beacon-chain/core/altair" + stateAltair "github.com/prysmaticlabs/prysm/beacon-chain/state/v2" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + statepb "github.com/prysmaticlabs/prysm/proto/prysm/v2/state" + "github.com/prysmaticlabs/prysm/shared/testutil/require" +) + +func TestFuzzProcessDeposits_10000(t *testing.T) { + fuzzer := fuzz.NewWithSeed(0) + state := &statepb.BeaconStateAltair{} + deposits := make([]*ethpb.Deposit, 100) + ctx := context.Background() + for i := 0; i < 10000; i++ { + fuzzer.Fuzz(state) + for i := range deposits { + fuzzer.Fuzz(deposits[i]) + } + s, err := stateAltair.InitializeFromProtoUnsafe(state) + require.NoError(t, err) + r, err := altair.ProcessDeposits(ctx, s, deposits) + if err != nil && r != nil { + t.Fatalf("return value should be nil on err. found: %v on error: %v for state: %v and block: %v", r, err, state, deposits) + } + } +} + +func TestFuzzProcessDeposit_10000(t *testing.T) { + fuzzer := fuzz.NewWithSeed(0) + state := &statepb.BeaconStateAltair{} + deposit := ðpb.Deposit{} + + for i := 0; i < 10000; i++ { + fuzzer.Fuzz(state) + fuzzer.Fuzz(deposit) + s, err := stateAltair.InitializeFromProtoUnsafe(state) + require.NoError(t, err) + r, err := altair.ProcessDeposit(context.Background(), s, deposit, true) + if err != nil && r != nil { + t.Fatalf("return value should be nil on err. found: %v on error: %v for state: %v and block: %v", r, err, state, deposit) + } + } +} diff --git a/beacon-chain/core/altair/deposit_test.go b/beacon-chain/core/altair/deposit_test.go new file mode 100644 index 0000000000..428009355a --- /dev/null +++ b/beacon-chain/core/altair/deposit_test.go @@ -0,0 +1,246 @@ +package altair_test + +import ( + "context" + "testing" + + "github.com/prysmaticlabs/prysm/beacon-chain/core/altair" + "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" + stateAltair "github.com/prysmaticlabs/prysm/beacon-chain/state/v2" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + statepb "github.com/prysmaticlabs/prysm/proto/prysm/v2/state" + "github.com/prysmaticlabs/prysm/shared/bls" + "github.com/prysmaticlabs/prysm/shared/bytesutil" + "github.com/prysmaticlabs/prysm/shared/params" + "github.com/prysmaticlabs/prysm/shared/testutil" + "github.com/prysmaticlabs/prysm/shared/testutil/require" + "github.com/prysmaticlabs/prysm/shared/trieutil" +) + +func TestProcessDeposits_SameValidatorMultipleDepositsSameBlock(t *testing.T) { + // Same validator created 3 valid deposits within the same block + dep, _, err := testutil.DeterministicDepositsAndKeysSameValidator(3) + require.NoError(t, err) + eth1Data, err := testutil.DeterministicEth1Data(len(dep)) + require.NoError(t, err) + registry := []*ethpb.Validator{ + { + PublicKey: []byte{1}, + WithdrawalCredentials: []byte{1, 2, 3}, + }, + } + balances := []uint64{0} + beaconState, err := stateAltair.InitializeFromProto(&statepb.BeaconStateAltair{ + Validators: registry, + Balances: balances, + Eth1Data: eth1Data, + Fork: &statepb.Fork{ + PreviousVersion: params.BeaconConfig().GenesisForkVersion, + CurrentVersion: params.BeaconConfig().GenesisForkVersion, + }, + }) + require.NoError(t, err) + newState, err := altair.ProcessDeposits(context.Background(), beaconState, []*ethpb.Deposit{dep[0], dep[1], dep[2]}) + require.NoError(t, err, "Expected block deposits to process correctly") + require.Equal(t, 2, len(newState.Validators()), "Incorrect validator count") +} + +func TestProcessDeposits_MerkleBranchFailsVerification(t *testing.T) { + deposit := ðpb.Deposit{ + Data: ðpb.Deposit_Data{ + PublicKey: bytesutil.PadTo([]byte{1, 2, 3}, 48), + WithdrawalCredentials: make([]byte, 32), + Signature: make([]byte, 96), + }, + } + leaf, err := deposit.Data.HashTreeRoot() + require.NoError(t, err) + + // We then create a merkle branch for the test. + depositTrie, err := trieutil.GenerateTrieFromItems([][]byte{leaf[:]}, params.BeaconConfig().DepositContractTreeDepth) + require.NoError(t, err, "Could not generate trie") + proof, err := depositTrie.MerkleProof(0) + require.NoError(t, err, "Could not generate proof") + + deposit.Proof = proof + beaconState, err := stateAltair.InitializeFromProto(&statepb.BeaconStateAltair{ + Eth1Data: ðpb.Eth1Data{ + DepositRoot: []byte{0}, + BlockHash: []byte{1}, + }, + }) + require.NoError(t, err) + want := "deposit root did not verify" + _, err = altair.ProcessDeposits(context.Background(), beaconState, []*ethpb.Deposit{deposit}) + require.ErrorContains(t, want, err) +} + +func TestProcessDeposits_AddsNewValidatorDeposit(t *testing.T) { + dep, _, err := testutil.DeterministicDepositsAndKeys(1) + require.NoError(t, err) + eth1Data, err := testutil.DeterministicEth1Data(len(dep)) + require.NoError(t, err) + + registry := []*ethpb.Validator{ + { + PublicKey: []byte{1}, + WithdrawalCredentials: []byte{1, 2, 3}, + }, + } + balances := []uint64{0} + beaconState, err := stateAltair.InitializeFromProto(&statepb.BeaconStateAltair{ + Validators: registry, + Balances: balances, + Eth1Data: eth1Data, + Fork: &statepb.Fork{ + PreviousVersion: params.BeaconConfig().GenesisForkVersion, + CurrentVersion: params.BeaconConfig().GenesisForkVersion, + }, + }) + require.NoError(t, err) + newState, err := altair.ProcessDeposits(context.Background(), beaconState, []*ethpb.Deposit{dep[0]}) + require.NoError(t, err, "Expected block deposits to process correctly") + if newState.Balances()[1] != dep[0].Data.Amount { + t.Errorf( + "Expected state validator balances index 0 to equal %d, received %d", + dep[0].Data.Amount, + newState.Balances()[1], + ) + } +} + +func TestProcessDeposits_RepeatedDeposit_IncreasesValidatorBalance(t *testing.T) { + sk, err := bls.RandKey() + require.NoError(t, err) + deposit := ðpb.Deposit{ + Data: ðpb.Deposit_Data{ + PublicKey: sk.PublicKey().Marshal(), + Amount: 1000, + WithdrawalCredentials: make([]byte, 32), + Signature: make([]byte, 96), + }, + } + sr, err := helpers.ComputeSigningRoot(deposit.Data, bytesutil.ToBytes(3, 32)) + require.NoError(t, err) + sig := sk.Sign(sr[:]) + deposit.Data.Signature = sig.Marshal() + leaf, err := deposit.Data.HashTreeRoot() + require.NoError(t, err) + + // We then create a merkle branch for the test. + depositTrie, err := trieutil.GenerateTrieFromItems([][]byte{leaf[:]}, params.BeaconConfig().DepositContractTreeDepth) + require.NoError(t, err, "Could not generate trie") + proof, err := depositTrie.MerkleProof(0) + require.NoError(t, err, "Could not generate proof") + + deposit.Proof = proof + registry := []*ethpb.Validator{ + { + PublicKey: []byte{1, 2, 3}, + }, + { + PublicKey: sk.PublicKey().Marshal(), + WithdrawalCredentials: []byte{1}, + }, + } + balances := []uint64{0, 50} + root := depositTrie.Root() + beaconState, err := stateAltair.InitializeFromProto(&statepb.BeaconStateAltair{ + Validators: registry, + Balances: balances, + Eth1Data: ðpb.Eth1Data{ + DepositRoot: root[:], + BlockHash: root[:], + }, + }) + require.NoError(t, err) + newState, err := altair.ProcessDeposits(context.Background(), beaconState, []*ethpb.Deposit{deposit}) + require.NoError(t, err, "Process deposit failed") + require.Equal(t, uint64(1000+50), newState.Balances()[1], "Expected balance at index 1 to be 1050") +} + +func TestProcessDeposit_AddsNewValidatorDeposit(t *testing.T) { + // Similar to TestProcessDeposits_AddsNewValidatorDeposit except that this test directly calls ProcessDeposit + dep, _, err := testutil.DeterministicDepositsAndKeys(1) + require.NoError(t, err) + eth1Data, err := testutil.DeterministicEth1Data(len(dep)) + require.NoError(t, err) + + registry := []*ethpb.Validator{ + { + PublicKey: []byte{1}, + WithdrawalCredentials: []byte{1, 2, 3}, + }, + } + balances := []uint64{0} + beaconState, err := stateAltair.InitializeFromProto(&statepb.BeaconStateAltair{ + Validators: registry, + Balances: balances, + Eth1Data: eth1Data, + Fork: &statepb.Fork{ + PreviousVersion: params.BeaconConfig().GenesisForkVersion, + CurrentVersion: params.BeaconConfig().GenesisForkVersion, + }, + }) + require.NoError(t, err) + newState, err := altair.ProcessDeposit(context.Background(), beaconState, dep[0], true) + require.NoError(t, err, "Process deposit failed") + require.Equal(t, 2, len(newState.Validators()), "Expected validator list to have length 2") + require.Equal(t, 2, len(newState.Balances()), "Expected validator balances list to have length 2") + if newState.Balances()[1] != dep[0].Data.Amount { + t.Errorf( + "Expected state validator balances index 1 to equal %d, received %d", + dep[0].Data.Amount, + newState.Balances()[1], + ) + } +} + +func TestProcessDeposit_SkipsInvalidDeposit(t *testing.T) { + // Same test settings as in TestProcessDeposit_AddsNewValidatorDeposit, except that we use an invalid signature + dep, _, err := testutil.DeterministicDepositsAndKeys(1) + require.NoError(t, err) + dep[0].Data.Signature = make([]byte, 96) + trie, _, err := testutil.DepositTrieFromDeposits(dep) + require.NoError(t, err) + root := trie.Root() + eth1Data := ðpb.Eth1Data{ + DepositRoot: root[:], + DepositCount: 1, + } + registry := []*ethpb.Validator{ + { + PublicKey: []byte{1}, + WithdrawalCredentials: []byte{1, 2, 3}, + }, + } + balances := []uint64{0} + beaconState, err := stateAltair.InitializeFromProto(&statepb.BeaconStateAltair{ + Validators: registry, + Balances: balances, + Eth1Data: eth1Data, + Fork: &statepb.Fork{ + PreviousVersion: params.BeaconConfig().GenesisForkVersion, + CurrentVersion: params.BeaconConfig().GenesisForkVersion, + }, + }) + require.NoError(t, err) + newState, err := altair.ProcessDeposit(context.Background(), beaconState, dep[0], true) + require.NoError(t, err, "Expected invalid block deposit to be ignored without error") + + if newState.Eth1DepositIndex() != 1 { + t.Errorf( + "Expected Eth1DepositIndex to be increased by 1 after processing an invalid deposit, received change: %v", + newState.Eth1DepositIndex(), + ) + } + if len(newState.Validators()) != 1 { + t.Errorf("Expected validator list to have length 1, received: %v", len(newState.Validators())) + } + if len(newState.Balances()) != 1 { + t.Errorf("Expected validator balances list to have length 1, received: %v", len(newState.Balances())) + } + if newState.Balances()[0] != 0 { + t.Errorf("Expected validator balance at index 0 to stay 0, received: %v", newState.Balances()[0]) + } +} diff --git a/beacon-chain/state/v2/BUILD.bazel b/beacon-chain/state/v2/BUILD.bazel index f3ed422bc4..784d521542 100644 --- a/beacon-chain/state/v2/BUILD.bazel +++ b/beacon-chain/state/v2/BUILD.bazel @@ -20,8 +20,6 @@ go_library( importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state/v2", visibility = [ "//beacon-chain:__subpackages__", - "//shared/testutil:__pkg__", - "//spectest:__subpackages__", ], deps = [ "//beacon-chain/state:go_default_library",