From 97663548a1e2280a081745e5fa25c289f7121c0b Mon Sep 17 00:00:00 2001 From: Preston Van Loon Date: Mon, 9 May 2022 11:51:48 -0500 Subject: [PATCH] fuzz: Add fuzz tests for sparse merkle trie (#10662) * Add fuzz tests for sparse merkle trie and change HTR signature to return an error * fix capitalization of error message --- beacon-chain/blockchain/service_test.go | 3 +- .../cache/depositcache/deposits_cache_test.go | 12 +- beacon-chain/core/altair/deposit_test.go | 6 +- beacon-chain/core/blocks/deposit_test.go | 14 +- beacon-chain/core/helpers/genesis.go | 5 +- beacon-chain/powchain/deposit_test.go | 15 +- beacon-chain/powchain/log_processing.go | 29 +++- .../v1alpha1/validator/proposer_deposits.go | 8 +- .../prysm/v1alpha1/validator/proposer_test.go | 83 +++++++--- .../prysm/v1alpha1/validator/server_test.go | 4 +- .../prysm/v1alpha1/validator/status_test.go | 56 +++++-- container/trie/BUILD.bazel | 7 +- container/trie/sparse_merkle.go | 36 +++-- container/trie/sparse_merkle_test.go | 33 ++-- .../trie/sparse_merkle_trie_fuzz_test.go | 148 ++++++++++++++++++ ...4599b62223e7bade8dafdd8a1c53b8f881e8f79d99 | 2 + ...74d4d5360ffbe979064b58f619eb3d5fddfdae64c8 | 4 + ...0e4266850e109a9e6bc6bab59953b2a30027f66a93 | 3 + ...db07e0dc5ecfe453c6c95e4597476616ba0358c236 | 6 + contracts/deposit/deposit_tree_test.go | 16 +- runtime/interop/generate_genesis_state.go | 5 +- .../interop/generate_genesis_state_test.go | 3 +- testing/util/deposits.go | 5 +- testing/util/deposits_test.go | 5 +- 24 files changed, 418 insertions(+), 90 deletions(-) create mode 100644 container/trie/sparse_merkle_trie_fuzz_test.go create mode 100644 container/trie/testdata/fuzz/FuzzSparseMerkleTrie_HashTreeRoot/5838cdfae7b16cde2707c04599b62223e7bade8dafdd8a1c53b8f881e8f79d99 create mode 100644 container/trie/testdata/fuzz/FuzzSparseMerkleTrie_Insert/d863478086b8f8e8c854ec74d4d5360ffbe979064b58f619eb3d5fddfdae64c8 create mode 100644 container/trie/testdata/fuzz/FuzzSparseMerkleTrie_MerkleProof/ec4893c52de558dbac9a850e4266850e109a9e6bc6bab59953b2a30027f66a93 create mode 100644 container/trie/testdata/fuzz/FuzzSparseMerkleTrie_VerifyMerkleProofWithDepth/826f574b1f684ccfa610c2db07e0dc5ecfe453c6c95e4597476616ba0358c236 diff --git a/beacon-chain/blockchain/service_test.go b/beacon-chain/blockchain/service_test.go index 9da1fb0b2c..4cadd4e8c8 100644 --- a/beacon-chain/blockchain/service_test.go +++ b/beacon-chain/blockchain/service_test.go @@ -221,7 +221,8 @@ func TestChainService_InitializeBeaconChain(t *testing.T) { require.NoError(t, err) trie, _, err := util.DepositTrieFromDeposits(deposits) require.NoError(t, err) - hashTreeRoot := trie.HashTreeRoot() + hashTreeRoot, err := trie.HashTreeRoot() + require.NoError(t, err) genState, err := transition.EmptyGenesisState() require.NoError(t, err) err = genState.SetEth1Data(ðpb.Eth1Data{ diff --git a/beacon-chain/cache/depositcache/deposits_cache_test.go b/beacon-chain/cache/depositcache/deposits_cache_test.go index 1e02373c49..00ddac8f5d 100644 --- a/beacon-chain/cache/depositcache/deposits_cache_test.go +++ b/beacon-chain/cache/depositcache/deposits_cache_test.go @@ -430,7 +430,11 @@ func TestFinalizedDeposits_DepositsCachedCorrectly(t *testing.T) { } trie, err := trie.GenerateTrieFromItems(deps, params.BeaconConfig().DepositContractTreeDepth) require.NoError(t, err, "Could not generate deposit trie") - assert.Equal(t, trie.HashTreeRoot(), cachedDeposits.Deposits.HashTreeRoot()) + rootA, err := trie.HashTreeRoot() + require.NoError(t, err) + rootB, err := cachedDeposits.Deposits.HashTreeRoot() + require.NoError(t, err) + assert.Equal(t, rootA, rootB) } func TestFinalizedDeposits_UtilizesPreviouslyCachedDeposits(t *testing.T) { @@ -488,7 +492,11 @@ func TestFinalizedDeposits_UtilizesPreviouslyCachedDeposits(t *testing.T) { } trie, err := trie.GenerateTrieFromItems(deps, params.BeaconConfig().DepositContractTreeDepth) require.NoError(t, err, "Could not generate deposit trie") - assert.Equal(t, trie.HashTreeRoot(), cachedDeposits.Deposits.HashTreeRoot()) + rootA, err := trie.HashTreeRoot() + require.NoError(t, err) + rootB, err := cachedDeposits.Deposits.HashTreeRoot() + require.NoError(t, err) + assert.Equal(t, rootA, rootB) } func TestFinalizedDeposits_HandleZeroDeposits(t *testing.T) { diff --git a/beacon-chain/core/altair/deposit_test.go b/beacon-chain/core/altair/deposit_test.go index 64e9c57bee..4a1a87f5a3 100644 --- a/beacon-chain/core/altair/deposit_test.go +++ b/beacon-chain/core/altair/deposit_test.go @@ -143,7 +143,8 @@ func TestProcessDeposits_RepeatedDeposit_IncreasesValidatorBalance(t *testing.T) }, } balances := []uint64{0, 50} - root := depositTrie.HashTreeRoot() + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) beaconState, err := stateAltair.InitializeFromProto(ðpb.BeaconStateAltair{ Validators: registry, Balances: balances, @@ -202,7 +203,8 @@ func TestProcessDeposit_SkipsInvalidDeposit(t *testing.T) { dep[0].Data.Signature = make([]byte, 96) trie, _, err := util.DepositTrieFromDeposits(dep) require.NoError(t, err) - root := trie.HashTreeRoot() + root, err := trie.HashTreeRoot() + require.NoError(t, err) eth1Data := ðpb.Eth1Data{ DepositRoot: root[:], DepositCount: 1, diff --git a/beacon-chain/core/blocks/deposit_test.go b/beacon-chain/core/blocks/deposit_test.go index 2068cffd38..d04cc3921d 100644 --- a/beacon-chain/core/blocks/deposit_test.go +++ b/beacon-chain/core/blocks/deposit_test.go @@ -173,7 +173,8 @@ func TestProcessDeposits_RepeatedDeposit_IncreasesValidatorBalance(t *testing.T) }, } balances := []uint64{0, 50} - root := depositTrie.HashTreeRoot() + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Validators: registry, Balances: balances, @@ -233,7 +234,8 @@ func TestProcessDeposit_SkipsInvalidDeposit(t *testing.T) { dep[0].Data.Signature = make([]byte, 96) trie, _, err := util.DepositTrieFromDeposits(dep) require.NoError(t, err) - root := trie.HashTreeRoot() + root, err := trie.HashTreeRoot() + require.NoError(t, err) eth1Data := ðpb.Eth1Data{ DepositRoot: root[:], DepositCount: 1, @@ -289,7 +291,9 @@ func TestPreGenesisDeposits_SkipInvalidDeposit(t *testing.T) { require.NoError(t, err) dep[i].Proof = proof } - root := trie.HashTreeRoot() + root, err := trie.HashTreeRoot() + require.NoError(t, err) + eth1Data := ðpb.Eth1Data{ DepositRoot: root[:], DepositCount: 1, @@ -376,7 +380,9 @@ func TestProcessDeposit_RepeatedDeposit_IncreasesValidatorBalance(t *testing.T) }, } balances := []uint64{0, 50} - root := depositTrie.HashTreeRoot() + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Validators: registry, Balances: balances, diff --git a/beacon-chain/core/helpers/genesis.go b/beacon-chain/core/helpers/genesis.go index bc4cc8d36f..c8adce6231 100644 --- a/beacon-chain/core/helpers/genesis.go +++ b/beacon-chain/core/helpers/genesis.go @@ -41,7 +41,10 @@ func UpdateGenesisEth1Data(state state.BeaconState, deposits []*ethpb.Deposit, e } } - depositRoot := t.HashTreeRoot() + depositRoot, err := t.HashTreeRoot() + if err != nil { + return nil, err + } eth1Data.DepositRoot = depositRoot[:] err = state.SetEth1Data(eth1Data) if err != nil { diff --git a/beacon-chain/powchain/deposit_test.go b/beacon-chain/powchain/deposit_test.go index 724b03a96d..e9edbe74e3 100644 --- a/beacon-chain/powchain/deposit_test.go +++ b/beacon-chain/powchain/deposit_test.go @@ -138,7 +138,8 @@ func TestProcessDeposit_InvalidPublicKey(t *testing.T) { deposits[0].Proof, err = trie.MerkleProof(0) require.NoError(t, err) - root := trie.HashTreeRoot() + root, err := trie.HashTreeRoot() + require.NoError(t, err) eth1Data := ðpb.Eth1Data{ DepositCount: 1, @@ -178,7 +179,8 @@ func TestProcessDeposit_InvalidSignature(t *testing.T) { trie, err := trie.GenerateTrieFromItems([][]byte{leaf[:]}, params.BeaconConfig().DepositContractTreeDepth) require.NoError(t, err) - root := trie.HashTreeRoot() + root, err := trie.HashTreeRoot() + require.NoError(t, err) eth1Data := ðpb.Eth1Data{ DepositCount: 1, @@ -213,7 +215,8 @@ func TestProcessDeposit_UnableToVerify(t *testing.T) { trie, _, err := util.DepositTrieFromDeposits(deposits) require.NoError(t, err) - root := trie.HashTreeRoot() + root, err := trie.HashTreeRoot() + require.NoError(t, err) eth1Data := ðpb.Eth1Data{ DepositCount: 1, DepositRoot: root[:], @@ -265,7 +268,8 @@ func TestProcessDeposit_IncompleteDeposit(t *testing.T) { trie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) require.NoError(t, err) - root := trie.HashTreeRoot() + root, err := trie.HashTreeRoot() + require.NoError(t, err) eth1Data := ðpb.Eth1Data{ DepositCount: 1, DepositRoot: root[:], @@ -281,7 +285,8 @@ func TestProcessDeposit_IncompleteDeposit(t *testing.T) { for i := 0; i < int(factor-1); i++ { assert.NoError(t, trie.Insert(dataRoot[:], i)) - trieRoot := trie.HashTreeRoot() + trieRoot, err := trie.HashTreeRoot() + require.NoError(t, err) eth1Data.DepositRoot = trieRoot[:] eth1Data.DepositCount = uint64(i + 1) diff --git a/beacon-chain/powchain/log_processing.go b/beacon-chain/powchain/log_processing.go index 93d894547a..b237d94999 100644 --- a/beacon-chain/powchain/log_processing.go +++ b/beacon-chain/powchain/log_processing.go @@ -140,7 +140,7 @@ func (s *Service) ProcessDepositLog(ctx context.Context, depositLog gethTypes.Lo depositHash, err := depositData.HashTreeRoot() if err != nil { - return errors.Wrap(err, "Unable to determine hashed value of deposit") + return errors.Wrap(err, "unable to determine hashed value of deposit") } // Defensive check to validate incoming index. @@ -158,20 +158,27 @@ func (s *Service) ProcessDepositLog(ctx context.Context, depositLog gethTypes.Lo if !s.chainStartData.Chainstarted { proof, err := s.depositTrie.MerkleProof(int(index)) if err != nil { - return errors.Wrap(err, "Unable to generate merkle proof for deposit") + return errors.Wrap(err, "unable to generate merkle proof for deposit") } deposit.Proof = proof } // We always store all historical deposits in the DB. - err = s.cfg.depositCache.InsertDeposit(ctx, deposit, depositLog.BlockNumber, index, s.depositTrie.HashTreeRoot()) + root, err := s.depositTrie.HashTreeRoot() + if err != nil { + return errors.Wrap(err, "unable to determine root of deposit trie") + } + err = s.cfg.depositCache.InsertDeposit(ctx, deposit, depositLog.BlockNumber, index, root) if err != nil { return errors.Wrap(err, "unable to insert deposit into cache") } validData := true if !s.chainStartData.Chainstarted { s.chainStartData.ChainstartDeposits = append(s.chainStartData.ChainstartDeposits, deposit) - root := s.depositTrie.HashTreeRoot() + root, err := s.depositTrie.HashTreeRoot() + if err != nil { + return errors.Wrap(err, "unable to determine root of deposit trie") + } eth1Data := ðpb.Eth1Data{ DepositRoot: root[:], DepositCount: uint64(len(s.chainStartData.ChainstartDeposits)), @@ -181,7 +188,11 @@ func (s *Service) ProcessDepositLog(ctx context.Context, depositLog gethTypes.Lo validData = false } } else { - s.cfg.depositCache.InsertPendingDeposit(ctx, deposit, depositLog.BlockNumber, index, s.depositTrie.HashTreeRoot()) + root, err := s.depositTrie.HashTreeRoot() + if err != nil { + return errors.Wrap(err, "unable to determine root of deposit trie") + } + s.cfg.depositCache.InsertPendingDeposit(ctx, deposit, depositLog.BlockNumber, index, root) } if validData { log.WithFields(logrus.Fields{ @@ -225,12 +236,16 @@ func (s *Service) ProcessChainStart(genesisTime uint64, eth1BlockHash [32]byte, for i := range s.chainStartData.ChainstartDeposits { proof, err := s.depositTrie.MerkleProof(i) if err != nil { - log.Errorf("Unable to generate deposit proof %v", err) + log.Errorf("unable to generate deposit proof %v", err) } s.chainStartData.ChainstartDeposits[i].Proof = proof } - root := s.depositTrie.HashTreeRoot() + root, err := s.depositTrie.HashTreeRoot() + if err != nil { // This should never happen. + log.WithError(err).Error("unable to determine root of deposit trie, aborting chain start") + return + } s.chainStartData.Eth1Data = ðpb.Eth1Data{ DepositCount: uint64(len(s.chainStartData.ChainstartDeposits)), DepositRoot: root[:], diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deposits.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deposits.go index 05fd3e4ca5..c84ca988c0 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deposits.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deposits.go @@ -197,10 +197,16 @@ func (vs *Server) rebuildDepositTrie(ctx context.Context, canonicalEth1Data *eth // validate that the provided deposit trie matches up with the canonical eth1 data provided. func validateDepositTrie(trie *trie.SparseMerkleTrie, canonicalEth1Data *ethpb.Eth1Data) (bool, error) { + if trie == nil || canonicalEth1Data == nil { + return false, errors.New("nil trie or eth1data provided") + } if trie.NumOfItems() != int(canonicalEth1Data.DepositCount) { return false, errors.Errorf("wanted the canonical count of %d but received %d", canonicalEth1Data.DepositCount, trie.NumOfItems()) } - rt := trie.HashTreeRoot() + rt, err := trie.HashTreeRoot() + if err != nil { + return false, err + } if !bytes.Equal(rt[:], canonicalEth1Data.DepositRoot) { return false, errors.Errorf("wanted the canonical deposit root of %#x but received %#x", canonicalEth1Data.DepositRoot, rt) } diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go index 4f25ac8de4..7cde8f9f5a 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go @@ -503,10 +503,14 @@ func TestProposer_PendingDeposits_OutsideEth1FollowWindow(t *testing.T) { require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) - assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root)) } for _, dp := range recentDeposits { - depositCache.InsertPendingDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, depositTrie.HashTreeRoot()) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + depositCache.InsertPendingDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root) } blk := util.NewBeaconBlock() @@ -638,10 +642,14 @@ func TestProposer_PendingDeposits_FollowsCorrectEth1Block(t *testing.T) { require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) - assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root)) } for _, dp := range recentDeposits { - depositCache.InsertPendingDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, depositTrie.HashTreeRoot()) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + depositCache.InsertPendingDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root) } bs := &Server{ @@ -737,10 +745,14 @@ func TestProposer_PendingDeposits_CantReturnBelowStateEth1DepositIndex(t *testin require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) - assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, root)) } for _, dp := range recentDeposits { - depositCache.InsertPendingDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, depositTrie.HashTreeRoot()) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + depositCache.InsertPendingDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, root) } bs := &Server{ @@ -833,10 +845,14 @@ func TestProposer_PendingDeposits_CantReturnMoreThanMax(t *testing.T) { require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) - assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, height.Uint64(), dp.Index, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, height.Uint64(), dp.Index, root)) } for _, dp := range recentDeposits { - depositCache.InsertPendingDeposit(ctx, dp.Deposit, height.Uint64(), dp.Index, depositTrie.HashTreeRoot()) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + depositCache.InsertPendingDeposit(ctx, dp.Deposit, height.Uint64(), dp.Index, root) } bs := &Server{ @@ -927,10 +943,14 @@ func TestProposer_PendingDeposits_CantReturnMoreThanDepositCount(t *testing.T) { require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) - assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, root)) } for _, dp := range recentDeposits { - depositCache.InsertPendingDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, depositTrie.HashTreeRoot()) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + depositCache.InsertPendingDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, root) } bs := &Server{ @@ -1036,10 +1056,14 @@ func TestProposer_DepositTrie_UtilizesCachedFinalizedDeposits(t *testing.T) { require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) - assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root)) } for _, dp := range recentDeposits { - depositCache.InsertPendingDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, depositTrie.HashTreeRoot()) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + depositCache.InsertPendingDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root) } bs := &Server{ @@ -1055,8 +1079,10 @@ func TestProposer_DepositTrie_UtilizesCachedFinalizedDeposits(t *testing.T) { trie, err := bs.depositTrie(ctx, ðpb.Eth1Data{}, big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance))) require.NoError(t, err) - actualRoot := trie.HashTreeRoot() - expectedRoot := depositTrie.HashTreeRoot() + actualRoot, err := trie.HashTreeRoot() + require.NoError(t, err) + expectedRoot, err := depositTrie.HashTreeRoot() + require.NoError(t, err) assert.Equal(t, expectedRoot, actualRoot, "Incorrect deposit trie root") } @@ -1146,10 +1172,14 @@ func TestProposer_DepositTrie_RebuildTrie(t *testing.T) { require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) - assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root)) } for _, dp := range recentDeposits { - depositCache.InsertPendingDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, depositTrie.HashTreeRoot()) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + depositCache.InsertPendingDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root) } d := depositCache.AllDepositContainers(ctx) origDeposit, ok := proto.Clone(d[0].Deposit).(*ethpb.Deposit) @@ -1177,8 +1207,10 @@ func TestProposer_DepositTrie_RebuildTrie(t *testing.T) { trie, err := bs.depositTrie(ctx, ðpb.Eth1Data{}, big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance))) require.NoError(t, err) - expectedRoot := depositTrie.HashTreeRoot() - actualRoot := trie.HashTreeRoot() + expectedRoot, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + actualRoot, err := trie.HashTreeRoot() + require.NoError(t, err) assert.Equal(t, expectedRoot, actualRoot, "Incorrect deposit trie root") } @@ -1230,7 +1262,8 @@ func TestProposer_ValidateDepositTrie(t *testing.T) { assert.NoError(t, trie.Insert([]byte{'a'}, 0)) assert.NoError(t, trie.Insert([]byte{'b'}, 1)) assert.NoError(t, trie.Insert([]byte{'c'}, 2)) - rt := trie.HashTreeRoot() + rt, err := trie.HashTreeRoot() + require.NoError(t, err) return ðpb.Eth1Data{DepositRoot: rt[:], DepositCount: 3, BlockHash: []byte{}} }, trieCreator: func() *trie.SparseMerkleTrie { @@ -1274,7 +1307,9 @@ func TestProposer_Eth1Data_MajorityVote(t *testing.T) { require.NoError(t, err) depositCache, err := depositcache.New() require.NoError(t, err) - assert.NoError(t, depositCache.InsertDeposit(context.Background(), dc.Deposit, dc.Eth1BlockHeight, dc.Index, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(context.Background(), dc.Deposit, dc.Eth1BlockHeight, dc.Index, root)) t.Run("choose highest count", func(t *testing.T) { t.Skip() @@ -1976,10 +2011,14 @@ func TestProposer_Deposits_ReturnsEmptyList_IfLatestEth1DataEqGenesisEth1Block(t require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) - assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, root)) } for _, dp := range recentDeposits { - depositCache.InsertPendingDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, depositTrie.HashTreeRoot()) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + depositCache.InsertPendingDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, root) } bs := &Server{ diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/server_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/server_test.go index ae31f5d80d..fbc6a9c337 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/server_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/server_test.go @@ -137,7 +137,9 @@ func TestWaitForActivation_ValidatorOriginallyExists(t *testing.T) { depositCache, err := depositcache.New() require.NoError(t, err) - assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 10 /*blockNum*/, 0, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 10 /*blockNum*/, 0, root)) trie, err := v1.InitializeFromProtoUnsafe(beaconState) require.NoError(t, err) vs := &Server{ diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/status_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/status_test.go index 122ba72be4..c0a4764c81 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/status_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/status_test.go @@ -41,7 +41,9 @@ func TestValidatorStatus_DepositedEth1(t *testing.T) { depositCache, err := depositcache.New() require.NoError(t, err) - assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, root)) height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix() p := &mockPOW.POWChain{ TimesByHeight: map[int]uint64{ @@ -84,7 +86,9 @@ func TestValidatorStatus_Deposited(t *testing.T) { depositCache, err := depositcache.New() require.NoError(t, err) - assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, root)) height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix() p := &mockPOW.POWChain{ TimesByHeight: map[int]uint64{ @@ -134,7 +138,9 @@ func TestValidatorStatus_PartiallyDeposited(t *testing.T) { depositCache, err := depositcache.New() require.NoError(t, err) - assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, root)) height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix() p := &mockPOW.POWChain{ TimesByHeight: map[int]uint64{ @@ -202,7 +208,9 @@ func TestValidatorStatus_Pending(t *testing.T) { depositCache, err := depositcache.New() require.NoError(t, err) - assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, root)) height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix() p := &mockPOW.POWChain{ @@ -247,7 +255,9 @@ func TestValidatorStatus_Active(t *testing.T) { depositCache, err := depositcache.New() require.NoError(t, err) - assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, root)) // Active because activation epoch <= current epoch < exit epoch. activeEpoch := helpers.ActivationExitEpoch(0) @@ -334,7 +344,9 @@ func TestValidatorStatus_Exiting(t *testing.T) { depositCache, err := depositcache.New() require.NoError(t, err) - assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, root)) height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix() p := &mockPOW.POWChain{ TimesByHeight: map[int]uint64{ @@ -391,7 +403,9 @@ func TestValidatorStatus_Slashing(t *testing.T) { depositCache, err := depositcache.New() require.NoError(t, err) - assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, root)) height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix() p := &mockPOW.POWChain{ TimesByHeight: map[int]uint64{ @@ -449,7 +463,9 @@ func TestValidatorStatus_Exited(t *testing.T) { depositCache, err := depositcache.New() require.NoError(t, err) - assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, root)) height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix() p := &mockPOW.POWChain{ TimesByHeight: map[int]uint64{ @@ -532,11 +548,15 @@ func TestActivationStatus_OK(t *testing.T) { depositCache, err := depositcache.New() require.NoError(t, err) - assert.NoError(t, depositCache.InsertDeposit(ctx, dep, 10 /*blockNum*/, 0, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, dep, 10 /*blockNum*/, 0, root)) dep = deposits[2] assert.NoError(t, depositTrie.Insert(dep.Data.Signature, 15)) - assert.NoError(t, depositCache.InsertDeposit(context.Background(), dep, 0, 1, depositTrie.HashTreeRoot())) + root, err = depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(context.Background(), dep, 0, 1, root)) vs := &Server{ Ctx: context.Background(), @@ -677,7 +697,9 @@ func TestValidatorStatus_CorrectActivationQueue(t *testing.T) { deposit := ðpb.Deposit{ Data: depData, } - assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, int64(i), depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, int64(i), root)) } @@ -758,10 +780,14 @@ func TestMultipleValidatorStatus_Pubkeys(t *testing.T) { require.NoError(t, err) dep := deposits[0] - assert.NoError(t, depositCache.InsertDeposit(ctx, dep, 10 /*blockNum*/, 0, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, dep, 10 /*blockNum*/, 0, root)) dep = deposits[2] assert.NoError(t, depositTrie.Insert(dep.Data.Signature, 15)) - assert.NoError(t, depositCache.InsertDeposit(context.Background(), dep, 0, 1, depositTrie.HashTreeRoot())) + root, err = depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(context.Background(), dep, 0, 1, root)) vs := &Server{ Ctx: context.Background(), @@ -918,7 +944,9 @@ func TestValidatorStatus_Invalid(t *testing.T) { depositCache, err := depositcache.New() require.NoError(t, err) - assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, depositTrie.HashTreeRoot())) + root, err := depositTrie.HashTreeRoot() + require.NoError(t, err) + assert.NoError(t, depositCache.InsertDeposit(ctx, deposit, 0 /*blockNum*/, 0, root)) height := time.Unix(int64(params.BeaconConfig().Eth1FollowDistance), 0).Unix() p := &mockPOW.POWChain{ TimesByHeight: map[int]uint64{ diff --git a/container/trie/BUILD.bazel b/container/trie/BUILD.bazel index 7e766f07b8..62489a5c7a 100644 --- a/container/trie/BUILD.bazel +++ b/container/trie/BUILD.bazel @@ -19,7 +19,11 @@ go_library( go_test( name = "go_default_test", size = "small", - srcs = ["sparse_merkle_test.go"], + srcs = [ + "sparse_merkle_test.go", + "sparse_merkle_trie_fuzz_test.go", + ], + data = glob(["testdata/**"]), deps = [ ":go_default_library", "//config/fieldparams:go_default_library", @@ -31,5 +35,6 @@ go_test( "//testing/assert:go_default_library", "//testing/require:go_default_library", "@com_github_ethereum_go_ethereum//accounts/abi/bind:go_default_library", + "@com_github_golang_protobuf//proto:go_default_library", ], ) diff --git a/container/trie/sparse_merkle.go b/container/trie/sparse_merkle.go index 502d33d9dc..5507c8f055 100644 --- a/container/trie/sparse_merkle.go +++ b/container/trie/sparse_merkle.go @@ -81,7 +81,11 @@ func (m *SparseMerkleTrie) Items() [][]byte { // HashTreeRoot of the Merkle trie as defined in the deposit contract. // Spec Definition: // sha256(concat(node, self.to_little_endian_64(self.deposit_count), slice(zero_bytes32, start=0, len=24))) -func (m *SparseMerkleTrie) HashTreeRoot() [32]byte { +func (m *SparseMerkleTrie) HashTreeRoot() ([32]byte, error) { + if len(m.branches) == 0 || len(m.branches[len(m.branches)-1]) == 0 { + return [32]byte{}, errors.New("invalid branches provided to compute root") + } + enc := [32]byte{} depositCount := uint64(len(m.originalItems)) if len(m.originalItems) == 1 && bytes.Equal(m.originalItems[0], ZeroHashes[0][:]) { @@ -89,7 +93,7 @@ func (m *SparseMerkleTrie) HashTreeRoot() [32]byte { depositCount = 0 } binary.LittleEndian.PutUint64(enc[:], depositCount) - return hash.Hash(append(m.branches[len(m.branches)-1][0], enc[:]...)) + return hash.Hash(append(m.branches[len(m.branches)-1][0], enc[:]...)), nil } // Insert an item into the trie. @@ -97,6 +101,12 @@ func (m *SparseMerkleTrie) Insert(item []byte, index int) error { if index < 0 { return fmt.Errorf("negative index provided: %d", index) } + if len(m.branches) == 0 { + return errors.New("invalid trie: no branches") + } + if m.depth > uint(len(m.branches)) { + return errors.New("invalid trie: depth is greater than number of branches") + } for index >= len(m.branches[0]) { m.branches[0] = append(m.branches[0], ZeroHashes[0][:]) } @@ -143,11 +153,17 @@ func (m *SparseMerkleTrie) MerkleProof(index int) ([][]byte, error) { if index < 0 { return nil, fmt.Errorf("merkle index is negative: %d", index) } - merkleIndex := uint(index) + if len(m.branches) == 0 { + return nil, errors.New("invalid trie: no branches") + } leaves := m.branches[0] if index >= len(leaves) { return nil, fmt.Errorf("merkle index out of range in trie, max range: %d, received: %d", len(leaves), index) } + if m.depth > uint(len(m.branches)) { + return nil, errors.New("invalid trie: depth is greater than number of branches") + } + merkleIndex := uint(index) proof := make([][]byte, m.depth+1) for i := uint(0); i < m.depth; i++ { subIndex := (merkleIndex / (1 << i)) ^ 1 @@ -185,6 +201,9 @@ func VerifyMerkleProofWithDepth(root, item []byte, merkleIndex uint64, proof [][ if uint64(len(proof)) != depth+1 { return false } + if depth >= 64 { + return false // PowerOf2 would overflow. + } node := bytesutil.ToBytes32(item) for i := uint64(0); i <= depth; i++ { if (merkleIndex / math.PowerOf2(i) % 2) != 0 { @@ -200,15 +219,10 @@ func VerifyMerkleProofWithDepth(root, item []byte, merkleIndex uint64, proof [][ // VerifyMerkleProof given a trie root, a leaf, the generalized merkle index // of the leaf in the trie, and the proof itself. func VerifyMerkleProof(root, item []byte, merkleIndex uint64, proof [][]byte) bool { - node := bytesutil.ToBytes32(item) - for i := 0; i < len(proof); i++ { - if (merkleIndex / math.PowerOf2(uint64(i)) % 2) != 0 { - node = hash.Hash(append(proof[i], node[:]...)) - } else { - node = hash.Hash(append(node[:], proof[i]...)) - } + if len(proof) == 0 { + return false } - return bytes.Equal(root, node[:]) + return VerifyMerkleProofWithDepth(root, item, merkleIndex, proof, uint64(len(proof)-1)) } // Copy performs a deep copy of the trie. diff --git a/container/trie/sparse_merkle_test.go b/container/trie/sparse_merkle_test.go index 463aac13a6..f0b12ba857 100644 --- a/container/trie/sparse_merkle_test.go +++ b/container/trie/sparse_merkle_test.go @@ -79,7 +79,9 @@ func TestMerkleTrieRoot_EmptyTrie(t *testing.T) { depRoot, err := testAccount.Contract.GetDepositRoot(&bind.CallOpts{}) require.NoError(t, err) - require.DeepEqual(t, depRoot, trie.HashTreeRoot()) + root, err := trie.HashTreeRoot() + require.NoError(t, err) + require.DeepEqual(t, depRoot, root) } func TestGenerateTrieFromItems_NoItemsProvided(t *testing.T) { @@ -104,7 +106,8 @@ func TestMerkleTrie_VerifyMerkleProofWithDepth(t *testing.T) { proof, err := m.MerkleProof(0) require.NoError(t, err) require.Equal(t, int(params.BeaconConfig().DepositContractTreeDepth)+1, len(proof)) - root := m.HashTreeRoot() + root, err := m.HashTreeRoot() + require.NoError(t, err) if ok := trie.VerifyMerkleProofWithDepth(root[:], items[0], 0, proof, params.BeaconConfig().DepositContractTreeDepth); !ok { t.Error("First Merkle proof did not verify") } @@ -130,7 +133,8 @@ func TestMerkleTrie_VerifyMerkleProof(t *testing.T) { proof, err := m.MerkleProof(0) require.NoError(t, err) require.Equal(t, int(params.BeaconConfig().DepositContractTreeDepth)+1, len(proof)) - root := m.HashTreeRoot() + root, err := m.HashTreeRoot() + require.NoError(t, err) if ok := trie.VerifyMerkleProof(root[:], items[0], 0, proof); !ok { t.Error("First Merkle proof did not verify") } @@ -170,14 +174,16 @@ func TestMerkleTrie_VerifyMerkleProof_TrieUpdated(t *testing.T) { require.NoError(t, err) proof, err := m.MerkleProof(0) require.NoError(t, err) - root := m.HashTreeRoot() + root, err := m.HashTreeRoot() + require.NoError(t, err) require.Equal(t, true, trie.VerifyMerkleProofWithDepth(root[:], items[0], 0, proof, depth)) // Now we update the trie. assert.NoError(t, m.Insert([]byte{5}, 3)) proof, err = m.MerkleProof(3) require.NoError(t, err) - root = m.HashTreeRoot() + root, err = m.HashTreeRoot() + require.NoError(t, err) if ok := trie.VerifyMerkleProofWithDepth(root[:], []byte{5}, 3, proof, depth); !ok { t.Error("Second Merkle proof did not verify") } @@ -200,10 +206,13 @@ func TestRoundtripProto_OK(t *testing.T) { require.NoError(t, err) protoTrie := m.ToProto() - depositRoot := m.HashTreeRoot() + depositRoot, err := m.HashTreeRoot() + require.NoError(t, err) newTrie := trie.CreateTrieFromProto(protoTrie) - require.DeepEqual(t, depositRoot, newTrie.HashTreeRoot()) + root, err := newTrie.HashTreeRoot() + require.NoError(t, err) + require.DeepEqual(t, depositRoot, root) } @@ -221,8 +230,11 @@ func TestCopy_OK(t *testing.T) { if copiedTrie == source { t.Errorf("Original trie returned.") } - copyHash := copiedTrie.HashTreeRoot() - require.DeepEqual(t, copyHash, copiedTrie.HashTreeRoot()) + a, err := copiedTrie.HashTreeRoot() + require.NoError(t, err) + b, err := source.HashTreeRoot() + require.NoError(t, err) + require.DeepEqual(t, a, b) } func BenchmarkGenerateTrieFromItems(b *testing.B) { @@ -296,7 +308,8 @@ func BenchmarkVerifyMerkleProofWithDepth(b *testing.B) { proof, err := m.MerkleProof(2) require.NoError(b, err) - root := m.HashTreeRoot() + root, err := m.HashTreeRoot() + require.NoError(b, err) b.StartTimer() for i := 0; i < b.N; i++ { if ok := trie.VerifyMerkleProofWithDepth(root[:], items[2], 2, proof, params.BeaconConfig().DepositContractTreeDepth); !ok { diff --git a/container/trie/sparse_merkle_trie_fuzz_test.go b/container/trie/sparse_merkle_trie_fuzz_test.go new file mode 100644 index 0000000000..1845fdb0e5 --- /dev/null +++ b/container/trie/sparse_merkle_trie_fuzz_test.go @@ -0,0 +1,148 @@ +package trie_test + +import ( + "testing" + + "github.com/golang/protobuf/proto" + "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/container/trie" + "github.com/prysmaticlabs/prysm/crypto/hash" + ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/testing/require" +) + +func FuzzSparseMerkleTrie_HashTreeRoot(f *testing.F) { + h := hash.Hash([]byte("hi")) + pb := ðpb.SparseMerkleTrie{ + Layers: []*ethpb.TrieLayer{ + { + Layer: [][]byte{h[:]}, + }, + { + Layer: [][]byte{h[:]}, + }, + { + Layer: [][]byte{}, + }, + }, + Depth: 4, + } + b, err := proto.Marshal(pb) + require.NoError(f, err) + f.Add(b) + + f.Fuzz(func(t *testing.T, b []byte) { + pb := ðpb.SparseMerkleTrie{} + if err := proto.Unmarshal(b, pb); err != nil { + return + } + _, err := trie.CreateTrieFromProto(pb).HashTreeRoot() + if err != nil { + return + } + }) +} + +func FuzzSparseMerkleTrie_MerkleProof(f *testing.F) { + h := hash.Hash([]byte("hi")) + pb := ðpb.SparseMerkleTrie{ + Layers: []*ethpb.TrieLayer{ + { + Layer: [][]byte{h[:]}, + }, + { + Layer: [][]byte{h[:]}, + }, + { + Layer: [][]byte{}, + }, + }, + Depth: 4, + } + b, err := proto.Marshal(pb) + require.NoError(f, err) + f.Add(b, 0) + + f.Fuzz(func(t *testing.T, b []byte, i int) { + pb := ðpb.SparseMerkleTrie{} + if err := proto.Unmarshal(b, pb); err != nil { + return + } + _, err := trie.CreateTrieFromProto(pb).MerkleProof(i) + if err != nil { + return + } + }) +} + +func FuzzSparseMerkleTrie_Insert(f *testing.F) { + h := hash.Hash([]byte("hi")) + pb := ðpb.SparseMerkleTrie{ + Layers: []*ethpb.TrieLayer{ + { + Layer: [][]byte{h[:]}, + }, + { + Layer: [][]byte{h[:]}, + }, + { + Layer: [][]byte{}, + }, + }, + Depth: 4, + } + b, err := proto.Marshal(pb) + require.NoError(f, err) + f.Add(b, []byte{}, 0) + + f.Fuzz(func(t *testing.T, b, item []byte, i int) { + pb := ðpb.SparseMerkleTrie{} + if err := proto.Unmarshal(b, pb); err != nil { + return + } + if err := trie.CreateTrieFromProto(pb).Insert(item, i); err != nil { + return + } + }) +} + +func FuzzSparseMerkleTrie_VerifyMerkleProofWithDepth(f *testing.F) { + splitProofs := func(proofRaw []byte) [][]byte { + var proofs [][]byte + for i := 0; i < len(proofRaw); i += 32 { + end := i + 32 + if end >= len(proofRaw) { + end = len(proofRaw) - 1 + } + proofs = append(proofs, proofRaw[i:end]) + } + return proofs + } + + items := [][]byte{ + []byte("A"), + []byte("B"), + []byte("C"), + []byte("D"), + []byte("E"), + []byte("F"), + []byte("G"), + []byte("H"), + } + m, err := trie.GenerateTrieFromItems(items, params.BeaconConfig().DepositContractTreeDepth) + require.NoError(f, err) + proof, err := m.MerkleProof(0) + require.NoError(f, err) + require.Equal(f, int(params.BeaconConfig().DepositContractTreeDepth)+1, len(proof)) + root, err := m.HashTreeRoot() + require.NoError(f, err) + var proofRaw []byte + for _, p := range proof { + proofRaw = append(proofRaw, p...) + } + f.Add(root[:], items[0], uint64(0), proofRaw, params.BeaconConfig().DepositContractTreeDepth) + + f.Fuzz(func(t *testing.T, root, item []byte, merkleIndex uint64, proofRaw []byte, depth uint64) { + trie.VerifyMerkleProofWithDepth(root, item, merkleIndex, splitProofs(proofRaw), depth) + }) +} diff --git a/container/trie/testdata/fuzz/FuzzSparseMerkleTrie_HashTreeRoot/5838cdfae7b16cde2707c04599b62223e7bade8dafdd8a1c53b8f881e8f79d99 b/container/trie/testdata/fuzz/FuzzSparseMerkleTrie_HashTreeRoot/5838cdfae7b16cde2707c04599b62223e7bade8dafdd8a1c53b8f881e8f79d99 new file mode 100644 index 0000000000..67322c7048 --- /dev/null +++ b/container/trie/testdata/fuzz/FuzzSparseMerkleTrie_HashTreeRoot/5838cdfae7b16cde2707c04599b62223e7bade8dafdd8a1c53b8f881e8f79d99 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("") diff --git a/container/trie/testdata/fuzz/FuzzSparseMerkleTrie_Insert/d863478086b8f8e8c854ec74d4d5360ffbe979064b58f619eb3d5fddfdae64c8 b/container/trie/testdata/fuzz/FuzzSparseMerkleTrie_Insert/d863478086b8f8e8c854ec74d4d5360ffbe979064b58f619eb3d5fddfdae64c8 new file mode 100644 index 0000000000..af4d585a98 --- /dev/null +++ b/container/trie/testdata/fuzz/FuzzSparseMerkleTrie_Insert/d863478086b8f8e8c854ec74d4d5360ffbe979064b58f619eb3d5fddfdae64c8 @@ -0,0 +1,4 @@ +go test fuzz v1 +[]byte("") +[]byte("0") +int(41) diff --git a/container/trie/testdata/fuzz/FuzzSparseMerkleTrie_MerkleProof/ec4893c52de558dbac9a850e4266850e109a9e6bc6bab59953b2a30027f66a93 b/container/trie/testdata/fuzz/FuzzSparseMerkleTrie_MerkleProof/ec4893c52de558dbac9a850e4266850e109a9e6bc6bab59953b2a30027f66a93 new file mode 100644 index 0000000000..29e4800b2f --- /dev/null +++ b/container/trie/testdata/fuzz/FuzzSparseMerkleTrie_MerkleProof/ec4893c52de558dbac9a850e4266850e109a9e6bc6bab59953b2a30027f66a93 @@ -0,0 +1,3 @@ +go test fuzz v1 +[]byte("") +int(63) diff --git a/container/trie/testdata/fuzz/FuzzSparseMerkleTrie_VerifyMerkleProofWithDepth/826f574b1f684ccfa610c2db07e0dc5ecfe453c6c95e4597476616ba0358c236 b/container/trie/testdata/fuzz/FuzzSparseMerkleTrie_VerifyMerkleProofWithDepth/826f574b1f684ccfa610c2db07e0dc5ecfe453c6c95e4597476616ba0358c236 new file mode 100644 index 0000000000..e6c83421f0 --- /dev/null +++ b/container/trie/testdata/fuzz/FuzzSparseMerkleTrie_VerifyMerkleProofWithDepth/826f574b1f684ccfa610c2db07e0dc5ecfe453c6c95e4597476616ba0358c236 @@ -0,0 +1,6 @@ +go test fuzz v1 +[]byte("0") +[]byte("000000000") +uint64(0) +[]byte("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))00000000000000000000000000000000000000000000000000000000000000") +uint64(71) diff --git a/contracts/deposit/deposit_tree_test.go b/contracts/deposit/deposit_tree_test.go index fb75b1e190..4d66a99c9a 100644 --- a/contracts/deposit/deposit_tree_test.go +++ b/contracts/deposit/deposit_tree_test.go @@ -23,7 +23,9 @@ func TestDepositTrieRoot_OK(t *testing.T) { depRoot, err := testAcc.Contract.GetDepositRoot(&bind.CallOpts{}) require.NoError(t, err) - assert.Equal(t, depRoot, localTrie.HashTreeRoot(), "Local deposit trie root and contract deposit trie root are not equal") + localRoot, err := localTrie.HashTreeRoot() + require.NoError(t, err) + assert.Equal(t, depRoot, localRoot, "Local deposit trie root and contract deposit trie root are not equal") privKeys, pubKeys, err := interop.DeterministicallyGenerateKeys(0 /*startIndex*/, 101) require.NoError(t, err) @@ -47,7 +49,9 @@ func TestDepositTrieRoot_OK(t *testing.T) { assert.NoError(t, localTrie.Insert(item[:], i)) depRoot, err = testAcc.Contract.GetDepositRoot(&bind.CallOpts{}) require.NoError(t, err) - assert.Equal(t, depRoot, localTrie.HashTreeRoot(), "Local deposit trie root and contract deposit trie root are not equal for index %d", i) + localRoot, err := localTrie.HashTreeRoot() + require.NoError(t, err) + assert.Equal(t, depRoot, localRoot, "Local deposit trie root and contract deposit trie root are not equal for index %d", i) } } @@ -61,7 +65,9 @@ func TestDepositTrieRoot_Fail(t *testing.T) { depRoot, err := testAcc.Contract.GetDepositRoot(&bind.CallOpts{}) require.NoError(t, err) - assert.Equal(t, depRoot, localTrie.HashTreeRoot(), "Local deposit trie root and contract deposit trie root are not equal") + localRoot, err := localTrie.HashTreeRoot() + require.NoError(t, err) + assert.Equal(t, depRoot, localRoot, "Local deposit trie root and contract deposit trie root are not equal") privKeys, pubKeys, err := interop.DeterministicallyGenerateKeys(0 /*startIndex*/, 101) require.NoError(t, err) @@ -89,6 +95,8 @@ func TestDepositTrieRoot_Fail(t *testing.T) { depRoot, err = testAcc.Contract.GetDepositRoot(&bind.CallOpts{}) require.NoError(t, err) - assert.NotEqual(t, depRoot, localTrie.HashTreeRoot(), "Local deposit trie root and contract deposit trie root are equal for index %d", i) + localRoot, err := localTrie.HashTreeRoot() + require.NoError(t, err) + assert.NotEqual(t, depRoot, localRoot, "Local deposit trie root and contract deposit trie root are equal for index %d", i) } } diff --git a/runtime/interop/generate_genesis_state.go b/runtime/interop/generate_genesis_state.go index 09721ceca4..3bdfba2fc5 100644 --- a/runtime/interop/generate_genesis_state.go +++ b/runtime/interop/generate_genesis_state.go @@ -54,7 +54,10 @@ func GenerateGenesisStateFromDepositData( if err != nil { return nil, nil, errors.Wrap(err, "could not generate deposits from the deposit data provided") } - root := trie.HashTreeRoot() + root, err := trie.HashTreeRoot() + if err != nil { + return nil, nil, errors.Wrap(err, "could not hash tree root of deposit trie") + } if genesisTime == 0 { genesisTime = uint64(time.Now().Unix()) } diff --git a/runtime/interop/generate_genesis_state_test.go b/runtime/interop/generate_genesis_state_test.go index 7a6a690460..cf6d6a01df 100644 --- a/runtime/interop/generate_genesis_state_test.go +++ b/runtime/interop/generate_genesis_state_test.go @@ -23,7 +23,8 @@ func TestGenerateGenesisState(t *testing.T) { require.NoError(t, err) deposits, err := interop.GenerateDepositsFromData(depositDataItems, tr) require.NoError(t, err) - root := tr.HashTreeRoot() + root, err := tr.HashTreeRoot() + require.NoError(t, err) genesisState, err := transition.GenesisBeaconState(context.Background(), deposits, 0, ð.Eth1Data{ DepositRoot: root[:], DepositCount: uint64(len(deposits)), diff --git a/testing/util/deposits.go b/testing/util/deposits.go index 13cd7dbd17..e45c779324 100644 --- a/testing/util/deposits.go +++ b/testing/util/deposits.go @@ -242,7 +242,10 @@ func DeterministicEth1Data(size int) (*ethpb.Eth1Data, error) { if err != nil { return nil, errors.Wrap(err, "failed to create trie") } - root := depositTrie.HashTreeRoot() + root, err := depositTrie.HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "failed to compute deposit trie root") + } eth1Data := ðpb.Eth1Data{ BlockHash: root[:], DepositRoot: root[:], diff --git a/testing/util/deposits_test.go b/testing/util/deposits_test.go index 2f6b433d92..b2354947bb 100644 --- a/testing/util/deposits_test.go +++ b/testing/util/deposits_test.go @@ -276,7 +276,10 @@ func TestDepositTrieFromDeposits(t *testing.T) { depositTrie, _, err := DepositTrieFromDeposits(deposits) require.NoError(t, err) - root := depositTrie.HashTreeRoot() + root, err := depositTrie.HashTreeRoot() + if err != nil { + t.Fatal(err) + } if !bytes.Equal(root[:], eth1Data.DepositRoot) { t.Fatal("expected deposit trie root to equal eth1data deposit root") }