mirror of https://github.com/ethereum/go-ethereum
cmd, core, trie: verkle-capable `geth init` (#28270)
This change allows the creation of a genesis block for verkle testnets. This makes for a chunk of code that is easier to review and still touches many discussion points.pull/28521/head
parent
f265cc24b4
commit
fa8d39807d
@ -0,0 +1,342 @@ |
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package utils |
||||
|
||||
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 ( |
||||
// 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 |
||||
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]
|
||||
|
||||
// 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 { |
||||
lru lru.BasicLRU[string, *verkle.Point] |
||||
lock sync.RWMutex |
||||
} |
||||
|
||||
// NewPointCache returns the cache with specified size.
|
||||
func NewPointCache(maxItems int) *PointCache { |
||||
return &PointCache{ |
||||
lru: lru.NewBasicLRU[string, *verkle.Point](maxItems), |
||||
} |
||||
} |
||||
|
||||
// Get returns the cached commitment for the specified address, or computing
|
||||
// it on the flight.
|
||||
func (c *PointCache) Get(addr []byte) *verkle.Point { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
|
||||
p, ok := c.lru.Get(string(addr)) |
||||
if ok { |
||||
cacheHitGauge.Inc(1) |
||||
return p |
||||
} |
||||
cacheMissGauge.Inc(1) |
||||
p = evaluateAddressPoint(addr) |
||||
c.lru.Add(string(addr), p) |
||||
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 *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
|
||||
// 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, index0Point) |
||||
|
||||
return pointToHash(ret, subIndex) |
||||
} |
||||
|
||||
// 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 |
||||
|
||||
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) |
||||
} |
||||
|
||||
// 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) |
||||
} |
||||
|
||||
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 |
||||
} |
||||
|
||||
// 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 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<codeStorageDelta.
|
||||
key.Add(headerStorageOffset, &key) |
||||
|
||||
// In this branch, the tree-index is zero since we're in the account header,
|
||||
// and the sub-index is the LSB of the modified storage key.
|
||||
return zero, byte(key[0] & 0xFF) |
||||
} |
||||
// We first divide by VerkleNodeWidth to create room to avoid an overflow next.
|
||||
key.Rsh(&key, uint(verkleNodeWidthLog2)) |
||||
|
||||
// We add mainStorageOffset/VerkleNodeWidth which can't overflow.
|
||||
key.Add(&key, mainStorageOffsetLshVerkleNodeWidth) |
||||
|
||||
// The sub-index is the LSB of the original storage key, since mainStorageOffset
|
||||
// doesn't affect this byte, so we can avoid masks or shifts.
|
||||
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) |
||||
return GetTreeKeyWithEvaluatedAddress(evaluated, treeIndex, subIndex) |
||||
} |
||||
|
||||
func pointToHash(evaluated *verkle.Point, suffix byte) []byte { |
||||
// The output of Byte() is big endian 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.
|
||||
bytes := evaluated.Bytes() |
||||
for i := 0; i < 16; i++ { |
||||
bytes[31-i], bytes[i] = bytes[i], bytes[31-i] |
||||
} |
||||
bytes[31] = suffix |
||||
return bytes[:] |
||||
} |
||||
|
||||
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, index0Point) |
||||
return ret |
||||
} |
@ -0,0 +1,139 @@ |
||||
// 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 <http://www.gnu.org/licenses/>
|
||||
|
||||
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)) |
||||
} |
||||
} |
@ -0,0 +1,375 @@ |
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package trie |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"errors" |
||||
"fmt" |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/ethdb" |
||||
"github.com/ethereum/go-ethereum/trie/trienode" |
||||
"github.com/ethereum/go-ethereum/trie/utils" |
||||
"github.com/gballet/go-verkle" |
||||
"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 |
||||
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.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: node, |
||||
db: db, |
||||
cache: cache, |
||||
reader: reader, |
||||
}, nil |
||||
} |
||||
|
||||
// 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 |
||||
} |
||||
|
||||
// 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) { |
||||
var ( |
||||
acc = &types.StateAccount{} |
||||
values [][]byte |
||||
err error |
||||
) |
||||
switch n := t.root.(type) { |
||||
case *verkle.InternalNode: |
||||
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 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 |
||||
} |
||||
|
||||
// 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) |
||||
) |
||||
values[utils.VersionLeafKey] = zero[:] |
||||
values[utils.CodeKeccakLeafKey] = acc.CodeHash[:] |
||||
|
||||
// Encode nonce in little-endian
|
||||
binary.LittleEndian.PutUint64(nonce[:], acc.Nonce) |
||||
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 n := t.root.(type) { |
||||
case *verkle.InternalNode: |
||||
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 |
||||
} |
||||
// TODO figure out if the code size needs to be updated, too
|
||||
return nil |
||||
} |
||||
|
||||
// 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 { |
||||
// 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[:]) |
||||
} |
||||
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) |
||||
) |
||||
for i := 0; i < verkle.NodeWidth; i++ { |
||||
values[i] = zero[:] |
||||
} |
||||
switch n := t.root.(type) { |
||||
case *verkle.InternalNode: |
||||
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 |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// 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 { |
||||
var zero [32]byte |
||||
k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key) |
||||
return t.root.Insert(k, zero[:], t.nodeResolver) |
||||
} |
||||
|
||||
// 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() |
||||
} |
||||
|
||||
// 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 { |
||||
return common.Hash{}, nil, errors.New("unexpected root node type") |
||||
} |
||||
nodes, err := root.BatchSerialize() |
||||
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
|
||||
return t.Hash(), nodeset, nil |
||||
} |
||||
|
||||
// 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") |
||||
} |
||||
|
||||
// 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, |
||||
cache: t.cache, |
||||
reader: t.reader, |
||||
} |
||||
} |
||||
|
||||
// IsVerkle indicates if the trie is a Verkle trie.
|
||||
func (t *VerkleTrie) IsVerkle() bool { |
||||
return true |
||||
} |
||||
|
||||
// ChunkedCode represents a sequence of 32-bytes chunks of code (31 bytes of which
|
||||
// are actual code, and 1 byte is the pushdata offset).
|
||||
type ChunkedCode []byte |
||||
|
||||
// Copy the values here so as to avoid an import cycle
|
||||
const ( |
||||
PUSH1 = byte(0x60) |
||||
PUSH32 = byte(0x7f) |
||||
) |
||||
|
||||
// ChunkifyCode generates the chunked version of an array representing EVM bytecode
|
||||
func ChunkifyCode(code []byte) ChunkedCode { |
||||
var ( |
||||
chunkOffset = 0 // offset in the chunk
|
||||
chunkCount = len(code) / 31 |
||||
codeOffset = 0 // offset in the code
|
||||
) |
||||
if len(code)%31 != 0 { |
||||
chunkCount++ |
||||
} |
||||
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.
|
||||
end := 31 * (i + 1) |
||||
if len(code) < end { |
||||
end = len(code) |
||||
} |
||||
copy(chunks[i*32+1:], code[31*i:end]) // copy the code itself
|
||||
|
||||
// chunk offset = taken from the last chunk.
|
||||
if chunkOffset > 31 { |
||||
// skip offset calculation if push data covers the whole chunk
|
||||
chunks[i*32] = 31 |
||||
chunkOffset = 1 |
||||
continue |
||||
} |
||||
chunks[32*i] = byte(chunkOffset) |
||||
chunkOffset = 0 |
||||
|
||||
// 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) |
||||
if codeOffset+1 >= 31*(i+1) { |
||||
codeOffset++ |
||||
chunkOffset = codeOffset - 31*(i+1) |
||||
break |
||||
} |
||||
} |
||||
} |
||||
} |
||||
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) |
||||
values [][]byte |
||||
key []byte |
||||
err error |
||||
) |
||||
for i, chunknr := 0, uint64(0); i < len(chunks); i, chunknr = i+32, chunknr+1 { |
||||
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.CodeChunkKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), uint256.NewInt(chunknr)) |
||||
} |
||||
values[groupOffset] = chunks[i : i+32] |
||||
|
||||
// Reuse the calculated key to also update the code size.
|
||||
if i == 0 { |
||||
cs := make([]byte, 32) |
||||
binary.LittleEndian.PutUint64(cs, uint64(len(code))) |
||||
values[utils.CodeSizeLeafKey] = cs |
||||
} |
||||
if groupOffset == 255 || len(chunks)-i <= 32 { |
||||
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{}) |
||||
} |
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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") |
||||
} |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue