mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 21:56:43 +00:00
trie{,/utils}: add tests for eip7864 binary trees
Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
This commit is contained in:
parent
9b2e8e7ce3
commit
2656abc317
12 changed files with 1568 additions and 3 deletions
3
go.mod
3
go.mod
|
|
@ -62,6 +62,7 @@ require (
|
|||
github.com/supranational/blst v0.3.14
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
|
||||
github.com/urfave/cli/v2 v2.27.5
|
||||
github.com/zeebo/blake3 v0.2.4
|
||||
go.uber.org/automaxprocs v1.5.2
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
|
|
@ -115,7 +116,7 @@ require (
|
|||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/kilic/bls12-381 v0.1.0 // indirect
|
||||
github.com/klauspost/compress v1.16.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
|
|
|
|||
10
go.sum
10
go.sum
|
|
@ -220,8 +220,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
|
||||
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
|
|
@ -365,6 +365,12 @@ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBi
|
|||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
||||
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
|
|
|
|||
319
trie/binary.go
Normal file
319
trie/binary.go
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
// Copyright 2025 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"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"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/bintrie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
"github.com/ethereum/go-ethereum/triedb/database"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// zero is the zero value for a 32-byte array.
|
||||
var zero [32]byte
|
||||
|
||||
// NewBinaryNode creates a new empty binary trie
|
||||
func NewBinaryNode() bintrie.BinaryNode {
|
||||
return bintrie.Empty{}
|
||||
}
|
||||
|
||||
// BinaryTrie is a wrapper around VerkleNode that implements the trie.Trie
|
||||
// interface so that Verkle trees can be reused verbatim.
|
||||
type BinaryTrie struct {
|
||||
root bintrie.BinaryNode
|
||||
reader *trieReader
|
||||
}
|
||||
|
||||
// ToDot converts the binary trie to a DOT language representation. Useful for debugging.
|
||||
func (trie *BinaryTrie) ToDot() string {
|
||||
trie.root.Hash()
|
||||
return bintrie.ToDot(trie.root)
|
||||
}
|
||||
|
||||
// NewBinaryTrie creates a new binary trie.
|
||||
func NewBinaryTrie(root common.Hash, db database.NodeDatabase) (*BinaryTrie, 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 := NewBinaryNode()
|
||||
if root != types.EmptyVerkleHash && root != types.EmptyRootHash {
|
||||
blob, err := reader.node(nil, common.Hash{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err = bintrie.DeserializeNode(blob, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &BinaryTrie{
|
||||
root: node,
|
||||
reader: reader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FlatdbNodeResolver is a node resolver that reads nodes from the flatdb.
|
||||
func (trie *BinaryTrie) FlatdbNodeResolver(path []byte, hash common.Hash) ([]byte, error) {
|
||||
// empty nodes will be serialized as common.Hash{}, so capture
|
||||
// this special use case.
|
||||
if hash == (common.Hash{}) {
|
||||
return nil, nil // empty node
|
||||
}
|
||||
return trie.reader.node(path, hash)
|
||||
}
|
||||
|
||||
// GetKey returns the sha3 preimage of a hashed key that was previously used
|
||||
// to store a value.
|
||||
func (trie *BinaryTrie) GetKey(key []byte) []byte {
|
||||
return key
|
||||
}
|
||||
|
||||
// Get returns the value for key stored in the trie. The value bytes must
|
||||
// not be modified by the caller. If a node was not found in the database, a
|
||||
// trie.MissingNodeError is returned.
|
||||
func (trie *BinaryTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
|
||||
return trie.root.Get(utils.GetBinaryTreeKey(addr, key), trie.FlatdbNodeResolver)
|
||||
}
|
||||
|
||||
// GetWithHashedKey returns the value, assuming that the key has already
|
||||
// been hashed.
|
||||
func (trie *BinaryTrie) GetWithHashedKey(key []byte) ([]byte, error) {
|
||||
return trie.root.Get(key, trie.FlatdbNodeResolver)
|
||||
}
|
||||
|
||||
// GetAccount returns the account information for the given address.
|
||||
func (trie *BinaryTrie) GetAccount(addr common.Address) (*types.StateAccount, error) {
|
||||
acc := &types.StateAccount{}
|
||||
versionkey := utils.GetBinaryTreeKey(addr, zero[:])
|
||||
var (
|
||||
values [][]byte
|
||||
err error
|
||||
)
|
||||
switch r := trie.root.(type) {
|
||||
case *bintrie.InternalNode:
|
||||
values, err = r.GetValuesAtStem(versionkey[:31], trie.FlatdbNodeResolver)
|
||||
case *bintrie.StemNode:
|
||||
values = r.Values
|
||||
case bintrie.Empty:
|
||||
return nil, nil
|
||||
default:
|
||||
// This will cover HashedNode but that should be fine since the
|
||||
// root node should always be resolved.
|
||||
return nil, errInvalidRootType
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetAccount (%x) error: %v", addr, err)
|
||||
}
|
||||
|
||||
// The following code is required for the MPT->VKT conversion.
|
||||
// An account can be partially migrated, where storage slots were moved to the VKT
|
||||
// but not yet the account. This means some account information as (header) storage slots
|
||||
// are in the VKT but basic account information must be read in the base tree (MPT).
|
||||
// TODO: we can simplify this logic depending if the conversion is in progress or finished.
|
||||
emptyAccount := true
|
||||
|
||||
for i := 0; values != nil && i <= utils.CodeHashLeafKey && emptyAccount; i++ {
|
||||
emptyAccount = emptyAccount && values[i] == nil
|
||||
}
|
||||
if emptyAccount {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// if the account has been deleted, then values[10] will be 0 and not nil. If it has
|
||||
// been recreated after that, then its code keccak will NOT be 0. So return `nil` if
|
||||
// the nonce, and values[10], and code keccak is 0.
|
||||
if bytes.Equal(values[utils.BasicDataLeafKey], zero[:]) && len(values) > 10 && len(values[10]) > 0 && bytes.Equal(values[utils.CodeHashLeafKey], zero[:]) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
acc.Nonce = binary.BigEndian.Uint64(values[utils.BasicDataLeafKey][utils.BasicDataNonceOffset:])
|
||||
var balance [16]byte
|
||||
copy(balance[:], values[utils.BasicDataLeafKey][utils.BasicDataBalanceOffset:])
|
||||
acc.Balance = new(uint256.Int).SetBytes(balance[:])
|
||||
acc.CodeHash = values[utils.CodeHashLeafKey]
|
||||
|
||||
return acc, nil
|
||||
}
|
||||
|
||||
// UpdateAccount updates the account information for the given address.
|
||||
func (trie *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error {
|
||||
var (
|
||||
err error
|
||||
basicData [32]byte
|
||||
values = make([][]byte, bintrie.NodeWidth)
|
||||
stem = utils.GetBinaryTreeKey(addr, zero[:])
|
||||
)
|
||||
|
||||
binary.BigEndian.PutUint32(basicData[utils.BasicDataCodeSizeOffset-1:], uint32(codeLen))
|
||||
binary.BigEndian.PutUint64(basicData[utils.BasicDataNonceOffset:], acc.Nonce)
|
||||
// Because the balance is a max of 16 bytes, truncate
|
||||
// the extra values. This happens in devmode, where
|
||||
// 0xff**32 is allocated to the developer account.
|
||||
balanceBytes := acc.Balance.Bytes()
|
||||
// TODO: reduce the size of the allocation in devmode, then panic instead
|
||||
// of truncating.
|
||||
if len(balanceBytes) > 16 {
|
||||
balanceBytes = balanceBytes[16:]
|
||||
}
|
||||
copy(basicData[32-len(balanceBytes):], balanceBytes[:])
|
||||
values[utils.BasicDataLeafKey] = basicData[:]
|
||||
values[utils.CodeHashLeafKey] = acc.CodeHash[:]
|
||||
|
||||
trie.root, err = trie.root.InsertValuesAtStem(stem, values, trie.FlatdbNodeResolver, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateStem updates the values for the given stem key.
|
||||
func (trie *BinaryTrie) UpdateStem(key []byte, values [][]byte) error {
|
||||
var err error
|
||||
trie.root, err = trie.root.InsertValuesAtStem(key, values, trie.FlatdbNodeResolver, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update 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.
|
||||
func (trie *BinaryTrie) UpdateStorage(address common.Address, key, value []byte) error {
|
||||
k := utils.GetBinaryTreeKeyStorageSlot(address, key)
|
||||
var v [32]byte
|
||||
if len(value) >= 32 {
|
||||
copy(v[:], value[:32])
|
||||
} else {
|
||||
copy(v[32-len(value):], value[:])
|
||||
}
|
||||
root, err := trie.root.Insert(k, v[:], trie.FlatdbNodeResolver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("UpdateStorage (%x) error: %v", address, err)
|
||||
}
|
||||
trie.root = root
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAccount is a no-op as it is disabled in stateless.
|
||||
func (trie *BinaryTrie) DeleteAccount(addr common.Address) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes any existing value for key from the trie. If a node was not
|
||||
// found in the database, a trie.MissingNodeError is returned.
|
||||
func (trie *BinaryTrie) DeleteStorage(addr common.Address, key []byte) error {
|
||||
k := utils.GetBinaryTreeKey(addr, key)
|
||||
var zero [32]byte
|
||||
root, err := trie.root.Insert(k, zero[:], trie.FlatdbNodeResolver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DeleteStorage (%x) error: %v", addr, err)
|
||||
}
|
||||
trie.root = root
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (trie *BinaryTrie) Hash() common.Hash {
|
||||
return trie.root.Hash()
|
||||
}
|
||||
|
||||
// Commit writes all nodes to the trie's memory database, tracking the internal
|
||||
// and external (for account tries) references.
|
||||
func (trie *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
|
||||
root := trie.root.(*bintrie.InternalNode)
|
||||
nodeset := trienode.NewNodeSet(common.Hash{})
|
||||
|
||||
err := root.CollectNodes(nil, func(path []byte, node bintrie.BinaryNode) {
|
||||
serialized := bintrie.SerializeNode(node)
|
||||
nodeset.AddNode(path, trienode.New(common.Hash{}, serialized))
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("CollectNodes failed: %v", err))
|
||||
}
|
||||
|
||||
// Serialize root commitment form
|
||||
return trie.Hash(), nodeset, nil
|
||||
}
|
||||
|
||||
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
|
||||
// starts at the key after the given start key.
|
||||
func (trie *BinaryTrie) NodeIterator(startKey []byte) (NodeIterator, error) {
|
||||
return newBinaryNodeIterator(trie, nil)
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
func (trie *BinaryTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of the trie.
|
||||
func (trie *BinaryTrie) Copy() *BinaryTrie {
|
||||
return &BinaryTrie{
|
||||
root: trie.root.Copy(),
|
||||
reader: trie.reader,
|
||||
}
|
||||
}
|
||||
|
||||
// IsVerkle returns true if the trie is a Verkle tree.
|
||||
func (trie *BinaryTrie) IsVerkle() bool {
|
||||
// TODO @gballet This is technically NOT a verkle tree, but it has the same
|
||||
// behavior and basic structure, so for all intents and purposes, it can be
|
||||
// treated as such. Rename this when verkle gets removed.
|
||||
return true
|
||||
}
|
||||
|
||||
// Note: the basic data leaf needs to have been previously created for this to work
|
||||
func (trie *BinaryTrie) 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, bintrie.NodeWidth)
|
||||
var offset [32]byte
|
||||
binary.LittleEndian.PutUint64(offset[24:], chunknr+128)
|
||||
key = utils.GetBinaryTreeKey(addr, offset[:])
|
||||
}
|
||||
values[groupOffset] = chunks[i : i+32]
|
||||
|
||||
if groupOffset == 255 || len(chunks)-i <= 32 {
|
||||
err = trie.UpdateStem(key[:31], values)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
236
trie/binary_iterator.go
Normal file
236
trie/binary_iterator.go
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
// Copyright 2025 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package trie
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||
)
|
||||
|
||||
type binaryNodeIteratorState struct {
|
||||
Node bintrie.BinaryNode
|
||||
Index int
|
||||
}
|
||||
|
||||
type binaryNodeIterator struct {
|
||||
trie *BinaryTrie
|
||||
current bintrie.BinaryNode
|
||||
lastErr error
|
||||
|
||||
stack []binaryNodeIteratorState
|
||||
}
|
||||
|
||||
func newBinaryNodeIterator(trie *BinaryTrie, _ []byte) (NodeIterator, error) {
|
||||
if trie.Hash() == zero {
|
||||
return new(nodeIterator), nil
|
||||
}
|
||||
it := &binaryNodeIterator{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 *binaryNodeIterator) Next(descend bool) bool {
|
||||
if it.lastErr == errIteratorEnd {
|
||||
it.lastErr = errIteratorEnd
|
||||
return false
|
||||
}
|
||||
|
||||
if len(it.stack) == 0 {
|
||||
it.stack = append(it.stack, binaryNodeIteratorState{Node: it.trie.root})
|
||||
it.current = it.trie.root
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
switch node := it.current.(type) {
|
||||
case *bintrie.InternalNode:
|
||||
// index: 0 = nothing visited, 1=left visited, 2=right visited
|
||||
context := &it.stack[len(it.stack)-1]
|
||||
|
||||
// recurse into both children
|
||||
if context.Index == 0 {
|
||||
if _, isempty := node.Left.(bintrie.Empty); node.Left != nil && !isempty {
|
||||
it.stack = append(it.stack, binaryNodeIteratorState{Node: node.Left})
|
||||
it.current = node.Left
|
||||
return it.Next(descend)
|
||||
}
|
||||
|
||||
context.Index++
|
||||
}
|
||||
|
||||
if context.Index == 1 {
|
||||
if _, isempty := node.Right.(bintrie.Empty); node.Right != nil && !isempty {
|
||||
it.stack = append(it.stack, binaryNodeIteratorState{Node: node.Right})
|
||||
it.current = node.Right
|
||||
return it.Next(descend)
|
||||
}
|
||||
|
||||
context.Index++
|
||||
}
|
||||
|
||||
// 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 *bintrie.StemNode:
|
||||
// Look for the next non-empty value
|
||||
for i := it.stack[len(it.stack)-1].Index; i < 256; i++ {
|
||||
if node.Values[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 bintrie.HashedNode:
|
||||
// resolve the node
|
||||
data, err := it.trie.FlatdbNodeResolver(it.Path(), common.Hash(node))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
it.current, err = bintrie.DeserializeNode(data, 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]
|
||||
if parent.Index == 0 {
|
||||
parent.Node.(*bintrie.InternalNode).Left = it.current
|
||||
} else {
|
||||
parent.Node.(*bintrie.InternalNode).Right = it.current
|
||||
}
|
||||
return it.Next(descend)
|
||||
case bintrie.Empty:
|
||||
// do nothing
|
||||
return false
|
||||
default:
|
||||
panic("invalid node type")
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns the error status of the iterator.
|
||||
func (it *binaryNodeIterator) Error() error {
|
||||
if it.lastErr == errIteratorEnd {
|
||||
return nil
|
||||
}
|
||||
return it.lastErr
|
||||
}
|
||||
|
||||
// Hash returns the hash of the current node.
|
||||
func (it *binaryNodeIterator) Hash() common.Hash {
|
||||
return it.current.Hash()
|
||||
}
|
||||
|
||||
// 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 *binaryNodeIterator) Parent() common.Hash {
|
||||
return it.stack[len(it.stack)-1].Node.Hash()
|
||||
}
|
||||
|
||||
// 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 *binaryNodeIterator) 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 *binaryNodeIterator) NodeBlob() []byte {
|
||||
panic("not completely implemented")
|
||||
}
|
||||
|
||||
// Leaf returns true iff the current node is a leaf node.
|
||||
func (it *binaryNodeIterator) Leaf() bool {
|
||||
_, ok := it.current.(*bintrie.StemNode)
|
||||
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 *binaryNodeIterator) LeafKey() []byte {
|
||||
leaf, ok := it.current.(*bintrie.StemNode)
|
||||
if !ok {
|
||||
panic("Leaf() called on an binary 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 *binaryNodeIterator) LeafBlob() []byte {
|
||||
leaf, ok := it.current.(*bintrie.StemNode)
|
||||
if !ok {
|
||||
panic("LeafBlob() called on an binary node iterator not at a leaf location")
|
||||
}
|
||||
|
||||
return leaf.Values[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 *binaryNodeIterator) LeafProof() [][]byte {
|
||||
_, ok := it.current.(*bintrie.StemNode)
|
||||
if !ok {
|
||||
panic("LeafProof() called on an binary 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 *binaryNodeIterator) AddResolver(NodeResolver) {
|
||||
// Not implemented, but should not panic
|
||||
}
|
||||
69
trie/binary_iterator_test.go
Normal file
69
trie/binary_iterator_test.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2025 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package trie
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
func TestBinaryIterator(t *testing.T) {
|
||||
trie, err := NewBinaryTrie(types.EmptyVerkleHash, newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
account0 := &types.StateAccount{
|
||||
Nonce: 1,
|
||||
Balance: uint256.NewInt(2),
|
||||
Root: types.EmptyRootHash,
|
||||
CodeHash: nil,
|
||||
}
|
||||
// NOTE: the code size isn't written to the trie via TryUpdateAccount
|
||||
// so it will be missing from the test nodes.
|
||||
trie.UpdateAccount(common.Address{}, account0, 0)
|
||||
account1 := &types.StateAccount{
|
||||
Nonce: 1337,
|
||||
Balance: uint256.NewInt(2000),
|
||||
Root: types.EmptyRootHash,
|
||||
CodeHash: nil,
|
||||
}
|
||||
// This address is meant to hash to a value that has the same first byte as 0xbf
|
||||
var clash = common.HexToAddress("69fd8034cdb20934dedffa7dccb4fb3b8062a8be")
|
||||
trie.UpdateAccount(clash, account1, 0)
|
||||
|
||||
// Manually go over every node to check that we get all
|
||||
// the correct nodes.
|
||||
it, err := trie.NodeIterator(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var leafcount int
|
||||
for it.Next(true) {
|
||||
t.Logf("Node: %x", it.Path())
|
||||
if it.Leaf() {
|
||||
leafcount++
|
||||
t.Logf("\tLeaf: %x", it.LeafKey())
|
||||
}
|
||||
}
|
||||
if leafcount != 2 {
|
||||
t.Fatalf("invalid leaf count: %d != 6", leafcount)
|
||||
}
|
||||
}
|
||||
198
trie/binary_test.go
Normal file
198
trie/binary_test.go
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
// Copyright 2025 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"
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||
)
|
||||
|
||||
var (
|
||||
zeroKey = [32]byte{}
|
||||
oneKey = common.HexToHash("0101010101010101010101010101010101010101010101010101010101010101")
|
||||
twoKey = common.HexToHash("0202020202020202020202020202020202020202020202020202020202020202")
|
||||
threeKey = common.HexToHash("0303030303030303030303030303030303030303030303030303030303030303")
|
||||
fourKey = common.HexToHash("0404040404040404040404040404040404040404040404040404040404040404")
|
||||
ffKey = common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
)
|
||||
|
||||
func TestSingleEntry(t *testing.T) {
|
||||
tree := NewBinaryNode()
|
||||
tree, err := tree.Insert(zeroKey[:], oneKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tree.GetHeight() != 1 {
|
||||
t.Fatal("invalid depth")
|
||||
}
|
||||
expected := common.HexToHash("694545468677064fd833cddc8455762fe6b21c6cabe2fc172529e0f573181cd5")
|
||||
got := tree.Hash()
|
||||
if got != expected {
|
||||
t.Fatalf("invalid tree root, got %x, want %x", got, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTwoEntriesDiffFirstBit(t *testing.T) {
|
||||
var err error
|
||||
tree := NewBinaryNode()
|
||||
tree, err = tree.Insert(zeroKey[:], oneKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tree, err = tree.Insert(common.HexToHash("8000000000000000000000000000000000000000000000000000000000000000").Bytes(), twoKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tree.GetHeight() != 2 {
|
||||
t.Fatal("invalid height")
|
||||
}
|
||||
if tree.Hash() != common.HexToHash("85fc622076752a6fcda2c886c18058d639066a83473d9684704b5a29455ed2ed") {
|
||||
t.Fatal("invalid tree root")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOneStemColocatedValues(t *testing.T) {
|
||||
var err error
|
||||
tree := NewBinaryNode()
|
||||
tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000003").Bytes(), oneKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000004").Bytes(), twoKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000009").Bytes(), threeKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tree, err = tree.Insert(common.HexToHash("00000000000000000000000000000000000000000000000000000000000000FF").Bytes(), fourKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tree.GetHeight() != 1 {
|
||||
t.Fatal("invalid height")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTwoStemColocatedValues(t *testing.T) {
|
||||
var err error
|
||||
tree := NewBinaryNode()
|
||||
// stem: 0...0
|
||||
tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000003").Bytes(), oneKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000004").Bytes(), twoKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// stem: 10...0
|
||||
tree, err = tree.Insert(common.HexToHash("8000000000000000000000000000000000000000000000000000000000000003").Bytes(), oneKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tree, err = tree.Insert(common.HexToHash("8000000000000000000000000000000000000000000000000000000000000004").Bytes(), twoKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tree.GetHeight() != 2 {
|
||||
t.Fatal("invalid height")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTwoKeysMatchFirst42Bits(t *testing.T) {
|
||||
var err error
|
||||
tree := NewBinaryNode()
|
||||
// key1 and key 2 have the same prefix of 42 bits (b0*42+b1+b1) and differ after.
|
||||
key1 := common.HexToHash("0000000000C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0").Bytes()
|
||||
key2 := common.HexToHash("0000000000E00000000000000000000000000000000000000000000000000000").Bytes()
|
||||
tree, err = tree.Insert(key1, oneKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tree, err = tree.Insert(key2, twoKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tree.GetHeight() != 1+42+1 {
|
||||
t.Fatal("invalid height")
|
||||
}
|
||||
}
|
||||
func TestInsertDuplicateKey(t *testing.T) {
|
||||
var err error
|
||||
tree := NewBinaryNode()
|
||||
tree, err = tree.Insert(oneKey[:], oneKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tree, err = tree.Insert(oneKey[:], twoKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tree.GetHeight() != 1 {
|
||||
t.Fatal("invalid height")
|
||||
}
|
||||
// Verify that the value is updated
|
||||
if !bytes.Equal(tree.(*bintrie.StemNode).Values[1], twoKey[:]) {
|
||||
t.Fatal("invalid height")
|
||||
}
|
||||
}
|
||||
func TestLargeNumberOfEntries(t *testing.T) {
|
||||
var err error
|
||||
tree := NewBinaryNode()
|
||||
for i := range 256 {
|
||||
var key [32]byte
|
||||
key[0] = byte(i)
|
||||
tree, err = tree.Insert(key[:], ffKey[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
height := tree.GetHeight()
|
||||
if height != 1+8 {
|
||||
t.Fatalf("invalid height, wanted %d, got %d", 1+8, height)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMerkleizeMultipleEntries(t *testing.T) {
|
||||
var err error
|
||||
tree := NewBinaryNode()
|
||||
keys := [][]byte{
|
||||
zeroKey[:],
|
||||
common.HexToHash("8000000000000000000000000000000000000000000000000000000000000000").Bytes(),
|
||||
common.HexToHash("0100000000000000000000000000000000000000000000000000000000000000").Bytes(),
|
||||
common.HexToHash("8100000000000000000000000000000000000000000000000000000000000000").Bytes(),
|
||||
}
|
||||
for i, key := range keys {
|
||||
var v [32]byte
|
||||
binary.LittleEndian.PutUint64(v[:8], uint64(i))
|
||||
tree, err = tree.Insert(key, v[:], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
got := tree.Hash()
|
||||
expected := common.HexToHash("8c74de28e6bb6b2296cae37cff16266e2dbf533bc204fa4cb0c237761ae8a2c8")
|
||||
if got != expected {
|
||||
t.Fatalf("invalid root, expected=%x, got = %x", expected, got)
|
||||
}
|
||||
}
|
||||
121
trie/bintrie/binary_node.go
Normal file
121
trie/bintrie/binary_node.go
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright 2025 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 bintrie
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
type (
|
||||
NodeFlushFn func([]byte, BinaryNode)
|
||||
NodeResolverFn func([]byte, common.Hash) ([]byte, error)
|
||||
)
|
||||
|
||||
// zero is the zero value for a 32-byte array.
|
||||
var zero [32]byte
|
||||
|
||||
const (
|
||||
NodeWidth = 256 // Number of child per leaf node
|
||||
StemSize = 31 // Number of bytes to travel before reaching a group of leaves
|
||||
)
|
||||
|
||||
// BinaryNode is an interface for a binary trie node.
|
||||
type BinaryNode interface {
|
||||
Get([]byte, NodeResolverFn) ([]byte, error)
|
||||
Insert([]byte, []byte, NodeResolverFn) (BinaryNode, error)
|
||||
// Commit() common.Hash
|
||||
Copy() BinaryNode
|
||||
Hash() common.Hash
|
||||
GetValuesAtStem([]byte, NodeResolverFn) ([][]byte, error)
|
||||
InsertValuesAtStem([]byte, [][]byte, NodeResolverFn, int) (BinaryNode, error)
|
||||
CollectNodes([]byte, NodeFlushFn) error
|
||||
|
||||
toDot(parent, path string) string
|
||||
GetHeight() int
|
||||
}
|
||||
|
||||
// SerializeNode serializes a binary trie node into a byte slice.
|
||||
func SerializeNode(node BinaryNode) []byte {
|
||||
switch n := (node).(type) {
|
||||
case *InternalNode:
|
||||
var serialized [65]byte
|
||||
serialized[0] = 1
|
||||
copy(serialized[1:33], n.Left.Hash().Bytes())
|
||||
copy(serialized[33:65], n.Right.Hash().Bytes())
|
||||
return serialized[:]
|
||||
case *StemNode:
|
||||
var serialized [32 + 256*32]byte
|
||||
serialized[0] = 2
|
||||
copy(serialized[1:32], node.(*StemNode).Stem)
|
||||
bitmap := serialized[32:64]
|
||||
offset := 64
|
||||
for i, v := range node.(*StemNode).Values {
|
||||
if v != nil {
|
||||
bitmap[i/8] |= 1 << (7 - (i % 8))
|
||||
copy(serialized[offset:offset+32], v)
|
||||
offset += 32
|
||||
}
|
||||
}
|
||||
return serialized[:]
|
||||
default:
|
||||
panic("invalid node type")
|
||||
}
|
||||
}
|
||||
|
||||
// DeserializeNode deserializes a binary trie node from a byte slice.
|
||||
func DeserializeNode(serialized []byte, depth int) (BinaryNode, error) {
|
||||
if len(serialized) == 0 {
|
||||
return Empty{}, nil
|
||||
}
|
||||
|
||||
switch serialized[0] {
|
||||
case 1:
|
||||
if len(serialized) != 65 {
|
||||
return nil, errors.New("invalid serialized node length")
|
||||
}
|
||||
return &InternalNode{
|
||||
depth: depth,
|
||||
Left: HashedNode(common.BytesToHash(serialized[1:33])),
|
||||
Right: HashedNode(common.BytesToHash(serialized[33:65])),
|
||||
}, nil
|
||||
case 2:
|
||||
var values [256][]byte
|
||||
bitmap := serialized[32:64]
|
||||
offset := 64
|
||||
|
||||
for i := range 256 {
|
||||
if bitmap[i/8]>>(7-(i%8))&1 == 1 {
|
||||
values[i] = serialized[offset : offset+32]
|
||||
offset += 32
|
||||
}
|
||||
}
|
||||
return &StemNode{
|
||||
Stem: serialized[1:32],
|
||||
Values: values[:],
|
||||
depth: depth,
|
||||
}, nil
|
||||
default:
|
||||
return nil, errors.New("invalid node type")
|
||||
}
|
||||
}
|
||||
|
||||
// ToDot converts the binary trie to a DOT language representation. Useful for debugging.
|
||||
func ToDot(root BinaryNode) string {
|
||||
return root.toDot("", "")
|
||||
}
|
||||
71
trie/bintrie/empty.go
Normal file
71
trie/bintrie/empty.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2025 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 bintrie
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
type Empty struct{}
|
||||
|
||||
func (e Empty) Get(_ []byte, _ NodeResolverFn) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e Empty) Insert(key []byte, value []byte, _ NodeResolverFn) (BinaryNode, error) {
|
||||
var values [256][]byte
|
||||
values[key[31]] = value
|
||||
return &StemNode{
|
||||
Stem: slices.Clone(key[:31]),
|
||||
Values: values[:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e Empty) Copy() BinaryNode {
|
||||
return Empty{}
|
||||
}
|
||||
|
||||
func (e Empty) Hash() common.Hash {
|
||||
return common.Hash{}
|
||||
}
|
||||
|
||||
func (e Empty) GetValuesAtStem(_ []byte, _ NodeResolverFn) ([][]byte, error) {
|
||||
var values [256][]byte
|
||||
return values[:], nil
|
||||
}
|
||||
|
||||
func (e Empty) InsertValuesAtStem(key []byte, values [][]byte, _ NodeResolverFn, depth int) (BinaryNode, error) {
|
||||
return &StemNode{
|
||||
Stem: slices.Clone(key[:31]),
|
||||
Values: values,
|
||||
depth: depth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e Empty) CollectNodes(_ []byte, _ NodeFlushFn) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Empty) toDot(parent string, path string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e Empty) GetHeight() int {
|
||||
return 0
|
||||
}
|
||||
66
trie/bintrie/hashed_node.go
Normal file
66
trie/bintrie/hashed_node.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2025 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 bintrie
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
type HashedNode common.Hash
|
||||
|
||||
func (h HashedNode) Get(_ []byte, _ NodeResolverFn) ([]byte, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (h HashedNode) Insert(key []byte, value []byte, resolver NodeResolverFn) (BinaryNode, error) {
|
||||
return nil, errors.New("insert not implemented for hashed node")
|
||||
}
|
||||
|
||||
func (h HashedNode) Copy() BinaryNode {
|
||||
nh := common.Hash(h)
|
||||
return HashedNode(nh)
|
||||
}
|
||||
|
||||
func (h HashedNode) Hash() common.Hash {
|
||||
return common.Hash(h)
|
||||
}
|
||||
|
||||
func (h HashedNode) GetValuesAtStem(_ []byte, _ NodeResolverFn) ([][]byte, error) {
|
||||
return nil, errors.New("attempted to get values from an unresolved node")
|
||||
}
|
||||
|
||||
func (h HashedNode) InsertValuesAtStem(key []byte, values [][]byte, resolver NodeResolverFn, depth int) (BinaryNode, error) {
|
||||
return nil, errors.New("insertValuesAtStem not implemented for hashed node")
|
||||
}
|
||||
|
||||
func (h HashedNode) toDot(parent string, path string) string {
|
||||
me := fmt.Sprintf("hash%s", path)
|
||||
ret := fmt.Sprintf("%s [label=\"%x\"]\n", me, h)
|
||||
ret = fmt.Sprintf("%s %s -> %s\n", ret, parent, me)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (h HashedNode) CollectNodes([]byte, NodeFlushFn) error {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
func (h HashedNode) GetHeight() int {
|
||||
panic("tried to get the height of a hashed node, this is a bug")
|
||||
}
|
||||
194
trie/bintrie/internal_node.go
Normal file
194
trie/bintrie/internal_node.go
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
// Copyright 2025 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 bintrie
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/zeebo/blake3"
|
||||
)
|
||||
|
||||
func keyToPath(depth int, key []byte) ([]byte, error) {
|
||||
path := make([]byte, 0, depth+1)
|
||||
|
||||
if depth > 31*8 {
|
||||
return nil, errors.New("node too deep")
|
||||
}
|
||||
|
||||
for i := range depth + 1 {
|
||||
bit := key[i/8] >> (7 - (i % 8)) & 1
|
||||
path = append(path, bit)
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// InternalNode is a binary trie internal node.
|
||||
type InternalNode struct {
|
||||
Left, Right BinaryNode
|
||||
depth int
|
||||
}
|
||||
|
||||
// GetValuesAtStem retrieves the group of values located at the given stem key.
|
||||
func (bt *InternalNode) GetValuesAtStem(stem []byte, resolver NodeResolverFn) ([][]byte, error) {
|
||||
if bt.depth > 31*8 {
|
||||
return nil, errors.New("node too deep")
|
||||
}
|
||||
|
||||
bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
|
||||
var child *BinaryNode
|
||||
if bit == 0 {
|
||||
child = &bt.Left
|
||||
} else {
|
||||
child = &bt.Right
|
||||
}
|
||||
|
||||
if hn, ok := (*child).(HashedNode); ok {
|
||||
path, err := keyToPath(bt.depth, stem)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetValuesAtStem resolve error: %w", err)
|
||||
}
|
||||
data, err := resolver(path, common.Hash(hn))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetValuesAtStem resolve error: %w", err)
|
||||
}
|
||||
node, err := DeserializeNode(data, bt.depth+1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetValuesAtStem node deserialization error: %w", err)
|
||||
}
|
||||
*child = node
|
||||
}
|
||||
return (*child).GetValuesAtStem(stem, resolver)
|
||||
}
|
||||
|
||||
// Get retrieves the value for the given key.
|
||||
func (bt *InternalNode) Get(key []byte, resolver NodeResolverFn) ([]byte, error) {
|
||||
values, err := bt.GetValuesAtStem(key[:31], resolver)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Get error: %w", err)
|
||||
}
|
||||
return values[key[31]], nil
|
||||
}
|
||||
|
||||
// Insert inserts a new key-value pair into the trie.
|
||||
func (bt *InternalNode) Insert(key []byte, value []byte, resolver NodeResolverFn) (BinaryNode, error) {
|
||||
var values [256][]byte
|
||||
values[key[31]] = value
|
||||
return bt.InsertValuesAtStem(key[:31], values[:], resolver, 0)
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of the node.
|
||||
func (bt *InternalNode) Copy() BinaryNode {
|
||||
return &InternalNode{
|
||||
Left: bt.Left.Copy(),
|
||||
Right: bt.Right.Copy(),
|
||||
depth: bt.depth,
|
||||
}
|
||||
}
|
||||
|
||||
// Hash returns the hash of the node.
|
||||
func (bt *InternalNode) Hash() common.Hash {
|
||||
h := blake3.New()
|
||||
if bt.Left != nil {
|
||||
h.Write(bt.Left.Hash().Bytes())
|
||||
} else {
|
||||
h.Write(zero[:])
|
||||
}
|
||||
if bt.Right != nil {
|
||||
h.Write(bt.Right.Hash().Bytes())
|
||||
} else {
|
||||
h.Write(zero[:])
|
||||
}
|
||||
return common.BytesToHash(h.Sum(nil))
|
||||
}
|
||||
|
||||
// InsertValuesAtStem inserts a full value group at the given stem in the internal node.
|
||||
// Already-existing values will be overwritten.
|
||||
func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolver NodeResolverFn, depth int) (BinaryNode, error) {
|
||||
bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
|
||||
var (
|
||||
child *BinaryNode
|
||||
err error
|
||||
)
|
||||
if bit == 0 {
|
||||
child = &bt.Left
|
||||
} else {
|
||||
child = &bt.Right
|
||||
}
|
||||
|
||||
*child, err = (*child).InsertValuesAtStem(stem, values, resolver, depth+1)
|
||||
return bt, err
|
||||
}
|
||||
|
||||
// CollectNodes collects all child nodes at a given path, and flushes it
|
||||
// into the provided node collector.
|
||||
func (bt *InternalNode) CollectNodes(path []byte, flushfn NodeFlushFn) error {
|
||||
if bt.Left != nil {
|
||||
var p [256]byte
|
||||
copy(p[:], path)
|
||||
childpath := p[:len(path)]
|
||||
childpath = append(childpath, 0)
|
||||
if err := bt.Left.CollectNodes(childpath, flushfn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if bt.Right != nil {
|
||||
var p [256]byte
|
||||
copy(p[:], path)
|
||||
childpath := p[:len(path)]
|
||||
childpath = append(childpath, 1)
|
||||
if err := bt.Right.CollectNodes(childpath, flushfn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
flushfn(path, bt)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHeight returns the height of the node.
|
||||
func (bt *InternalNode) GetHeight() int {
|
||||
var (
|
||||
leftHeight int
|
||||
rightHeight int
|
||||
)
|
||||
if bt.Left != nil {
|
||||
leftHeight = bt.Left.GetHeight()
|
||||
}
|
||||
if bt.Right != nil {
|
||||
rightHeight = bt.Right.GetHeight()
|
||||
}
|
||||
return 1 + max(leftHeight, rightHeight)
|
||||
}
|
||||
|
||||
func (bt *InternalNode) toDot(parent, path string) string {
|
||||
me := fmt.Sprintf("internal%s", path)
|
||||
ret := fmt.Sprintf("%s [label=\"I: %x\"]\n", me, bt.Hash())
|
||||
if len(parent) > 0 {
|
||||
ret = fmt.Sprintf("%s %s -> %s\n", ret, parent, me)
|
||||
}
|
||||
|
||||
if bt.Left != nil {
|
||||
ret = fmt.Sprintf("%s%s", ret, bt.Left.toDot(me, fmt.Sprintf("%s%02x", path, 0)))
|
||||
}
|
||||
if bt.Right != nil {
|
||||
ret = fmt.Sprintf("%s%s", ret, bt.Right.toDot(me, fmt.Sprintf("%s%02x", path, 1)))
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
216
trie/bintrie/stem_node.go
Normal file
216
trie/bintrie/stem_node.go
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
// Copyright 2025 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 bintrie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/zeebo/blake3"
|
||||
)
|
||||
|
||||
// StemNode represents a group of `NodeWith` values sharing the same stem.
|
||||
type StemNode struct {
|
||||
Stem []byte // Stem path to get to 256 values
|
||||
Values [][]byte // All values, indexed by the last byte of the key.
|
||||
depth int // Depth of the node
|
||||
}
|
||||
|
||||
// Get retrieves the value for the given key.
|
||||
func (bt *StemNode) Get(key []byte, _ NodeResolverFn) ([]byte, error) {
|
||||
panic("this should not be called directly")
|
||||
}
|
||||
|
||||
// Insert inserts a new key-value pair into the node.
|
||||
func (bt *StemNode) Insert(key []byte, value []byte, _ NodeResolverFn) (BinaryNode, error) {
|
||||
if !bytes.Equal(bt.Stem, key[:31]) {
|
||||
bitStem := bt.Stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
|
||||
|
||||
new := &InternalNode{depth: bt.depth}
|
||||
bt.depth++
|
||||
var child, other *BinaryNode
|
||||
if bitStem == 0 {
|
||||
new.Left = bt
|
||||
child = &new.Left
|
||||
other = &new.Right
|
||||
} else {
|
||||
new.Right = bt
|
||||
child = &new.Right
|
||||
other = &new.Left
|
||||
}
|
||||
|
||||
bitKey := key[new.depth/8] >> (7 - (new.depth % 8)) & 1
|
||||
if bitKey == bitStem {
|
||||
var err error
|
||||
*child, err = (*child).Insert(key, value, nil)
|
||||
if err != nil {
|
||||
return new, fmt.Errorf("insert error: %w", err)
|
||||
}
|
||||
*other = Empty{}
|
||||
} else {
|
||||
var values [256][]byte
|
||||
values[key[31]] = value
|
||||
*other = &StemNode{
|
||||
Stem: slices.Clone(key[:31]),
|
||||
Values: values[:],
|
||||
depth: new.depth + 1,
|
||||
}
|
||||
}
|
||||
|
||||
return new, nil
|
||||
}
|
||||
if len(value) != 32 {
|
||||
return bt, errors.New("invalid insertion: value length")
|
||||
}
|
||||
|
||||
bt.Values[key[31]] = value
|
||||
return bt, nil
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of the node.
|
||||
func (bt *StemNode) Copy() BinaryNode {
|
||||
var values [256][]byte
|
||||
for i, v := range bt.Values {
|
||||
values[i] = slices.Clone(v)
|
||||
}
|
||||
return &StemNode{
|
||||
Stem: slices.Clone(bt.Stem),
|
||||
Values: values[:],
|
||||
depth: bt.depth,
|
||||
}
|
||||
}
|
||||
|
||||
// GetHeight returns the height of the node.
|
||||
func (bt *StemNode) GetHeight() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Hash returns the hash of the node.
|
||||
func (bt *StemNode) Hash() common.Hash {
|
||||
var data [NodeWidth]common.Hash
|
||||
for i, v := range bt.Values {
|
||||
if v != nil {
|
||||
h := blake3.Sum256(v)
|
||||
data[i] = common.BytesToHash(h[:])
|
||||
}
|
||||
}
|
||||
|
||||
h := blake3.New()
|
||||
for level := 1; level <= 8; level++ {
|
||||
for i := range NodeWidth / (1 << level) {
|
||||
h.Reset()
|
||||
|
||||
if data[i*2] == (common.Hash{}) && data[i*2+1] == (common.Hash{}) {
|
||||
data[i] = common.Hash{}
|
||||
continue
|
||||
}
|
||||
|
||||
h.Write(data[i*2][:])
|
||||
h.Write(data[i*2+1][:])
|
||||
data[i] = common.Hash(h.Sum(nil))
|
||||
}
|
||||
}
|
||||
|
||||
h.Reset()
|
||||
h.Write(bt.Stem)
|
||||
h.Write([]byte{0})
|
||||
h.Write(data[0][:])
|
||||
return common.BytesToHash(h.Sum(nil))
|
||||
}
|
||||
|
||||
// CollectNodes collects all child nodes at a given path, and flushes it
|
||||
// into the provided node collector.
|
||||
func (bt *StemNode) CollectNodes(path []byte, flush NodeFlushFn) error {
|
||||
flush(path, bt)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValuesAtStem retrieves the group of values located at the given stem key.
|
||||
func (bt *StemNode) GetValuesAtStem(_ []byte, _ NodeResolverFn) ([][]byte, error) {
|
||||
return bt.Values[:], nil
|
||||
}
|
||||
|
||||
// InsertValuesAtStem inserts a full value group at the given stem in the internal node.
|
||||
// Already-existing values will be overwritten.
|
||||
func (bt *StemNode) InsertValuesAtStem(key []byte, values [][]byte, _ NodeResolverFn, depth int) (BinaryNode, error) {
|
||||
if !bytes.Equal(bt.Stem, key[:31]) {
|
||||
bitStem := bt.Stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
|
||||
|
||||
new := &InternalNode{depth: bt.depth}
|
||||
bt.depth++
|
||||
var child, other *BinaryNode
|
||||
if bitStem == 0 {
|
||||
new.Left = bt
|
||||
child = &new.Left
|
||||
other = &new.Right
|
||||
} else {
|
||||
new.Right = bt
|
||||
child = &new.Right
|
||||
other = &new.Left
|
||||
}
|
||||
|
||||
bitKey := key[new.depth/8] >> (7 - (new.depth % 8)) & 1
|
||||
if bitKey == bitStem {
|
||||
var err error
|
||||
*child, err = (*child).InsertValuesAtStem(key, values, nil, depth+1)
|
||||
if err != nil {
|
||||
return new, fmt.Errorf("insert error: %w", err)
|
||||
}
|
||||
*other = Empty{}
|
||||
} else {
|
||||
*other = &StemNode{
|
||||
Stem: slices.Clone(key[:31]),
|
||||
Values: values,
|
||||
depth: new.depth + 1,
|
||||
}
|
||||
}
|
||||
|
||||
return new, nil
|
||||
}
|
||||
|
||||
// same stem, just merge the two value lists
|
||||
for i, v := range values {
|
||||
if v != nil {
|
||||
bt.Values[i] = v
|
||||
}
|
||||
}
|
||||
return bt, nil
|
||||
}
|
||||
|
||||
func (bt *StemNode) toDot(parent, path string) string {
|
||||
me := fmt.Sprintf("stem%s", path)
|
||||
ret := fmt.Sprintf("%s [label=\"stem=%x c=%x\"]\n", me, bt.Stem, bt.Hash())
|
||||
ret = fmt.Sprintf("%s %s -> %s\n", ret, parent, me)
|
||||
for i, v := range bt.Values {
|
||||
if v != nil {
|
||||
ret = fmt.Sprintf("%s%s%x [label=\"%x\"]\n", ret, me, i, v)
|
||||
ret = fmt.Sprintf("%s%s -> %s%x\n", ret, me, me, i)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Key returns the full key for the given index.
|
||||
func (bt *StemNode) Key(i int) []byte {
|
||||
var ret [32]byte
|
||||
copy(ret[:], bt.Stem)
|
||||
ret[StemSize] = byte(i)
|
||||
return ret[:]
|
||||
}
|
||||
68
trie/utils/binary.go
Normal file
68
trie/utils/binary.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2025 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"
|
||||
"crypto/sha256"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
var zeroHash = common.Hash{}
|
||||
|
||||
func GetBinaryTreeKey(addr common.Address, key []byte) []byte {
|
||||
hasher := sha256.New()
|
||||
hasher.Write(zeroHash[:12])
|
||||
hasher.Write(addr[:])
|
||||
hasher.Write(key[:31])
|
||||
k := hasher.Sum(nil)
|
||||
k[31] = key[31]
|
||||
return k
|
||||
}
|
||||
|
||||
func GetBinaryTreeKeyCodeHash(addr common.Address) []byte {
|
||||
var k [32]byte
|
||||
k[31] = CodeHashLeafKey
|
||||
return GetBinaryTreeKey(addr, k[:])
|
||||
}
|
||||
|
||||
func GetBinaryTreeKeyStorageSlot(address common.Address, key []byte) []byte {
|
||||
var k [32]byte
|
||||
|
||||
// Case when the key belongs to the account header
|
||||
if bytes.Equal(key[:31], zeroHash[:31]) && key[31] < 64 {
|
||||
k[31] = 64 + key[31]
|
||||
return GetBinaryTreeKey(address, k[:])
|
||||
}
|
||||
|
||||
// Set the main storage offset
|
||||
// note that the first 64 bytes of the main offset storage
|
||||
// are unreachable, which is consistent with the spec and
|
||||
// what verkle does.
|
||||
k[0] = 1 // 1 << 248
|
||||
copy(k[1:], key[:31])
|
||||
k[31] = key[31]
|
||||
|
||||
return GetBinaryTreeKey(address, k[:])
|
||||
}
|
||||
|
||||
func GetBinaryTreeKeyCodeChunk(address common.Address, chunknr *uint256.Int) []byte {
|
||||
chunkOffset := new(uint256.Int).Add(codeOffset, chunknr).Bytes()
|
||||
return GetBinaryTreeKey(address, chunkOffset)
|
||||
}
|
||||
Loading…
Reference in a new issue