mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-22 20:56:42 +00:00
cmd/evm/internal/t8ntool: support for verkle-at-genesis
Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com> fix tests in https://github.com/ethereum/go-ethereum/pull/32445 (#548) * fix tests * fix MakePreState not respecting verkle flag bring in verkle iterator and verkle subcommand (#549) push missing verkle iterator files (#550) fix rebase issue cmd/evm/internal/t8n: Binary at genesis support - Verkle <-> Binary swap (#552) * remove: Verkle-related files with no codebase deps * fixup * refactor: update Prestate and transition logic for binary trie support - Introduced a new field `BT` in the `Prestate` struct to accommodate binary trie data. - Updated the `MakePreState` function to replace the `verkle` flag with `isBintrie` for better clarity. - Renamed functions and variables related to Verkle to their binary trie counterparts, including `BinKey`, `BinKeys`, and `BinTrieRoot`. - Added TODO comments for further updates and clarifications regarding the transition from Verkle to binary trie structures. * feat: add ChunkifyCode function for EVM bytecode chunking - Introduced the `ChunkedCode` type to represent 32-byte chunks of EVM bytecode. - Implemented the `ChunkifyCode` function to convert a byte array of EVM bytecode into chunked format. - Updated the `UpdateContractCode` method to utilize the new `ChunkifyCode` function. * refactor: rename Verkle commands to Binary Trie equivalents We leave the command name equal to keep support with https://github.com/ethereum/execution-spec-tests/tree/verkle/main * refactor: update flags and transition logic for Binary Trie support - Renamed `OutputVKTFlag` to `OutputBTFlag`. - Renamed `InputVKTFlag` to `InputBTFlag` and updated its usage description for prestate input. - Adjusted `Transition` function to utilize the new `BT` field for Binary Trie data instead of Verkle. - Updated `dispatchOutput` function to handle Binary Trie output correctly. * refactor: update DumpVKTLeaves to DumpBinTrieLeaves and adjust trie usage * refactor: update functions and comments for Binary Trie implementation - Replaced references to Verkle Trie with Binary Trie in various functions. - Updated `genBinTrieFromAlloc` to return a Binary Trie instead of a Verkle Trie. - Adjusted output functions to utilize Binary Trie methods and updated associated comments. * refactor: rename flags for Binary Trie integration * feat: add BinaryCodeChunkKey and BinaryCodeChunkCode functions for code chunking - Introduced `BinaryCodeChunkKey` to compute the tree key of a code-chunk for a given address. - Added `BinaryCodeChunkCode` to return the chunkification of bytecode. * feat: introduce TransitionTrie for Binary Trie integration - Added a new `TransitionTrie` type to facilitate the integration of the Binary Trie with the existing MPT trie. - Updated the `newTrieReader` function to utilize the new `TransitionTrie` implementation. - The transition tree has been moved to its own package `core/state/transitiontrie`, resolving the import cycle issue we get into when including `BinaryTrie` within `database.go`/ * refactor: update references to TransitionTrie and BinaryTrie integration - Replaced instances of `bintrie.BinaryTrie` with `transitiontrie.TransitionTrie` in the `execution.go`, `dump.go`, and `reader.go`. - Adjusted the `newTrieReader` function with atemporary workaround for import cycles. * trie/bintrie: fix StemNode serialization to only return actual data This fixes the "invalid serialized node length" error that occurred when Binary Trie nodes were persisted and later reloaded from the database. Issue Discovery: The error manifested when running execution-spec-tests with Binary Trie mode. After committing state changes, attempting to create a new StateDB with the committed root would fail with "invalid serialized node length" when deserializing the root node. Root Cause Analysis: Through debug logging, discovered that: 1. StemNodes were being serialized with 97 bytes of actual data 2. But the SerializeNode function was returning the entire 8224-byte buffer 3. When the node was later loaded and deserialized, it received 97 bytes 4. The deserializer expected at least 128 bytes for the bitmap and values 5. This mismatch caused the deserialization to fail The Fix: Changed SerializeNode to return only the actual data (serialized[:offset]) instead of the entire pre-allocated buffer (serialized[:]). This ensures that only the meaningful bytes are persisted to the database. This aligns the serialization with the deserialization logic, which correctly handles variable-length StemNode data based on which values are actually present in the node. * Fix Binary Trie StemNode serialization array size calculation This fixes a critical off-by-one error in the StemNode serialization buffer that was causing "invalid serialized node length" errors during execution-spec-tests. The error was discovered when running execution-spec-tests with Binary Trie mode: ``` cd ../execution-spec-tests && uv run fill --fork Verkle -v -m blockchain_test \ -k test_contract_creation -n 1 --evm-bin=PATH_TO/go-ethereum/evm ``` The tests were failing with: ``` ERROR: error getting prestate: invalid serialized node length ``` Through systematic debugging with hex dumps and bitmap analysis, we discovered that the serialized array was incorrectly sized: **Incorrect**: `var serialized [32 + 32 + 256*32]byte` - This allocated: 32 + 32 + 8192 = 8256 bytes - Missing 1 byte for the node type prefix **Correct**: `var serialized [1 + 31 + 32 + 256*32]byte` - This allocates: 1 + 31 + 32 + 8192 = 8256 bytes - Properly accounts for all fields per EIP-7864 The layout should be: - 1 byte: node type (nodeTypeStem) - 31 bytes: stem (path prefix) - 32 bytes: bitmap indicating which of 256 values are present - Up to 256*32 bytes: the actual values (32 bytes each) 1. Corrected array size from `[32 + 32 + 256*32]` to `[1 + 31 + 32 + 256*32]` 2. Cleaned up type assertions to use `n` from the type switch instead of `node.(*StemNode)` 3. This ensures proper alignment of all fields during serialization The misalignment was causing the deserializer to interpret value data as bitmap data, leading to impossible bitmap patterns (e.g., 122 bits set requiring 3904 bytes when only 97 bytes were available). After this fix, the "invalid serialized node length" errors are completely resolved. The execution-spec-tests now progress past the serialization stage, confirming that Binary Trie nodes are being correctly serialized and deserialized. * various tweaks 1. move the transition trie package to trie, as this will be requested by the geth team and it's where it belongs. 2. panic if state creation has failed in execution.go, to catch issues early. 3. use a BinaryTrie and not a TransitionTrie, as evm t8n doesn't support state trie transitions at this stage. * more replacements of TransitionTrie with VerkleTrie 1. TransitionTrie doesn't work at this stage, we are only testing the binary trees at genesis, so let's make sure TransitionTrie isn't activated anywhere. 2. There was a superfluous check for Transitioned(), which actually made the code take the wrong turn: if the verkle fork has occured, and if the transition isn't in progress, it means that the conversion has ended* * to be complete, it could be that this is in the period before the start of the iterator sweep, but in that case the transition tree also needs to be returned. So there is a missing granularity in this code, but it's ok at this stage since the transition isn't supported yet. * fix(state): Use BinaryTrie instead of VerkleTrie when IsVerkle is set The codebase uses IsVerkle flag to indicate Binary Trie mode (not renamed to avoid large diff). However, OpenTrie was incorrectly creating a VerkleTrie which expects Verkle node format, while we store Binary Trie nodes. This mismatch caused "invalid serialized node length" errors when VerkleTrie tried to deserialize Binary Trie nodes. We left some instantiation of `VerkleTree` arround which we have found and changed too here. * fix(bintrie): Remove incorrect type assertion in BinaryTrie.Commit The Commit function incorrectly assumed that the root node was always an InternalNode, causing a panic when the root was a StemNode: "interface conversion: bintrie.BinaryNode is *bintrie.StemNode, not *bintrie.InternalNode" Changes: - Remove type assertion `root := t.root.(*InternalNode)` - Call CollectNodes directly on t.root which works for any BinaryNode type - Add comment explaining the root can be any BinaryNode type This fix allows BinaryTrie to properly commit trees where the root is a StemNode, which can happen in small tries or during initial setup. * fix(t8ntool): Add error handling and debug logging to MakePreState Previously, state.New errors were silently ignored, leading to nil pointer panics later when statedb was accessed. This made debugging difficult as the actual error was hidden. Changes: - Add explicit error checking when creating initial statedb - Add explicit error checking when re-opening statedb after commit - Include meaningful error messages with context (e.g., root hash) * fix(bintrie): Fix iterator to properly handle StemNode leaf values This commit fixes two in the BinaryTrie iterator: 1. Fixed Leaf() method to only return true when positioned at a specific non-nil value within a StemNode, not just at the StemNode itself. The iterator tracks position using an Index that points to the NEXT position after the current value. 2. Fixed stack underflow in Next() method by checking if we're at the root before popping from the stack. This prevents index out of range errors when iterating through the tree. These fixes resolve panics in DumpBinTrieLeaves when generating VKT output for Binary Tries in the t8n tool. * fix: handle nil child nodes in BinaryTrie InternalNode When inserting values into an InternalNode with nil children, the code was attempting to dereference nil pointers, causing panics. This fix ensures that nil children are initialized to Empty{} nodes before attempting to call methods on them. This resolves crashes when building the Binary Trie from an empty or sparse initial state. * fix: preserve Binary Trie in Apply after commit When in Binary Trie mode (isEIP4762), the state.New() call after commit was creating a new StateDB with MPT trie, losing the Binary Trie. This resulted in nil trie when attempting to dump Binary Trie leaves. Fix: Skip state.New() for Binary Trie mode since the trie is already correct after commit. Only reopen state for MPT mode. * fix(t8n): preserve Binary Trie after commit and use correct JSON field name This commit fixes two issues that prevented VKT (Binary Trie) data from being correctly generated and passed to execution-spec-tests: 1. Binary Trie Preservation (execution.go): After statedb.Commit(), the code was calling state.New() which recreated the StateDB with an MPT trie, losing the Binary Trie. For Binary Trie mode (EIP-4762), we now skip state.New() since the trie is already correct after commit. 2. JSON Field Name (transition.go): The dispatch function was using name="bt" which created a JSON output field called "bt", but execution-spec-tests expects the field to be called "vkt" for compatibility with the upstream implementation. Changed to use name="vkt" instead. So translating, I'm dumb and I shouldn't have changed them.. These fixes allow execution-spec-tests to successfully extract Binary Trie leaf data from t8n output via stdout JSON parsing. Fixes test failures in: - tests/verkle/eip6800_genesis_verkle_tree/test_contract_creation.py * fix(t8n): revert change to avoid hardcoded MPT creation. Prior, the code was working such that we were getting `nil` when re-opening. We weren't using `TransitionTree` thus reader and other interfaces were working hardcoded to MPT. This was fixed in prev. commits. Thus, this was no longer needed. * fix(t8ntool, bintrie): address PR review comments * fix(t8ntool): update JSON field name for Binary Trie in Prestate There was a leftover as we modified already in `transition.go` flagnames to be `vkt` again and prevent errors for now. * refactor: use named constants for binary node serialization sizes Replace magic numbers with named constants for better code clarity: - NodeTypeBytes = 1 (size of node type prefix) - HashSize = 32 (size of a hash) - BitmapSize = 32 (size of the bitmap in stem nodes) This makes the serialization format more self-documenting and easier to maintain. * refactor: replace magic numbers with named constants in Binary Trie Replace hardcoded values (31, 32, 256) with named constants throughout the Binary Trie implementation for improved code maintainability. - Add NodeWidth (256), StemSize (31), HashSize (32), BitmapSize (32), NodeTypeBytes (1) constants - Update all serialization, deserialization, and node operations to use constants - Revert error wrapping to maintain test compatibility * feat: add GetBinaryTreeKeyBasicData function for Binary Trie Implement GetBinaryTreeKeyBasicData function similar to GetBinaryTreeKeyCodeHash but with offset 0 (BasicDataLeafKey) instead of 1, as per EIP-7864 tree embedding spec. * fix(t8ntool, bintrie): leftovers overlooked from review addressed * fix(bintrie): rename NodeWidth to StemNodeWidth throughout package The constant NodeWidth was renamed to StemNodeWidth to better reflect its purpose as the number of children per stem node (256 values). This change ensures consistency across the Binary Trie implementation and aligns with the EIP-7864 specification for Binary Merkle Tree format. Changes made: - Updated constant definition in binary_node.go from NodeWidth to StemNodeWidth - Replaced all references across the bintrie package files - Ensures compilation succeeds and all tests pass This fix was necessary after recent refactoring of the Binary Trie code that replaced the Verkle tree implementation at genesis. --------- Co-authored-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> fix: implement InsertValuesAtStem for HashedNode in Binary Trie (#554) This fixes the "insertValuesAtStem not implemented for hashed node" error that was blocking contract creation tests in Binary Trie mode. The implementation follows the pattern established in InternalNode.GetValuesAtStem: 1. Generate the path for the node's position in the tree 2. Resolve the hashed node to get actual node data from the database 3. Deserialize the resolved data into a concrete node type 4. Call InsertValuesAtStem on the resolved node This allows the Binary Trie to handle cases where parts of the tree are not in memory but need to be modified during state transitions, maintaining the lazy-loading design.
This commit is contained in:
parent
243407a3aa
commit
a0e6f53977
24 changed files with 749 additions and 129 deletions
|
|
@ -18,6 +18,7 @@ package t8ntool
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
stdmath "math"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -45,6 +46,7 @@ import (
|
|||
type Prestate struct {
|
||||
Env stEnv `json:"env"`
|
||||
Pre types.GenesisAlloc `json:"pre"`
|
||||
BT map[common.Hash]hexutil.Bytes `json:"vkt,omitempty"`
|
||||
}
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type ExecutionResult -field-override executionResultMarshaling -out gen_execresult.go
|
||||
|
|
@ -142,7 +144,8 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
return h
|
||||
}
|
||||
var (
|
||||
statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre)
|
||||
isEIP4762 = chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp)
|
||||
statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre, isEIP4762)
|
||||
signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp)
|
||||
gaspool = new(core.GasPool)
|
||||
blockHash = common.Hash{0x13, 0x37}
|
||||
|
|
@ -191,7 +194,6 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
Time: pre.Env.ParentTimestamp,
|
||||
ExcessBlobGas: pre.Env.ParentExcessBlobGas,
|
||||
BlobGasUsed: pre.Env.ParentBlobGasUsed,
|
||||
BaseFee: pre.Env.ParentBaseFee,
|
||||
}
|
||||
header := &types.Header{
|
||||
Time: pre.Env.Timestamp,
|
||||
|
|
@ -301,6 +303,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
// Amount is in gwei, turn into wei
|
||||
amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei))
|
||||
statedb.AddBalance(w.Address, uint256.MustFromBig(amount), tracing.BalanceIncreaseWithdrawal)
|
||||
|
||||
if isEIP4762 {
|
||||
statedb.AccessEvents().AddAccount(w.Address, true, stdmath.MaxUint64)
|
||||
}
|
||||
}
|
||||
|
||||
// Gather the execution-layer triggered requests.
|
||||
|
|
@ -361,8 +367,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
execRs.Requests = requests
|
||||
}
|
||||
|
||||
// Re-create statedb instance with new root upon the updated database
|
||||
// for accessing latest states.
|
||||
// Re-create statedb instance with new root for MPT mode
|
||||
statedb, err = state.New(root, statedb.Database())
|
||||
if err != nil {
|
||||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not reopen state: %v", err))
|
||||
|
|
@ -371,12 +376,17 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
return statedb, execRs, body, nil
|
||||
}
|
||||
|
||||
func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB {
|
||||
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true})
|
||||
func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool) *state.StateDB {
|
||||
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsVerkle: isBintrie})
|
||||
sdb := state.NewDatabase(tdb, nil)
|
||||
statedb, err := state.New(types.EmptyRootHash, sdb)
|
||||
|
||||
root := types.EmptyRootHash
|
||||
if isBintrie {
|
||||
root = types.EmptyBinaryHash
|
||||
}
|
||||
statedb, err := state.New(root, sdb)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to create initial state: %v", err))
|
||||
panic(fmt.Errorf("failed to create initial statedb: %v", err))
|
||||
}
|
||||
for addr, a := range accounts {
|
||||
statedb.SetCode(addr, a.Code, tracing.CodeChangeUnspecified)
|
||||
|
|
@ -387,18 +397,23 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB
|
|||
}
|
||||
}
|
||||
// Commit and re-open to start with a clean state.
|
||||
root, err := statedb.Commit(0, false, false)
|
||||
mptRoot, err := statedb.Commit(0, false, false)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to commit initial state: %v", err))
|
||||
}
|
||||
statedb, err = state.New(root, sdb)
|
||||
// If bintrie mode started, check if conversion happened
|
||||
if isBintrie {
|
||||
return statedb
|
||||
}
|
||||
// For MPT mode, reopen the state with the committed root
|
||||
statedb, err = state.New(mptRoot, sdb)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to reopen state after commit: %v", err))
|
||||
}
|
||||
return statedb
|
||||
}
|
||||
|
||||
func rlpHash(x interface{}) (h common.Hash) {
|
||||
func rlpHash(x any) (h common.Hash) {
|
||||
hw := sha3.NewLegacyKeccak256()
|
||||
rlp.Encode(hw, x)
|
||||
hw.Sum(h[:0])
|
||||
|
|
|
|||
|
|
@ -88,6 +88,22 @@ var (
|
|||
"\t<file> - into the file <file> ",
|
||||
Value: "block.json",
|
||||
}
|
||||
OutputBTFlag = &cli.StringFlag{
|
||||
Name: "output.vkt",
|
||||
Usage: "Determines where to put the `BT` of the post-state.\n" +
|
||||
"\t`stdout` - into the stdout output\n" +
|
||||
"\t`stderr` - into the stderr output\n" +
|
||||
"\t<file> - into the file <file> ",
|
||||
Value: "vkt.json",
|
||||
}
|
||||
OutputWitnessFlag = &cli.StringFlag{
|
||||
Name: "output.witness",
|
||||
Usage: "Determines where to put the `witness` of the post-state.\n" +
|
||||
"\t`stdout` - into the stdout output\n" +
|
||||
"\t`stderr` - into the stderr output\n" +
|
||||
"\t<file> - into the file <file> ",
|
||||
Value: "witness.json",
|
||||
}
|
||||
InputAllocFlag = &cli.StringFlag{
|
||||
Name: "input.alloc",
|
||||
Usage: "`stdin` or file name of where to find the prestate alloc to use.",
|
||||
|
|
@ -123,6 +139,11 @@ var (
|
|||
Usage: "`stdin` or file name of where to find the transactions list in RLP form.",
|
||||
Value: "txs.rlp",
|
||||
}
|
||||
// TODO(@CPerezz): rename `Name` of the file in a follow-up PR (relays on EEST -> https://github.com/ethereum/execution-spec-tests/tree/verkle/main)
|
||||
InputBTFlag = &cli.StringFlag{
|
||||
Name: "input.vkt",
|
||||
Usage: "`stdin` or file name of where to find the prestate BT.",
|
||||
}
|
||||
SealCliqueFlag = &cli.StringFlag{
|
||||
Name: "seal.clique",
|
||||
Usage: "Seal block with Clique. `stdin` or file name of where to find the Clique sealing data.",
|
||||
|
|
|
|||
|
|
@ -28,15 +28,21 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
@ -77,6 +83,7 @@ var (
|
|||
type input struct {
|
||||
Alloc types.GenesisAlloc `json:"alloc,omitempty"`
|
||||
Env *stEnv `json:"env,omitempty"`
|
||||
BT map[common.Hash]hexutil.Bytes `json:"vkt,omitempty"`
|
||||
Txs []*txWithKey `json:"txs,omitempty"`
|
||||
TxRlp string `json:"txsRlp,omitempty"`
|
||||
}
|
||||
|
|
@ -93,13 +100,13 @@ func Transition(ctx *cli.Context) error {
|
|||
prestate Prestate
|
||||
txIt txIterator // txs to apply
|
||||
allocStr = ctx.String(InputAllocFlag.Name)
|
||||
|
||||
btStr = ctx.String(InputBTFlag.Name)
|
||||
envStr = ctx.String(InputEnvFlag.Name)
|
||||
txStr = ctx.String(InputTxsFlag.Name)
|
||||
inputData = &input{}
|
||||
)
|
||||
// Figure out the prestate alloc
|
||||
if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector {
|
||||
if allocStr == stdinSelector || btStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector {
|
||||
decoder := json.NewDecoder(os.Stdin)
|
||||
if err := decoder.Decode(inputData); err != nil {
|
||||
return NewError(ErrorJson, fmt.Errorf("failed unmarshalling stdin: %v", err))
|
||||
|
|
@ -111,7 +118,13 @@ func Transition(ctx *cli.Context) error {
|
|||
}
|
||||
}
|
||||
prestate.Pre = inputData.Alloc
|
||||
if btStr != stdinSelector && btStr != "" {
|
||||
if err := readFile(btStr, "BT", &inputData.BT); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
prestate.BT = inputData.BT
|
||||
// Set the block environment
|
||||
if envStr != stdinSelector {
|
||||
var env stEnv
|
||||
|
|
@ -183,8 +196,18 @@ func Transition(ctx *cli.Context) error {
|
|||
}
|
||||
// Dump the execution result
|
||||
collector := make(Alloc)
|
||||
var btleaves map[common.Hash]hexutil.Bytes
|
||||
isBinary := chainConfig.IsVerkle(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp)
|
||||
if !isBinary {
|
||||
s.DumpToCollector(collector, nil)
|
||||
return dispatchOutput(ctx, baseDir, result, collector, body)
|
||||
} else {
|
||||
btleaves = make(map[common.Hash]hexutil.Bytes)
|
||||
if err := s.DumpBinTrieLeaves(btleaves); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return dispatchOutput(ctx, baseDir, result, collector, body, btleaves)
|
||||
}
|
||||
|
||||
func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error {
|
||||
|
|
@ -306,7 +329,7 @@ func saveFile(baseDir, filename string, data interface{}) error {
|
|||
|
||||
// dispatchOutput writes the output data to either stderr or stdout, or to the specified
|
||||
// files
|
||||
func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes) error {
|
||||
func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes, bt map[common.Hash]hexutil.Bytes) error {
|
||||
stdOutObject := make(map[string]interface{})
|
||||
stdErrObject := make(map[string]interface{})
|
||||
dispatch := func(baseDir, fName, name string, obj interface{}) error {
|
||||
|
|
@ -333,6 +356,13 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a
|
|||
if err := dispatch(baseDir, ctx.String(OutputBodyFlag.Name), "body", body); err != nil {
|
||||
return err
|
||||
}
|
||||
// Only write bt output if we actually have binary trie leaves
|
||||
if bt != nil {
|
||||
if err := dispatch(baseDir, ctx.String(OutputBTFlag.Name), "vkt", bt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(stdOutObject) > 0 {
|
||||
b, err := json.MarshalIndent(stdOutObject, "", " ")
|
||||
if err != nil {
|
||||
|
|
@ -351,3 +381,172 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// The logic for tree key
|
||||
// BinKey computes the tree key given an address and an optional
|
||||
// slot number.
|
||||
func BinKey(ctx *cli.Context) error {
|
||||
if ctx.Args().Len() == 0 || ctx.Args().Len() > 2 {
|
||||
return errors.New("invalid number of arguments: expecting an address and an optional slot number")
|
||||
}
|
||||
|
||||
addr, err := hexutil.Decode(ctx.Args().Get(0))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding address: %w", err)
|
||||
}
|
||||
|
||||
if ctx.Args().Len() == 2 {
|
||||
slot, err := hexutil.Decode(ctx.Args().Get(1))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding slot: %w", err)
|
||||
}
|
||||
fmt.Printf("%#x\n", bintrie.GetBinaryTreeKeyStorageSlot(common.BytesToAddress(addr), slot))
|
||||
} else {
|
||||
fmt.Printf("%#x\n", bintrie.GetBinaryTreeKeyBasicData(common.BytesToAddress(addr)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BinKeys computes a set of tree keys given a genesis alloc.
|
||||
func BinKeys(ctx *cli.Context) error {
|
||||
var allocStr = ctx.String(InputAllocFlag.Name)
|
||||
var alloc core.GenesisAlloc
|
||||
// Figure out the prestate alloc
|
||||
if allocStr == stdinSelector {
|
||||
decoder := json.NewDecoder(os.Stdin)
|
||||
if err := decoder.Decode(&alloc); err != nil {
|
||||
return NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err))
|
||||
}
|
||||
}
|
||||
if allocStr != stdinSelector {
|
||||
if err := readFile(allocStr, "alloc", &alloc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bt, err := genBinTrieFromAlloc(alloc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating bt: %w", err)
|
||||
}
|
||||
|
||||
collector := make(map[common.Hash]hexutil.Bytes)
|
||||
it, err := bt.NodeIterator(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for it.Next(true) {
|
||||
if it.Leaf() {
|
||||
collector[common.BytesToHash(it.LeafKey())] = it.LeafBlob()
|
||||
}
|
||||
}
|
||||
|
||||
output, err := json.MarshalIndent(collector, "", "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error outputting tree: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(string(output))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BinTrieRoot computes the root of a Binary Trie from a genesis alloc.
|
||||
func BinTrieRoot(ctx *cli.Context) error {
|
||||
var allocStr = ctx.String(InputAllocFlag.Name)
|
||||
var alloc core.GenesisAlloc
|
||||
if allocStr == stdinSelector {
|
||||
decoder := json.NewDecoder(os.Stdin)
|
||||
if err := decoder.Decode(&alloc); err != nil {
|
||||
return NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err))
|
||||
}
|
||||
}
|
||||
if allocStr != stdinSelector {
|
||||
if err := readFile(allocStr, "alloc", &alloc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bt, err := genBinTrieFromAlloc(alloc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating bt: %w", err)
|
||||
}
|
||||
fmt.Println(bt.Hash().Hex())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(@CPerezz): Should this go to `bintrie` module?
|
||||
func genBinTrieFromAlloc(alloc core.GenesisAlloc) (*bintrie.BinaryTrie, error) {
|
||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, triedb.NewDatabase(rawdb.NewMemoryDatabase(),
|
||||
&triedb.Config{
|
||||
IsVerkle: true,
|
||||
}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for addr, acc := range alloc {
|
||||
for slot, value := range acc.Storage {
|
||||
err := bt.UpdateStorage(addr, slot.Bytes(), value.Big().Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error inserting storage: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
account := &types.StateAccount{
|
||||
Balance: uint256.MustFromBig(acc.Balance),
|
||||
Nonce: acc.Nonce,
|
||||
CodeHash: crypto.Keccak256Hash(acc.Code).Bytes(),
|
||||
Root: common.Hash{},
|
||||
}
|
||||
err := bt.UpdateAccount(addr, account, len(acc.Code))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error inserting account: %w", err)
|
||||
}
|
||||
|
||||
err = bt.UpdateContractCode(addr, common.BytesToHash(account.CodeHash), acc.Code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error inserting code: %w", err)
|
||||
}
|
||||
}
|
||||
return bt, nil
|
||||
}
|
||||
|
||||
// BinaryCodeChunkKey computes the tree key of a code-chunk for a given address.
|
||||
func BinaryCodeChunkKey(ctx *cli.Context) error {
|
||||
if ctx.Args().Len() == 0 || ctx.Args().Len() > 2 {
|
||||
return errors.New("invalid number of arguments: expecting an address and an code-chunk number")
|
||||
}
|
||||
|
||||
addr, err := hexutil.Decode(ctx.Args().Get(0))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding address: %w", err)
|
||||
}
|
||||
chunkNumberBytes, err := hexutil.Decode(ctx.Args().Get(1))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding chunk number: %w", err)
|
||||
}
|
||||
var chunkNumber uint256.Int
|
||||
chunkNumber.SetBytes(chunkNumberBytes)
|
||||
|
||||
fmt.Printf("%#x\n", bintrie.GetBinaryTreeKeyCodeChunk(common.BytesToAddress(addr), &chunkNumber))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BinaryCodeChunkCode returns the code chunkification for a given code.
|
||||
func BinaryCodeChunkCode(ctx *cli.Context) error {
|
||||
if ctx.Args().Len() == 0 || ctx.Args().Len() > 1 {
|
||||
return errors.New("invalid number of arguments: expecting a bytecode")
|
||||
}
|
||||
|
||||
bytecode, err := hexutil.Decode(ctx.Args().Get(0))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding address: %w", err)
|
||||
}
|
||||
|
||||
chunkedCode := bintrie.ChunkifyCode(bytecode)
|
||||
fmt.Printf("%#x\n", chunkedCode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,16 +146,64 @@ var (
|
|||
t8ntool.TraceEnableCallFramesFlag,
|
||||
t8ntool.OutputBasedir,
|
||||
t8ntool.OutputAllocFlag,
|
||||
t8ntool.OutputBTFlag,
|
||||
t8ntool.OutputWitnessFlag,
|
||||
t8ntool.OutputResultFlag,
|
||||
t8ntool.OutputBodyFlag,
|
||||
t8ntool.InputAllocFlag,
|
||||
t8ntool.InputEnvFlag,
|
||||
t8ntool.InputBTFlag,
|
||||
t8ntool.InputTxsFlag,
|
||||
t8ntool.ForknameFlag,
|
||||
t8ntool.ChainIDFlag,
|
||||
t8ntool.RewardFlag,
|
||||
},
|
||||
}
|
||||
|
||||
verkleCommand = &cli.Command{
|
||||
Name: "verkle",
|
||||
Aliases: []string{"vkt"},
|
||||
Usage: "Binary Trie helpers",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "tree-keys",
|
||||
Aliases: []string{"v"},
|
||||
Usage: "compute a set of binary trie keys, given their source addresses and optional slot numbers",
|
||||
Action: t8ntool.BinKeys,
|
||||
Flags: []cli.Flag{
|
||||
t8ntool.InputAllocFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "single-key",
|
||||
Aliases: []string{"vk"},
|
||||
Usage: "compute the binary trie key given an address and optional slot number",
|
||||
Action: t8ntool.BinKey,
|
||||
},
|
||||
{
|
||||
Name: "code-chunk-key",
|
||||
Aliases: []string{"vck"},
|
||||
Usage: "compute the binary trie key given an address and chunk number",
|
||||
Action: t8ntool.BinaryCodeChunkKey,
|
||||
},
|
||||
{
|
||||
Name: "chunkify-code",
|
||||
Aliases: []string{"vcc"},
|
||||
Usage: "chunkify a given bytecode for a binary trie",
|
||||
Action: t8ntool.BinaryCodeChunkCode,
|
||||
},
|
||||
{
|
||||
Name: "state-root",
|
||||
Aliases: []string{"vsr"},
|
||||
Usage: "compute the state-root of a binary trie for the given alloc",
|
||||
Action: t8ntool.BinTrieRoot,
|
||||
Flags: []cli.Flag{
|
||||
t8ntool.InputAllocFlag,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
transactionCommand = &cli.Command{
|
||||
Name: "transaction",
|
||||
Aliases: []string{"t9n"},
|
||||
|
|
@ -210,6 +258,7 @@ func init() {
|
|||
stateTransitionCommand,
|
||||
transactionCommand,
|
||||
blockBuilderCommand,
|
||||
verkleCommand,
|
||||
}
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
flags.MigrateGlobalFlags(ctx)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
|
|
@ -242,7 +244,9 @@ func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
|
|||
panic("transition isn't supported yet")
|
||||
}
|
||||
if ts.Transitioned() {
|
||||
return trie.NewVerkleTrie(root, db.triedb, db.pointCache)
|
||||
// Use BinaryTrie instead of VerkleTrie when IsVerkle is set
|
||||
// (IsVerkle actually means Binary Trie mode in this codebase)
|
||||
return bintrie.NewBinaryTrie(root, db.triedb)
|
||||
}
|
||||
}
|
||||
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
|
||||
|
|
@ -302,7 +306,7 @@ func mustCopyTrie(t Trie) Trie {
|
|||
return t.Copy()
|
||||
case *trie.VerkleTrie:
|
||||
return t.Copy()
|
||||
case *trie.TransitionTrie:
|
||||
case *transitiontrie.TransitionTrie:
|
||||
return t.Copy()
|
||||
default:
|
||||
panic(fmt.Errorf("unknown trie type %T", t))
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||
)
|
||||
|
||||
// DumpConfig is a set of options to control what portions of the state will be
|
||||
|
|
@ -221,6 +222,28 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
|||
return nextKey
|
||||
}
|
||||
|
||||
// DumpBinTrieLeaves collects all binary trie leaf nodes into the provided map.
|
||||
func (s *StateDB) DumpBinTrieLeaves(collector map[common.Hash]hexutil.Bytes) error {
|
||||
if s.trie == nil {
|
||||
trie, err := s.db.OpenTrie(s.originalRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.trie = trie
|
||||
}
|
||||
|
||||
it, err := s.trie.(*bintrie.BinaryTrie).NodeIterator(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for it.Next(true) {
|
||||
if it.Leaf() {
|
||||
collector[common.BytesToHash(it.LeafKey())] = it.LeafBlob()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RawDump returns the state. If the processing is aborted e.g. due to options
|
||||
// reaching Max, the `Next` key is set on the returned Dump.
|
||||
func (s *StateDB) RawDump(opts *DumpConfig) Dump {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import (
|
|||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
"github.com/ethereum/go-ethereum/triedb/database"
|
||||
|
|
@ -242,7 +244,11 @@ func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCach
|
|||
if !db.IsVerkle() {
|
||||
tr, err = trie.NewStateTrie(trie.StateTrieID(root), db)
|
||||
} else {
|
||||
tr, err = trie.NewVerkleTrie(root, db, cache)
|
||||
// When IsVerkle() is true, create a BinaryTrie wrapped in TransitionTrie
|
||||
binTrie, binErr := bintrie.NewBinaryTrie(root, db)
|
||||
if binErr != nil {
|
||||
return nil, binErr
|
||||
}
|
||||
|
||||
// Based on the transition status, determine if the overlay
|
||||
// tree needs to be created, or if a single, target tree is
|
||||
|
|
@ -253,8 +259,24 @@ func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCach
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr = trie.NewTransitionTrie(mpt, tr.(*trie.VerkleTrie), false)
|
||||
tr = transitiontrie.NewTransitionTrie(mpt, binTrie, false)
|
||||
} else {
|
||||
// HACK: Use TransitionTrie with nil base as a wrapper to make BinaryTrie
|
||||
// satisfy the Trie interface. This works around the import cycle between
|
||||
// trie and trie/bintrie packages.
|
||||
//
|
||||
// TODO: In future PRs, refactor the package structure to avoid this hack:
|
||||
// - Option 1: Move common interfaces (Trie, NodeIterator) to a separate
|
||||
// package that both trie and trie/bintrie can import
|
||||
// - Option 2: Create a factory function in the trie package that returns
|
||||
// BinaryTrie as a Trie interface without direct import
|
||||
// - Option 3: Move BinaryTrie to the main trie package
|
||||
//
|
||||
// The current approach works but adds unnecessary overhead and complexity
|
||||
// by using TransitionTrie when there's no actual transition happening.
|
||||
tr = transitiontrie.NewTransitionTrie(nil, binTrie, false)
|
||||
}
|
||||
err = binErr
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
|
@ -501,7 +502,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
|
|||
// Verkle uses only one tree, and the copy has already been
|
||||
// made in mustCopyTrie.
|
||||
obj.trie = db.trie
|
||||
case *trie.TransitionTrie:
|
||||
case *transitiontrie.TransitionTrie:
|
||||
// Same thing for the transition tree, since the MPT is
|
||||
// read-only.
|
||||
obj.trie = db.trie
|
||||
|
|
|
|||
|
|
@ -117,19 +117,20 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t
|
|||
return UnsupportedForkError{t.json.Network}
|
||||
}
|
||||
// import pre accounts & construct test genesis block & state root
|
||||
// Commit genesis state
|
||||
gspec := t.genesis(config)
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
tconf = &triedb.Config{
|
||||
Preimages: true,
|
||||
IsVerkle: gspec.Config.VerkleTime != nil && *gspec.Config.VerkleTime <= gspec.Timestamp,
|
||||
}
|
||||
)
|
||||
if scheme == rawdb.PathScheme {
|
||||
if scheme == rawdb.PathScheme || tconf.IsVerkle {
|
||||
tconf.PathDB = pathdb.Defaults
|
||||
} else {
|
||||
tconf.HashDB = hashdb.Defaults
|
||||
}
|
||||
// Commit genesis state
|
||||
gspec := t.genesis(config)
|
||||
|
||||
// if ttd is not specified, set an arbitrary huge value
|
||||
if gspec.Config.TerminalTotalDifficulty == nil {
|
||||
|
|
|
|||
|
|
@ -720,6 +720,25 @@ var Forks = map[string]*params.ChainConfig{
|
|||
BPO4: params.DefaultBPO4BlobConfig,
|
||||
},
|
||||
},
|
||||
"Verkle": {
|
||||
ChainID: big.NewInt(1),
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
EIP150Block: big.NewInt(0),
|
||||
EIP155Block: big.NewInt(0),
|
||||
EIP158Block: big.NewInt(0),
|
||||
ByzantiumBlock: big.NewInt(0),
|
||||
ConstantinopleBlock: big.NewInt(0),
|
||||
PetersburgBlock: big.NewInt(0),
|
||||
IstanbulBlock: big.NewInt(0),
|
||||
MuirGlacierBlock: big.NewInt(0),
|
||||
BerlinBlock: big.NewInt(0),
|
||||
LondonBlock: big.NewInt(0),
|
||||
ArrowGlacierBlock: big.NewInt(0),
|
||||
MergeNetsplitBlock: big.NewInt(0),
|
||||
TerminalTotalDifficulty: big.NewInt(0),
|
||||
ShanghaiTime: u64(0),
|
||||
VerkleTime: u64(0),
|
||||
},
|
||||
}
|
||||
|
||||
var bpo1BlobConfig = ¶ms.BlobConfig{
|
||||
|
|
|
|||
|
|
@ -31,8 +31,11 @@ type (
|
|||
var zero [32]byte
|
||||
|
||||
const (
|
||||
NodeWidth = 256 // Number of child per leaf node
|
||||
StemNodeWidth = 256 // Number of child per leaf node
|
||||
StemSize = 31 // Number of bytes to travel before reaching a group of leaves
|
||||
NodeTypeBytes = 1 // Size of node type prefix in serialization
|
||||
HashSize = 32 // Size of a hash in bytes
|
||||
BitmapSize = 32 // Size of the bitmap in a stem node
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -58,25 +61,28 @@ type BinaryNode interface {
|
|||
func SerializeNode(node BinaryNode) []byte {
|
||||
switch n := (node).(type) {
|
||||
case *InternalNode:
|
||||
var serialized [65]byte
|
||||
// InternalNode: 1 byte type + 32 bytes left hash + 32 bytes right hash
|
||||
var serialized [NodeTypeBytes + HashSize + HashSize]byte
|
||||
serialized[0] = nodeTypeInternal
|
||||
copy(serialized[1:33], n.left.Hash().Bytes())
|
||||
copy(serialized[33:65], n.right.Hash().Bytes())
|
||||
return serialized[:]
|
||||
case *StemNode:
|
||||
var serialized [32 + 32 + 256*32]byte
|
||||
// StemNode: 1 byte type + 31 bytes stem + 32 bytes bitmap + 256*32 bytes values
|
||||
var serialized [NodeTypeBytes + StemSize + BitmapSize + StemNodeWidth*HashSize]byte
|
||||
serialized[0] = nodeTypeStem
|
||||
copy(serialized[1:32], node.(*StemNode).Stem)
|
||||
bitmap := serialized[32:64]
|
||||
offset := 64
|
||||
for i, v := range node.(*StemNode).Values {
|
||||
copy(serialized[NodeTypeBytes:NodeTypeBytes+StemSize], n.Stem)
|
||||
bitmap := serialized[NodeTypeBytes+StemSize : NodeTypeBytes+StemSize+BitmapSize]
|
||||
offset := NodeTypeBytes + StemSize + BitmapSize
|
||||
for i, v := range n.Values {
|
||||
if v != nil {
|
||||
bitmap[i/8] |= 1 << (7 - (i % 8))
|
||||
copy(serialized[offset:offset+32], v)
|
||||
offset += 32
|
||||
copy(serialized[offset:offset+HashSize], v)
|
||||
offset += HashSize
|
||||
}
|
||||
}
|
||||
return serialized[:]
|
||||
// Only return the actual data, not the entire array
|
||||
return serialized[:offset]
|
||||
default:
|
||||
panic("invalid node type")
|
||||
}
|
||||
|
|
@ -104,21 +110,21 @@ func DeserializeNode(serialized []byte, depth int) (BinaryNode, error) {
|
|||
if len(serialized) < 64 {
|
||||
return nil, invalidSerializedLength
|
||||
}
|
||||
var values [256][]byte
|
||||
bitmap := serialized[32:64]
|
||||
offset := 64
|
||||
var values [StemNodeWidth][]byte
|
||||
bitmap := serialized[NodeTypeBytes+StemSize : NodeTypeBytes+StemSize+BitmapSize]
|
||||
offset := NodeTypeBytes + StemSize + BitmapSize
|
||||
|
||||
for i := range 256 {
|
||||
for i := range StemNodeWidth {
|
||||
if bitmap[i/8]>>(7-(i%8))&1 == 1 {
|
||||
if len(serialized) < offset+32 {
|
||||
if len(serialized) < offset+HashSize {
|
||||
return nil, invalidSerializedLength
|
||||
}
|
||||
values[i] = serialized[offset : offset+32]
|
||||
offset += 32
|
||||
values[i] = serialized[offset : offset+HashSize]
|
||||
offset += HashSize
|
||||
}
|
||||
}
|
||||
return &StemNode{
|
||||
Stem: serialized[1:32],
|
||||
Stem: serialized[NodeTypeBytes : NodeTypeBytes+StemSize],
|
||||
Values: values[:],
|
||||
depth: depth,
|
||||
}, nil
|
||||
|
|
|
|||
|
|
@ -77,12 +77,12 @@ func TestSerializeDeserializeInternalNode(t *testing.T) {
|
|||
// TestSerializeDeserializeStemNode tests serialization and deserialization of StemNode
|
||||
func TestSerializeDeserializeStemNode(t *testing.T) {
|
||||
// Create a stem node with some values
|
||||
stem := make([]byte, 31)
|
||||
stem := make([]byte, StemSize)
|
||||
for i := range stem {
|
||||
stem[i] = byte(i)
|
||||
}
|
||||
|
||||
var values [256][]byte
|
||||
var values [StemNodeWidth][]byte
|
||||
// Add some values at different indices
|
||||
values[0] = common.HexToHash("0x0101010101010101010101010101010101010101010101010101010101010101").Bytes()
|
||||
values[10] = common.HexToHash("0x0202020202020202020202020202020202020202020202020202020202020202").Bytes()
|
||||
|
|
@ -103,7 +103,7 @@ func TestSerializeDeserializeStemNode(t *testing.T) {
|
|||
}
|
||||
|
||||
// Check the stem is correctly serialized
|
||||
if !bytes.Equal(serialized[1:32], stem) {
|
||||
if !bytes.Equal(serialized[1:1+StemSize], stem) {
|
||||
t.Errorf("Stem mismatch in serialized data")
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +136,7 @@ func TestSerializeDeserializeStemNode(t *testing.T) {
|
|||
}
|
||||
|
||||
// Check that other values are nil
|
||||
for i := range NodeWidth {
|
||||
for i := range StemNodeWidth {
|
||||
if i == 0 || i == 10 || i == 255 {
|
||||
continue
|
||||
}
|
||||
|
|
@ -218,15 +218,15 @@ func TestKeyToPath(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "max valid depth",
|
||||
depth: 31 * 8,
|
||||
key: make([]byte, 32),
|
||||
expected: make([]byte, 31*8+1),
|
||||
depth: StemSize * 8,
|
||||
key: make([]byte, HashSize),
|
||||
expected: make([]byte, StemSize*8+1),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "depth too large",
|
||||
depth: 31*8 + 1,
|
||||
key: make([]byte, 32),
|
||||
depth: StemSize*8 + 1,
|
||||
key: make([]byte, HashSize),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,8 +46,27 @@ 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) InsertValuesAtStem(stem []byte, values [][]byte, resolver NodeResolverFn, depth int) (BinaryNode, error) {
|
||||
// Step 1: Generate the path for this node's position in the tree
|
||||
path, err := keyToPath(depth, stem)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("InsertValuesAtStem path generation error: %w", err)
|
||||
}
|
||||
|
||||
// Step 2: Resolve the hashed node to get the actual node data
|
||||
data, err := resolver(path, common.Hash(h))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("InsertValuesAtStem resolve error: %w", err)
|
||||
}
|
||||
|
||||
// Step 3: Deserialize the resolved data into a concrete node
|
||||
node, err := DeserializeNode(data, depth)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("InsertValuesAtStem node deserialization error: %w", err)
|
||||
}
|
||||
|
||||
// Step 4: Call InsertValuesAtStem on the resolved concrete node
|
||||
return node.InsertValuesAtStem(stem, values, resolver, depth)
|
||||
}
|
||||
|
||||
func (h HashedNode) toDot(parent string, path string) string {
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ func TestHashedNodeCopy(t *testing.T) {
|
|||
func TestHashedNodeInsert(t *testing.T) {
|
||||
node := HashedNode(common.HexToHash("0x1234"))
|
||||
|
||||
key := make([]byte, 32)
|
||||
value := make([]byte, 32)
|
||||
key := make([]byte, HashSize)
|
||||
value := make([]byte, HashSize)
|
||||
|
||||
_, err := node.Insert(key, value, nil, 0)
|
||||
if err == nil {
|
||||
|
|
@ -76,7 +76,7 @@ func TestHashedNodeInsert(t *testing.T) {
|
|||
func TestHashedNodeGetValuesAtStem(t *testing.T) {
|
||||
node := HashedNode(common.HexToHash("0x1234"))
|
||||
|
||||
stem := make([]byte, 31)
|
||||
stem := make([]byte, StemSize)
|
||||
_, err := node.GetValuesAtStem(stem, nil)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for GetValuesAtStem on HashedNode")
|
||||
|
|
@ -91,8 +91,8 @@ func TestHashedNodeGetValuesAtStem(t *testing.T) {
|
|||
func TestHashedNodeInsertValuesAtStem(t *testing.T) {
|
||||
node := HashedNode(common.HexToHash("0x1234"))
|
||||
|
||||
stem := make([]byte, 31)
|
||||
values := make([][]byte, 256)
|
||||
stem := make([]byte, StemSize)
|
||||
values := make([][]byte, StemNodeWidth)
|
||||
|
||||
_, err := node.InsertValuesAtStem(stem, values, nil, 0)
|
||||
if err == nil {
|
||||
|
|
|
|||
|
|
@ -128,6 +128,12 @@ func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolve
|
|||
} else {
|
||||
child = &bt.right
|
||||
}
|
||||
|
||||
// Initialize child to Empty if it's nil
|
||||
if *child == nil {
|
||||
*child = Empty{}
|
||||
}
|
||||
|
||||
*child, err = (*child).InsertValuesAtStem(stem, values, resolver, depth+1)
|
||||
return bt, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,6 +108,11 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
|
|||
}
|
||||
|
||||
// go back to parent to get the next leaf
|
||||
// Check if we're at the root before popping
|
||||
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++
|
||||
|
|
@ -183,9 +188,31 @@ func (it *binaryNodeIterator) NodeBlob() []byte {
|
|||
}
|
||||
|
||||
// Leaf returns true iff the current node is a leaf node.
|
||||
// In a Binary Trie, a StemNode contains up to 256 leaf values.
|
||||
// The iterator is only considered to be "at a leaf" when it's positioned
|
||||
// at a specific non-nil value within the StemNode, not just at the StemNode itself.
|
||||
func (it *binaryNodeIterator) Leaf() bool {
|
||||
_, ok := it.current.(*StemNode)
|
||||
return ok
|
||||
sn, ok := it.current.(*StemNode)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if we have a valid stack position
|
||||
if len(it.stack) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// The Index in the stack state points to the NEXT position after the current value.
|
||||
// So if Index is 0, we haven't started iterating through the values yet.
|
||||
// If Index is 5, we're currently at value[4] (the 5th value, 0-indexed).
|
||||
idx := it.stack[len(it.stack)-1].Index
|
||||
if idx == 0 || idx > 256 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if there's actually a value at the current position
|
||||
currentValueIndex := idx - 1
|
||||
return sn.Values[currentValueIndex] != nil
|
||||
}
|
||||
|
||||
// LeafKey returns the key of the leaf. The method panics if the iterator is not
|
||||
|
|
@ -219,7 +246,7 @@ func (it *binaryNodeIterator) LeafProof() [][]byte {
|
|||
panic("LeafProof() called on an binary node iterator not at a leaf location")
|
||||
}
|
||||
|
||||
proof := make([][]byte, 0, len(it.stack)+NodeWidth)
|
||||
proof := make([][]byte, 0, len(it.stack)+StemNodeWidth)
|
||||
|
||||
// Build proof by walking up the stack and collecting sibling hashes
|
||||
for i := range it.stack[:len(it.stack)-2] {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,12 @@ func GetBinaryTreeKey(addr common.Address, key []byte) []byte {
|
|||
return k
|
||||
}
|
||||
|
||||
func GetBinaryTreeKeyBasicData(addr common.Address) []byte {
|
||||
var k [32]byte
|
||||
k[31] = BasicDataLeafKey
|
||||
return GetBinaryTreeKey(addr, k[:])
|
||||
}
|
||||
|
||||
func GetBinaryTreeKeyCodeHash(addr common.Address) []byte {
|
||||
var k [32]byte
|
||||
k[31] = CodeHashLeafKey
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import (
|
|||
|
||||
// StemNode represents a group of `NodeWith` values sharing the same stem.
|
||||
type StemNode struct {
|
||||
Stem []byte // Stem path to get to 256 values
|
||||
Stem []byte // Stem path to get to StemNodeWidth values
|
||||
Values [][]byte // All values, indexed by the last byte of the key.
|
||||
depth int // Depth of the node
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ func (bt *StemNode) Get(key []byte, _ NodeResolverFn) ([]byte, error) {
|
|||
|
||||
// Insert inserts a new key-value pair into the node.
|
||||
func (bt *StemNode) Insert(key []byte, value []byte, _ NodeResolverFn, depth int) (BinaryNode, error) {
|
||||
if !bytes.Equal(bt.Stem, key[:31]) {
|
||||
if !bytes.Equal(bt.Stem, key[:StemSize]) {
|
||||
bitStem := bt.Stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
|
||||
|
||||
n := &InternalNode{depth: bt.depth}
|
||||
|
|
@ -65,26 +65,26 @@ func (bt *StemNode) Insert(key []byte, value []byte, _ NodeResolverFn, depth int
|
|||
}
|
||||
*other = Empty{}
|
||||
} else {
|
||||
var values [256][]byte
|
||||
values[key[31]] = value
|
||||
var values [StemNodeWidth][]byte
|
||||
values[key[StemSize]] = value
|
||||
*other = &StemNode{
|
||||
Stem: slices.Clone(key[:31]),
|
||||
Stem: slices.Clone(key[:StemSize]),
|
||||
Values: values[:],
|
||||
depth: depth + 1,
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
if len(value) != 32 {
|
||||
if len(value) != HashSize {
|
||||
return bt, errors.New("invalid insertion: value length")
|
||||
}
|
||||
bt.Values[key[31]] = value
|
||||
bt.Values[key[StemSize]] = value
|
||||
return bt, nil
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of the node.
|
||||
func (bt *StemNode) Copy() BinaryNode {
|
||||
var values [256][]byte
|
||||
var values [StemNodeWidth][]byte
|
||||
for i, v := range bt.Values {
|
||||
values[i] = slices.Clone(v)
|
||||
}
|
||||
|
|
@ -102,7 +102,7 @@ func (bt *StemNode) GetHeight() int {
|
|||
|
||||
// Hash returns the hash of the node.
|
||||
func (bt *StemNode) Hash() common.Hash {
|
||||
var data [NodeWidth]common.Hash
|
||||
var data [StemNodeWidth]common.Hash
|
||||
for i, v := range bt.Values {
|
||||
if v != nil {
|
||||
h := sha256.Sum256(v)
|
||||
|
|
@ -112,7 +112,7 @@ func (bt *StemNode) Hash() common.Hash {
|
|||
|
||||
h := sha256.New()
|
||||
for level := 1; level <= 8; level++ {
|
||||
for i := range NodeWidth / (1 << level) {
|
||||
for i := range StemNodeWidth / (1 << level) {
|
||||
h.Reset()
|
||||
|
||||
if data[i*2] == (common.Hash{}) && data[i*2+1] == (common.Hash{}) {
|
||||
|
|
@ -148,7 +148,7 @@ func (bt *StemNode) GetValuesAtStem(_ []byte, _ NodeResolverFn) ([][]byte, error
|
|||
// 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]) {
|
||||
if !bytes.Equal(bt.Stem, key[:StemSize]) {
|
||||
bitStem := bt.Stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
|
||||
|
||||
n := &InternalNode{depth: bt.depth}
|
||||
|
|
@ -174,7 +174,7 @@ func (bt *StemNode) InsertValuesAtStem(key []byte, values [][]byte, _ NodeResolv
|
|||
*other = Empty{}
|
||||
} else {
|
||||
*other = &StemNode{
|
||||
Stem: slices.Clone(key[:31]),
|
||||
Stem: slices.Clone(key[:StemSize]),
|
||||
Values: values,
|
||||
depth: n.depth + 1,
|
||||
}
|
||||
|
|
@ -206,7 +206,7 @@ func (bt *StemNode) toDot(parent, path string) string {
|
|||
|
||||
// Key returns the full key for the given index.
|
||||
func (bt *StemNode) Key(i int) []byte {
|
||||
var ret [32]byte
|
||||
var ret [HashSize]byte
|
||||
copy(ret[:], bt.Stem)
|
||||
ret[StemSize] = byte(i)
|
||||
return ret[:]
|
||||
|
|
|
|||
|
|
@ -33,6 +33,84 @@ import (
|
|||
|
||||
var errInvalidRootType = errors.New("invalid root type")
|
||||
|
||||
// ChunkedCode represents a sequence of HashSize-byte chunks of code (StemSize bytes of which
|
||||
// are actual code, and NodeTypeBytes 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
|
||||
// according to EIP-7864 specification.
|
||||
//
|
||||
// The code is divided into HashSize-byte chunks, where each chunk contains:
|
||||
// - Byte 0: Metadata byte indicating the number of leading bytes that are PUSHDATA (0-StemSize)
|
||||
// - Bytes 1-StemSize: Actual code bytes
|
||||
//
|
||||
// This format enables stateless clients to validate jump destinations within a chunk
|
||||
// without requiring additional context. When a PUSH instruction's data spans multiple
|
||||
// chunks, the metadata byte tells us how many bytes at the start of the chunk are
|
||||
// part of the previous chunk's PUSH instruction data.
|
||||
//
|
||||
// For example:
|
||||
// - If a chunk starts with regular code: metadata byte = 0
|
||||
// - If a PUSH32 instruction starts at byte 30 of chunk N:
|
||||
// - Chunk N: normal, contains PUSH32 opcode + 1 byte of data
|
||||
// - Chunk N+1: metadata = StemSize (entire chunk is PUSH data)
|
||||
// - Chunk N+2: metadata = 1 (first byte is PUSH data, then normal code resumes)
|
||||
//
|
||||
// This chunking approach ensures that jump destination validity can be determined
|
||||
// by examining only the chunk containing the potential JUMPDEST, making it ideal
|
||||
// for stateless execution and verkle/binary tries.
|
||||
//
|
||||
// Reference: https://eips.ethereum.org/EIPS/eip-7864
|
||||
func ChunkifyCode(code []byte) ChunkedCode {
|
||||
var (
|
||||
chunkOffset = 0 // offset in the chunk
|
||||
chunkCount = len(code) / StemSize
|
||||
codeOffset = 0 // offset in the code
|
||||
)
|
||||
if len(code)%StemSize != 0 {
|
||||
chunkCount++
|
||||
}
|
||||
chunks := make([]byte, chunkCount*HashSize)
|
||||
for i := 0; i < chunkCount; i++ {
|
||||
// number of bytes to copy, StemSize unless the end of the code has been reached.
|
||||
end := StemSize * (i + 1)
|
||||
if len(code) < end {
|
||||
end = len(code)
|
||||
}
|
||||
copy(chunks[i*HashSize+1:], code[StemSize*i:end]) // copy the code itself
|
||||
|
||||
// chunk offset = taken from the last chunk.
|
||||
if chunkOffset > StemSize {
|
||||
// skip offset calculation if push data covers the whole chunk
|
||||
chunks[i*HashSize] = StemSize
|
||||
chunkOffset = 1
|
||||
continue
|
||||
}
|
||||
chunks[HashSize*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 >= StemSize*(i+1) {
|
||||
codeOffset++
|
||||
chunkOffset = codeOffset - StemSize*(i+1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return chunks
|
||||
}
|
||||
|
||||
// NewBinaryNode creates a new empty binary trie
|
||||
func NewBinaryNode() BinaryNode {
|
||||
return Empty{}
|
||||
|
|
@ -114,7 +192,7 @@ func (t *BinaryTrie) GetAccount(addr common.Address) (*types.StateAccount, error
|
|||
)
|
||||
switch r := t.root.(type) {
|
||||
case *InternalNode:
|
||||
values, err = r.GetValuesAtStem(key[:31], t.nodeResolver)
|
||||
values, err = r.GetValuesAtStem(key[:StemSize], t.nodeResolver)
|
||||
case *StemNode:
|
||||
values = r.Values
|
||||
case Empty:
|
||||
|
|
@ -168,8 +246,8 @@ func (t *BinaryTrie) GetStorage(addr common.Address, key []byte) ([]byte, error)
|
|||
func (t *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error {
|
||||
var (
|
||||
err error
|
||||
basicData [32]byte
|
||||
values = make([][]byte, NodeWidth)
|
||||
basicData [HashSize]byte
|
||||
values = make([][]byte, StemNodeWidth)
|
||||
stem = GetBinaryTreeKey(addr, zero[:])
|
||||
)
|
||||
binary.BigEndian.PutUint32(basicData[BasicDataCodeSizeOffset-1:], uint32(codeLen))
|
||||
|
|
@ -177,14 +255,14 @@ func (t *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccount,
|
|||
|
||||
// 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.
|
||||
// 0xff**HashSize 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[:])
|
||||
copy(basicData[HashSize-len(balanceBytes):], balanceBytes[:])
|
||||
values[BasicDataLeafKey] = basicData[:]
|
||||
values[CodeHashLeafKey] = acc.CodeHash[:]
|
||||
|
||||
|
|
@ -205,11 +283,11 @@ func (t *BinaryTrie) UpdateStem(key []byte, values [][]byte) error {
|
|||
// database, a trie.MissingNodeError is returned.
|
||||
func (t *BinaryTrie) UpdateStorage(address common.Address, key, value []byte) error {
|
||||
k := GetBinaryTreeKeyStorageSlot(address, key)
|
||||
var v [32]byte
|
||||
if len(value) >= 32 {
|
||||
copy(v[:], value[:32])
|
||||
var v [HashSize]byte
|
||||
if len(value) >= HashSize {
|
||||
copy(v[:], value[:HashSize])
|
||||
} else {
|
||||
copy(v[32-len(value):], value[:])
|
||||
copy(v[HashSize-len(value):], value[:])
|
||||
}
|
||||
root, err := t.root.Insert(k, v[:], t.nodeResolver, 0)
|
||||
if err != nil {
|
||||
|
|
@ -228,7 +306,7 @@ func (t *BinaryTrie) DeleteAccount(addr common.Address) error {
|
|||
// found in the database, a trie.MissingNodeError is returned.
|
||||
func (t *BinaryTrie) DeleteStorage(addr common.Address, key []byte) error {
|
||||
k := GetBinaryTreeKey(addr, key)
|
||||
var zero [32]byte
|
||||
var zero [HashSize]byte
|
||||
root, err := t.root.Insert(k, zero[:], t.nodeResolver, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DeleteStorage (%x) error: %v", addr, err)
|
||||
|
|
@ -246,10 +324,10 @@ func (t *BinaryTrie) Hash() common.Hash {
|
|||
// Commit writes all nodes to the trie's memory database, tracking the internal
|
||||
// and external (for account tries) references.
|
||||
func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) {
|
||||
root := t.root.(*InternalNode)
|
||||
nodeset := trienode.NewNodeSet(common.Hash{})
|
||||
|
||||
err := root.CollectNodes(nil, func(path []byte, node BinaryNode) {
|
||||
// The root can be any type of BinaryNode (InternalNode, StemNode, etc.)
|
||||
err := t.root.CollectNodes(nil, func(path []byte, node BinaryNode) {
|
||||
serialized := SerializeNode(node)
|
||||
nodeset.AddNode(path, trienode.NewNodeWithPrev(common.Hash{}, serialized, t.tracer.Get(path)))
|
||||
})
|
||||
|
|
@ -299,23 +377,23 @@ func (t *BinaryTrie) IsVerkle() bool {
|
|||
// Note: the basic data leaf needs to have been previously created for this to work
|
||||
func (t *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
|
||||
var (
|
||||
chunks = trie.ChunkifyCode(code)
|
||||
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
|
||||
for i, chunknr := 0, uint64(0); i < len(chunks); i, chunknr = i+HashSize, chunknr+1 {
|
||||
groupOffset := (chunknr + 128) % StemNodeWidth
|
||||
if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ {
|
||||
values = make([][]byte, NodeWidth)
|
||||
var offset [32]byte
|
||||
values = make([][]byte, StemNodeWidth)
|
||||
var offset [HashSize]byte
|
||||
binary.LittleEndian.PutUint64(offset[24:], chunknr+128)
|
||||
key = GetBinaryTreeKey(addr, offset[:])
|
||||
}
|
||||
values[groupOffset] = chunks[i : i+32]
|
||||
values[groupOffset] = chunks[i : i+HashSize]
|
||||
|
||||
if groupOffset == 255 || len(chunks)-i <= 32 {
|
||||
err = t.UpdateStem(key[:31], values)
|
||||
if groupOffset == StemNodeWidth-1 || len(chunks)-i <= HashSize {
|
||||
err = t.UpdateStem(key[:StemSize], values)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
zeroKey = [32]byte{}
|
||||
zeroKey = [HashSize]byte{}
|
||||
oneKey = common.HexToHash("0101010101010101010101010101010101010101010101010101010101010101")
|
||||
twoKey = common.HexToHash("0202020202020202020202020202020202020202020202020202020202020202")
|
||||
threeKey = common.HexToHash("0303030303030303030303030303030303030303030303030303030303030303")
|
||||
|
|
@ -158,8 +158,8 @@ func TestInsertDuplicateKey(t *testing.T) {
|
|||
func TestLargeNumberOfEntries(t *testing.T) {
|
||||
var err error
|
||||
tree := NewBinaryNode()
|
||||
for i := range 256 {
|
||||
var key [32]byte
|
||||
for i := range StemNodeWidth {
|
||||
var key [HashSize]byte
|
||||
key[0] = byte(i)
|
||||
tree, err = tree.Insert(key[:], ffKey[:], nil, 0)
|
||||
if err != nil {
|
||||
|
|
@ -182,7 +182,7 @@ func TestMerkleizeMultipleEntries(t *testing.T) {
|
|||
common.HexToHash("8100000000000000000000000000000000000000000000000000000000000000").Bytes(),
|
||||
}
|
||||
for i, key := range keys {
|
||||
var v [32]byte
|
||||
var v [HashSize]byte
|
||||
binary.LittleEndian.PutUint64(v[:8], uint64(i))
|
||||
tree, err = tree.Insert(key, v[:], nil, 0)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package trie
|
||||
package transitiontrie
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -22,8 +22,9 @@ import (
|
|||
"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"
|
||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/ethereum/go-verkle"
|
||||
)
|
||||
|
||||
// TransitionTrie is a trie that implements a façade design pattern, presenting
|
||||
|
|
@ -31,13 +32,16 @@ import (
|
|||
// first from the overlay trie, and falls back to the base trie if the key isn't
|
||||
// found. All writes go to the overlay trie.
|
||||
type TransitionTrie struct {
|
||||
overlay *VerkleTrie
|
||||
base *SecureTrie
|
||||
overlay *bintrie.BinaryTrie
|
||||
base *trie.SecureTrie
|
||||
storage bool
|
||||
}
|
||||
|
||||
// NewTransitionTrie creates a new TransitionTrie.
|
||||
func NewTransitionTrie(base *SecureTrie, overlay *VerkleTrie, st bool) *TransitionTrie {
|
||||
// Note: base can be nil when using TransitionTrie as a wrapper for BinaryTrie
|
||||
// to work around import cycles. This is a temporary hack that should be
|
||||
// refactored in future PRs (see core/state/reader.go for details).
|
||||
func NewTransitionTrie(base *trie.SecureTrie, overlay *bintrie.BinaryTrie, st bool) *TransitionTrie {
|
||||
return &TransitionTrie{
|
||||
overlay: overlay,
|
||||
base: base,
|
||||
|
|
@ -46,12 +50,12 @@ func NewTransitionTrie(base *SecureTrie, overlay *VerkleTrie, st bool) *Transiti
|
|||
}
|
||||
|
||||
// Base returns the base trie.
|
||||
func (t *TransitionTrie) Base() *SecureTrie {
|
||||
func (t *TransitionTrie) Base() *trie.SecureTrie {
|
||||
return t.base
|
||||
}
|
||||
|
||||
// Overlay returns the overlay trie.
|
||||
func (t *TransitionTrie) Overlay() *VerkleTrie {
|
||||
func (t *TransitionTrie) Overlay() *bintrie.BinaryTrie {
|
||||
return t.overlay
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +65,10 @@ func (t *TransitionTrie) GetKey(key []byte) []byte {
|
|||
if key := t.overlay.GetKey(key); key != nil {
|
||||
return key
|
||||
}
|
||||
if t.base != nil {
|
||||
return t.base.GetKey(key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStorage returns the value for key stored in the trie. The value bytes must
|
||||
|
|
@ -74,8 +81,11 @@ func (t *TransitionTrie) GetStorage(addr common.Address, key []byte) ([]byte, er
|
|||
if len(val) != 0 {
|
||||
return val, nil
|
||||
}
|
||||
if t.base != nil {
|
||||
// TODO also insert value into overlay
|
||||
return t.base.GetStorage(addr, key)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// PrefetchStorage attempts to resolve specific storage slots from the database
|
||||
|
|
@ -102,7 +112,10 @@ func (t *TransitionTrie) GetAccount(address common.Address) (*types.StateAccount
|
|||
if data != nil {
|
||||
return data, nil
|
||||
}
|
||||
if t.base != nil {
|
||||
return t.base.GetAccount(address)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// PrefetchAccount attempts to resolve specific accounts from the database
|
||||
|
|
@ -174,7 +187,7 @@ func (t *TransitionTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSe
|
|||
|
||||
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
|
||||
// starts at the key after the given start key.
|
||||
func (t *TransitionTrie) NodeIterator(startKey []byte) (NodeIterator, error) {
|
||||
func (t *TransitionTrie) NodeIterator(startKey []byte) (trie.NodeIterator, error) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
|
|
@ -197,14 +210,10 @@ func (t *TransitionTrie) IsVerkle() bool {
|
|||
|
||||
// UpdateStem updates a group of values, given the stem they are using. If
|
||||
// a value already exists, it is overwritten.
|
||||
// TODO: This is Verkle-specific and requires access to private fields.
|
||||
// Not currently used in the codebase.
|
||||
func (t *TransitionTrie) UpdateStem(key []byte, values [][]byte) error {
|
||||
trie := t.overlay
|
||||
switch root := trie.root.(type) {
|
||||
case *verkle.InternalNode:
|
||||
return root.InsertValuesAtStem(key, values, t.overlay.nodeResolver)
|
||||
default:
|
||||
panic("invalid root type")
|
||||
}
|
||||
panic("UpdateStem is not implemented for TransitionTrie")
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of the transition trie.
|
||||
|
|
@ -45,6 +45,10 @@ var (
|
|||
verkleNodeWidth = uint256.NewInt(256)
|
||||
codeStorageDelta = uint256.NewInt(0).Sub(codeOffset, headerStorageOffset)
|
||||
mainStorageOffsetLshVerkleNodeWidth = new(uint256.Int).Lsh(uint256.NewInt(1), 248-uint(verkleNodeWidthLog2))
|
||||
CodeOffset = uint256.NewInt(128)
|
||||
VerkleNodeWidth = uint256.NewInt(256)
|
||||
HeaderStorageOffset = uint256.NewInt(64)
|
||||
VerkleNodeWidthLog2 = 8
|
||||
|
||||
index0Point *verkle.Point // pre-computed commitment of polynomial [2+256*64]
|
||||
|
||||
|
|
@ -200,6 +204,22 @@ func CodeChunkKey(address []byte, chunk *uint256.Int) []byte {
|
|||
return GetTreeKey(address, treeIndex, subIndex)
|
||||
}
|
||||
|
||||
func GetTreeKeyCodeChunkIndices(chunk *uint256.Int) (*uint256.Int, byte) {
|
||||
chunkOffset := new(uint256.Int).Add(CodeOffset, chunk)
|
||||
treeIndex := new(uint256.Int).Div(chunkOffset, VerkleNodeWidth)
|
||||
subIndexMod := new(uint256.Int).Mod(chunkOffset, VerkleNodeWidth)
|
||||
var subIndex byte
|
||||
if len(subIndexMod) != 0 {
|
||||
subIndex = byte(subIndexMod[0])
|
||||
}
|
||||
return treeIndex, subIndex
|
||||
}
|
||||
|
||||
func GetTreeKeyCodeChunk(address []byte, chunk *uint256.Int) []byte {
|
||||
treeIndex, subIndex := GetTreeKeyCodeChunkIndices(chunk)
|
||||
return GetTreeKey(address, treeIndex, subIndex)
|
||||
}
|
||||
|
||||
func StorageIndex(storageKey []byte) (*uint256.Int, byte) {
|
||||
// If the storage slot is in the header, we need to add the header offset.
|
||||
var key uint256.Int
|
||||
|
|
@ -297,3 +317,97 @@ func evaluateAddressPoint(address []byte) *verkle.Point {
|
|||
ret.Add(ret, index0Point)
|
||||
return ret
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func GetTreeKeyStorageSlotWithEvaluatedAddress(evaluated *verkle.Point, storageKey []byte) []byte {
|
||||
treeIndex, subIndex := GetTreeKeyStorageSlotTreeIndexes(storageKey)
|
||||
return GetTreeKeyWithEvaluatedAddess(evaluated, treeIndex, subIndex)
|
||||
}
|
||||
|
||||
func GetTreeKeyStorageSlotTreeIndexes(storageKey []byte) (*uint256.Int, byte) {
|
||||
var pos uint256.Int
|
||||
pos.SetBytes(storageKey)
|
||||
|
||||
// If the storage slot is in the header, we need to add the header offset.
|
||||
if pos.Cmp(codeStorageDelta) < 0 {
|
||||
// This addition is always safe; it can't ever overflow since pos<codeStorageDelta.
|
||||
pos.Add(HeaderStorageOffset, &pos)
|
||||
|
||||
// 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(pos[0] & 0xFF)
|
||||
}
|
||||
// If the storage slot is in the main storage, we need to add the main storage offset.
|
||||
|
||||
// The first MAIN_STORAGE_OFFSET group will see its
|
||||
// first 64 slots unreachable. This is either a typo in the
|
||||
// spec or intended to conserve the 256-u256
|
||||
// aligment. If we decide to ever access these 64
|
||||
// slots, uncomment this.
|
||||
// // Get the new offset since we now know that we are above 64.
|
||||
// pos.Sub(&pos, codeStorageDelta)
|
||||
// suffix := byte(pos[0] & 0xFF)
|
||||
suffix := storageKey[len(storageKey)-1]
|
||||
|
||||
// We first divide by VerkleNodeWidth to create room to avoid an overflow next.
|
||||
pos.Rsh(&pos, uint(VerkleNodeWidthLog2))
|
||||
|
||||
// We add mainStorageOffset/VerkleNodeWidth which can't overflow.
|
||||
pos.Add(&pos, 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 &pos, suffix
|
||||
}
|
||||
|
||||
func GetTreeKeyWithEvaluatedAddess(evaluated *verkle.Point, treeIndex *uint256.Int, subIndex byte) []byte {
|
||||
var poly [5]fr.Element
|
||||
|
||||
poly[0].SetZero()
|
||||
poly[1].SetZero()
|
||||
poly[2].SetZero()
|
||||
|
||||
trieIndexBytes := treeIndex.Bytes32()
|
||||
verkle.FromBytes(&poly[3], trieIndexBytes[16:])
|
||||
verkle.FromBytes(&poly[4], trieIndexBytes[:16])
|
||||
|
||||
cfg := verkle.GetConfig()
|
||||
ret := cfg.CommitToPoly(poly[:], 0)
|
||||
|
||||
// add the pre-evaluated address
|
||||
ret.Add(ret, evaluated)
|
||||
|
||||
return PointToHash(ret, subIndex)
|
||||
}
|
||||
|
||||
func GetTreeKeyBasicDataEvaluatedAddress(addrp *verkle.Point) []byte {
|
||||
return GetTreeKeyWithEvaluatedAddess(addrp, zero, BasicDataLeafKey)
|
||||
}
|
||||
|
||||
func PointToHash(evaluated *verkle.Point, suffix byte) []byte {
|
||||
retb := verkle.HashPointToBytes(evaluated)
|
||||
retb[31] = suffix
|
||||
return retb[:]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -300,7 +300,8 @@ func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) {
|
|||
//
|
||||
// TODO(gballet, rjl493456442) implement it.
|
||||
func (t *VerkleTrie) NodeIterator(startKey []byte) (NodeIterator, error) {
|
||||
panic("not implemented")
|
||||
// TODO(@CPerezz): remove.
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
// Prove implements state.Trie, constructing a Merkle proof for key. The result
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database {
|
|||
if config.HashDB != nil && config.PathDB != nil {
|
||||
log.Crit("Both 'hash' and 'path' mode are configured")
|
||||
}
|
||||
if config.PathDB != nil {
|
||||
if config.PathDB != nil || config.IsVerkle {
|
||||
db.backend = pathdb.New(diskdb, config.PathDB, config.IsVerkle)
|
||||
} else {
|
||||
db.backend = hashdb.New(diskdb, config.HashDB)
|
||||
|
|
|
|||
Loading…
Reference in a new issue