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")
+ }
+ }
+ }
+}