diff --git a/.github/workflows/stable-spec-tests.yml b/.github/workflows/stable-spec-tests.yml deleted file mode 100644 index 3c5477150eb..00000000000 --- a/.github/workflows/stable-spec-tests.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: Execution Spec Tests - Consume (stable) - -on: - push: - branches: [master] - pull_request: - branches: [master, kaustinen-with-shapella] - workflow_dispatch: - -env: - FIXTURES_TAG: "verkle@v0.0.9-alpha-1" - -jobs: - setup: - runs-on: ubuntu-latest - steps: - - name: Checkout go-ethereum - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.12.4" - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.4 - - - name: Build geth evm - run: | - go build -v ./cmd/evm - mkdir -p ${{ github.workspace }}/bin - mv evm ${{ github.workspace }}/bin/evm - chmod +x ${{ github.workspace }}/bin/evm - - - name: Archive built evm - uses: actions/upload-artifact@v4 - with: - name: evm - path: ${{ github.workspace }}/bin/evm - - consume: - runs-on: ubuntu-latest - needs: setup - strategy: - matrix: - filename: - [ - fixtures_verkle-genesis.tar.gz, - ] - steps: - - name: Download geth evm - uses: actions/download-artifact@v4 - with: - name: evm - path: ./bin - - - name: Make evm binary executable and add to PATH - run: | - chmod +x ./bin/evm - echo "${{ github.workspace }}/bin" >> $GITHUB_PATH - - - name: Download fixtures - uses: robinraju/release-downloader@v1 - with: - repository: "ethereum/execution-spec-tests" - tag: "${{ env.FIXTURES_TAG }}" - fileName: "${{ matrix.filename }}" - extract: true - - name: Clone execution-spec-tests and consume tests - run: | - curl -LsSf https://astral.sh/uv/install.sh | sh - git clone https://github.com/ethereum/execution-spec-tests -b ${{ env.FIXTURES_TAG }} --depth 1 - cd execution-spec-tests - uv run consume direct --evm-bin="${{ github.workspace }}/bin/evm" --input=../fixtures -n auto - shell: bash diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 87bd57fbee2..34f39f05638 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -39,6 +39,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/bintrie" "github.com/ethereum/go-ethereum/triedb" "github.com/holiman/uint256" "golang.org/x/crypto/sha3" @@ -47,7 +48,7 @@ import ( type Prestate struct { Env stEnv `json:"env"` Pre types.GenesisAlloc `json:"pre"` - VKT map[common.Hash]hexutil.Bytes `json:"vkt,omitempty"` + 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 @@ -418,25 +419,28 @@ 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)) } + body, _ := rlp.EncodeToBytes(includedTxs) return statedb, execRs, body, nil } -func MakePreState(db ethdb.Database, chainConfig *params.ChainConfig, pre *Prestate, verkle bool) *state.StateDB { - tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsVerkle: verkle}) +func MakePreState(db ethdb.Database, chainConfig *params.ChainConfig, pre *Prestate, isBintrie bool) *state.StateDB { + tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsVerkle: isBintrie}) sdb := state.NewDatabase(tdb, nil) root := types.EmptyRootHash - if verkle { - root = types.EmptyVerkleHash + if isBintrie { + root = types.EmptyBinaryHash + } + statedb, err := state.New(root, sdb) + if err != nil { + panic(fmt.Errorf("failed to create initial statedb: %v", err)) } - statedb, _ := state.New(root, sdb) for addr, a := range pre.Pre { statedb.SetCode(addr, a.Code, tracing.CodeChangeUnspecified) statedb.SetNonce(addr, a.Nonce, tracing.NonceChangeGenesis) @@ -450,13 +454,20 @@ func MakePreState(db ethdb.Database, chainConfig *params.ChainConfig, pre *Prest if err != nil { panic(err) } - // If verkle mode started, establish the conversion - if verkle { - if _, ok := statedb.GetTrie().(*trie.VerkleTrie); ok { + // If bintrie mode started, check if conversion happened + if isBintrie { + if _, ok := statedb.GetTrie().(*bintrie.BinaryTrie); ok { return statedb } + //TODO(@CPerezz): Fix this in upstream geth + // If we're in bintrie mode but don't have a BinaryTrie, something went wrong + panic(fmt.Errorf("binary trie mode enabled but trie is %T, not *bintrie.BinaryTrie", statedb.GetTrie())) + } + // For MPT mode, reopen the state with the committed root + statedb, err = state.New(mptRoot, sdb) + if err != nil { + panic(fmt.Errorf("failed to re-open statedb after commit with root %x: %v", mptRoot, err)) } - statedb, _ = state.New(mptRoot, sdb) return statedb } diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go index 52f7b05a20f..2581e58b03b 100644 --- a/cmd/evm/internal/t8ntool/flags.go +++ b/cmd/evm/internal/t8ntool/flags.go @@ -88,9 +88,9 @@ var ( "\t - into the file ", Value: "block.json", } - OutputVKTFlag = &cli.StringFlag{ + OutputBTFlag = &cli.StringFlag{ Name: "output.vkt", - Usage: "Determines where to put the `VKT` of the post-state.\n" + + 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 - into the file ", @@ -139,9 +139,10 @@ var ( Usage: "`stdin` or file name of where to find the transactions list in RLP form.", Value: "txs.rlp", } - InputVKTFlag = &cli.StringFlag{ + // 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 VKT.", + Usage: "`stdin` or file name of where to find the prestate BT.", } SealCliqueFlag = &cli.StringFlag{ Name: "seal.clique", diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index acd93a3a3d1..c47561e57b0 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -40,8 +40,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/tests" - "github.com/ethereum/go-ethereum/trie" - "github.com/ethereum/go-ethereum/trie/utils" + "github.com/ethereum/go-ethereum/trie/bintrie" "github.com/ethereum/go-ethereum/triedb" "github.com/holiman/uint256" "github.com/urfave/cli/v2" @@ -84,7 +83,7 @@ var ( type input struct { Alloc types.GenesisAlloc `json:"alloc,omitempty"` Env *stEnv `json:"env,omitempty"` - VKT map[common.Hash]hexutil.Bytes `json:"vkt,omitempty"` + BT map[common.Hash]hexutil.Bytes `json:"vkt,omitempty"` Txs []*txWithKey `json:"txs,omitempty"` TxRlp string `json:"txsRlp,omitempty"` } @@ -101,13 +100,13 @@ func Transition(ctx *cli.Context) error { prestate Prestate txIt txIterator // txs to apply allocStr = ctx.String(InputAllocFlag.Name) - vktStr = ctx.String(InputVKTFlag.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 || vktStr == 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)) @@ -119,12 +118,13 @@ func Transition(ctx *cli.Context) error { } } prestate.Pre = inputData.Alloc - if vktStr != stdinSelector && vktStr != "" { - if err := readFile(vktStr, "VKT", &inputData.VKT); err != nil { + if btStr != stdinSelector && btStr != "" { + if err := readFile(btStr, "BT", &inputData.BT); err != nil { return err } } - prestate.VKT = inputData.VKT + + prestate.BT = inputData.BT // Set the block environment if envStr != stdinSelector { var env stEnv @@ -196,16 +196,18 @@ func Transition(ctx *cli.Context) error { } // Dump the execution result collector := make(Alloc) - var vktleaves map[common.Hash]hexutil.Bytes - if !chainConfig.IsVerkle(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp) { + 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) } else { - vktleaves = make(map[common.Hash]hexutil.Bytes) - if err := s.DumpVKTLeaves(vktleaves); err != nil { + btleaves = make(map[common.Hash]hexutil.Bytes) + if err := s.DumpBinTrieLeaves(btleaves); err != nil { return err } } - return dispatchOutput(ctx, baseDir, result, collector, body, vktleaves) + + return dispatchOutput(ctx, baseDir, result, collector, body, btleaves) } func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error { @@ -327,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, vkt map[common.Hash]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 { @@ -354,11 +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 } - if vkt != nil { - if err := dispatch(baseDir, ctx.String(OutputVKTFlag.Name), "vkt", vkt); err != nil { + // 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 { @@ -378,9 +382,10 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a return nil } -// VerkleKey computes the tree key given an address and an optional +// The logic for tree key +// BinKey computes the tree key given an address and an optional // slot number. -func VerkleKey(ctx *cli.Context) error { +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") } @@ -390,21 +395,20 @@ func VerkleKey(ctx *cli.Context) error { return fmt.Errorf("error decoding address: %w", err) } - ap := utils.EvaluateAddressPoint(addr) 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", utils.GetTreeKeyStorageSlotWithEvaluatedAddress(ap, slot)) + fmt.Printf("%#x\n", bintrie.GetBinaryTreeKeyStorageSlot(common.BytesToAddress(addr), slot)) } else { - fmt.Printf("%#x\n", utils.GetTreeKeyBasicDataEvaluatedAddress(ap)) + fmt.Printf("%#x\n", bintrie.GetBinaryTreeKeyBasicData(common.BytesToAddress(addr))) } return nil } -// VerkleKeys computes a set of tree keys given a genesis alloc. -func VerkleKeys(ctx *cli.Context) error { +// 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 @@ -420,13 +424,13 @@ func VerkleKeys(ctx *cli.Context) error { } } - vkt, err := genVktFromAlloc(alloc) + bt, err := genBinTrieFromAlloc(alloc) if err != nil { - return fmt.Errorf("error generating vkt: %w", err) + return fmt.Errorf("error generating bt: %w", err) } collector := make(map[common.Hash]hexutil.Bytes) - it, err := vkt.NodeIterator(nil) + it, err := bt.NodeIterator(nil) if err != nil { panic(err) } @@ -446,8 +450,8 @@ func VerkleKeys(ctx *cli.Context) error { return nil } -// VerkleRoot computes the root of a VKT from a genesis alloc. -func VerkleRoot(ctx *cli.Context) error { +// 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 { @@ -462,27 +466,28 @@ func VerkleRoot(ctx *cli.Context) error { } } - vkt, err := genVktFromAlloc(alloc) + bt, err := genBinTrieFromAlloc(alloc) if err != nil { - return fmt.Errorf("error generating vkt: %w", err) + return fmt.Errorf("error generating bt: %w", err) } - fmt.Println(vkt.Hash().Hex()) + fmt.Println(bt.Hash().Hex()) return nil } -func genVktFromAlloc(alloc core.GenesisAlloc) (*trie.VerkleTrie, error) { - vkt, err := trie.NewVerkleTrie(types.EmptyVerkleHash, triedb.NewDatabase(rawdb.NewMemoryDatabase(), +// 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, - }), utils.NewPointCache(1024)) + })) if err != nil { return nil, err } for addr, acc := range alloc { for slot, value := range acc.Storage { - err := vkt.UpdateStorage(addr, slot.Bytes(), value.Big().Bytes()) + err := bt.UpdateStorage(addr, slot.Bytes(), value.Big().Bytes()) if err != nil { return nil, fmt.Errorf("error inserting storage: %w", err) } @@ -494,21 +499,21 @@ func genVktFromAlloc(alloc core.GenesisAlloc) (*trie.VerkleTrie, error) { CodeHash: crypto.Keccak256Hash(acc.Code).Bytes(), Root: common.Hash{}, } - err := vkt.UpdateAccount(addr, account, len(acc.Code)) + err := bt.UpdateAccount(addr, account, len(acc.Code)) if err != nil { return nil, fmt.Errorf("error inserting account: %w", err) } - err = vkt.UpdateContractCode(addr, common.BytesToHash(account.CodeHash), acc.Code) + err = bt.UpdateContractCode(addr, common.BytesToHash(account.CodeHash), acc.Code) if err != nil { return nil, fmt.Errorf("error inserting code: %w", err) } } - return vkt, nil + return bt, nil } -// VerkleCodeChunkKey computes the tree key of a code-chunk for a given address. -func VerkleCodeChunkKey(ctx *cli.Context) error { +// 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") } @@ -524,13 +529,13 @@ func VerkleCodeChunkKey(ctx *cli.Context) error { var chunkNumber uint256.Int chunkNumber.SetBytes(chunkNumberBytes) - fmt.Printf("%#x\n", utils.GetTreeKeyCodeChunk(addr, &chunkNumber)) + fmt.Printf("%#x\n", bintrie.GetBinaryTreeKeyCodeChunk(common.BytesToAddress(addr), &chunkNumber)) return nil } -// VerkleChunkifyCode returns the code chunkification for a given code. -func VerkleChunkifyCode(ctx *cli.Context) error { +// 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") } @@ -540,7 +545,7 @@ func VerkleChunkifyCode(ctx *cli.Context) error { return fmt.Errorf("error decoding address: %w", err) } - chunkedCode := trie.ChunkifyCode(bytecode) + chunkedCode := bintrie.ChunkifyCode(bytecode) fmt.Printf("%#x\n", chunkedCode) return nil diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 93bf593a32f..b6bebba483c 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -146,13 +146,13 @@ var ( t8ntool.TraceEnableCallFramesFlag, t8ntool.OutputBasedir, t8ntool.OutputAllocFlag, - t8ntool.OutputVKTFlag, + t8ntool.OutputBTFlag, t8ntool.OutputWitnessFlag, t8ntool.OutputResultFlag, t8ntool.OutputBodyFlag, t8ntool.InputAllocFlag, t8ntool.InputEnvFlag, - t8ntool.InputVKTFlag, + t8ntool.InputBTFlag, t8ntool.InputTxsFlag, t8ntool.ForknameFlag, t8ntool.ChainIDFlag, @@ -163,13 +163,13 @@ var ( verkleCommand = &cli.Command{ Name: "verkle", Aliases: []string{"vkt"}, - Usage: "Verkle helpers", + Usage: "Binary Trie helpers", Subcommands: []*cli.Command{ { Name: "tree-keys", Aliases: []string{"v"}, - Usage: "compute a set of verkle tree keys, given their source addresses and optional slot numbers", - Action: t8ntool.VerkleKeys, + Usage: "compute a set of binary trie keys, given their source addresses and optional slot numbers", + Action: t8ntool.BinKeys, Flags: []cli.Flag{ t8ntool.InputAllocFlag, }, @@ -177,26 +177,26 @@ var ( { Name: "single-key", Aliases: []string{"vk"}, - Usage: "compute the verkle tree key given an address and optional slot number", - Action: t8ntool.VerkleKey, + 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 verkle tree key given an address and chunk number", - Action: t8ntool.VerkleCodeChunkKey, + 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", - Action: t8ntool.VerkleChunkifyCode, + 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 verkle tree for the given alloc", - Action: t8ntool.VerkleRoot, + Usage: "compute the state-root of a binary trie for the given alloc", + Action: t8ntool.BinTrieRoot, Flags: []cli.Flag{ t8ntool.InputAllocFlag, }, diff --git a/core/state/database.go b/core/state/database.go index 3a0ac422ee4..d30b260c305 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -28,6 +28,7 @@ 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/trienode" "github.com/ethereum/go-ethereum/trie/utils" "github.com/ethereum/go-ethereum/triedb" @@ -242,7 +243,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) diff --git a/core/state/dump.go b/core/state/dump.go index ebcbbc293ce..4a17d1adde6 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -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,7 +222,8 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] return nextKey } -func (s *StateDB) DumpVKTLeaves(collector map[common.Hash]hexutil.Bytes) error { +// 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 { @@ -230,7 +232,7 @@ func (s *StateDB) DumpVKTLeaves(collector map[common.Hash]hexutil.Bytes) error { s.trie = trie } - it, err := s.trie.(*trie.VerkleTrie).NodeIterator(nil) + it, err := s.trie.(*bintrie.BinaryTrie).NodeIterator(nil) if err != nil { panic(err) } diff --git a/core/state/reader.go b/core/state/reader.go index 3e8b31b6be3..fb23c84837d 100644 --- a/core/state/reader.go +++ b/core/state/reader.go @@ -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 diff --git a/trie/bintrie/binary_node.go b/trie/bintrie/binary_node.go index 1c003a6c8fd..690489b2aa1 100644 --- a/trie/bintrie/binary_node.go +++ b/trie/bintrie/binary_node.go @@ -31,8 +31,11 @@ type ( 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 + 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 diff --git a/trie/bintrie/binary_node_test.go b/trie/bintrie/binary_node_test.go index b21daaab697..242743ba53b 100644 --- a/trie/bintrie/binary_node_test.go +++ b/trie/bintrie/binary_node_test.go @@ -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, }, } diff --git a/trie/bintrie/hashed_node.go b/trie/bintrie/hashed_node.go index 8f9fd66a59a..59e295cfcec 100644 --- a/trie/bintrie/hashed_node.go +++ b/trie/bintrie/hashed_node.go @@ -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 { diff --git a/trie/bintrie/hashed_node_test.go b/trie/bintrie/hashed_node_test.go index 0c19ae0c57d..9ebc764ce47 100644 --- a/trie/bintrie/hashed_node_test.go +++ b/trie/bintrie/hashed_node_test.go @@ -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 { diff --git a/trie/bintrie/internal_node.go b/trie/bintrie/internal_node.go index f3ddd1aab02..6c1a56dbf2f 100644 --- a/trie/bintrie/internal_node.go +++ b/trie/bintrie/internal_node.go @@ -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 } diff --git a/trie/bintrie/iterator.go b/trie/bintrie/iterator.go index a6bab2bcfa9..9b863ed1e3f 100644 --- a/trie/bintrie/iterator.go +++ b/trie/bintrie/iterator.go @@ -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] { diff --git a/trie/bintrie/key_encoding.go b/trie/bintrie/key_encoding.go index 13c20573710..cda797521a6 100644 --- a/trie/bintrie/key_encoding.go +++ b/trie/bintrie/key_encoding.go @@ -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 diff --git a/trie/bintrie/stem_node.go b/trie/bintrie/stem_node.go index 50c06c9761e..4a12157477f 100644 --- a/trie/bintrie/stem_node.go +++ b/trie/bintrie/stem_node.go @@ -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[:] diff --git a/trie/bintrie/trie.go b/trie/bintrie/trie.go index 0a8bd325f58..a3846240b93 100644 --- a/trie/bintrie/trie.go +++ b/trie/bintrie/trie.go @@ -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) diff --git a/trie/bintrie/trie_test.go b/trie/bintrie/trie_test.go index 84f76895494..ca02cfaa1f3 100644 --- a/trie/bintrie/trie_test.go +++ b/trie/bintrie/trie_test.go @@ -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 { diff --git a/trie/transition.go b/trie/transitiontrie/transition.go similarity index 85% rename from trie/transition.go rename to trie/transitiontrie/transition.go index da49c6cdc2c..ed9e30bfba6 100644 --- a/trie/transition.go +++ b/trie/transitiontrie/transition.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package 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 } - return t.base.GetKey(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 } - // TODO also insert value into overlay - return t.base.GetStorage(addr, key) + 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 } - return t.base.GetAccount(address) + 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,21 +210,21 @@ 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. func (t *TransitionTrie) Copy() *TransitionTrie { + var baseCopy *trie.SecureTrie + if t.base != nil { + baseCopy = t.base.Copy() + } return &TransitionTrie{ overlay: t.overlay.Copy(), - base: t.base.Copy(), + base: baseCopy, storage: t.storage, } } diff --git a/trie/verkle.go b/trie/verkle.go index 1b491594d71..70793330c53 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -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) { - return newVerkleNodeIterator(t, nil) + // TODO(@CPerezz): remove. + return nil, errors.New("not implemented") } // Prove implements state.Trie, constructing a Merkle proof for key. The result diff --git a/trie/verkle_iterator.go b/trie/verkle_iterator.go deleted file mode 100644 index ee2fd8c68b2..00000000000 --- a/trie/verkle_iterator.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2021 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package trie - -import ( - "github.com/ethereum/go-ethereum/common" - - "github.com/ethereum/go-verkle" -) - -type verkleNodeIteratorState struct { - Node verkle.VerkleNode - Index int // points to _next_ value -} - -type verkleNodeIterator struct { - trie *VerkleTrie - current verkle.VerkleNode - lastErr error - - stack []verkleNodeIteratorState -} - -func newVerkleNodeIterator(trie *VerkleTrie, _ []byte) (NodeIterator, error) { - if trie.Hash() == (common.Hash{}) { - return new(nodeIterator), nil - } - it := &verkleNodeIterator{trie: trie, current: trie.root} - // it.err = it.seek(start) - return it, nil -} - -// Next moves the iterator to the next node. If the parameter is false, any child -// nodes will be skipped. -func (it *verkleNodeIterator) Next(descend bool) bool { - if it.lastErr == errIteratorEnd { - it.lastErr = errIteratorEnd - return false - } - - if len(it.stack) == 0 { - it.stack = append(it.stack, verkleNodeIteratorState{Node: it.trie.root, Index: 0}) - it.current = it.trie.root - - return true - } - - switch node := it.current.(type) { - case *verkle.InternalNode: - context := &it.stack[len(it.stack)-1] - - // Look for the next non-empty child - children := node.Children() - for ; context.Index < len(children); context.Index++ { - if _, ok := children[context.Index].(verkle.Empty); !ok { - it.stack = append(it.stack, verkleNodeIteratorState{Node: children[context.Index], Index: 0}) - it.current = children[context.Index] - return it.Next(descend) - } - } - - // Reached the end of this node, go back to the parent, if - // this isn't root. - if len(it.stack) == 1 { - it.lastErr = errIteratorEnd - return false - } - it.stack = it.stack[:len(it.stack)-1] - it.current = it.stack[len(it.stack)-1].Node - it.stack[len(it.stack)-1].Index++ - return it.Next(descend) - case *verkle.LeafNode: - // Look for the next non-empty value - for i := it.stack[len(it.stack)-1].Index; i < 256; i++ { - if node.Value(i) != nil { - it.stack[len(it.stack)-1].Index = i + 1 - return true - } - } - - // go back to parent to get the next leaf - it.stack = it.stack[:len(it.stack)-1] - it.current = it.stack[len(it.stack)-1].Node - it.stack[len(it.stack)-1].Index++ - return it.Next(descend) - case verkle.HashedNode: - // resolve the node - data, err := it.trie.reader.Node(it.Path(), common.Hash{}) - if err != nil { - panic(err) - } - it.current, err = verkle.ParseNode(data, byte(len(it.stack)-1)) - if err != nil { - panic(err) - } - - // update the stack and parent with the resolved node - it.stack[len(it.stack)-1].Node = it.current - parent := &it.stack[len(it.stack)-2] - parent.Node.(*verkle.InternalNode).SetChild(parent.Index, it.current) - return it.Next(true) - default: - panic("invalid node type") - } -} - -// Error returns the error status of the iterator. -func (it *verkleNodeIterator) Error() error { - if it.lastErr == errIteratorEnd { - return nil - } - return it.lastErr -} - -// Hash returns the hash of the current node. -func (it *verkleNodeIterator) Hash() common.Hash { - return it.current.Commit().Bytes() -} - -// Parent returns the hash of the parent of the current node. The hash may be the one -// grandparent if the immediate parent is an internal node with no hash. -func (it *verkleNodeIterator) Parent() common.Hash { - return it.stack[len(it.stack)-1].Node.Commit().Bytes() -} - -// Path returns the hex-encoded path to the current node. -// Callers must not retain references to the return value after calling Next. -// For leaf nodes, the last element of the path is the 'terminator symbol' 0x10. -func (it *verkleNodeIterator) Path() []byte { - if it.Leaf() { - return it.LeafKey() - } - var path []byte - for i, state := range it.stack { - // skip the last byte - if i >= len(it.stack)-1 { - break - } - path = append(path, byte(state.Index)) - } - return path -} - -func (it *verkleNodeIterator) NodeBlob() []byte { - panic("not completely implemented") -} - -// Leaf returns true iff the current node is a leaf node. -func (it *verkleNodeIterator) Leaf() bool { - _, ok := it.current.(*verkle.LeafNode) - return ok -} - -// LeafKey returns the key of the leaf. The method panics if the iterator is not -// positioned at a leaf. Callers must not retain references to the value after -// calling Next. -func (it *verkleNodeIterator) LeafKey() []byte { - leaf, ok := it.current.(*verkle.LeafNode) - if !ok { - panic("Leaf() called on an verkle node iterator not at a leaf location") - } - - return leaf.Key(it.stack[len(it.stack)-1].Index - 1) -} - -// LeafBlob returns the content of the leaf. The method panics if the iterator -// is not positioned at a leaf. Callers must not retain references to the value -// after calling Next. -func (it *verkleNodeIterator) LeafBlob() []byte { - leaf, ok := it.current.(*verkle.LeafNode) - if !ok { - panic("LeafBlob() called on an verkle node iterator not at a leaf location") - } - - return leaf.Value(it.stack[len(it.stack)-1].Index - 1) -} - -// LeafProof returns the Merkle proof of the leaf. The method panics if the -// iterator is not positioned at a leaf. Callers must not retain references -// to the value after calling Next. -func (it *verkleNodeIterator) LeafProof() [][]byte { - _, ok := it.current.(*verkle.LeafNode) - if !ok { - panic("LeafProof() called on an verkle node iterator not at a leaf location") - } - - // return it.trie.Prove(leaf.Key()) - panic("not completely implemented") -} - -// AddResolver sets an intermediate database to use for looking up trie nodes -// before reaching into the real persistent layer. -// -// This is not required for normal operation, rather is an optimization for -// cases where trie nodes can be recovered from some external mechanism without -// reading from disk. In those cases, this resolver allows short circuiting -// accesses and returning them from memory. -// -// Before adding a similar mechanism to any other place in Geth, consider -// making trie.Database an interface and wrapping at that level. It's a huge -// refactor, but it could be worth it if another occurrence arises. -func (it *verkleNodeIterator) AddResolver(NodeResolver) { - // Not implemented, but should not panic -} diff --git a/trie/verkle_iterator_test.go b/trie/verkle_iterator_test.go deleted file mode 100644 index e1ec6701078..00000000000 --- a/trie/verkle_iterator_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2023 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package trie - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/trie/utils" - "github.com/holiman/uint256" -) - -func TestVerkleIterator(t *testing.T) { - trie, err := NewVerkleTrie(types.EmptyVerkleHash, newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme), utils.NewPointCache(1024)) - if err != nil { - panic(err) - } - account0 := &types.StateAccount{ - Nonce: 1, - Balance: new(uint256.Int).SetUint64(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: new(uint256.Int).SetUint64(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) - } -}