package trie_test import ( "bytes" "context" "encoding/binary" "github.com/ledgerwatch/erigon-lib/common/hexutil" "github.com/ledgerwatch/erigon-lib/kv/dbutils" "math/big" "testing" "time" "github.com/holiman/uint256" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/hexutility" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon-lib/kv/memdb" "github.com/ledgerwatch/erigon/core/types/accounts" "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/eth/stagedsync" "github.com/ledgerwatch/erigon/turbo/trie" "github.com/ledgerwatch/log/v3" "github.com/stretchr/testify/require" ) // initialFlatDBTrieBuild leverages the stagedsync code to perform the initial // trie computation while also collecting the assorted hashes and loading them // into the TrieOfAccounts and TrieOfStorage tables func initialFlatDBTrieBuild(t *testing.T, db kv.RwDB) libcommon.Hash { t.Helper() //startTime := time.Now() tx, err := db.BeginRw(context.Background()) require.NoError(t, err) defer tx.Rollback() stageTrieCfg := stagedsync.StageTrieCfg(db, false, false, false, t.TempDir(), nil, nil, false, nil) hash, err := stagedsync.RegenerateIntermediateHashes("test", tx, stageTrieCfg, libcommon.Hash{}, context.Background(), log.New()) require.NoError(t, err) tx.Commit() //t.Logf("Initial hash is %s and took %v", hash, time.Since(startTime)) return hash } // seedInitialAccounts uses the provided hashes to commit simpleAccountValBytes // at each corresponding key in the HashedAccounts table. func seedInitialAccounts(t *testing.T, db kv.RwDB, hashes []libcommon.Hash) { tx, err := db.BeginRw(context.Background()) require.NoError(t, err) defer tx.Rollback() for _, hash := range hashes { //t.Logf("Seeding initial account with hash %s", hash) err = tx.Put(kv.HashedAccounts, hash[:], simpleAccountValBytes) require.NoError(t, err) } err = tx.Commit() require.NoError(t, err) } // seedModifiedAccounts uses the provided hashes to commit // simpleModifiedAccountValBytes at each corresponding key in the HashedAccounts // table. It returns a slice of keys to be used when constructing the retain // list for the FlatDBTrie computations. func seedModifiedAccounts(t *testing.T, db kv.RwDB, hashes []libcommon.Hash) [][]byte { toRetain := make([][]byte, 0, len(hashes)) tx, err := db.BeginRw(context.Background()) require.NoError(t, err) defer tx.Rollback() for _, hash := range hashes { //t.Logf("Seeding modified account with hash %s", hash) err = tx.Put(kv.HashedAccounts, hash[:], simpleModifiedAccountValBytes) require.NoError(t, err) toRetain = append(toRetain, append([]byte{}, hash[:]...)) } err = tx.Commit() require.NoError(t, err) return toRetain } // seedInitialStorage creates a single account with hash storageAccountHash in // the HashedAccounts table, as well as a storage entry in HashedStorage for // this account with value storageInitialValue for each provided hash. A slice // of the constructed storage keys is returned. func seedInitialStorage(t *testing.T, db kv.RwDB, hashes []libcommon.Hash) [][]byte { // We seed a single account, and all storage keys belong to this account. keys := make([][]byte, 0, len(hashes)) tx, err := db.BeginRw(context.Background()) require.NoError(t, err) defer tx.Rollback() err = tx.Put(kv.HashedAccounts, storageAccountHash[:], simpleContractAccountValBytes) require.NoError(t, err) var storageKey [72]byte binary.BigEndian.PutUint64(storageKey[32:40], 1) for _, hash := range hashes { copy(storageKey[:32], storageAccountHash[:]) copy(storageKey[40:], hash[:]) //t.Logf("Seeding storage with key 0x%x", storageKey) err = tx.Put(kv.HashedStorage, storageKey[:], storageInitialValue[:]) require.NoError(t, err) keys = append(keys, append([]byte{}, storageKey[:]...)) } err = tx.Commit() require.NoError(t, err) return keys } // seedModifedStorage writes a storage entry in HashedStorage for // storageAccountHash with value storageModifiedValue for each provided hash. A // slice containing the modified keys is returned to help the caller construct // the retain list necessary for the FlatDBTrie computations. func seedModifiedStorage(t *testing.T, db kv.RwDB, hashes []libcommon.Hash) [][]byte { // Modify this same account's storage entries. For now, this test only // validates modifying existing or adding new keys (no deletions). toRetain := make([][]byte, 0, len(hashes)) var storageKey [72]byte binary.BigEndian.PutUint64(storageKey[32:40], 1) tx, err := db.BeginRw(context.Background()) require.NoError(t, err) defer tx.Rollback() for _, hash := range hashes { copy(storageKey[:32], storageAccountHash[:]) copy(storageKey[40:], hash[:]) //t.Logf("Seeding storage with modified hash 0x%x", storageKey) err = tx.Put(kv.HashedStorage, storageKey[:], storageModifiedValue[:]) require.NoError(t, err) toRetain = append(toRetain, append([]byte{}, storageKey[:]...)) } err = tx.Commit() require.NoError(t, err) return toRetain } // rebuildFlatDBTrieHash will re-compute the root hash using the given retain // list. Even without a retain list, this is not a no-op in the accounts case, // as the root of the trie is not actually stored and must be re-computed // regardless. However, for storage entries at least one element must be in the // retain list or the root is simply returned. func rebuildFlatDBTrieHash(t *testing.T, rl *trie.RetainList, db kv.RoDB) libcommon.Hash { t.Helper() //startTime := time.Now() loader := trie.NewFlatDBTrieLoader("test", rl, nil, nil, false) tx, err := db.BeginRo(context.Background()) defer tx.Rollback() require.NoError(t, err) hash, err := loader.CalcTrieRoot(tx, nil) tx.Rollback() require.NoError(t, err) //t.Logf("Rebuilt hash is %s and took %v", hash, time.Since(startTime)) return hash } // proveFlatDB will generate an account proof result utilizing the FlatDBTrie // hashing mechanism. The proofKeys cannot span more than one account (ie, an // account and its storage nodes is acceptable). It returns the root hash for // the computation as well as the proof result. func proveFlatDB(t *testing.T, db kv.RoDB, accountMissing bool, retainKeys, proofKeys [][]byte) (libcommon.Hash, *accounts.AccProofResult) { t.Helper() //startTime := time.Now() rl := trie.NewRetainList(0) for _, retainKey := range retainKeys { rl.AddKeyWithMarker(retainKey, true) } loader := trie.NewFlatDBTrieLoader("test", rl, nil, nil, false) acc := &accounts.Account{} if !accountMissing { acc.Incarnation = 1 acc.Initialised = true } pr := trie.NewManualProofRetainer(t, acc, rl, proofKeys) loader.SetProofRetainer(pr) tx, err := db.BeginRo(context.Background()) defer tx.Rollback() require.NoError(t, err) hash, err := loader.CalcTrieRoot(tx, nil) tx.Rollback() require.NoError(t, err) //t.Logf("Proof root hash is %s and took %v", hash, time.Since(startTime)) res, err := pr.ProofResult() require.NoError(t, err) return hash, res } // logTrieTables simply writes the TrieOfAccounts and TrieOfStorage to the test // output. It can be very helpful when debugging failed fuzzing cases. func logTrieTables(t *testing.T, db kv.RoDB) { //nolint t.Helper() tx, err := db.BeginRo(context.Background()) require.NoError(t, err) defer tx.Rollback() trieAccC, err := tx.Cursor(kv.TrieOfAccounts) require.NoError(t, err) defer trieAccC.Close() var k, v []byte t.Logf("TrieOfAccounts iteration begin") for k, v, err = trieAccC.First(); err == nil && k != nil; k, v, err = trieAccC.Next() { t.Logf("\t%x=%x", k, v) } require.NoError(t, err) t.Logf("TrieOfAccounts iteration end") trieStorageC, err := tx.CursorDupSort(kv.TrieOfStorage) require.NoError(t, err) defer trieStorageC.Close() t.Logf("TrieOfStorage iteration begin") for k, v, err = trieStorageC.First(); err == nil && k != nil; k, v, err = trieStorageC.Next() { t.Logf("\t%x=%x", k, v) } require.NoError(t, err) t.Logf("TrieStorage iteration end") } var ( // simpleAccountValBytes is a simple marshaled EoA without storage or balance simpleAccountValBytes = func() []byte { accVal := accounts.Account{Nonce: 1, Initialised: true} encodedAccVal := make([]byte, accVal.EncodingLengthForStorage()) accVal.EncodeForStorage(encodedAccVal) return encodedAccVal }() simpleModifiedAccountValBytes = func() []byte { accVal := accounts.Account{Nonce: 2, Initialised: true} encodedAccVal := make([]byte, accVal.EncodingLengthForStorage()) accVal.EncodeForStorage(encodedAccVal) return encodedAccVal }() // simpleContractAccountValBytes is a simple marshaled account with storage and code simpleContractAccountValBytes = func() []byte { accVal := accounts.Account{Nonce: 1, Initialised: true, Incarnation: 1, CodeHash: libcommon.Hash{9}} encodedAccVal := make([]byte, accVal.EncodingLengthForStorage()) accVal.EncodeForStorage(encodedAccVal) return encodedAccVal }() storageAccountHash = libcommon.Hash{1} storageAccountCodeHash = libcommon.Hash{9} storageInitialValue = libcommon.Hash{2} storageModifiedValue = libcommon.Hash{3} // missingHash is a constant value that is impossible for the fuzzers to // generate (since the last 26 bytes of all fuzzing keys must be 0). The // tests know that when this value is encountered the expected result should // be 'missing', ie in the storage proof the value should be 0, and in the // account proof the nonce/codeHash/storageHash/balance should be empty. missingHash = libcommon.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") ) // naiveTriesAndHashFromDB constructs the naive trie implementation by simply adding // all of the accounts and storage keys linearly. It also returns a map of // storage tries func naiveTriesAndHashFromDB(t *testing.T, db kv.RoDB) (*trie.Trie, *trie.Trie, libcommon.Hash) { t.Helper() startTime := time.Now() testTrie := trie.NewTestRLPTrie(libcommon.Hash{}) tx, err := db.BeginRo(context.Background()) require.NoError(t, err) defer tx.Rollback() ac, err := tx.Cursor(kv.HashedAccounts) require.NoError(t, err) defer ac.Close() var k, v []byte var storageTrie *trie.Trie for k, v, err = ac.First(); k != nil; k, v, err = ac.Next() { accHash := k[:32] var acc accounts.Account err := acc.DecodeForStorage(v) require.NoError(t, err) sc, err := tx.Cursor(kv.HashedStorage) require.NoError(t, err) defer sc.Close() startStorageKey := dbutils.GenerateCompositeStorageKey(libcommon.BytesToHash(k), acc.Incarnation, libcommon.Hash{}) endStorageKey := dbutils.GenerateCompositeStorageKey(libcommon.BytesToHash(k), acc.Incarnation+1, libcommon.Hash{}) storageTrie = trie.NewTestRLPTrie(libcommon.Hash{}) for k, v, err = sc.Seek(startStorageKey); k != nil; k, v, err = sc.Next() { if bytes.Compare(k, endStorageKey) >= 0 { break } sk := k[32+8:] sv := append([]byte{}, v...) esv, err := trie.EncodeAsValue(sv) require.NoError(t, err) storageTrie.Update(sk, esv) } require.NoError(t, err) acc.Root = storageTrie.Hash() accBytes := make([]byte, acc.EncodingLengthForHashing()) acc.EncodeForHashing(accBytes) testTrie.Update(accHash, accBytes) } require.NoError(t, err) naiveHash := testTrie.Hash() t.Logf("Naive hash is %s and took %v", naiveHash, time.Since(startTime)) return testTrie, storageTrie, naiveHash } // addFuzzTrieSeeds adds a common set of trie hashes which are problematic for // the flatDB trie hash construction. Because there is a code path for account // tries and a slightly separate one for storage tries, we use this common set // for both. These seeeds were constructed by executing the fuzzing until a // failing case was detected, then refining to the minimal set of keys (and // re-naming nibbles to be more readable if possible"). func addFuzzTrieSeeds(f *testing.F) { f.Helper() noModificationTests := [][]string{ {"0x0a00", "0x0bc0", "0x0bd0"}, {"0xff00", "0xfff0", "0xffff"}, {"0xa000", "0xaa00", "0xaaa0", "0xb000", "0xcc00", "0xccc0", "0xcdd0"}, {"0xa000", "0xaa00", "0xbb00", "0xbbb0", "0xbbbb"}, {"0xa00000", "0xaaa000", "0xaaaa00", "0xaaaaa0", "0xb00000", "0xccc000", "0xccc0c0", "0xcccc00"}, {"a000", "b000", "b0b0", "bbb0"}, {"a00000", "bbbb00", "bbbb0b", "bbbbb0", "c00000", "cc0000", "ccc000"}, } for _, tt := range noModificationTests { var buffer bytes.Buffer fragLen := len(tt[0]) require.Equal(f, 0, fragLen%2, "Fragments must be even in length") for _, fragment := range tt { buffer.Write(hexutility.FromHex(fragment)) require.Len(f, fragment, fragLen, "All fragments must be the same length") } f.Add(len(tt), 0, buffer.Bytes()) } modificationTests := []struct { initial []string modified []string }{ {initial: []string{"0xaa"}, modified: []string{"0xbb"}}, {initial: []string{"0xa000", "0xa0aa", "0xaaaa"}, modified: []string{"0xa00a"}}, {initial: []string{"0xa000", "0xa0a0", "0xab00", "0xabb0"}, modified: []string{"0xaa00"}}, {initial: []string{"0xaaa0", "0xaaaa", "0xaaba", "0xaba0", "0xabb0"}, modified: []string{"0xaaa0", "0xaba0"}}, } for _, tt := range modificationTests { var buffer bytes.Buffer fragLen := len(tt.initial[0]) require.Equal(f, 0, fragLen%2, "Fragments must be even in length") for _, fragment := range tt.initial { buffer.Write(hexutility.FromHex(fragment)) require.Len(f, fragment, fragLen, "All fragments must be the same length") } for _, fragment := range tt.modified { buffer.Write(hexutility.FromHex(fragment)) require.Len(f, fragment, fragLen, "All fragments must be the same length") } f.Add(len(tt.initial), len(tt.modified), buffer.Bytes()) } } // validateFuzzInputs ensures that the raw fuzzing inputs can be mapped into the // actual required fuzzing structure of a series of hashes. For the purpose of // narrowing the search space, we require that all hashes are between 1 and 6 // bytes long. func validateFuzzInputs(t *testing.T, initialCount, modifiedCount int, hashFragments []byte) ([]libcommon.Hash, []libcommon.Hash) { t.Helper() if initialCount <= 0 || modifiedCount < 0 || len(hashFragments) == 0 { t.Skip("account count must be positive and hashFragments cannot be empty") } count := initialCount + modifiedCount if count > 100 { t.Skip("all failures thusfar can be reproduced within 10 keys, so 100 seems like a reasonable cutoff to balance speed of fuzzing with coverage") } if len(hashFragments)%count != 0 { t.Skip("hash fragments must be divisible by accountCount") } fragmentSize := len(hashFragments) / count if fragmentSize > 6 { t.Skip("hash fragments cannot exceed 6 bytes") } uniqueInitialKeys := map[libcommon.Hash]struct{}{} uniqueModifiedKeys := map[libcommon.Hash]struct{}{} var inititalKeys, modifiedKeys []libcommon.Hash for i := 0; i < count; i++ { var hash libcommon.Hash copy(hash[:], hashFragments[i*fragmentSize:(i+1)*fragmentSize]) if i < initialCount { if _, ok := uniqueInitialKeys[hash]; ok { t.Skip("hash fragments must be unique") } uniqueInitialKeys[hash] = struct{}{} inititalKeys = append(inititalKeys, hash) } else { if _, ok := uniqueModifiedKeys[hash]; ok { t.Skip("hash fragments must be unique") } uniqueModifiedKeys[hash] = struct{}{} modifiedKeys = append(modifiedKeys, hash) } } return inititalKeys, modifiedKeys } // FuzzTrieRootStorage will fuzz assorted storage hash tables. First, it will // build an initial hash, adding the storage keys to a single account entry. // Then, if the set of modifications was empty, it will verify that the same // hash is re-computed. Then, it will compute the naive trie hash and compare it // with the flat db hash. In the event that the modfiications are empty, this // check additionally confirms that the initial trie hashing was correct. // See the validateFuzzInputs function for details on how the paramaters are // translated to initial and modified hashes. func FuzzTrieRootStorage(f *testing.F) { addFuzzTrieSeeds(f) f.Fuzz(func(t *testing.T, initialCount, modifiedCount int, hashFragments []byte) { initialKeys, modifiedKeys := validateFuzzInputs(t, initialCount, modifiedCount, hashFragments) db := memdb.NewTestDB(t) defer db.Close() storageKeys := seedInitialStorage(t, db, initialKeys) initialHash := initialFlatDBTrieBuild(t, db) //logTrieTables(t, db) retainKeys := seedModifiedStorage(t, db, modifiedKeys) rl := trie.NewRetainList(0) if len(retainKeys) == 0 { // If there are no modifications, we add the first storage key to the // retain list, because the inputs are random, this is not necessarily // the actual lexicographically first key, but without some retained key // then the root hash is simply returned. rl.AddKey(storageKeys[0]) } for _, retainKey := range retainKeys { rl.AddKeyWithMarker(retainKey, true) } reHash := rebuildFlatDBTrieHash(t, rl, db) if len(modifiedKeys) == 0 { require.Equal(t, initialHash, reHash) } _, _, naiveHash := naiveTriesAndHashFromDB(t, db) require.Equal(t, reHash, naiveHash) }) } // FuzzTrieRootAccounts will fuzz assorted account hash tables. First, it will // build an initial hash, then modify the account hash table and re-compute the // hash. If the set of modifications was empty, it will verify that the same // hash is re-computed. Then, it will compute the naive trie hash and compare // it with the flat db hash. In the event that the modfiications are empty, // this check additionally confirms that the initial trie hashing was correct. // See the validateFuzzInputs function for details on how the paramaters are // translated to initial and modified hashes. func FuzzTrieRootAccounts(f *testing.F) { addFuzzTrieSeeds(f) f.Fuzz(func(t *testing.T, initialCount, modifiedCount int, hashFragments []byte) { initialKeys, modifiedKeys := validateFuzzInputs(t, initialCount, modifiedCount, hashFragments) db := memdb.NewTestDB(t) defer db.Close() seedInitialAccounts(t, db, initialKeys) initialHash := initialFlatDBTrieBuild(t, db) //logTrieTables(t, db) retainKeys := seedModifiedAccounts(t, db, modifiedKeys) rl := trie.NewRetainList(0) for _, retainKey := range retainKeys { rl.AddKeyWithMarker(retainKey, true) } reHash := rebuildFlatDBTrieHash(t, rl, db) if len(modifiedKeys) == 0 { require.Equal(t, initialHash, reHash) } _, _, naiveHash := naiveTriesAndHashFromDB(t, db) require.Equal(t, reHash, naiveHash) }) } // FuzzTrieRootAccountProofs seeds the database with an initial set of accounts and // populates the trie tables. Then, it modifies the database of accounts but // does not update the trie tables. Finally, for every seeded account (modified // and not), it computes a proof for that key and verifies that the proof // matches the one as computed by the naive trie implementation and that that // proof is valid. func FuzzTrieRootAccountProofs(f *testing.F) { addFuzzTrieSeeds(f) f.Fuzz(func(t *testing.T, initialCount, modifiedCount int, hashFragments []byte) { initialKeys, modifiedKeys := validateFuzzInputs(t, initialCount, modifiedCount, hashFragments) db := memdb.NewTestDB(t) defer db.Close() seedInitialAccounts(t, db, initialKeys) initialFlatDBTrieBuild(t, db) retainKeys := seedModifiedAccounts(t, db, modifiedKeys) naiveTrie, _, naiveHash := naiveTriesAndHashFromDB(t, db) allKeys := make([]libcommon.Hash, 1, initialCount+modifiedCount+1) allKeys[0] = missingHash wasModified := map[libcommon.Hash]struct{}{} for _, hash := range modifiedKeys { allKeys = append(allKeys, hash) wasModified[hash] = struct{}{} } for _, hash := range initialKeys { if _, ok := wasModified[hash]; ok { continue } allKeys = append(allKeys, hash) } for _, hash := range allKeys { t.Logf("Processing account key %x", hash) // First compute the naive proof and verify that the proof is correct. naiveProofBytes, err := naiveTrie.Prove(hash[:], 0, false) require.NoError(t, err) naiveAccountProof := make([]hexutility.Bytes, len(naiveProofBytes)) for i, part := range naiveProofBytes { naiveAccountProof[i] = hexutility.Bytes(part) } var nonce uint64 var codeHash, storageHash libcommon.Hash if hash != missingHash { nonce = uint64(1) if _, ok := wasModified[hash]; ok { nonce++ } storageHash = trie.EmptyRoot codeHash = trie.EmptyCodeHash } naiveProof := &accounts.AccProofResult{ Nonce: hexutil.Uint64(nonce), AccountProof: naiveAccountProof, StorageHash: storageHash, Balance: (*hexutil.Big)(new(uint256.Int).ToBig()), StorageProof: []accounts.StorProofResult{}, CodeHash: codeHash, } err = trie.VerifyAccountProofByHash(naiveHash, hash, naiveProof) require.NoError(t, err, "Invalid naive proof") // Now do the same but with the 'real' flat implementation and verify // that the result matches byte for byte. flatHash, flatProof := proveFlatDB(t, db, hash == missingHash, retainKeys, [][]byte{hash[:]}) require.Equal(t, naiveHash, flatHash) require.Equal(t, len(naiveProof.AccountProof), len(flatProof.AccountProof)) for i, proof := range naiveProof.AccountProof { require.Equal(t, proof, flatProof.AccountProof[i], "mismatch at index %d", i) } flatProof.Nonce = hexutil.Uint64(nonce) flatProof.CodeHash = codeHash require.Equal(t, flatProof, naiveProof, "for key %s", hash) } }) } // FuzzTrieRootStorageProofs seeds the database with an initial account and set // of storage nodes for that account and populates the trie tables. Then, it // modifies the database of storage but does not update the trie tables. // Finally, for every seeded storage key (modified and not), it computes a proof for // that key and verifies that the proof matches the one as computed by the naive // trie implementation and that that proof is valid. func FuzzTrieRootStorageProofs(f *testing.F) { addFuzzTrieSeeds(f) f.Fuzz(func(t *testing.T, initialCount, modifiedCount int, hashFragments []byte) { initialKeys, modifiedKeys := validateFuzzInputs(t, initialCount, modifiedCount, hashFragments) db := memdb.NewTestDB(t) defer db.Close() initialStorageKeys := seedInitialStorage(t, db, initialKeys) initialFlatDBTrieBuild(t, db) retainKeys := seedModifiedStorage(t, db, modifiedKeys) naiveTrie, naiveStorageTrie, naiveHash := naiveTriesAndHashFromDB(t, db) allKeys := make([][]byte, 1, initialCount+modifiedCount+1) allKeys[0] = dbutils.GenerateCompositeStorageKey(storageAccountHash, 1, missingHash) wasModified := map[string]struct{}{} for _, key := range retainKeys { allKeys = append(allKeys, key) wasModified[string(key)] = struct{}{} } for _, key := range initialStorageKeys { if _, ok := wasModified[string(key)]; ok { continue } allKeys = append(allKeys, key) } // omniProof constructs a proof for the account and all of the keys to be // tested. We later check that this composite proof matches the individual // proofs on an element basis. _, omniProof := proveFlatDB(t, db, false, retainKeys, allKeys) for i, storageKey := range allKeys { //t.Logf("Processing storage key %x", storageKey) // First compute the naive proof and verify that the proof is correct. naiveAccountProofBytes, err := naiveTrie.Prove(storageAccountHash[:], 0, false) require.NoError(t, err) naiveAccountProof := make([]hexutility.Bytes, len(naiveAccountProofBytes)) for i, part := range naiveAccountProofBytes { naiveAccountProof[i] = hexutility.Bytes(part) } naiveStorageProofBytes, err := naiveStorageTrie.Prove(storageKey[40:], 0, true) require.NoError(t, err) naiveStorageProof := make([]hexutility.Bytes, len(naiveStorageProofBytes)) for i, part := range naiveStorageProofBytes { naiveStorageProof[i] = hexutility.Bytes(part) } var storageKeyHash libcommon.Hash copy(storageKeyHash[:], storageKey[40:]) var storageValue big.Int if storageKeyHash != missingHash { if _, ok := wasModified[string(storageKey)]; !ok { storageValue.SetBytes(storageInitialValue[:]) } else { storageValue.SetBytes(storageModifiedValue[:]) } } naiveProof := &accounts.AccProofResult{ Nonce: 1, AccountProof: naiveAccountProof, StorageHash: crypto.Keccak256Hash(naiveStorageProof[0]), Balance: (*hexutil.Big)(new(uint256.Int).ToBig()), StorageProof: []accounts.StorProofResult{ { Key: trie.FakePreimage(storageKeyHash), Value: (*hexutil.Big)(&storageValue), Proof: naiveStorageProof, }, }, CodeHash: storageAccountCodeHash, } err = trie.VerifyAccountProofByHash(naiveHash, storageAccountHash, naiveProof) require.NoError(t, err, "Invalid naive account proof") err = trie.VerifyStorageProofByHash(naiveProof.StorageHash, storageKeyHash, naiveProof.StorageProof[0]) require.NoError(t, err, "Invalid naive storage proof") // Now do the same but with the 'real' flat implementation and verify // that the result matches byte for byte. flatHash, flatProof := proveFlatDB(t, db, false, retainKeys, [][]byte{storageAccountHash[:], storageKey}) require.Equal(t, naiveHash, flatHash) for i, proof := range naiveProof.AccountProof { require.Equal(t, proof, flatProof.AccountProof[i], "mismatch at index %d", i) } flatProof.Nonce = 1 flatProof.CodeHash = storageAccountCodeHash require.Equal(t, flatProof, naiveProof, "failed for hash %s", storageKeyHash) require.Equal(t, flatProof.StorageProof[0], omniProof.StorageProof[i]) } }) }