diff --git a/core/state/database.go b/core/state/database.go index 9f183da0f7fd..b55f870d906f 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" + "github.com/crate-crypto/go-ipa/banderwagon" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/core/rawdb" @@ -29,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/utils" - "github.com/gballet/go-verkle" ) const ( @@ -38,6 +38,12 @@ const ( // Cache size granted for caching clean code. codeCacheSize = 64 * 1024 * 1024 + + // commitmentSize is the size of commitment stored in cache. + commitmentSize = banderwagon.UncompressedSize + + // Cache item granted for caching commitment results. + commitmentCacheItems = 64 * 1024 * 1024 / (commitmentSize + common.AddressLength) ) // Database wraps access to tries and contract code. @@ -72,11 +78,6 @@ type Trie interface { // TODO(fjl): remove this when StateTrie is removed GetKey([]byte) []byte - // GetStorage returns the value for key stored in the trie. The value bytes - // must not be modified by the caller. If a node was not found in the database, - // a trie.MissingNodeError is returned. - GetStorage(addr common.Address, key []byte) ([]byte, error) - // GetAccount abstracts an account read from the trie. It retrieves the // account blob from the trie with provided account address and decodes it // with associated decoding algorithm. If the specified account is not in @@ -85,27 +86,32 @@ type Trie interface { // be returned. GetAccount(address common.Address) (*types.StateAccount, error) - // UpdateStorage associates key with value in the trie. If value has length zero, - // any existing value is deleted from the trie. The value bytes must not be modified - // by the caller while they are stored in the trie. If a node was not found in the - // database, a trie.MissingNodeError is returned. - UpdateStorage(addr common.Address, key, value []byte) error + // GetStorage returns the value for key stored in the trie. The value bytes + // must not be modified by the caller. If a node was not found in the database, + // a trie.MissingNodeError is returned. + GetStorage(addr common.Address, key []byte) ([]byte, error) // UpdateAccount abstracts an account write to the trie. It encodes the // provided account object with associated algorithm and then updates it // in the trie with provided address. UpdateAccount(address common.Address, account *types.StateAccount) error - // UpdateContractCode abstracts code write to the trie. It is expected - // to be moved to the stateWriter interface when the latter is ready. - UpdateContractCode(address common.Address, codeHash common.Hash, code []byte) error + // UpdateStorage associates key with value in the trie. If value has length zero, + // any existing value is deleted from the trie. The value bytes must not be modified + // by the caller while they are stored in the trie. If a node was not found in the + // database, a trie.MissingNodeError is returned. + UpdateStorage(addr common.Address, key, value []byte) error + + // DeleteAccount abstracts an account deletion from the trie. + DeleteAccount(address common.Address) error // DeleteStorage removes any existing value for key from the trie. If a node // was not found in the database, a trie.MissingNodeError is returned. DeleteStorage(addr common.Address, key []byte) error - // DeleteAccount abstracts an account deletion from the trie. - DeleteAccount(address common.Address) error + // UpdateContractCode abstracts code write to the trie. It is expected + // to be moved to the stateWriter interface when the latter is ready. + UpdateContractCode(address common.Address, codeHash common.Hash, code []byte) error // Hash returns the root hash of the trie. It does not write to the database and // can be used even if the trie doesn't have one. @@ -173,25 +179,7 @@ type cachingDB struct { // OpenTrie opens the main account trie at a specific root hash. func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { if db.triedb.IsVerkle() { - reader, err := db.triedb.Reader(root) - if err != nil { - return nil, fmt.Errorf("failed to get node reader in OpenTrie: %w", err) - } - - var verkleroot verkle.VerkleNode - if root != (common.Hash{}) && root != types.EmptyRootHash { - verklerootbytes, err := reader.Node(common.Hash{}, nil, common.Hash{}) - if err != nil { - return nil, fmt.Errorf("failed to get serialized root node in OpenTrie: %w", err) - } - verkleroot, err = verkle.ParseNode(verklerootbytes, 0) - if err != nil { - return nil, fmt.Errorf("failed to deserialize root node in OpenTrie: %w", err) - } - } else { - verkleroot = verkle.New() - } - return trie.NewVerkleTrie(root, verkleroot, db.triedb, utils.NewPointCache(), true) + return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(commitmentCacheItems)) } tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) if err != nil { diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index 5a1f9aa080a7..c2a49417d458 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -305,7 +305,8 @@ func (sf *subfetcher) loop() { } sf.trie = trie } else { - // the trie argument can be nil as verkle doesn't support prefetching + // The trie argument can be nil as verkle doesn't support prefetching + // yet. TODO FIX IT(rjl493456442), otherwise code will panic here. trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil) if err != nil { log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) diff --git a/core/types/hashes.go b/core/types/hashes.go index 3a787aa136f8..43e9130fd170 100644 --- a/core/types/hashes.go +++ b/core/types/hashes.go @@ -23,7 +23,7 @@ import ( ) var ( - // EmptyRootHash is the known root hash of an empty trie. + // EmptyRootHash is the known root hash of an empty merkle trie. EmptyRootHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") // EmptyUncleHash is the known hash of the empty uncle set. @@ -40,6 +40,9 @@ var ( // EmptyWithdrawalsHash is the known hash of the empty withdrawal set. EmptyWithdrawalsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // EmptyVerkleHash is the known hash of an empty verkle trie. + EmptyVerkleHash = common.Hash{} ) // TrieRootHash returns the hash itself if it's non-empty or the predefined diff --git a/light/odr_test.go b/light/odr_test.go index c415d73e7ef2..de12f9b7ef0e 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -89,7 +89,7 @@ func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error { t state.Trie ) if len(req.Id.AccountAddress) > 0 { - t, err = odr.serverState.OpenStorageTrie(req.Id.StateRoot, common.BytesToAddress(req.Id.AccountAddress), req.Id.Root) + t, err = odr.serverState.OpenStorageTrie(req.Id.StateRoot, common.BytesToAddress(req.Id.AccountAddress), req.Id.Root, nil) } else { t, err = odr.serverState.OpenTrie(req.Id.Root) } diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go index 17fdf1ade343..0ed8f83e7502 100644 --- a/trie/utils/verkle.go +++ b/trie/utils/verkle.go @@ -1,4 +1,4 @@ -// Copyright 2021 go-ethereum Authors +// Copyright 2023 go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -21,11 +21,15 @@ import ( "sync" "github.com/crate-crypto/go-ipa/bandersnatch/fr" + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/metrics" "github.com/gballet/go-verkle" "github.com/holiman/uint256" ) const ( + // The spec of verkle key encoding can be found here. + // https://notes.ethereum.org/@vbuterin/verkle_tree_eip#Tree-embedding VersionLeafKey = 0 BalanceLeafKey = 1 NonceLeafKey = 2 @@ -35,59 +39,81 @@ const ( var ( zero = uint256.NewInt(0) - VerkleNodeWidthLog2 = 8 - HeaderStorageOffset = uint256.NewInt(64) - mainStorageOffsetLshVerkleNodeWidth = new(uint256.Int).Lsh(uint256.NewInt(256), 31-uint(VerkleNodeWidthLog2)) - CodeOffset = uint256.NewInt(128) - MainStorageOffset = new(uint256.Int).Lsh(uint256.NewInt(256), 31) - VerkleNodeWidth = uint256.NewInt(256) - codeStorageDelta = uint256.NewInt(0).Sub(CodeOffset, HeaderStorageOffset) - - getTreePolyIndex0Point *verkle.Point + verkleNodeWidthLog2 = 8 + headerStorageOffset = uint256.NewInt(64) + mainStorageOffsetLshVerkleNodeWidth = new(uint256.Int).Lsh(uint256.NewInt(256), 31-uint(verkleNodeWidthLog2)) + codeOffset = uint256.NewInt(128) + verkleNodeWidth = uint256.NewInt(256) + codeStorageDelta = uint256.NewInt(0).Sub(codeOffset, headerStorageOffset) + + index0Point *verkle.Point // pre-computed commitment of polynomial [2+256*64] + + // cacheHitGauge is the metric to track how many cache hit occurred. + cacheHitGauge = metrics.NewRegisteredGauge("trie/verkle/cache/hit", nil) + + // cacheMissGauge is the metric to track how many cache miss occurred. + cacheMissGauge = metrics.NewRegisteredGauge("trie/verkle/cache/miss", nil) ) +func init() { + // The byte array is the Marshalled output of the point computed as such: + // + // var ( + // config = verkle.GetConfig() + // fr verkle.Fr + // ) + // verkle.FromLEBytes(&fr, []byte{2, 64}) + // point := config.CommitToPoly([]verkle.Fr{fr}, 1) + index0Point = new(verkle.Point) + err := index0Point.SetBytes([]byte{34, 25, 109, 242, 193, 5, 144, 224, 76, 52, 189, 92, 197, 126, 9, 145, 27, 152, 199, 130, 165, 3, 210, 27, 193, 131, 142, 28, 110, 26, 16, 191}) + if err != nil { + panic(err) + } +} + +// PointCache is the LRU cache for storing evaluated address commitment. type PointCache struct { - cache map[string]*verkle.Point - lock sync.RWMutex + lru lru.BasicLRU[string, *verkle.Point] + lock sync.RWMutex } -func NewPointCache() *PointCache { +// NewPointCache returns the cache with specified size. +func NewPointCache(maxItems int) *PointCache { return &PointCache{ - cache: make(map[string]*verkle.Point), + lru: lru.NewBasicLRU[string, *verkle.Point](maxItems), } } -func (pc *PointCache) GetTreeKeyHeader(addr []byte) *verkle.Point { - pc.lock.RLock() - point, ok := pc.cache[string(addr)] - pc.lock.RUnlock() +// get loads the cached commitment, or nil if it's not existent. +func (c *PointCache) get(addr string) (*verkle.Point, bool) { + c.lock.RLock() + defer c.lock.RUnlock() + + return c.lru.Get(addr) +} + +// Get returns the cached commitment for the specified address, or computing +// it on the flight. +func (c *PointCache) Get(addr []byte) *verkle.Point { + p, ok := c.get(string(addr)) if ok { - return point + cacheHitGauge.Inc(1) + return p } + cacheMissGauge.Inc(1) + p = evaluateAddressPoint(addr) - point = EvaluateAddressPoint(addr) - pc.lock.Lock() - pc.cache[string(addr)] = point - pc.lock.Unlock() - return point + c.lock.Lock() + c.lru.Add(string(addr), p) + c.lock.Unlock() + return p } -func (pc *PointCache) GetTreeKeyVersionCached(addr []byte) []byte { - p := pc.GetTreeKeyHeader(addr) - v := PointToHash(p, VersionLeafKey) - return v[:] -} - -func init() { - // The byte array is the Marshalled output of the point computed as such: - //cfg, _ := verkle.GetConfig() - //verkle.FromLEBytes(&getTreePolyIndex0Fr[0], []byte{2, 64}) - //= cfg.CommitToPoly(getTreePolyIndex0Fr[:], 1) - getTreePolyIndex0Point = new(verkle.Point) - err := getTreePolyIndex0Point.SetBytes([]byte{34, 25, 109, 242, 193, 5, 144, 224, 76, 52, 189, 92, 197, 126, 9, 145, 27, 152, 199, 130, 165, 3, 210, 27, 193, 131, 142, 28, 110, 26, 16, 191}) - if err != nil { - panic(err) - } +// GetStem returns the first 31 bytes of the tree key as the tree stem. It only +// works for the account metadata whose treeIndex is 0. +func (c *PointCache) GetStem(addr []byte) []byte { + p := c.Get(addr) + return pointToHash(p, 0)[:31] } // GetTreeKey performs both the work of the spec's get_tree_key function, and that @@ -100,7 +126,6 @@ func GetTreeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte { var aligned [32]byte address = append(aligned[:32-len(address)], address...) } - // poly = [2+256*64, address_le_low, address_le_high, tree_index_le_low, tree_index_le_high] var poly [5]fr.Element @@ -126,48 +151,73 @@ func GetTreeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte { ret := cfg.CommitToPoly(poly[:], 0) // add a constant point corresponding to poly[0]=[2+256*64]. - ret.Add(ret, getTreePolyIndex0Point) + ret.Add(ret, index0Point) - return PointToHash(ret, subIndex) + return pointToHash(ret, subIndex) } -func GetTreeKeyAccountLeaf(address []byte, leaf byte) []byte { - return GetTreeKey(address, zero, leaf) -} +// GetTreeKeyWithEvaluatedAddress is basically identical to GetTreeKey, the only +// difference is a part of polynomial is already evaluated. +// +// Specifically, poly = [2+256*64, address_le_low, address_le_high] is already +// evaluated. +func GetTreeKeyWithEvaluatedAddress(evaluated *verkle.Point, treeIndex *uint256.Int, subIndex byte) []byte { + var poly [5]fr.Element -func GetTreeKeyVersion(address []byte) []byte { - return GetTreeKey(address, zero, VersionLeafKey) + poly[0].SetZero() + poly[1].SetZero() + poly[2].SetZero() + + // little-endian, 32-byte aligned treeIndex + var index [32]byte + for i := 0; i < len(treeIndex); i++ { + binary.LittleEndian.PutUint64(index[i*8:(i+1)*8], treeIndex[i]) + } + verkle.FromLEBytes(&poly[3], index[:16]) + verkle.FromLEBytes(&poly[4], index[16:]) + + cfg := verkle.GetConfig() + ret := cfg.CommitToPoly(poly[:], 0) + + // add the pre-evaluated address + ret.Add(ret, evaluated) + + return pointToHash(ret, subIndex) } -func GetTreeKeyVersionWithEvaluatedAddress(addrp *verkle.Point) []byte { - return GetTreeKeyWithEvaluatedAddess(addrp, zero, VersionLeafKey) +// VersionKey returns the verkle tree key of the version field for the specified account. +func VersionKey(address []byte) []byte { + return GetTreeKey(address, zero, VersionLeafKey) } -func GetTreeKeyBalance(address []byte) []byte { +// BalanceKey returns the verkle tree key of the balance field for the specified account. +func BalanceKey(address []byte) []byte { return GetTreeKey(address, zero, BalanceLeafKey) } -func GetTreeKeyNonce(address []byte) []byte { +// NonceKey returns the verkle tree key of the nonce field for the specified account. +func NonceKey(address []byte) []byte { return GetTreeKey(address, zero, NonceLeafKey) } -func GetTreeKeyCodeKeccak(address []byte) []byte { +// CodeKeccakKey returns the verkle tree key of the code keccak field for +// the specified account. +func CodeKeccakKey(address []byte) []byte { return GetTreeKey(address, zero, CodeKeccakLeafKey) } -func GetTreeKeyCodeSize(address []byte) []byte { +// CodeSizeKey returns the verkle tree key of the code size field for the +// specified account. +func CodeSizeKey(address []byte) []byte { return GetTreeKey(address, zero, CodeSizeLeafKey) } -func GetTreeKeyCodeChunk(address []byte, chunk *uint256.Int) []byte { - treeIndex, subIndex := GetTreeKeyCodeChunkIndices(chunk) - return GetTreeKey(address, treeIndex, subIndex) -} - -func GetTreeKeyCodeChunkIndices(chunk *uint256.Int) (*uint256.Int, byte) { - chunkOffset := new(uint256.Int).Add(CodeOffset, chunk) - treeIndex := new(uint256.Int).Div(chunkOffset, VerkleNodeWidth) - subIndexMod := new(uint256.Int).Mod(chunkOffset, VerkleNodeWidth) +func codeChunkIndex(chunk *uint256.Int) (*uint256.Int, byte) { + var ( + chunkOffset = new(uint256.Int).Add(codeOffset, chunk) + treeIndex = new(uint256.Int).Div(chunkOffset, verkleNodeWidth) + subIndexMod = new(uint256.Int).Mod(chunkOffset, verkleNodeWidth) + ) var subIndex byte if len(subIndexMod) != 0 { subIndex = byte(subIndexMod[0]) @@ -175,78 +225,109 @@ func GetTreeKeyCodeChunkIndices(chunk *uint256.Int) (*uint256.Int, byte) { return treeIndex, subIndex } -func GetTreeKeyCodeChunkWithEvaluatedAddress(addressPoint *verkle.Point, chunk *uint256.Int) []byte { - chunkOffset := new(uint256.Int).Add(CodeOffset, chunk) - treeIndex := new(uint256.Int).Div(chunkOffset, VerkleNodeWidth) - subIndexMod := new(uint256.Int).Mod(chunkOffset, VerkleNodeWidth) - var subIndex byte - if len(subIndexMod) != 0 { - subIndex = byte(subIndexMod[0]) - } - return GetTreeKeyWithEvaluatedAddess(addressPoint, treeIndex, subIndex) +// CodeChunkKey returns the verkle tree key of the code chunk for the +// specified account. +func CodeChunkKey(address []byte, chunk *uint256.Int) []byte { + treeIndex, subIndex := codeChunkIndex(chunk) + return GetTreeKey(address, treeIndex, subIndex) } -func GetTreeKeyStorageSlot(address []byte, storageKey *uint256.Int) []byte { - pos := storageKey.Clone() - if storageKey.Cmp(codeStorageDelta) < 0 { - pos.Add(HeaderStorageOffset, storageKey) - } else { - pos.Add(MainStorageOffset, storageKey) - } - treeIndex := new(uint256.Int).Div(pos, VerkleNodeWidth) +func storageIndex(bytes []byte) (*uint256.Int, byte) { + // If the storage slot is in the header, we need to add the header offset. + var key uint256.Int + key.SetBytes(bytes) + if key.Cmp(codeStorageDelta) < 0 { + // This addition is always safe; it can't ever overflow since pos + +package utils + +import ( + "bytes" + "testing" + + "github.com/gballet/go-verkle" + "github.com/holiman/uint256" +) + +func TestTreeKey(t *testing.T) { + var ( + address = []byte{0x01} + addressEval = evaluateAddressPoint(address) + smallIndex = uint256.NewInt(1) + largeIndex = uint256.NewInt(10000) + smallStorage = []byte{0x1} + largeStorage = bytes.Repeat([]byte{0xff}, 16) + ) + if !bytes.Equal(VersionKey(address), VersionKeyWithEvaluatedAddress(addressEval)) { + t.Fatal("Unmatched version key") + } + if !bytes.Equal(BalanceKey(address), BalanceKeyWithEvaluatedAddress(addressEval)) { + t.Fatal("Unmatched balance key") + } + if !bytes.Equal(NonceKey(address), NonceKeyWithEvaluatedAddress(addressEval)) { + t.Fatal("Unmatched nonce key") + } + if !bytes.Equal(CodeKeccakKey(address), CodeKeccakKeyWithEvaluatedAddress(addressEval)) { + t.Fatal("Unmatched code keccak key") + } + if !bytes.Equal(CodeSizeKey(address), CodeSizeKeyWithEvaluatedAddress(addressEval)) { + t.Fatal("Unmatched code size key") + } + if !bytes.Equal(CodeChunkKey(address, smallIndex), CodeChunkKeyWithEvaluatedAddress(addressEval, smallIndex)) { + t.Fatal("Unmatched code chunk key") + } + if !bytes.Equal(CodeChunkKey(address, largeIndex), CodeChunkKeyWithEvaluatedAddress(addressEval, largeIndex)) { + t.Fatal("Unmatched code chunk key") + } + if !bytes.Equal(StorageSlotKey(address, smallStorage), StorageSlotKeyWithEvaluatedAddress(addressEval, smallStorage)) { + t.Fatal("Unmatched storage slot key") + } + if !bytes.Equal(StorageSlotKey(address, largeStorage), StorageSlotKeyWithEvaluatedAddress(addressEval, largeStorage)) { + t.Fatal("Unmatched storage slot key") + } +} + +// goos: darwin +// goarch: amd64 +// pkg: github.com/ethereum/go-ethereum/trie/utils +// cpu: VirtualApple @ 2.50GHz +// BenchmarkTreeKey +// BenchmarkTreeKey-8 398731 2961 ns/op 32 B/op 1 allocs/op +func BenchmarkTreeKey(b *testing.B) { + // Initialize the IPA settings which can be pretty expensive. + verkle.GetConfig() + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + BalanceKey([]byte{0x01}) + } +} + +// goos: darwin +// goarch: amd64 +// pkg: github.com/ethereum/go-ethereum/trie/utils +// cpu: VirtualApple @ 2.50GHz +// BenchmarkTreeKeyWithEvaluation +// BenchmarkTreeKeyWithEvaluation-8 513855 2324 ns/op 32 B/op 1 allocs/op +func BenchmarkTreeKeyWithEvaluation(b *testing.B) { + // Initialize the IPA settings which can be pretty expensive. + verkle.GetConfig() + + addr := []byte{0x01} + eval := evaluateAddressPoint(addr) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + BalanceKeyWithEvaluatedAddress(eval) + } +} + +// goos: darwin +// goarch: amd64 +// pkg: github.com/ethereum/go-ethereum/trie/utils +// cpu: VirtualApple @ 2.50GHz +// BenchmarkStorageKey +// BenchmarkStorageKey-8 230516 4584 ns/op 96 B/op 3 allocs/op +func BenchmarkStorageKey(b *testing.B) { + // Initialize the IPA settings which can be pretty expensive. + verkle.GetConfig() + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + StorageSlotKey([]byte{0x01}, bytes.Repeat([]byte{0xff}, 32)) + } +} + +// goos: darwin +// goarch: amd64 +// pkg: github.com/ethereum/go-ethereum/trie/utils +// cpu: VirtualApple @ 2.50GHz +// BenchmarkStorageKeyWithEvaluation +// BenchmarkStorageKeyWithEvaluation-8 320125 3753 ns/op 96 B/op 3 allocs/op +func BenchmarkStorageKeyWithEvaluation(b *testing.B) { + // Initialize the IPA settings which can be pretty expensive. + verkle.GetConfig() + + addr := []byte{0x01} + eval := evaluateAddressPoint(addr) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + StorageSlotKeyWithEvaluatedAddress(eval, bytes.Repeat([]byte{0xff}, 32)) + } +} diff --git a/trie/verkle.go b/trie/verkle.go index 68ace10a683c..89e2e534089f 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -1,4 +1,4 @@ -// Copyright 2021 go-ethereum Authors +// Copyright 2023 go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -31,207 +31,194 @@ import ( "github.com/holiman/uint256" ) +var ( + zero [32]byte + errInvalidRootType = errors.New("invalid node type for root") +) + // VerkleTrie is a wrapper around VerkleNode that implements the trie.Trie // interface so that Verkle trees can be reused verbatim. type VerkleTrie struct { - root verkle.VerkleNode - db *Database - pointCache *utils.PointCache - ended bool - rootHash common.Hash - reader *trieReader + root verkle.VerkleNode + db *Database + cache *utils.PointCache + reader *trieReader } -func (t *VerkleTrie) ToDot() string { - return verkle.ToDot(t.root) -} - -func NewVerkleTrie(rootHash common.Hash, root verkle.VerkleNode, db *Database, pointCache *utils.PointCache, ended bool) (*VerkleTrie, error) { - reader, err := newTrieReader(rootHash, common.Hash{}, db) +// NewVerkleTrie constructs a verkle tree based on the specified root hash. +func NewVerkleTrie(root common.Hash, db *Database, cache *utils.PointCache) (*VerkleTrie, error) { + reader, err := newTrieReader(root, common.Hash{}, db) if err != nil { return nil, err } + // Parse the root verkle node if it's not empty. + node := verkle.New() + if root != types.EmptyVerkleHash && root != types.EmptyRootHash { + blob, err := reader.node(nil, common.Hash{}) + if err != nil { + return nil, err + } + node, err = verkle.ParseNode(blob, 0) + if err != nil { + return nil, err + } + } return &VerkleTrie{ - root: root, - db: db, - pointCache: pointCache, - ended: ended, - rootHash: rootHash, - reader: reader, + root: node, + db: db, + cache: cache, + reader: reader, }, nil } -func (t *VerkleTrie) FlatdbNodeResolver(path []byte) ([]byte, error) { - return t.reader.reader.Node(t.reader.owner, path, common.Hash{}) -} - -var errInvalidRootType = errors.New("invalid node type for root") - // GetKey returns the sha3 preimage of a hashed key that was previously used // to store a value. func (t *VerkleTrie) GetKey(key []byte) []byte { return key } -// GetStorage returns the value for key stored in the trie. The value bytes -// must not be modified by the caller. If a node was not found in the database, -// a trie.MissingNodeError is returned. -func (t *VerkleTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) { - pointEval := t.pointCache.GetTreeKeyHeader(addr[:]) - k := utils.GetTreeKeyStorageSlotWithEvaluatedAddress(pointEval, key) - return t.root.Get(k, t.FlatdbNodeResolver) -} - -// GetWithHashedKey returns the value, assuming that the key has already -// been hashed. -func (t *VerkleTrie) GetWithHashedKey(key []byte) ([]byte, error) { - return t.root.Get(key, t.FlatdbNodeResolver) -} - +// GetAccount implements state.Trie, retrieving the account with the specified +// account address. If the specified account is not in the verkle tree, nil will +// be returned. If the tree is corrupted, an error will be returned. func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error) { - acc := &types.StateAccount{} - versionkey := t.pointCache.GetTreeKeyVersionCached(addr[:]) var ( + acc = &types.StateAccount{} values [][]byte err error ) - switch t.root.(type) { + switch n := t.root.(type) { case *verkle.InternalNode: - values, err = t.root.(*verkle.InternalNode).GetStem(versionkey[:31], t.FlatdbNodeResolver) + values, err = n.GetValuesAtStem(t.cache.GetStem(addr[:]), t.nodeResolver) + if err != nil { + return nil, fmt.Errorf("GetAccount (%x) error: %v", addr, err) + } default: return nil, errInvalidRootType } - if err != nil { - return nil, fmt.Errorf("GetAccount (%x) error: %v", addr, err) - } - if values == nil { return nil, nil } + // Decode nonce in little-endian if len(values[utils.NonceLeafKey]) > 0 { acc.Nonce = binary.LittleEndian.Uint64(values[utils.NonceLeafKey]) } - + // Decode balance in little-endian var balance [32]byte copy(balance[:], values[utils.BalanceLeafKey]) for i := 0; i < len(balance)/2; i++ { balance[len(balance)-i-1], balance[i] = balance[i], balance[len(balance)-i-1] } acc.Balance = new(big.Int).SetBytes(balance[:]) + + // Decode codehash acc.CodeHash = values[utils.CodeKeccakLeafKey] + // TODO account.Root is leave as empty. How should we handle the legacy account? return acc, nil } -var zero [32]byte +// GetStorage implements state.Trie, retrieving the storage slot with the specified +// account address and storage key. If the specified slot is not in the verkle tree, +// nil will be returned. If the tree is corrupted, an error will be returned. +func (t *VerkleTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) { + k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key) + val, err := t.root.Get(k, t.nodeResolver) + if err != nil { + return nil, err + } + return common.TrimLeftZeroes(val), nil +} +// UpdateAccount implements state.Trie, writing the provided account into the tree. +// If the tree is corrupted, an error will be returned. func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount) error { var ( err error nonce, balance [32]byte values = make([][]byte, verkle.NodeWidth) - stem = t.pointCache.GetTreeKeyVersionCached(addr[:]) ) - - // Only evaluate the polynomial once values[utils.VersionLeafKey] = zero[:] - values[utils.NonceLeafKey] = nonce[:] - values[utils.BalanceLeafKey] = balance[:] values[utils.CodeKeccakLeafKey] = acc.CodeHash[:] + // Encode nonce in little-endian binary.LittleEndian.PutUint64(nonce[:], acc.Nonce) - bbytes := acc.Balance.Bytes() - if len(bbytes) > 0 { - for i, b := range bbytes { - balance[len(bbytes)-i-1] = b + values[utils.NonceLeafKey] = nonce[:] + + // Encode balance in little-endian + bytes := acc.Balance.Bytes() + if len(bytes) > 0 { + for i, b := range bytes { + balance[len(bytes)-i-1] = b } } + values[utils.BalanceLeafKey] = balance[:] - switch root := t.root.(type) { + switch n := t.root.(type) { case *verkle.InternalNode: - err = root.InsertStem(stem, values, t.FlatdbNodeResolver) + err = n.InsertValuesAtStem(t.cache.GetStem(addr[:]), values, t.nodeResolver) + if err != nil { + return fmt.Errorf("UpdateAccount (%x) error: %v", addr, err) + } default: return errInvalidRootType } - if err != nil { - return fmt.Errorf("UpdateAccount (%x) error: %v", addr, err) - } // TODO figure out if the code size needs to be updated, too - return nil } -func (t *VerkleTrie) UpdateStem(key []byte, values [][]byte) error { - switch root := t.root.(type) { - case *verkle.InternalNode: - return root.InsertStem(key, values, t.FlatdbNodeResolver) - default: - panic("invalid root type") - } -} - -// UpdateStorage associates key with value in the trie. If value has length zero, -// any existing value is deleted from the trie. The value bytes must not be modified -// by the caller while they are stored in the trie. If a node was not found in the -// database, a trie.MissingNodeError is returned. +// UpdateStorage implements state.Trie, writing the provided storage slot into +// the tree. If the tree is corrupted, an error will be returned. func (t *VerkleTrie) UpdateStorage(address common.Address, key, value []byte) error { - k := utils.GetTreeKeyStorageSlotWithEvaluatedAddress(t.pointCache.GetTreeKeyHeader(address[:]), key) + // Left padding the slot value to 32 bytes. var v [32]byte if len(value) >= 32 { copy(v[:], value[:32]) } else { copy(v[32-len(value):], value[:]) } - return t.root.Insert(k, v[:], t.FlatdbNodeResolver) + k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(address.Bytes()), key) + return t.root.Insert(k, v[:], t.nodeResolver) } +// DeleteAccount implements state.Trie, deleting the specified account from the +// trie. If the account was not existent in the trie, no error will be returned. +// If the trie is corrupted, an error will be returned. func (t *VerkleTrie) DeleteAccount(addr common.Address) error { var ( err error values = make([][]byte, verkle.NodeWidth) - stem = t.pointCache.GetTreeKeyVersionCached(addr[:]) ) - for i := 0; i < verkle.NodeWidth; i++ { values[i] = zero[:] } - - switch root := t.root.(type) { + switch n := t.root.(type) { case *verkle.InternalNode: - err = root.InsertStem(stem, values, t.FlatdbNodeResolver) + err = n.InsertValuesAtStem(t.cache.GetStem(addr.Bytes()), values, t.nodeResolver) + if err != nil { + return fmt.Errorf("DeleteAccount (%x) error: %v", addr, err) + } default: return errInvalidRootType } - if err != nil { - return fmt.Errorf("DeleteAccount (%x) error: %v", addr, err) - } - // TODO figure out if the code size needs to be updated, too - return nil } -// DeleteStorage removes any existing value for key from the trie. If a node was -// not found in the database, a trie.MissingNodeError is returned. +// DeleteStorage implements state.Trie, deleting the specified storage slot from +// the trie. If the storage slot was not existent in the trie, no error will be +// returned. If the trie is corrupted, an error will be returned. func (t *VerkleTrie) DeleteStorage(addr common.Address, key []byte) error { - pointEval := t.pointCache.GetTreeKeyHeader(addr[:]) - k := utils.GetTreeKeyStorageSlotWithEvaluatedAddress(pointEval, key) var zero [32]byte - return t.root.Insert(k, zero[:], t.FlatdbNodeResolver) + k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key) + return t.root.Insert(k, zero[:], t.nodeResolver) } -// Hash returns the root hash of the trie. It does not write to the database and -// can be used even if the trie doesn't have one. +// Hash returns the root hash of the tree. It does not write to the database and +// can be used even if the tree doesn't have one. func (t *VerkleTrie) Hash() common.Hash { return t.root.Commit().Bytes() } -func nodeToDBKey(n verkle.VerkleNode) []byte { - ret := n.Commitment().Bytes() - return ret[:] -} - -// Commit writes all nodes to the trie's memory database, tracking the internal -// and external (for account tries) references. +// Commit writes all nodes to the tree's memory database. func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) { root, ok := t.root.(*verkle.InternalNode) if !ok { @@ -241,41 +228,43 @@ func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) { if err != nil { return common.Hash{}, nil, fmt.Errorf("serializing tree nodes: %s", err) } - nodeset := trienode.NewNodeSet(common.Hash{}) for _, node := range nodes { // hash parameter is not used in pathdb nodeset.AddNode(node.Path, trienode.New(common.Hash{}, node.SerializedBytes)) } - // Serialize root commitment form - t.rootHash = t.Hash() - return t.rootHash, nodeset, nil + return t.Hash(), nodeset, nil } -// NodeIterator returns an iterator that returns nodes of the trie. Iteration -// starts at the key after the given start key. +// NodeIterator implements state.Trie, returning an iterator that returns +// nodes of the trie. Iteration starts at the key after the given start key. +// +// TODO(gballet, rjl493456442) implement it. func (t *VerkleTrie) NodeIterator(startKey []byte) (NodeIterator, error) { - return newVerkleNodeIterator(t, nil) + panic("not implemented") } -// Prove constructs a Merkle proof for key. The result contains all encoded nodes -// on the path to the value at key. The value itself is also included in the last -// node and can be retrieved by verifying the proof. +// Prove implements state.Trie, constructing a Merkle proof for key. The result +// contains all encoded nodes on the path to the value at key. The value itself +// is also included in the last node and can be retrieved by verifying the proof. // // If the trie does not contain a value for key, the returned proof contains all // nodes of the longest existing prefix of the key (at least the root), ending // with the node that proves the absence of the key. +// +// TODO(gballet, rjl493456442) implement it. func (t *VerkleTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { panic("not implemented") } +// Copy returns a deep-copied verkle tree. func (t *VerkleTrie) Copy() *VerkleTrie { return &VerkleTrie{ - root: t.root.Copy(), - db: t.db, - pointCache: t.pointCache, - reader: t.reader, + root: t.root.Copy(), + db: t.db, + cache: t.cache, + reader: t.reader, } } @@ -291,11 +280,6 @@ type ChunkedCode []byte // Copy the values here so as to avoid an import cycle const ( PUSH1 = byte(0x60) - PUSH3 = byte(0x62) - PUSH4 = byte(0x63) - PUSH7 = byte(0x66) - PUSH21 = byte(0x74) - PUSH30 = byte(0x7d) PUSH32 = byte(0x7f) ) @@ -311,21 +295,16 @@ func ChunkifyCode(code []byte) ChunkedCode { } chunks := make([]byte, chunkCount*32) for i := 0; i < chunkCount; i++ { - // number of bytes to copy, 31 unless - // the end of the code has been reached. + // number of bytes to copy, 31 unless the end of the code has been reached. end := 31 * (i + 1) if len(code) < end { end = len(code) } + copy(chunks[i*32+1:], code[31*i:end]) // copy the code itself - // Copy the code itself - copy(chunks[i*32+1:], code[31*i:end]) - - // chunk offset = taken from the - // last chunk. + // chunk offset = taken from the last chunk. if chunkOffset > 31 { - // skip offset calculation if push - // data covers the whole chunk + // skip offset calculation if push data covers the whole chunk chunks[i*32] = 31 chunkOffset = 1 continue @@ -333,8 +312,8 @@ func ChunkifyCode(code []byte) ChunkedCode { chunks[32*i] = byte(chunkOffset) chunkOffset = 0 - // Check each instruction and update the offset - // it should be 0 unless a PUSHn overflows. + // Check each instruction and update the offset it should be 0 unless + // a PUSH-N overflows. for ; codeOffset < end; codeOffset++ { if code[codeOffset] >= PUSH1 && code[codeOffset] <= PUSH32 { codeOffset += int(code[codeOffset] - PUSH1 + 1) @@ -346,10 +325,11 @@ func ChunkifyCode(code []byte) ChunkedCode { } } } - return chunks } +// UpdateContractCode implements state.Trie, writing the provided contract code +// into the trie. func (t *VerkleTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error { var ( chunks = ChunkifyCode(code) @@ -361,7 +341,7 @@ func (t *VerkleTrie) UpdateContractCode(addr common.Address, codeHash common.Has groupOffset := (chunknr + 128) % 256 if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ { values = make([][]byte, verkle.NodeWidth) - key = utils.GetTreeKeyCodeChunkWithEvaluatedAddress(t.pointCache.GetTreeKeyHeader(addr[:]), uint256.NewInt(chunknr)) + key = utils.CodeChunkKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), uint256.NewInt(chunknr)) } values[groupOffset] = chunks[i : i+32] @@ -371,14 +351,25 @@ func (t *VerkleTrie) UpdateContractCode(addr common.Address, codeHash common.Has binary.LittleEndian.PutUint64(cs, uint64(len(code))) values[utils.CodeSizeLeafKey] = cs } - if groupOffset == 255 || len(chunks)-i <= 32 { - err = t.UpdateStem(key[:31], values) - - if err != nil { - return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err) + switch root := t.root.(type) { + case *verkle.InternalNode: + err = root.InsertValuesAtStem(key[:31], values, t.nodeResolver) + if err != nil { + return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err) + } + default: + return errInvalidRootType } } } return nil } + +func (t *VerkleTrie) ToDot() string { + return verkle.ToDot(t.root) +} + +func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) { + return t.reader.node(path, common.Hash{}) +} diff --git a/trie/verkle_iterator.go b/trie/verkle_iterator.go deleted file mode 100644 index c5f59a0f5937..000000000000 --- a/trie/verkle_iterator.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2021 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package trie - -import ( - "github.com/ethereum/go-ethereum/common" - - "github.com/gballet/go-verkle" -) - -type verkleNodeIteratorState struct { - Node verkle.VerkleNode - Index int -} - -type verkleNodeIterator struct { - trie *VerkleTrie - current verkle.VerkleNode - lastErr error - - stack []verkleNodeIteratorState -} - -func newVerkleNodeIterator(trie *VerkleTrie, start []byte) (NodeIterator, error) { - if trie.Hash() == zero { - return new(nodeIterator), nil - } - it := &verkleNodeIterator{trie: trie, current: trie.root} - // it.err = it.seek(start) - return it, nil -} - -// Next moves the iterator to the next node. If the parameter is false, any child -// nodes will be skipped. -func (it *verkleNodeIterator) Next(descend bool) bool { - if it.lastErr == errIteratorEnd { - it.lastErr = errIteratorEnd - return false - } - - if len(it.stack) == 0 { - it.stack = append(it.stack, verkleNodeIteratorState{Node: it.trie.root, Index: 0}) - it.current = it.trie.root - - return true - } - - switch node := it.current.(type) { - case *verkle.InternalNode: - context := &it.stack[len(it.stack)-1] - - // Look for the next non-empty child - children := node.Children() - for ; context.Index < len(children); context.Index++ { - if _, ok := children[context.Index].(verkle.Empty); !ok { - it.stack = append(it.stack, verkleNodeIteratorState{Node: children[context.Index], Index: 0}) - it.current = children[context.Index] - return it.Next(descend) - } - } - - // Reached the end of this node, go back to the parent, if - // this isn't root. - if len(it.stack) == 1 { - it.lastErr = errIteratorEnd - return false - } - it.stack = it.stack[:len(it.stack)-1] - it.current = it.stack[len(it.stack)-1].Node - it.stack[len(it.stack)-1].Index++ - return it.Next(descend) - case *verkle.LeafNode: - // Look for the next non-empty value - for i := it.stack[len(it.stack)-1].Index; i < 256; i++ { - if node.Value(i) != nil { - it.stack[len(it.stack)-1].Index = i + 1 - return true - } - } - - // go back to parent to get the next leaf - it.stack = it.stack[:len(it.stack)-1] - it.current = it.stack[len(it.stack)-1].Node - it.stack[len(it.stack)-1].Index++ - return it.Next(descend) - case *verkle.HashedNode: - // resolve the node - data, err := it.trie.db.diskdb.Get(nodeToDBKey(node)) - if err != nil { - panic(err) - } - it.current, err = verkle.ParseNode(data, byte(len(it.stack)-1)) - if err != nil { - panic(err) - } - - // update the stack and parent with the resolved node - it.stack[len(it.stack)-1].Node = it.current - parent := &it.stack[len(it.stack)-2] - parent.Node.(*verkle.InternalNode).SetChild(parent.Index, it.current) - return true - default: - panic("invalid node type") - } -} - -// Error returns the error status of the iterator. -func (it *verkleNodeIterator) Error() error { - if it.lastErr == errIteratorEnd { - return nil - } - return it.lastErr -} - -// Hash returns the hash of the current node. -func (it *verkleNodeIterator) Hash() common.Hash { - return it.current.Commit().Bytes() -} - -// Parent returns the hash of the parent of the current node. The hash may be the one -// grandparent if the immediate parent is an internal node with no hash. -func (it *verkleNodeIterator) Parent() common.Hash { - return it.stack[len(it.stack)-1].Node.Commit().Bytes() -} - -// Path returns the hex-encoded path to the current node. -// Callers must not retain references to the return value after calling Next. -// For leaf nodes, the last element of the path is the 'terminator symbol' 0x10. -func (it *verkleNodeIterator) Path() []byte { - if it.Leaf() { - return it.LeafKey() - } - var path []byte - for i, state := range it.stack { - // skip the last byte - if i <= len(it.stack)-1 { - break - } - path = append(path, byte(state.Index)) - } - return path -} - -func (it *verkleNodeIterator) NodeBlob() []byte { - panic("not completely implemented") -} - -// Leaf returns true iff the current node is a leaf node. -func (it *verkleNodeIterator) Leaf() bool { - _, ok := it.current.(*verkle.LeafNode) - return ok -} - -// LeafKey returns the key of the leaf. The method panics if the iterator is not -// positioned at a leaf. Callers must not retain references to the value after -// calling Next. -func (it *verkleNodeIterator) LeafKey() []byte { - leaf, ok := it.current.(*verkle.LeafNode) - if !ok { - panic("Leaf() called on an verkle node iterator not at a leaf location") - } - - return leaf.Key(it.stack[len(it.stack)-1].Index - 1) -} - -// LeafBlob returns the content of the leaf. The method panics if the iterator -// is not positioned at a leaf. Callers must not retain references to the value -// after calling Next. -func (it *verkleNodeIterator) LeafBlob() []byte { - leaf, ok := it.current.(*verkle.LeafNode) - if !ok { - panic("LeafBlob() called on an verkle node iterator not at a leaf location") - } - - return leaf.Value(it.stack[len(it.stack)-1].Index - 1) -} - -// LeafProof returns the Merkle proof of the leaf. The method panics if the -// iterator is not positioned at a leaf. Callers must not retain references -// to the value after calling Next. -func (it *verkleNodeIterator) LeafProof() [][]byte { - _, ok := it.current.(*verkle.LeafNode) - if !ok { - panic("LeafProof() called on an verkle node iterator not at a leaf location") - } - - // return it.trie.Prove(leaf.Key()) - panic("not completely implemented") -} - -// AddResolver sets an intermediate database to use for looking up trie nodes -// before reaching into the real persistent layer. -// -// This is not required for normal operation, rather is an optimization for -// cases where trie nodes can be recovered from some external mechanism without -// reading from disk. In those cases, this resolver allows short circuiting -// accesses and returning them from memory. -// -// Before adding a similar mechanism to any other place in Geth, consider -// making trie.Database an interface and wrapping at that level. It's a huge -// refactor, but it could be worth it if another occurrence arises. -func (it *verkleNodeIterator) AddResolver(NodeResolver) { - // Not implemented, but should not panic -} diff --git a/trie/verkle_test.go b/trie/verkle_test.go new file mode 100644 index 000000000000..44fb7dc29e9b --- /dev/null +++ b/trie/verkle_test.go @@ -0,0 +1,97 @@ +// Copyright 2023 go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "bytes" + "math/big" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" + "github.com/ethereum/go-ethereum/trie/utils" +) + +var ( + accounts = map[common.Address]*types.StateAccount{ + common.Address{1}: { + Nonce: 100, + Balance: big.NewInt(100), + CodeHash: common.Hash{0x1}.Bytes(), + }, + common.Address{2}: { + Nonce: 200, + Balance: big.NewInt(200), + CodeHash: common.Hash{0x2}.Bytes(), + }, + } + storages = map[common.Address]map[common.Hash][]byte{ + common.Address{1}: { + common.Hash{10}: []byte{10}, + common.Hash{11}: []byte{11}, + common.MaxHash: []byte{0xff}, + }, + common.Address{2}: { + common.Hash{20}: []byte{20}, + common.Hash{21}: []byte{21}, + common.MaxHash: []byte{0xff}, + }, + } +) + +func TestVerkleTreeReadWrite(t *testing.T) { + db := NewDatabase(rawdb.NewMemoryDatabase(), &Config{ + IsVerkle: true, + PathDB: pathdb.Defaults, + }) + defer db.Close() + + tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache(100)) + + for addr, acct := range accounts { + if err := tr.UpdateAccount(addr, acct); err != nil { + t.Fatalf("Failed to update account, %v", err) + } + for key, val := range storages[addr] { + if err := tr.UpdateStorage(addr, key.Bytes(), val); err != nil { + t.Fatalf("Failed to update account, %v", err) + } + } + } + + for addr, acct := range accounts { + stored, err := tr.GetAccount(addr) + if err != nil { + t.Fatalf("Failed to get account, %v", err) + } + if !reflect.DeepEqual(stored, acct) { + t.Fatal("account is not matched") + } + for key, val := range storages[addr] { + stored, err := tr.GetStorage(addr, key.Bytes()) + if err != nil { + t.Fatalf("Failed to get storage, %v", err) + } + if !bytes.Equal(stored, val) { + t.Fatal("storage is not matched") + } + } + } +}