From 037a03dbf9c9eeec00f1d2a31880990b27808938 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Fri, 27 Oct 2023 14:22:18 +0800 Subject: [PATCH 1/7] core, light, trie: polish code --- core/state/database.go | 59 +++---- core/types/hashes.go | 5 +- light/odr_test.go | 2 +- trie/utils/verkle.go | 300 ------------------------------------ trie/verkle.go | 283 ++++++++++++++++------------------ trie/verkle/utils.go | 316 ++++++++++++++++++++++++++++++++++++++ trie/verkle/utils_test.go | 62 ++++++++ trie/verkle_iterator.go | 218 -------------------------- trie/verkle_test.go | 97 ++++++++++++ 9 files changed, 638 insertions(+), 704 deletions(-) delete mode 100644 trie/utils/verkle.go create mode 100644 trie/verkle/utils.go create mode 100644 trie/verkle/utils_test.go delete mode 100644 trie/verkle_iterator.go create mode 100644 trie/verkle_test.go diff --git a/core/state/database.go b/core/state/database.go index 9f183da0f7fd..9dbeef325105 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -28,8 +28,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "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" + "github.com/ethereum/go-ethereum/trie/verkle" ) const ( @@ -38,6 +37,12 @@ const ( // Cache size granted for caching clean code. codeCacheSize = 64 * 1024 * 1024 + + // commitmentSize is the size of commitment stored in cache. + commitmentSize = 3 * 32 + + // Cache item granted for caching commitment results. + commitmentCacheItems = 64 * 1024 * 1024 / (commitmentSize + common.AddressLength) ) // Database wraps access to tries and contract code. @@ -72,11 +77,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 +85,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 +178,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, verkle.NewCache(commitmentCacheItems)) } tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) if err != nil { 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 deleted file mode 100644 index 17fdf1ade343..000000000000 --- a/trie/utils/verkle.go +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright 2021 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 utils - -import ( - "encoding/binary" - "sync" - - "github.com/crate-crypto/go-ipa/bandersnatch/fr" - "github.com/gballet/go-verkle" - "github.com/holiman/uint256" -) - -const ( - VersionLeafKey = 0 - BalanceLeafKey = 1 - NonceLeafKey = 2 - CodeKeccakLeafKey = 3 - CodeSizeLeafKey = 4 -) - -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 -) - -type PointCache struct { - cache map[string]*verkle.Point - lock sync.RWMutex -} - -func NewPointCache() *PointCache { - return &PointCache{ - cache: make(map[string]*verkle.Point), - } -} - -func (pc *PointCache) GetTreeKeyHeader(addr []byte) *verkle.Point { - pc.lock.RLock() - point, ok := pc.cache[string(addr)] - pc.lock.RUnlock() - if ok { - return point - } - - point = EvaluateAddressPoint(addr) - pc.lock.Lock() - pc.cache[string(addr)] = point - pc.lock.Unlock() - return point -} - -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) - } -} - -// GetTreeKey performs both the work of the spec's get_tree_key function, and that -// of pedersen_hash: it builds the polynomial in pedersen_hash without having to -// create a mostly zero-filled buffer and "type cast" it to a 128-long 16-byte -// array. Since at most the first 5 coefficients of the polynomial will be non-zero, -// these 5 coefficients are created directly. -func GetTreeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte { - if len(address) < 32 { - 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 - - // 32-byte address, interpreted as two little endian - // 16-byte numbers. - verkle.FromLEBytes(&poly[1], address[:16]) - verkle.FromLEBytes(&poly[2], address[16:]) - - // treeIndex must be interpreted as a 32-byte aligned little-endian integer. - // e.g: if treeIndex is 0xAABBCC, we need the byte representation to be 0xCCBBAA00...00. - // poly[3] = LE({CC,BB,AA,00...0}) (16 bytes), poly[4]=LE({00,00,...}) (16 bytes). - // - // To avoid unnecessary endianness conversions for go-ipa, we do some trick: - // - poly[3]'s byte representation is the same as the *top* 16 bytes (trieIndexBytes[16:]) of - // 32-byte aligned big-endian representation (BE({00,...,AA,BB,CC})). - // - poly[4]'s byte representation is the same as the *low* 16 bytes (trieIndexBytes[:16]) of - // the 32-byte aligned big-endian representation (BE({00,00,...}). - trieIndexBytes := treeIndex.Bytes32() - verkle.FromBytes(&poly[3], trieIndexBytes[16:]) - verkle.FromBytes(&poly[4], trieIndexBytes[:16]) - - cfg := verkle.GetConfig() - ret := cfg.CommitToPoly(poly[:], 0) - - // add a constant point corresponding to poly[0]=[2+256*64]. - ret.Add(ret, getTreePolyIndex0Point) - - return PointToHash(ret, subIndex) -} - -func GetTreeKeyAccountLeaf(address []byte, leaf byte) []byte { - return GetTreeKey(address, zero, leaf) -} - -func GetTreeKeyVersion(address []byte) []byte { - return GetTreeKey(address, zero, VersionLeafKey) -} - -func GetTreeKeyVersionWithEvaluatedAddress(addrp *verkle.Point) []byte { - return GetTreeKeyWithEvaluatedAddess(addrp, zero, VersionLeafKey) -} - -func GetTreeKeyBalance(address []byte) []byte { - return GetTreeKey(address, zero, BalanceLeafKey) -} - -func GetTreeKeyNonce(address []byte) []byte { - return GetTreeKey(address, zero, NonceLeafKey) -} - -func GetTreeKeyCodeKeccak(address []byte) []byte { - return GetTreeKey(address, zero, CodeKeccakLeafKey) -} - -func GetTreeKeyCodeSize(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) - var subIndex byte - if len(subIndexMod) != 0 { - subIndex = byte(subIndexMod[0]) - } - 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) -} - -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) - - // calculate the sub_index, i.e. the index in the stem tree. - // Because the modulus is 256, it's the last byte of treeIndex - subIndexMod := new(uint256.Int).Mod(pos, VerkleNodeWidth) - var subIndex byte - if len(subIndexMod) != 0 { - // uint256 is broken into 4 little-endian quads, - // each with native endianness. Extract the least - // significant byte. - subIndex = byte(subIndexMod[0]) - } - return GetTreeKey(address, treeIndex, subIndex) -} - -func PointToHash(evaluated *verkle.Point, suffix byte) []byte { - // The output of Byte() is big engian for banderwagon. This - // introduces an imbalance in the tree, because hashes are - // elements of a 253-bit field. This means more than half the - // tree would be empty. To avoid this problem, use a little - // endian commitment and chop the MSB. - retb := evaluated.Bytes() - for i := 0; i < 16; i++ { - retb[31-i], retb[i] = retb[i], retb[31-i] - } - retb[31] = suffix - return retb[:] -} - -func GetTreeKeyWithEvaluatedAddess(evaluated *verkle.Point, treeIndex *uint256.Int, subIndex byte) []byte { - var poly [5]fr.Element - - 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 EvaluateAddressPoint(address []byte) *verkle.Point { - if len(address) < 32 { - var aligned [32]byte - address = append(aligned[:32-len(address)], address...) - } - var poly [3]fr.Element - - poly[0].SetZero() - - // 32-byte address, interpreted as two little endian - // 16-byte numbers. - verkle.FromLEBytes(&poly[1], address[:16]) - verkle.FromLEBytes(&poly[2], address[16:]) - - cfg := verkle.GetConfig() - ret := cfg.CommitToPoly(poly[:], 0) - - // add a constant point - ret.Add(ret, getTreePolyIndex0Point) - - return ret -} - -func GetTreeKeyStorageSlotWithEvaluatedAddress(evaluated *verkle.Point, storageKey []byte) []byte { - treeIndex, subIndex := GetTreeKeyStorageSlotTreeIndexes(storageKey) - return GetTreeKeyWithEvaluatedAddess(evaluated, treeIndex, subIndex) -} - -func GetTreeKeyStorageSlotTreeIndexes(storageKey []byte) (*uint256.Int, byte) { - var pos uint256.Int - pos.SetBytes(storageKey) - - // If the storage slot is in the header, we need to add the header offset. - if pos.Cmp(codeStorageDelta) < 0 { - // This addition is always safe; it can't ever overflow since pos 0 { - acc.Nonce = binary.LittleEndian.Uint64(values[utils.NonceLeafKey]) + acct.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[:]) - acc.CodeHash = values[utils.CodeKeccakLeafKey] + acct.Balance = new(big.Int).SetBytes(balance[:]) - return acc, nil + // Decode codehash + acct.CodeHash = values[utils.CodeKeccakLeafKey] + + // TODO account.Root is leave as empty. How should we handle the legacy account? + return acct, 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 +} -func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount) error { +// 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, acct *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.CodeKeccakLeafKey] = acct.CodeHash[:] + + // Encode nonce in little-endian + binary.LittleEndian.PutUint64(nonce[:], acct.Nonce) values[utils.NonceLeafKey] = nonce[:] - values[utils.BalanceLeafKey] = balance[:] - values[utils.CodeKeccakLeafKey] = acc.CodeHash[:] - 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 + // Encode balance in little-endian + bytes := acct.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.InsertStem(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) + // Right 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.InsertStem(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,27 +228,24 @@ 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. 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 @@ -270,12 +254,13 @@ 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 +276,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 +291,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 +308,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 +321,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 +337,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 +347,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.InsertStem(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/utils.go b/trie/verkle/utils.go new file mode 100644 index 000000000000..492b1eb6f055 --- /dev/null +++ b/trie/verkle/utils.go @@ -0,0 +1,316 @@ +// 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 verkle + +import ( + "encoding/binary" + "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 ( + VersionLeafKey = 0 + BalanceLeafKey = 1 + NonceLeafKey = 2 + CodeKeccakLeafKey = 3 + CodeSizeLeafKey = 4 +) + +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) + verkleNodeWidth = uint256.NewInt(256) + codeStorageDelta = uint256.NewInt(0).Sub(codeOffset, headerStorageOffset) + + index0Point *verkle.Point // pre-computed commitment of polynomial [2+256*64, 0, 0, 0, 0] + + // 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) + } +} + +// Cache is the LRU cache for storing evaluated address commitment. +type Cache struct { + lru lru.BasicLRU[string, *verkle.Point] + lock sync.RWMutex +} + +// NewCache returns the cache with specified size. +func NewCache(maxItems int) *Cache { + return &Cache{ + lru: lru.NewBasicLRU[string, *verkle.Point](maxItems), + } +} + +// get loads the cached commitment, or nil if it's not existent. +func (c *Cache) 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 *Cache) Get(addr []byte) *verkle.Point { + p, ok := c.get(string(addr)) + if ok { + cacheHitGauge.Inc(1) + return p + } + cacheMissGauge.Inc(1) + p = evaluateAddressPoint(addr) + + c.lock.Lock() + c.lru.Add(string(addr), p) + c.lock.Unlock() + return p +} + +// 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 *Cache) GetStem(addr []byte) []byte { + p := c.Get(addr) + return pointToHash(p, 0)[:31] +} + +// treeKey performs both the work of the spec's get_tree_key function, and that +// of pedersen_hash: it builds the polynomial in pedersen_hash without having to +// create a mostly zero-filled buffer and "type cast" it to a 128-long 16-byte +// array. Since at most the first 5 coefficients of the polynomial will be non-zero, +// these 5 coefficients are created directly. +func treeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte { + if len(address) < 32 { + 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 + + // 32-byte address, interpreted as two little endian + // 16-byte numbers. + verkle.FromLEBytes(&poly[1], address[:16]) + verkle.FromLEBytes(&poly[2], address[16:]) + + // treeIndex must be interpreted as a 32-byte aligned little-endian integer. + // e.g: if treeIndex is 0xAABBCC, we need the byte representation to be 0xCCBBAA00...00. + // poly[3] = LE({CC,BB,AA,00...0}) (16 bytes), poly[4]=LE({00,00,...}) (16 bytes). + // + // To avoid unnecessary endianness conversions for go-ipa, we do some trick: + // - poly[3]'s byte representation is the same as the *top* 16 bytes (trieIndexBytes[16:]) of + // 32-byte aligned big-endian representation (BE({00,...,AA,BB,CC})). + // - poly[4]'s byte representation is the same as the *low* 16 bytes (trieIndexBytes[:16]) of + // the 32-byte aligned big-endian representation (BE({00,00,...}). + trieIndexBytes := treeIndex.Bytes32() + verkle.FromBytes(&poly[3], trieIndexBytes[16:]) + verkle.FromBytes(&poly[4], trieIndexBytes[:16]) + + cfg := verkle.GetConfig() + ret := cfg.CommitToPoly(poly[:], 0) + + // add a constant point corresponding to poly[0]=[2+256*64]. + ret.Add(ret, index0Point) + + return pointToHash(ret, subIndex) +} + +// treeKeyWithEvaluatedAddress is basically identical to treeKey, 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 treeKeyWithEvaluatedAddress(evaluated *verkle.Point, treeIndex *uint256.Int, subIndex byte) []byte { + var poly [5]fr.Element + + 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 VersionKey(address []byte) []byte { + return treeKey(address, zero, VersionLeafKey) +} + +func BalanceKey(address []byte) []byte { + return treeKey(address, zero, BalanceLeafKey) +} + +func NonceKey(address []byte) []byte { + return treeKey(address, zero, NonceLeafKey) +} + +func CodeKeccakKey(address []byte) []byte { + return treeKey(address, zero, CodeKeccakLeafKey) +} + +func CodeSizeKey(address []byte) []byte { + return treeKey(address, zero, CodeSizeLeafKey) +} + +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]) + } + return treeIndex, subIndex +} + +func CodeChunkKey(address []byte, chunk *uint256.Int) []byte { + treeIndex, subIndex := codeChunkIndex(chunk) + return treeKey(address, treeIndex, subIndex) +} + +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 verkle + +import ( + "bytes" + "testing" + + "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") + } +} 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..d41befbabe0a --- /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/verkle" +) + +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, verkle.NewCache(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") + } + } + } +} From f10c405ab92a81a523fee98252bd6a918b8bdd19 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Tue, 31 Oct 2023 11:42:47 +0800 Subject: [PATCH 2/7] core, trie: address comments --- core/state/database.go | 7 ++- trie/{verkle/utils.go => utils/verkle.go} | 58 +++++++++---------- .../utils_test.go => utils/verkle_test.go} | 2 +- trie/verkle.go | 36 +++++++----- trie/verkle_test.go | 4 +- 5 files changed, 56 insertions(+), 51 deletions(-) rename trie/{verkle/utils.go => utils/verkle.go} (84%) rename trie/{verkle/utils_test.go => utils/verkle_test.go} (99%) diff --git a/core/state/database.go b/core/state/database.go index 9dbeef325105..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" @@ -28,7 +29,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" - "github.com/ethereum/go-ethereum/trie/verkle" + "github.com/ethereum/go-ethereum/trie/utils" ) const ( @@ -39,7 +40,7 @@ const ( codeCacheSize = 64 * 1024 * 1024 // commitmentSize is the size of commitment stored in cache. - commitmentSize = 3 * 32 + commitmentSize = banderwagon.UncompressedSize // Cache item granted for caching commitment results. commitmentCacheItems = 64 * 1024 * 1024 / (commitmentSize + common.AddressLength) @@ -178,7 +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() { - return trie.NewVerkleTrie(root, db.triedb, verkle.NewCache(commitmentCacheItems)) + return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(commitmentCacheItems)) } tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) if err != nil { diff --git a/trie/verkle/utils.go b/trie/utils/verkle.go similarity index 84% rename from trie/verkle/utils.go rename to trie/utils/verkle.go index 492b1eb6f055..b4276e4a052f 100644 --- a/trie/verkle/utils.go +++ b/trie/utils/verkle.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package verkle +package utils import ( "encoding/binary" @@ -44,7 +44,7 @@ var ( verkleNodeWidth = uint256.NewInt(256) codeStorageDelta = uint256.NewInt(0).Sub(codeOffset, headerStorageOffset) - index0Point *verkle.Point // pre-computed commitment of polynomial [2+256*64, 0, 0, 0, 0] + 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) @@ -69,21 +69,21 @@ func init() { } } -// Cache is the LRU cache for storing evaluated address commitment. -type Cache struct { +// PointCache is the LRU cache for storing evaluated address commitment. +type PointCache struct { lru lru.BasicLRU[string, *verkle.Point] lock sync.RWMutex } -// NewCache returns the cache with specified size. -func NewCache(maxItems int) *Cache { - return &Cache{ +// NewPointCache returns the cache with specified size. +func NewPointCache(maxItems int) *PointCache { + return &PointCache{ lru: lru.NewBasicLRU[string, *verkle.Point](maxItems), } } // get loads the cached commitment, or nil if it's not existent. -func (c *Cache) get(addr string) (*verkle.Point, bool) { +func (c *PointCache) get(addr string) (*verkle.Point, bool) { c.lock.RLock() defer c.lock.RUnlock() @@ -92,7 +92,7 @@ func (c *Cache) get(addr string) (*verkle.Point, bool) { // Get returns the cached commitment for the specified address, or computing // it on the flight. -func (c *Cache) Get(addr []byte) *verkle.Point { +func (c *PointCache) Get(addr []byte) *verkle.Point { p, ok := c.get(string(addr)) if ok { cacheHitGauge.Inc(1) @@ -107,19 +107,19 @@ func (c *Cache) Get(addr []byte) *verkle.Point { return p } -// GetStem returns the first 31 bytes of the tree key as the tree stem. It only +// DeriveStem 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 *Cache) GetStem(addr []byte) []byte { +func (c *PointCache) DeriveStem(addr []byte) []byte { p := c.Get(addr) return pointToHash(p, 0)[:31] } -// treeKey performs both the work of the spec's get_tree_key function, and that +// GetTreeKey performs both the work of the spec's get_tree_key function, and that // of pedersen_hash: it builds the polynomial in pedersen_hash without having to // create a mostly zero-filled buffer and "type cast" it to a 128-long 16-byte // array. Since at most the first 5 coefficients of the polynomial will be non-zero, // these 5 coefficients are created directly. -func treeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte { +func GetTreeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte { if len(address) < 32 { var aligned [32]byte address = append(aligned[:32-len(address)], address...) @@ -154,12 +154,12 @@ func treeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte { return pointToHash(ret, subIndex) } -// treeKeyWithEvaluatedAddress is basically identical to treeKey, the only +// 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 treeKeyWithEvaluatedAddress(evaluated *verkle.Point, treeIndex *uint256.Int, subIndex byte) []byte { +func GetTreeKeyWithEvaluatedAddress(evaluated *verkle.Point, treeIndex *uint256.Int, subIndex byte) []byte { var poly [5]fr.Element poly[0].SetZero() @@ -184,23 +184,23 @@ func treeKeyWithEvaluatedAddress(evaluated *verkle.Point, treeIndex *uint256.Int } func VersionKey(address []byte) []byte { - return treeKey(address, zero, VersionLeafKey) + return GetTreeKey(address, zero, VersionLeafKey) } func BalanceKey(address []byte) []byte { - return treeKey(address, zero, BalanceLeafKey) + return GetTreeKey(address, zero, BalanceLeafKey) } func NonceKey(address []byte) []byte { - return treeKey(address, zero, NonceLeafKey) + return GetTreeKey(address, zero, NonceLeafKey) } func CodeKeccakKey(address []byte) []byte { - return treeKey(address, zero, CodeKeccakLeafKey) + return GetTreeKey(address, zero, CodeKeccakLeafKey) } func CodeSizeKey(address []byte) []byte { - return treeKey(address, zero, CodeSizeLeafKey) + return GetTreeKey(address, zero, CodeSizeLeafKey) } func codeChunkIndex(chunk *uint256.Int) (*uint256.Int, byte) { @@ -218,7 +218,7 @@ func codeChunkIndex(chunk *uint256.Int) (*uint256.Int, byte) { func CodeChunkKey(address []byte, chunk *uint256.Int) []byte { treeIndex, subIndex := codeChunkIndex(chunk) - return treeKey(address, treeIndex, subIndex) + return GetTreeKey(address, treeIndex, subIndex) } func storageIndex(bytes []byte) (*uint256.Int, byte) { @@ -246,37 +246,37 @@ func storageIndex(bytes []byte) (*uint256.Int, byte) { func StorageSlotKey(address []byte, storageKey []byte) []byte { treeIndex, subIndex := storageIndex(storageKey) - return treeKey(address, treeIndex, subIndex) + return GetTreeKey(address, treeIndex, subIndex) } func VersionKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte { - return treeKeyWithEvaluatedAddress(evaluated, zero, VersionLeafKey) + return GetTreeKeyWithEvaluatedAddress(evaluated, zero, VersionLeafKey) } func BalanceKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte { - return treeKeyWithEvaluatedAddress(evaluated, zero, BalanceLeafKey) + return GetTreeKeyWithEvaluatedAddress(evaluated, zero, BalanceLeafKey) } func NonceKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte { - return treeKeyWithEvaluatedAddress(evaluated, zero, NonceLeafKey) + return GetTreeKeyWithEvaluatedAddress(evaluated, zero, NonceLeafKey) } func CodeKeccakKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte { - return treeKeyWithEvaluatedAddress(evaluated, zero, CodeKeccakLeafKey) + return GetTreeKeyWithEvaluatedAddress(evaluated, zero, CodeKeccakLeafKey) } func CodeSizeKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte { - return treeKeyWithEvaluatedAddress(evaluated, zero, CodeSizeLeafKey) + return GetTreeKeyWithEvaluatedAddress(evaluated, zero, CodeSizeLeafKey) } func CodeChunkKeyWithEvaluatedAddress(addressPoint *verkle.Point, chunk *uint256.Int) []byte { treeIndex, subIndex := codeChunkIndex(chunk) - return treeKeyWithEvaluatedAddress(addressPoint, treeIndex, subIndex) + return GetTreeKeyWithEvaluatedAddress(addressPoint, treeIndex, subIndex) } func StorageSlotKeyWithEvaluatedAddress(evaluated *verkle.Point, storageKey []byte) []byte { treeIndex, subIndex := storageIndex(storageKey) - return treeKeyWithEvaluatedAddress(evaluated, treeIndex, subIndex) + return GetTreeKeyWithEvaluatedAddress(evaluated, treeIndex, subIndex) } func pointToHash(evaluated *verkle.Point, suffix byte) []byte { diff --git a/trie/verkle/utils_test.go b/trie/utils/verkle_test.go similarity index 99% rename from trie/verkle/utils_test.go rename to trie/utils/verkle_test.go index d75adf975826..ca9fc9e30042 100644 --- a/trie/verkle/utils_test.go +++ b/trie/utils/verkle_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see -package verkle +package utils import ( "bytes" diff --git a/trie/verkle.go b/trie/verkle.go index 8c236fbd2351..9baadd93e4be 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -26,7 +26,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie/trienode" - utils "github.com/ethereum/go-ethereum/trie/verkle" + "github.com/ethereum/go-ethereum/trie/utils" "github.com/gballet/go-verkle" "github.com/holiman/uint256" ) @@ -41,12 +41,12 @@ var ( type VerkleTrie struct { root verkle.VerkleNode db *Database - cache *utils.Cache + cache *utils.PointCache reader *trieReader } // NewVerkleTrie constructs a verkle tree based on the specified root hash. -func NewVerkleTrie(root common.Hash, db *Database, cache *utils.Cache) (*VerkleTrie, error) { +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 @@ -82,13 +82,13 @@ func (t *VerkleTrie) GetKey(key []byte) []byte { // be returned. If the tree is corrupted, an error will be returned. func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error) { var ( - acct = &types.StateAccount{} + acc = &types.StateAccount{} values [][]byte err error ) switch n := t.root.(type) { case *verkle.InternalNode: - values, err = n.GetStem(t.cache.GetStem(addr[:]), t.nodeResolver) + values, err = n.GetStem(t.cache.DeriveStem(addr[:]), t.nodeResolver) if err != nil { return nil, fmt.Errorf("GetAccount (%x) error: %v", addr, err) } @@ -100,7 +100,7 @@ func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error } // Decode nonce in little-endian if len(values[utils.NonceLeafKey]) > 0 { - acct.Nonce = binary.LittleEndian.Uint64(values[utils.NonceLeafKey]) + acc.Nonce = binary.LittleEndian.Uint64(values[utils.NonceLeafKey]) } // Decode balance in little-endian var balance [32]byte @@ -108,13 +108,13 @@ func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error for i := 0; i < len(balance)/2; i++ { balance[len(balance)-i-1], balance[i] = balance[i], balance[len(balance)-i-1] } - acct.Balance = new(big.Int).SetBytes(balance[:]) + acc.Balance = new(big.Int).SetBytes(balance[:]) // Decode codehash - acct.CodeHash = values[utils.CodeKeccakLeafKey] + acc.CodeHash = values[utils.CodeKeccakLeafKey] // TODO account.Root is leave as empty. How should we handle the legacy account? - return acct, nil + return acc, nil } // GetStorage implements state.Trie, retrieving the storage slot with the specified @@ -131,21 +131,21 @@ func (t *VerkleTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) // 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, acct *types.StateAccount) error { +func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount) error { var ( err error nonce, balance [32]byte values = make([][]byte, verkle.NodeWidth) ) values[utils.VersionLeafKey] = zero[:] - values[utils.CodeKeccakLeafKey] = acct.CodeHash[:] + values[utils.CodeKeccakLeafKey] = acc.CodeHash[:] // Encode nonce in little-endian - binary.LittleEndian.PutUint64(nonce[:], acct.Nonce) + binary.LittleEndian.PutUint64(nonce[:], acc.Nonce) values[utils.NonceLeafKey] = nonce[:] // Encode balance in little-endian - bytes := acct.Balance.Bytes() + bytes := acc.Balance.Bytes() if len(bytes) > 0 { for i, b := range bytes { balance[len(bytes)-i-1] = b @@ -155,7 +155,7 @@ func (t *VerkleTrie) UpdateAccount(addr common.Address, acct *types.StateAccount switch n := t.root.(type) { case *verkle.InternalNode: - err = n.InsertStem(t.cache.GetStem(addr[:]), values, t.nodeResolver) + err = n.InsertStem(t.cache.DeriveStem(addr[:]), values, t.nodeResolver) if err != nil { return fmt.Errorf("UpdateAccount (%x) error: %v", addr, err) } @@ -169,7 +169,7 @@ func (t *VerkleTrie) UpdateAccount(addr common.Address, acct *types.StateAccount // 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 { - // Right padding the slot value to 32 bytes. + // Left padding the slot value to 32 bytes. var v [32]byte if len(value) >= 32 { copy(v[:], value[:32]) @@ -193,7 +193,7 @@ func (t *VerkleTrie) DeleteAccount(addr common.Address) error { } switch n := t.root.(type) { case *verkle.InternalNode: - err = n.InsertStem(t.cache.GetStem(addr.Bytes()), values, t.nodeResolver) + err = n.InsertStem(t.cache.DeriveStem(addr.Bytes()), values, t.nodeResolver) if err != nil { return fmt.Errorf("DeleteAccount (%x) error: %v", addr, err) } @@ -239,6 +239,8 @@ func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) { // 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) { panic("not implemented") } @@ -250,6 +252,8 @@ func (t *VerkleTrie) NodeIterator(startKey []byte) (NodeIterator, error) { // 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") } diff --git a/trie/verkle_test.go b/trie/verkle_test.go index d41befbabe0a..44fb7dc29e9b 100644 --- a/trie/verkle_test.go +++ b/trie/verkle_test.go @@ -26,7 +26,7 @@ import ( "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/verkle" + "github.com/ethereum/go-ethereum/trie/utils" ) var ( @@ -63,7 +63,7 @@ func TestVerkleTreeReadWrite(t *testing.T) { }) defer db.Close() - tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, verkle.NewCache(100)) + tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache(100)) for addr, acct := range accounts { if err := tr.UpdateAccount(addr, acct); err != nil { From 59ffe0bc1ef8e6b214e5bd2a5f28dd0248c226e3 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Tue, 31 Oct 2023 14:30:51 +0800 Subject: [PATCH 3/7] trie/utils: add benchmark --- trie/utils/verkle.go | 1 + trie/utils/verkle_test.go | 77 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go index b4276e4a052f..ccbad0b3738d 100644 --- a/trie/utils/verkle.go +++ b/trie/utils/verkle.go @@ -276,6 +276,7 @@ func CodeChunkKeyWithEvaluatedAddress(addressPoint *verkle.Point, chunk *uint256 func StorageSlotKeyWithEvaluatedAddress(evaluated *verkle.Point, storageKey []byte) []byte { treeIndex, subIndex := storageIndex(storageKey) + //fmt.Println("treeIndex", treeIndex, "sub", subIndex) return GetTreeKeyWithEvaluatedAddress(evaluated, treeIndex, subIndex) } diff --git a/trie/utils/verkle_test.go b/trie/utils/verkle_test.go index ca9fc9e30042..cdbfb0bb40a7 100644 --- a/trie/utils/verkle_test.go +++ b/trie/utils/verkle_test.go @@ -18,6 +18,7 @@ package utils import ( "bytes" + "github.com/gballet/go-verkle" "testing" "github.com/holiman/uint256" @@ -60,3 +61,79 @@ func TestTreeKey(t *testing.T) { 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)) + } +} From 7ce79d7e4a3f8f8d7f4c312db397e00af5a2de53 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Tue, 31 Oct 2023 14:39:56 +0800 Subject: [PATCH 4/7] trie/utils: fix lint --- trie/utils/verkle_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trie/utils/verkle_test.go b/trie/utils/verkle_test.go index cdbfb0bb40a7..28b059c3794e 100644 --- a/trie/utils/verkle_test.go +++ b/trie/utils/verkle_test.go @@ -18,9 +18,9 @@ package utils import ( "bytes" - "github.com/gballet/go-verkle" "testing" + "github.com/gballet/go-verkle" "github.com/holiman/uint256" ) From 977722964e411501847764475cbf92a4d5ef487b Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Tue, 31 Oct 2023 14:44:35 +0800 Subject: [PATCH 5/7] core/state: add description --- core/state/trie_prefetcher.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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) From d04ccc62a1a3236aa7f51d369b5fcdc58168e3c9 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Tue, 31 Oct 2023 15:04:43 +0800 Subject: [PATCH 6/7] trie/utils: add function description --- trie/utils/verkle.go | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go index ccbad0b3738d..20e0d51ce1be 100644 --- a/trie/utils/verkle.go +++ b/trie/utils/verkle.go @@ -28,6 +28,8 @@ import ( ) 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 @@ -183,22 +185,29 @@ func GetTreeKeyWithEvaluatedAddress(evaluated *verkle.Point, treeIndex *uint256. return pointToHash(ret, subIndex) } +// VersionKey returns the verkle tree key of the version field for the specified account. func VersionKey(address []byte) []byte { return GetTreeKey(address, zero, VersionLeafKey) } +// BalanceKey returns the verkle tree key of the balance field for the specified account. func BalanceKey(address []byte) []byte { return GetTreeKey(address, zero, BalanceLeafKey) } +// NonceKey returns the verkle tree key of the nonce field for the specified account. func NonceKey(address []byte) []byte { return GetTreeKey(address, zero, NonceLeafKey) } +// 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) } +// 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) } @@ -216,6 +225,8 @@ func codeChunkIndex(chunk *uint256.Int) (*uint256.Int, byte) { return 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) @@ -244,39 +255,61 @@ func storageIndex(bytes []byte) (*uint256.Int, byte) { return &key, byte(key[0] & 0xFF) } +// StorageSlotKey returns the verkle tree key of the storage slot for the +// specified account. func StorageSlotKey(address []byte, storageKey []byte) []byte { treeIndex, subIndex := storageIndex(storageKey) return GetTreeKey(address, treeIndex, subIndex) } +// VersionKeyWithEvaluatedAddress returns the verkle tree key of the version +// field for the specified account. The difference between VersionKey is the +// address evaluation is already computed to minimize the computational overhead. func VersionKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte { return GetTreeKeyWithEvaluatedAddress(evaluated, zero, VersionLeafKey) } +// BalanceKeyWithEvaluatedAddress returns the verkle tree key of the balance +// field for the specified account. The difference between BalanceKey is the +// address evaluation is already computed to minimize the computational overhead. func BalanceKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte { return GetTreeKeyWithEvaluatedAddress(evaluated, zero, BalanceLeafKey) } +// NonceKeyWithEvaluatedAddress returns the verkle tree key of the nonce +// field for the specified account. The difference between NonceKey is the +// address evaluation is already computed to minimize the computational overhead. func NonceKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte { return GetTreeKeyWithEvaluatedAddress(evaluated, zero, NonceLeafKey) } +// CodeKeccakKeyWithEvaluatedAddress returns the verkle tree key of the code +// keccak for the specified account. The difference between CodeKeccakKey is the +// address evaluation is already computed to minimize the computational overhead. func CodeKeccakKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte { return GetTreeKeyWithEvaluatedAddress(evaluated, zero, CodeKeccakLeafKey) } +// CodeSizeKeyWithEvaluatedAddress returns the verkle tree key of the code +// size for the specified account. The difference between CodeSizeKey is the +// address evaluation is already computed to minimize the computational overhead. func CodeSizeKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte { return GetTreeKeyWithEvaluatedAddress(evaluated, zero, CodeSizeLeafKey) } +// CodeChunkKeyWithEvaluatedAddress returns the verkle tree key of the code +// chunk for the specified account. The difference between CodeChunkKey is the +// address evaluation is already computed to minimize the computational overhead. func CodeChunkKeyWithEvaluatedAddress(addressPoint *verkle.Point, chunk *uint256.Int) []byte { treeIndex, subIndex := codeChunkIndex(chunk) return GetTreeKeyWithEvaluatedAddress(addressPoint, treeIndex, subIndex) } +// StorageSlotKeyWithEvaluatedAddress returns the verkle tree key of the storage +// slot for the specified account. The difference between StorageSlotKey is the +// address evaluation is already computed to minimize the computational overhead. func StorageSlotKeyWithEvaluatedAddress(evaluated *verkle.Point, storageKey []byte) []byte { treeIndex, subIndex := storageIndex(storageKey) - //fmt.Println("treeIndex", treeIndex, "sub", subIndex) return GetTreeKeyWithEvaluatedAddress(evaluated, treeIndex, subIndex) } From e51af238ad40902bbd306093de25e442fa8f6072 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 31 Oct 2023 11:41:36 +0100 Subject: [PATCH 7/7] use new InsertValuesAtStem function name --- trie/utils/verkle.go | 4 ++-- trie/verkle.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go index 20e0d51ce1be..0ed8f83e7502 100644 --- a/trie/utils/verkle.go +++ b/trie/utils/verkle.go @@ -109,9 +109,9 @@ func (c *PointCache) Get(addr []byte) *verkle.Point { return p } -// DeriveStem returns the first 31 bytes of the tree key as the tree stem. It only +// 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) DeriveStem(addr []byte) []byte { +func (c *PointCache) GetStem(addr []byte) []byte { p := c.Get(addr) return pointToHash(p, 0)[:31] } diff --git a/trie/verkle.go b/trie/verkle.go index 9baadd93e4be..89e2e534089f 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -88,7 +88,7 @@ func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error ) switch n := t.root.(type) { case *verkle.InternalNode: - values, err = n.GetStem(t.cache.DeriveStem(addr[:]), t.nodeResolver) + values, err = n.GetValuesAtStem(t.cache.GetStem(addr[:]), t.nodeResolver) if err != nil { return nil, fmt.Errorf("GetAccount (%x) error: %v", addr, err) } @@ -155,7 +155,7 @@ func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount) switch n := t.root.(type) { case *verkle.InternalNode: - err = n.InsertStem(t.cache.DeriveStem(addr[:]), values, t.nodeResolver) + err = n.InsertValuesAtStem(t.cache.GetStem(addr[:]), values, t.nodeResolver) if err != nil { return fmt.Errorf("UpdateAccount (%x) error: %v", addr, err) } @@ -193,7 +193,7 @@ func (t *VerkleTrie) DeleteAccount(addr common.Address) error { } switch n := t.root.(type) { case *verkle.InternalNode: - err = n.InsertStem(t.cache.DeriveStem(addr.Bytes()), values, t.nodeResolver) + err = n.InsertValuesAtStem(t.cache.GetStem(addr.Bytes()), values, t.nodeResolver) if err != nil { return fmt.Errorf("DeleteAccount (%x) error: %v", addr, err) } @@ -354,7 +354,7 @@ func (t *VerkleTrie) UpdateContractCode(addr common.Address, codeHash common.Has if groupOffset == 255 || len(chunks)-i <= 32 { switch root := t.root.(type) { case *verkle.InternalNode: - err = root.InsertStem(key[:31], values, t.nodeResolver) + err = root.InsertValuesAtStem(key[:31], values, t.nodeResolver) if err != nil { return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err) }