diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 5852865f94..b569180852 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,25 +1,36 @@
# Lines starting with '#' are comments.
# Each line is a file pattern followed by one or more owners.
-accounts/usbwallet @karalabe
-accounts/scwallet @gballet
-accounts/abi @gballet @MariusVanDerWijden
-beacon/engine @lightclient
-cmd/clef @holiman
-cmd/evm @holiman @MariusVanDerWijden @lightclient
-consensus @karalabe
-core/ @karalabe @holiman @rjl493456442
-eth/ @karalabe @holiman @rjl493456442
-eth/catalyst/ @gballet @lightclient
+accounts/usbwallet/ @gballet
+accounts/scwallet/ @gballet
+accounts/abi/ @gballet @MariusVanDerWijden
+beacon/engine/ @MariusVanDerWijden @lightclient @fjl
+beacon/light/ @zsfelfoldi
+beacon/merkle/ @zsfelfoldi
+beacon/types/ @zsfelfoldi @fjl
+beacon/params/ @zsfelfoldi @fjl
+cmd/clef/ @holiman
+cmd/evm/ @holiman @MariusVanDerWijden @lightclient
+core/state/ @rjl493456442 @holiman
+crypto/ @gballet @jwasinger @holiman @fjl
+core/ @holiman @rjl493456442
+eth/ @holiman @rjl493456442
+eth/catalyst/ @MariusVanDerWijden @lightclient @fjl @jwasinger
eth/tracers/ @s1na
+ethclient/ @fjl
+ethdb/ @rjl493456442
+event/ @fjl
+trie/ @rjl493456442
+triedb/ @rjl493456442
core/tracing/ @s1na
graphql/ @s1na
-internal/ethapi @lightclient
-internal/era @lightclient
-les/ @zsfelfoldi @rjl493456442
-light/ @zsfelfoldi @rjl493456442
+internal/ethapi/ @fjl @s1na @lightclient
+internal/era/ @lightclient
+metrics/ @holiman
+miner/ @MariusVanDerWijden @holiman @fjl @rjl493456442
node/ @fjl
p2p/ @fjl @zsfelfoldi
+rlp/ @fjl
params/ @fjl @holiman @karalabe @gballet @rjl493456442 @zsfelfoldi
rpc/ @fjl @holiman
signer/ @holiman
diff --git a/beacon/engine/types.go b/beacon/engine/types.go
index 9f41aa04ca..984090ef89 100644
--- a/beacon/engine/types.go
+++ b/beacon/engine/types.go
@@ -265,15 +265,7 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
var requestsHash *common.Hash
if requests != nil {
- // Put back request type byte.
- typedRequests := make([][]byte, len(requests))
- for i, reqdata := range requests {
- typedReqdata := make([]byte, len(reqdata)+1)
- typedReqdata[0] = byte(i)
- copy(typedReqdata[1:], reqdata)
- typedRequests[i] = typedReqdata
- }
- h := types.CalcRequestsHash(typedRequests)
+ h := types.CalcRequestsHash(requests)
requestsHash = &h
}
@@ -343,20 +335,11 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
}
}
- // Remove type byte in requests.
- var plainRequests [][]byte
- if requests != nil {
- plainRequests = make([][]byte, len(requests))
- for i, reqdata := range requests {
- plainRequests[i] = reqdata[1:]
- }
- }
-
return &ExecutionPayloadEnvelope{
ExecutionPayload: data,
BlockValue: fees,
BlobsBundle: &bundle,
- Requests: plainRequests,
+ Requests: requests,
Override: false,
}
}
diff --git a/build/ci.go b/build/ci.go
index e3115af46d..fd51b90d17 100644
--- a/build/ci.go
+++ b/build/ci.go
@@ -74,7 +74,6 @@ var (
allToolsArchiveFiles = []string{
"COPYING",
executablePath("abigen"),
- executablePath("bootnode"),
executablePath("evm"),
executablePath("geth"),
executablePath("rlpdump"),
@@ -87,10 +86,6 @@ var (
BinaryName: "abigen",
Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.",
},
- {
- BinaryName: "bootnode",
- Description: "Ethereum bootnode.",
- },
{
BinaryName: "evm",
Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.",
diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go
deleted file mode 100644
index 350b85df1e..0000000000
--- a/cmd/bootnode/main.go
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum 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 General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-// bootnode runs a bootstrap node for the Ethereum Discovery Protocol.
-package main
-
-import (
- "crypto/ecdsa"
- "flag"
- "fmt"
- "net"
- "os"
- "time"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/discover"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/nat"
- "github.com/ethereum/go-ethereum/p2p/netutil"
-)
-
-func main() {
- var (
- listenAddr = flag.String("addr", ":30301", "listen address")
- genKey = flag.String("genkey", "", "generate a node key")
- writeAddr = flag.Bool("writeaddress", false, "write out the node's public key and quit")
- nodeKeyFile = flag.String("nodekey", "", "private key filename")
- nodeKeyHex = flag.String("nodekeyhex", "", "private key as hex (for testing)")
- natdesc = flag.String("nat", "none", "port mapping mechanism (any|none|upnp|pmp|pmp:|extip:)")
- netrestrict = flag.String("netrestrict", "", "restrict network communication to the given IP networks (CIDR masks)")
- runv5 = flag.Bool("v5", false, "run a v5 topic discovery bootnode")
- verbosity = flag.Int("verbosity", 3, "log verbosity (0-5)")
- vmodule = flag.String("vmodule", "", "log verbosity pattern")
-
- nodeKey *ecdsa.PrivateKey
- err error
- )
- flag.Parse()
-
- glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false))
- slogVerbosity := log.FromLegacyLevel(*verbosity)
- glogger.Verbosity(slogVerbosity)
- glogger.Vmodule(*vmodule)
- log.SetDefault(log.NewLogger(glogger))
-
- natm, err := nat.Parse(*natdesc)
- if err != nil {
- utils.Fatalf("-nat: %v", err)
- }
- switch {
- case *genKey != "":
- nodeKey, err = crypto.GenerateKey()
- if err != nil {
- utils.Fatalf("could not generate key: %v", err)
- }
- if err = crypto.SaveECDSA(*genKey, nodeKey); err != nil {
- utils.Fatalf("%v", err)
- }
- if !*writeAddr {
- return
- }
- case *nodeKeyFile == "" && *nodeKeyHex == "":
- utils.Fatalf("Use -nodekey or -nodekeyhex to specify a private key")
- case *nodeKeyFile != "" && *nodeKeyHex != "":
- utils.Fatalf("Options -nodekey and -nodekeyhex are mutually exclusive")
- case *nodeKeyFile != "":
- if nodeKey, err = crypto.LoadECDSA(*nodeKeyFile); err != nil {
- utils.Fatalf("-nodekey: %v", err)
- }
- case *nodeKeyHex != "":
- if nodeKey, err = crypto.HexToECDSA(*nodeKeyHex); err != nil {
- utils.Fatalf("-nodekeyhex: %v", err)
- }
- }
-
- if *writeAddr {
- fmt.Printf("%x\n", crypto.FromECDSAPub(&nodeKey.PublicKey)[1:])
- os.Exit(0)
- }
-
- var restrictList *netutil.Netlist
- if *netrestrict != "" {
- restrictList, err = netutil.ParseNetlist(*netrestrict)
- if err != nil {
- utils.Fatalf("-netrestrict: %v", err)
- }
- }
-
- addr, err := net.ResolveUDPAddr("udp", *listenAddr)
- if err != nil {
- utils.Fatalf("-ResolveUDPAddr: %v", err)
- }
- conn, err := net.ListenUDP("udp", addr)
- if err != nil {
- utils.Fatalf("-ListenUDP: %v", err)
- }
- defer conn.Close()
-
- db, _ := enode.OpenDB("")
- ln := enode.NewLocalNode(db, nodeKey)
-
- listenerAddr := conn.LocalAddr().(*net.UDPAddr)
- if natm != nil && !listenerAddr.IP.IsLoopback() {
- natAddr := doPortMapping(natm, ln, listenerAddr)
- if natAddr != nil {
- listenerAddr = natAddr
- }
- }
-
- printNotice(&nodeKey.PublicKey, *listenerAddr)
- cfg := discover.Config{
- PrivateKey: nodeKey,
- NetRestrict: restrictList,
- }
- if *runv5 {
- if _, err := discover.ListenV5(conn, ln, cfg); err != nil {
- utils.Fatalf("%v", err)
- }
- } else {
- if _, err := discover.ListenUDP(conn, ln, cfg); err != nil {
- utils.Fatalf("%v", err)
- }
- }
-
- select {}
-}
-
-func printNotice(nodeKey *ecdsa.PublicKey, addr net.UDPAddr) {
- if addr.IP.IsUnspecified() {
- addr.IP = net.IP{127, 0, 0, 1}
- }
- n := enode.NewV4(nodeKey, addr.IP, 0, addr.Port)
- fmt.Println(n.URLv4())
- fmt.Println("Note: you're using cmd/bootnode, a developer tool.")
- fmt.Println("We recommend using a regular node as bootstrap node for production deployments.")
-}
-
-func doPortMapping(natm nat.Interface, ln *enode.LocalNode, addr *net.UDPAddr) *net.UDPAddr {
- const (
- protocol = "udp"
- name = "ethereum discovery"
- )
- newLogger := func(external int, internal int) log.Logger {
- return log.New("proto", protocol, "extport", external, "intport", internal, "interface", natm)
- }
-
- var (
- intport = addr.Port
- extaddr = &net.UDPAddr{IP: addr.IP, Port: addr.Port}
- mapTimeout = nat.DefaultMapTimeout
- log = newLogger(addr.Port, intport)
- )
- addMapping := func() {
- // Get the external address.
- var err error
- extaddr.IP, err = natm.ExternalIP()
- if err != nil {
- log.Debug("Couldn't get external IP", "err", err)
- return
- }
- // Create the mapping.
- p, err := natm.AddMapping(protocol, extaddr.Port, intport, name, mapTimeout)
- if err != nil {
- log.Debug("Couldn't add port mapping", "err", err)
- return
- }
- if p != uint16(extaddr.Port) {
- extaddr.Port = int(p)
- log = newLogger(extaddr.Port, intport)
- log.Info("NAT mapped alternative port")
- } else {
- log.Info("NAT mapped port")
- }
- // Update IP/port information of the local node.
- ln.SetStaticIP(extaddr.IP)
- ln.SetFallbackUDP(extaddr.Port)
- }
-
- // Perform mapping once, synchronously.
- log.Info("Attempting port mapping")
- addMapping()
-
- // Refresh the mapping periodically.
- go func() {
- refresh := time.NewTimer(mapTimeout)
- defer refresh.Stop()
- for range refresh.C {
- addMapping()
- refresh.Reset(mapTimeout)
- }
- }()
-
- return extaddr
-}
diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go
index d5cd8d8e3d..2cb0531e28 100644
--- a/cmd/evm/blockrunner.go
+++ b/cmd/evm/blockrunner.go
@@ -22,79 +22,84 @@ import (
"fmt"
"os"
"regexp"
- "sort"
+ "slices"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/tracing"
- "github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/tests"
"github.com/urfave/cli/v2"
+ "golang.org/x/exp/maps"
)
-var RunFlag = &cli.StringFlag{
- Name: "run",
- Value: ".*",
- Usage: "Run only those tests matching the regular expression.",
-}
-
var blockTestCommand = &cli.Command{
Action: blockTestCmd,
Name: "blocktest",
Usage: "Executes the given blockchain tests",
- ArgsUsage: "",
- Flags: []cli.Flag{RunFlag},
+ ArgsUsage: "",
+ Flags: slices.Concat([]cli.Flag{
+ DumpFlag,
+ HumanReadableFlag,
+ RunFlag,
+ WitnessCrossCheckFlag,
+ }, traceFlags),
}
func blockTestCmd(ctx *cli.Context) error {
- if len(ctx.Args().First()) == 0 {
- return errors.New("path-to-test argument required")
+ path := ctx.Args().First()
+ if len(path) == 0 {
+ return errors.New("path argument required")
}
+ var (
+ collected = collectJSONFiles(path)
+ results []testResult
+ )
+ for _, fname := range collected {
+ r, err := runBlockTest(ctx, fname)
+ if err != nil {
+ return err
+ }
+ results = append(results, r...)
+ }
+ report(ctx, results)
+ return nil
+}
- var tracer *tracing.Hooks
- // Configure the EVM logger
- if ctx.Bool(MachineFlag.Name) {
- tracer = logger.NewJSONLogger(&logger.Config{
- EnableMemory: !ctx.Bool(DisableMemoryFlag.Name),
- DisableStack: ctx.Bool(DisableStackFlag.Name),
- DisableStorage: ctx.Bool(DisableStorageFlag.Name),
- EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name),
- }, os.Stderr)
- }
- // Load the test content from the input file
- src, err := os.ReadFile(ctx.Args().First())
+func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
+ src, err := os.ReadFile(fname)
if err != nil {
- return err
+ return nil, err
}
- var tests map[string]tests.BlockTest
+ var tests map[string]*tests.BlockTest
if err = json.Unmarshal(src, &tests); err != nil {
- return err
+ return nil, err
}
re, err := regexp.Compile(ctx.String(RunFlag.Name))
if err != nil {
- return fmt.Errorf("invalid regex -%s: %v", RunFlag.Name, err)
+ return nil, fmt.Errorf("invalid regex -%s: %v", RunFlag.Name, err)
}
+ tracer := tracerFromFlags(ctx)
- // Run them in order
- var keys []string
- for key := range tests {
- keys = append(keys, key)
- }
- sort.Strings(keys)
+ // Pull out keys to sort and ensure tests are run in order.
+ keys := maps.Keys(tests)
+ slices.Sort(keys)
+
+ // Run all the tests.
+ var results []testResult
for _, name := range keys {
if !re.MatchString(name) {
continue
}
- test := tests[name]
- if err := test.Run(false, rawdb.HashScheme, false, tracer, func(res error, chain *core.BlockChain) {
+ result := &testResult{Name: name, Pass: true}
+ if err := tests[name].Run(false, rawdb.HashScheme, ctx.Bool(WitnessCrossCheckFlag.Name), tracer, func(res error, chain *core.BlockChain) {
if ctx.Bool(DumpFlag.Name) {
- if state, _ := chain.State(); state != nil {
- fmt.Println(string(state.Dump(nil)))
+ if s, _ := chain.State(); s != nil {
+ result.State = dump(s)
}
}
}); err != nil {
- return fmt.Errorf("test %v: %w", name, err)
+ result.Pass, result.Error = false, err.Error()
}
+ results = append(results, *result)
}
- return nil
+ return results, nil
}
diff --git a/cmd/evm/compiler.go b/cmd/evm/compiler.go
deleted file mode 100644
index c071834b59..0000000000
--- a/cmd/evm/compiler.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum 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 General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "errors"
- "fmt"
- "os"
-
- "github.com/ethereum/go-ethereum/cmd/evm/internal/compiler"
-
- "github.com/urfave/cli/v2"
-)
-
-var compileCommand = &cli.Command{
- Action: compileCmd,
- Name: "compile",
- Usage: "Compiles easm source to evm binary",
- ArgsUsage: "",
-}
-
-func compileCmd(ctx *cli.Context) error {
- debug := ctx.Bool(DebugFlag.Name)
-
- if len(ctx.Args().First()) == 0 {
- return errors.New("filename required")
- }
-
- fn := ctx.Args().First()
- src, err := os.ReadFile(fn)
- if err != nil {
- return err
- }
-
- bin, err := compiler.Compile(fn, src, debug)
- if err != nil {
- return err
- }
- fmt.Println(bin)
- return nil
-}
diff --git a/cmd/evm/disasm.go b/cmd/evm/disasm.go
deleted file mode 100644
index b1f35cbaf5..0000000000
--- a/cmd/evm/disasm.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum 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 General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "errors"
- "fmt"
- "os"
- "strings"
-
- "github.com/ethereum/go-ethereum/core/asm"
- "github.com/urfave/cli/v2"
-)
-
-var disasmCommand = &cli.Command{
- Action: disasmCmd,
- Name: "disasm",
- Usage: "Disassembles evm binary",
- ArgsUsage: "",
-}
-
-func disasmCmd(ctx *cli.Context) error {
- var in string
- switch {
- case len(ctx.Args().First()) > 0:
- fn := ctx.Args().First()
- input, err := os.ReadFile(fn)
- if err != nil {
- return err
- }
- in = string(input)
- case ctx.IsSet(InputFlag.Name):
- in = ctx.String(InputFlag.Name)
- default:
- return errors.New("missing filename or --input value")
- }
-
- code := strings.TrimSpace(in)
- fmt.Printf("%v\n", code)
- return asm.PrintDisassembled(code)
-}
diff --git a/cmd/evm/eest.go b/cmd/evm/eest.go
new file mode 100644
index 0000000000..43071a3e5d
--- /dev/null
+++ b/cmd/evm/eest.go
@@ -0,0 +1,49 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
+
+package main
+
+import "regexp"
+
+// testMetadata provides more granular access to the test information encoded
+// within its filename by the execution spec test (EEST).
+type testMetadata struct {
+ fork string
+ module string // which python module gnerated the test, e.g. eip7702
+ file string // exact file the test came from, e.g. test_gas.py
+ function string // func that created the test, e.g. test_valid_mcopy_operations
+ parameters string // the name of the parameters which were used to fill the test, e.g. zero_inputs
+}
+
+// parseTestMetadata reads a test name and parses out more specific information
+// about the test.
+func parseTestMetadata(s string) *testMetadata {
+ var (
+ pattern = `tests\/([^\/]+)\/([^\/]+)\/([^:]+)::([^[]+)\[fork_([^-\]]+)-[^-]+-(.+)\]`
+ re = regexp.MustCompile(pattern)
+ )
+ match := re.FindStringSubmatch(s)
+ if len(match) == 0 {
+ return nil
+ }
+ return &testMetadata{
+ fork: match[5],
+ module: match[2],
+ file: match[3],
+ function: match[4],
+ parameters: match[6],
+ }
+}
diff --git a/cmd/evm/eofparse.go b/cmd/evm/eofparse.go
index 2122270942..df8581146a 100644
--- a/cmd/evm/eofparse.go
+++ b/cmd/evm/eofparse.go
@@ -31,13 +31,41 @@ import (
"github.com/urfave/cli/v2"
)
+var jt vm.JumpTable
+
+const initcode = "INITCODE"
+
func init() {
jt = vm.NewPragueEOFInstructionSetForTesting()
}
var (
- jt vm.JumpTable
- initcode = "INITCODE"
+ hexFlag = &cli.StringFlag{
+ Name: "hex",
+ Usage: "Single container data parse and validation",
+ }
+ refTestFlag = &cli.StringFlag{
+ Name: "test",
+ Usage: "Path to EOF validation reference test.",
+ }
+ eofParseCommand = &cli.Command{
+ Name: "eofparse",
+ Aliases: []string{"eof"},
+ Usage: "Parses hex eof container and returns validation errors (if any)",
+ Action: eofParseAction,
+ Flags: []cli.Flag{
+ hexFlag,
+ refTestFlag,
+ },
+ }
+ eofDumpCommand = &cli.Command{
+ Name: "eofdump",
+ Usage: "Parses hex eof container and prints out human-readable representation of the container.",
+ Action: eofDumpAction,
+ Flags: []cli.Flag{
+ hexFlag,
+ },
+ }
)
func eofParseAction(ctx *cli.Context) error {
diff --git a/cmd/evm/internal/compiler/compiler.go b/cmd/evm/internal/compiler/compiler.go
deleted file mode 100644
index 54981b6697..0000000000
--- a/cmd/evm/internal/compiler/compiler.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum 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 General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package compiler
-
-import (
- "errors"
- "fmt"
-
- "github.com/ethereum/go-ethereum/core/asm"
-)
-
-func Compile(fn string, src []byte, debug bool) (string, error) {
- compiler := asm.NewCompiler(debug)
- compiler.Feed(asm.Lex(src, debug))
-
- bin, compileErrors := compiler.Compile()
- if len(compileErrors) > 0 {
- // report errors
- for _, err := range compileErrors {
- fmt.Printf("%s:%v\n", fn, err)
- }
- return "", errors.New("compiling failed")
- }
- return bin, nil
-}
diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go
index c0623a69bf..7a0de86a11 100644
--- a/cmd/evm/internal/t8ntool/execution.go
+++ b/cmd/evm/internal/t8ntool/execution.go
@@ -253,16 +253,13 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
statedb.SetTxContext(tx.Hash(), txIndex)
var (
- txContext = core.NewEVMTxContext(msg)
- snapshot = statedb.Snapshot()
- prevGas = gaspool.Gas()
+ snapshot = statedb.Snapshot()
+ prevGas = gaspool.Gas()
)
if tracer != nil && tracer.OnTxStart != nil {
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
}
// (ret []byte, usedGas uint64, failed bool, err error)
-
- evm.SetTxContext(txContext)
msgResult, err := core.ApplyMessage(evm, msg, gaspool)
if err != nil {
statedb.RevertToSnapshot(snapshot)
@@ -366,21 +363,19 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
// Gather the execution-layer triggered requests.
var requests [][]byte
if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) {
- // EIP-6110 deposits
+ requests = [][]byte{}
+ // EIP-6110
var allLogs []*types.Log
for _, receipt := range receipts {
allLogs = append(allLogs, receipt.Logs...)
}
- depositRequests, err := core.ParseDepositLogs(allLogs, chainConfig)
- if err != nil {
+ if err := core.ParseDepositLogs(&requests, allLogs, chainConfig); err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
}
- requests = append(requests, depositRequests)
-
- // EIP-7002 withdrawals
- requests = append(requests, core.ProcessWithdrawalQueue(evm))
- // EIP-7251 consolidations
- requests = append(requests, core.ProcessConsolidationQueue(evm))
+ // EIP-7002
+ core.ProcessWithdrawalQueue(&requests, evm)
+ // EIP-7251
+ core.ProcessConsolidationQueue(&requests, evm)
}
// Commit block
diff --git a/cmd/evm/main.go b/cmd/evm/main.go
index 0d4471b8d5..6e0345ce63 100644
--- a/cmd/evm/main.go
+++ b/cmd/evm/main.go
@@ -19,11 +19,14 @@ package main
import (
"fmt"
- "math/big"
+ "io/fs"
"os"
- "slices"
+ "path/filepath"
"github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/tracing"
+ "github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/urfave/cli/v2"
@@ -33,122 +36,100 @@ import (
_ "github.com/ethereum/go-ethereum/eth/tracers/native"
)
+// Some other nice-to-haves:
+// * accumulate traces into an object to bundle with test
+// * write tx identifier for trace before hand (blocktest only)
+// * combine blocktest and statetest runner logic using unified test interface
+
+const traceCategory = "TRACING"
+
var (
- DebugFlag = &cli.BoolFlag{
- Name: "debug",
- Usage: "output full trace logs",
- Category: flags.VMCategory,
- }
- StatDumpFlag = &cli.BoolFlag{
- Name: "statdump",
- Usage: "displays stack and heap memory information",
- Category: flags.VMCategory,
- }
- CodeFlag = &cli.StringFlag{
- Name: "code",
- Usage: "EVM code",
- Category: flags.VMCategory,
- }
- CodeFileFlag = &cli.StringFlag{
- Name: "codefile",
- Usage: "File containing EVM code. If '-' is specified, code is read from stdin ",
- Category: flags.VMCategory,
- }
- GasFlag = &cli.Uint64Flag{
- Name: "gas",
- Usage: "gas limit for the evm",
- Value: 10000000000,
- Category: flags.VMCategory,
- }
- PriceFlag = &flags.BigFlag{
- Name: "price",
- Usage: "price set for the evm",
- Value: new(big.Int),
- Category: flags.VMCategory,
- }
- ValueFlag = &flags.BigFlag{
- Name: "value",
- Usage: "value set for the evm",
- Value: new(big.Int),
- Category: flags.VMCategory,
- }
- DumpFlag = &cli.BoolFlag{
- Name: "dump",
- Usage: "dumps the state after the run",
- Category: flags.VMCategory,
- }
- InputFlag = &cli.StringFlag{
- Name: "input",
- Usage: "input for the EVM",
- Category: flags.VMCategory,
- }
- InputFileFlag = &cli.StringFlag{
- Name: "inputfile",
- Usage: "file containing input for the EVM",
- Category: flags.VMCategory,
+ // Test running flags.
+ RunFlag = &cli.StringFlag{
+ Name: "run",
+ Value: ".*",
+ Usage: "Run only those tests matching the regular expression.",
}
BenchFlag = &cli.BoolFlag{
Name: "bench",
Usage: "benchmark the execution",
Category: flags.VMCategory,
}
- CreateFlag = &cli.BoolFlag{
- Name: "create",
- Usage: "indicates the action should be create rather than call",
- Category: flags.VMCategory,
+ WitnessCrossCheckFlag = &cli.BoolFlag{
+ Name: "cross-check",
+ Aliases: []string{"xc"},
+ Usage: "Cross-check stateful execution against stateless, verifying the witness generation.",
}
- GenesisFlag = &cli.StringFlag{
- Name: "prestate",
- Usage: "JSON file with prestate (genesis) config",
- Category: flags.VMCategory,
+
+ // Debugging flags.
+ DumpFlag = &cli.BoolFlag{
+ Name: "dump",
+ Usage: "dumps the state after the run",
+ }
+ HumanReadableFlag = &cli.BoolFlag{
+ Name: "human",
+ Usage: "\"Human-readable\" output",
+ }
+ StatDumpFlag = &cli.BoolFlag{
+ Name: "statdump",
+ Usage: "displays stack and heap memory information",
+ }
+
+ // Tracing flags.
+ TraceFlag = &cli.BoolFlag{
+ Name: "trace",
+ Usage: "Enable tracing and output trace log.",
+ Category: traceCategory,
+ }
+ TraceFormatFlag = &cli.StringFlag{
+ Name: "trace.format",
+ Usage: "Trace output format to use (struct|json)",
+ Value: "struct",
+ Category: traceCategory,
+ }
+ TraceDisableMemoryFlag = &cli.BoolFlag{
+ Name: "trace.nomemory",
+ Aliases: []string{"nomemory"},
+ Value: true,
+ Usage: "disable memory output",
+ Category: traceCategory,
+ }
+ TraceDisableStackFlag = &cli.BoolFlag{
+ Name: "trace.nostack",
+ Aliases: []string{"nostack"},
+ Usage: "disable stack output",
+ Category: traceCategory,
+ }
+ TraceDisableStorageFlag = &cli.BoolFlag{
+ Name: "trace.nostorage",
+ Aliases: []string{"nostorage"},
+ Usage: "disable storage output",
+ Category: traceCategory,
+ }
+ TraceDisableReturnDataFlag = &cli.BoolFlag{
+ Name: "trace.noreturndata",
+ Aliases: []string{"noreturndata"},
+ Value: true,
+ Usage: "enable return data output",
+ Category: traceCategory,
+ }
+
+ // Deprecated flags.
+ DebugFlag = &cli.BoolFlag{
+ Name: "debug",
+ Usage: "output full trace logs (deprecated)",
+ Hidden: true,
+ Category: traceCategory,
}
MachineFlag = &cli.BoolFlag{
Name: "json",
- Usage: "output trace logs in machine readable format (json)",
- Category: flags.VMCategory,
- }
- SenderFlag = &cli.StringFlag{
- Name: "sender",
- Usage: "The transaction origin",
- Category: flags.VMCategory,
- }
- ReceiverFlag = &cli.StringFlag{
- Name: "receiver",
- Usage: "The transaction receiver (execution context)",
- Category: flags.VMCategory,
- }
- DisableMemoryFlag = &cli.BoolFlag{
- Name: "nomemory",
- Value: true,
- Usage: "disable memory output",
- Category: flags.VMCategory,
- }
- DisableStackFlag = &cli.BoolFlag{
- Name: "nostack",
- Usage: "disable stack output",
- Category: flags.VMCategory,
- }
- DisableStorageFlag = &cli.BoolFlag{
- Name: "nostorage",
- Usage: "disable storage output",
- Category: flags.VMCategory,
- }
- DisableReturnDataFlag = &cli.BoolFlag{
- Name: "noreturndata",
- Value: true,
- Usage: "enable return data output",
- Category: flags.VMCategory,
- }
- refTestFlag = &cli.StringFlag{
- Name: "test",
- Usage: "Path to EOF validation reference test.",
- }
- hexFlag = &cli.StringFlag{
- Name: "hex",
- Usage: "single container data parse and validation",
+ Usage: "output trace logs in machine readable format, json (deprecated)",
+ Hidden: true,
+ Category: traceCategory,
}
)
+// Command definitions.
var (
stateTransitionCommand = &cli.Command{
Name: "transition",
@@ -175,7 +156,6 @@ var (
t8ntool.RewardFlag,
},
}
-
transactionCommand = &cli.Command{
Name: "transaction",
Aliases: []string{"t9n"},
@@ -203,62 +183,27 @@ var (
t8ntool.SealCliqueFlag,
},
}
- eofParseCommand = &cli.Command{
- Name: "eofparse",
- Aliases: []string{"eof"},
- Usage: "Parses hex eof container and returns validation errors (if any)",
- Action: eofParseAction,
- Flags: []cli.Flag{
- hexFlag,
- refTestFlag,
- },
- }
-
- eofDumpCommand = &cli.Command{
- Name: "eofdump",
- Usage: "Parses hex eof container and prints out human-readable representation of the container.",
- Action: eofDumpAction,
- Flags: []cli.Flag{
- hexFlag,
- },
- }
)
-// vmFlags contains flags related to running the EVM.
-var vmFlags = []cli.Flag{
- CodeFlag,
- CodeFileFlag,
- CreateFlag,
- GasFlag,
- PriceFlag,
- ValueFlag,
- InputFlag,
- InputFileFlag,
- GenesisFlag,
- SenderFlag,
- ReceiverFlag,
-}
-
// traceFlags contains flags that configure tracing output.
var traceFlags = []cli.Flag{
- BenchFlag,
+ TraceFlag,
+ TraceFormatFlag,
+ TraceDisableStackFlag,
+ TraceDisableMemoryFlag,
+ TraceDisableStorageFlag,
+ TraceDisableReturnDataFlag,
+
+ // deprecated
DebugFlag,
- DumpFlag,
MachineFlag,
- StatDumpFlag,
- DisableMemoryFlag,
- DisableStackFlag,
- DisableStorageFlag,
- DisableReturnDataFlag,
}
var app = flags.NewApp("the evm command line interface")
func init() {
- app.Flags = slices.Concat(vmFlags, traceFlags, debug.Flags)
+ app.Flags = debug.Flags
app.Commands = []*cli.Command{
- compileCommand,
- disasmCommand,
runCommand,
blockTestCommand,
stateTestCommand,
@@ -280,11 +225,56 @@ func init() {
func main() {
if err := app.Run(os.Args); err != nil {
- code := 1
- if ec, ok := err.(*t8ntool.NumberedError); ok {
- code = ec.ExitCode()
- }
fmt.Fprintln(os.Stderr, err)
- os.Exit(code)
+ os.Exit(1)
}
}
+
+// tracerFromFlags parses the cli flags and returns the specified tracer.
+func tracerFromFlags(ctx *cli.Context) *tracing.Hooks {
+ config := &logger.Config{
+ EnableMemory: !ctx.Bool(TraceDisableMemoryFlag.Name),
+ DisableStack: ctx.Bool(TraceDisableStackFlag.Name),
+ DisableStorage: ctx.Bool(TraceDisableStorageFlag.Name),
+ EnableReturnData: !ctx.Bool(TraceDisableReturnDataFlag.Name),
+ }
+ switch {
+ case ctx.Bool(TraceFlag.Name) && ctx.String(TraceFormatFlag.Name) == "struct":
+ return logger.NewStreamingStructLogger(config, os.Stderr).Hooks()
+ case ctx.Bool(TraceFlag.Name) && ctx.String(TraceFormatFlag.Name) == "json":
+ return logger.NewJSONLogger(config, os.Stderr)
+ case ctx.Bool(MachineFlag.Name):
+ return logger.NewJSONLogger(config, os.Stderr)
+ case ctx.Bool(DebugFlag.Name):
+ return logger.NewStreamingStructLogger(config, os.Stderr).Hooks()
+ default:
+ return nil
+ }
+}
+
+// collectJSONFiles walks the given path and accumulates all files with json
+// extension.
+func collectJSONFiles(path string) []string {
+ var out []string
+ err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if !info.IsDir() && filepath.Ext(info.Name()) == ".json" {
+ out = append(out, path)
+ }
+ return nil
+ })
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ }
+ return out
+}
+
+// dump returns a state dump for the most current trie.
+func dump(s *state.StateDB) *state.Dump {
+ root := s.IntermediateRoot(false)
+ cpy, _ := state.New(root, s.Database())
+ dump := cpy.RawDump(nil)
+ return &dump
+}
diff --git a/cmd/evm/reporter.go b/cmd/evm/reporter.go
new file mode 100644
index 0000000000..f6249e1843
--- /dev/null
+++ b/cmd/evm/reporter.go
@@ -0,0 +1,87 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
+
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/urfave/cli/v2"
+)
+
+const (
+ PASS = "\033[32mPASS\033[0m"
+ FAIL = "\033[31mFAIL\033[0m"
+)
+
+// testResult contains the execution status after running a state test, any
+// error that might have occurred and a dump of the final state if requested.
+type testResult struct {
+ Name string `json:"name"`
+ Pass bool `json:"pass"`
+ Root *common.Hash `json:"stateRoot,omitempty"`
+ Fork string `json:"fork"`
+ Error string `json:"error,omitempty"`
+ State *state.Dump `json:"state,omitempty"`
+ Stats *execStats `json:"benchStats,omitempty"`
+}
+
+func (r testResult) String() string {
+ var status string
+ if r.Pass {
+ status = fmt.Sprintf("[%s]", PASS)
+ } else {
+ status = fmt.Sprintf("[%s]", FAIL)
+ }
+ info := r.Name
+ m := parseTestMetadata(r.Name)
+ if m != nil {
+ info = fmt.Sprintf("%s %s, param=%s", m.module, m.function, m.parameters)
+ }
+ var extra string
+ if !r.Pass {
+ extra = fmt.Sprintf(", err=%v, fork=%s", r.Error, r.Fork)
+ }
+ out := fmt.Sprintf("%s %s%s", status, info, extra)
+ if r.State != nil {
+ state, _ := json.MarshalIndent(r.State, "", " ")
+ out += "\n" + string(state)
+ }
+ return out
+}
+
+// report prints the after-test summary.
+func report(ctx *cli.Context, results []testResult) {
+ if ctx.Bool(HumanReadableFlag.Name) {
+ pass := 0
+ for _, r := range results {
+ if r.Pass {
+ pass++
+ }
+ }
+ for _, r := range results {
+ fmt.Println(r)
+ }
+ fmt.Println("--")
+ fmt.Printf("%d tests passed, %d tests failed.\n", pass, len(results)-pass)
+ return
+ }
+ out, _ := json.MarshalIndent(results, "", " ")
+ fmt.Println(string(out))
+}
diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go
index 2884487faa..59ad8687d5 100644
--- a/cmd/evm/runner.go
+++ b/cmd/evm/runner.go
@@ -18,6 +18,7 @@ package main
import (
"bytes"
+ "encoding/hex"
"encoding/json"
"fmt"
"io"
@@ -25,16 +26,17 @@ import (
"os"
goruntime "runtime"
"slices"
+ "strings"
"testing"
"time"
- "github.com/ethereum/go-ethereum/cmd/evm/internal/compiler"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"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/core/vm/runtime"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
@@ -51,14 +53,82 @@ var runCommand = &cli.Command{
Usage: "Run arbitrary evm binary",
ArgsUsage: "",
Description: `The run command runs arbitrary EVM code.`,
- Flags: slices.Concat(vmFlags, traceFlags),
+ Flags: slices.Concat([]cli.Flag{
+ BenchFlag,
+ CodeFileFlag,
+ CreateFlag,
+ GasFlag,
+ GenesisFlag,
+ InputFlag,
+ InputFileFlag,
+ PriceFlag,
+ ReceiverFlag,
+ SenderFlag,
+ ValueFlag,
+ StatDumpFlag,
+ }, traceFlags),
}
+var (
+ CodeFileFlag = &cli.StringFlag{
+ Name: "codefile",
+ Usage: "File containing EVM code. If '-' is specified, code is read from stdin ",
+ Category: flags.VMCategory,
+ }
+ CreateFlag = &cli.BoolFlag{
+ Name: "create",
+ Usage: "Indicates the action should be create rather than call",
+ Category: flags.VMCategory,
+ }
+ GasFlag = &cli.Uint64Flag{
+ Name: "gas",
+ Usage: "Gas limit for the evm",
+ Value: 10000000000,
+ Category: flags.VMCategory,
+ }
+ GenesisFlag = &cli.StringFlag{
+ Name: "prestate",
+ Usage: "JSON file with prestate (genesis) config",
+ Category: flags.VMCategory,
+ }
+ InputFlag = &cli.StringFlag{
+ Name: "input",
+ Usage: "Input for the EVM",
+ Category: flags.VMCategory,
+ }
+ InputFileFlag = &cli.StringFlag{
+ Name: "inputfile",
+ Usage: "File containing input for the EVM",
+ Category: flags.VMCategory,
+ }
+ PriceFlag = &flags.BigFlag{
+ Name: "price",
+ Usage: "Price set for the evm",
+ Value: new(big.Int),
+ Category: flags.VMCategory,
+ }
+ ReceiverFlag = &cli.StringFlag{
+ Name: "receiver",
+ Usage: "The transaction receiver (execution context)",
+ Category: flags.VMCategory,
+ }
+ SenderFlag = &cli.StringFlag{
+ Name: "sender",
+ Usage: "The transaction origin",
+ Category: flags.VMCategory,
+ }
+ ValueFlag = &flags.BigFlag{
+ Name: "value",
+ Usage: "Value set for the evm",
+ Value: new(big.Int),
+ Category: flags.VMCategory,
+ }
+)
+
// readGenesis will read the given JSON format genesis file and return
// the initialized Genesis structure
func readGenesis(genesisPath string) *core.Genesis {
// Make sure we have a valid genesis JSON
- //genesisPath := ctx.Args().First()
if len(genesisPath) == 0 {
utils.Fatalf("Must supply path to genesis JSON file")
}
@@ -128,16 +198,15 @@ func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) ([]byte, exe
func runCmd(ctx *cli.Context) error {
logconfig := &logger.Config{
- EnableMemory: !ctx.Bool(DisableMemoryFlag.Name),
- DisableStack: ctx.Bool(DisableStackFlag.Name),
- DisableStorage: ctx.Bool(DisableStorageFlag.Name),
- EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name),
+ EnableMemory: !ctx.Bool(TraceDisableMemoryFlag.Name),
+ DisableStack: ctx.Bool(TraceDisableStackFlag.Name),
+ DisableStorage: ctx.Bool(TraceDisableStorageFlag.Name),
+ EnableReturnData: !ctx.Bool(TraceDisableReturnDataFlag.Name),
Debug: ctx.Bool(DebugFlag.Name),
}
var (
tracer *tracing.Hooks
- debugLogger *logger.StructLogger
prestate *state.StateDB
chainConfig *params.ChainConfig
sender = common.BytesToAddress([]byte("sender"))
@@ -149,10 +218,7 @@ func runCmd(ctx *cli.Context) error {
if ctx.Bool(MachineFlag.Name) {
tracer = logger.NewJSONLogger(logconfig, os.Stdout)
} else if ctx.Bool(DebugFlag.Name) {
- debugLogger = logger.NewStructLogger(logconfig)
- tracer = debugLogger.Hooks()
- } else {
- debugLogger = logger.NewStructLogger(logconfig)
+ tracer = logger.NewStreamingStructLogger(logconfig, os.Stderr).Hooks()
}
initialGas := ctx.Uint64(GasFlag.Name)
@@ -188,48 +254,35 @@ func runCmd(ctx *cli.Context) error {
var code []byte
codeFileFlag := ctx.String(CodeFileFlag.Name)
- codeFlag := ctx.String(CodeFlag.Name)
+ hexcode := ctx.Args().First()
- // The '--code' or '--codefile' flag overrides code in state
- if codeFileFlag != "" || codeFlag != "" {
- var hexcode []byte
- if codeFileFlag != "" {
- var err error
- // If - is specified, it means that code comes from stdin
- if codeFileFlag == "-" {
- //Try reading from stdin
- if hexcode, err = io.ReadAll(os.Stdin); err != nil {
- fmt.Printf("Could not load code from stdin: %v\n", err)
- os.Exit(1)
- }
- } else {
- // Codefile with hex assembly
- if hexcode, err = os.ReadFile(codeFileFlag); err != nil {
- fmt.Printf("Could not load code from file: %v\n", err)
- os.Exit(1)
- }
- }
- } else {
- hexcode = []byte(codeFlag)
- }
- hexcode = bytes.TrimSpace(hexcode)
- if len(hexcode)%2 != 0 {
- fmt.Printf("Invalid input length for hex data (%d)\n", len(hexcode))
+ // The '--codefile' flag overrides code in state
+ if codeFileFlag == "-" {
+ // If - is specified, it means that code comes from stdin
+ // Try reading from stdin
+ input, err := io.ReadAll(os.Stdin)
+ if err != nil {
+ fmt.Printf("Could not load code from stdin: %v\n", err)
os.Exit(1)
}
- code = common.FromHex(string(hexcode))
- } else if fn := ctx.Args().First(); len(fn) > 0 {
- // EASM-file to compile
- src, err := os.ReadFile(fn)
+ hexcode = string(input)
+ } else if codeFileFlag != "" {
+ // Codefile with hex assembly
+ input, err := os.ReadFile(codeFileFlag)
if err != nil {
- return err
+ fmt.Printf("Could not load code from file: %v\n", err)
+ os.Exit(1)
}
- bin, err := compiler.Compile(fn, src, false)
- if err != nil {
- return err
- }
- code = common.Hex2Bytes(bin)
+ hexcode = string(input)
}
+
+ hexcode = strings.TrimSpace(hexcode)
+ if len(hexcode)%2 != 0 {
+ fmt.Printf("Invalid input length for hex data (%d)\n", len(hexcode))
+ os.Exit(1)
+ }
+ code = common.FromHex(hexcode)
+
runtimeConfig := runtime.Config{
Origin: sender,
State: prestate,
@@ -310,12 +363,10 @@ func runCmd(ctx *cli.Context) error {
}
if ctx.Bool(DebugFlag.Name) {
- if debugLogger != nil {
- fmt.Fprintln(os.Stderr, "#### TRACE ####")
- logger.WriteTrace(os.Stderr, debugLogger.StructLogs())
+ if logs := runtimeConfig.State.Logs(); len(logs) > 0 {
+ fmt.Fprintln(os.Stderr, "### LOGS")
+ writeLogs(os.Stderr, logs)
}
- fmt.Fprintln(os.Stderr, "#### LOGS ####")
- logger.WriteLogs(os.Stderr, runtimeConfig.State.Logs())
}
if bench || ctx.Bool(StatDumpFlag.Name) {
@@ -334,3 +385,16 @@ allocated bytes: %d
return nil
}
+
+// writeLogs writes vm logs in a readable format to the given writer
+func writeLogs(writer io.Writer, logs []*types.Log) {
+ for _, log := range logs {
+ fmt.Fprintf(writer, "LOG%d: %x bn=%d txi=%x\n", len(log.Topics), log.Address, log.BlockNumber, log.TxIndex)
+
+ for i, topic := range log.Topics {
+ fmt.Fprintf(writer, "%08d %x\n", i, topic)
+ }
+ fmt.Fprint(writer, hex.Dump(log.Data))
+ fmt.Fprintln(writer)
+ }
+}
diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go
index d0a0d3287c..323b7d60ab 100644
--- a/cmd/evm/staterunner.go
+++ b/cmd/evm/staterunner.go
@@ -21,12 +21,12 @@ import (
"encoding/json"
"fmt"
"os"
+ "regexp"
+ "slices"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/tests"
"github.com/urfave/cli/v2"
@@ -35,157 +35,124 @@ import (
var (
forkFlag = &cli.StringFlag{
Name: "statetest.fork",
- Usage: "The hard-fork to run the test against",
+ Usage: "Only run tests for the specified fork.",
Category: flags.VMCategory,
}
idxFlag = &cli.IntFlag{
Name: "statetest.index",
- Usage: "The index of the subtest to run",
+ Usage: "The index of the subtest to run.",
Category: flags.VMCategory,
Value: -1, // default to select all subtest indices
}
- testNameFlag = &cli.StringFlag{
- Name: "statetest.name",
- Usage: "The name of the state test to run",
- Category: flags.VMCategory,
- }
)
var stateTestCommand = &cli.Command{
Action: stateTestCmd,
Name: "statetest",
Usage: "Executes the given state tests. Filenames can be fed via standard input (batch mode) or as an argument (one-off execution).",
ArgsUsage: "",
- Flags: []cli.Flag{
- forkFlag,
- idxFlag,
- testNameFlag,
- },
-}
-
-// StatetestResult contains the execution status after running a state test, any
-// error that might have occurred and a dump of the final state if requested.
-type StatetestResult struct {
- Name string `json:"name"`
- Pass bool `json:"pass"`
- Root *common.Hash `json:"stateRoot,omitempty"`
- Fork string `json:"fork"`
- Error string `json:"error,omitempty"`
- State *state.Dump `json:"state,omitempty"`
- BenchStats *execStats `json:"benchStats,omitempty"`
+ Flags: slices.Concat([]cli.Flag{
+ DumpFlag,
+ HumanReadableFlag,
+ RunFlag,
+ }, traceFlags),
}
func stateTestCmd(ctx *cli.Context) error {
- // Configure the EVM logger
- config := &logger.Config{
- EnableMemory: !ctx.Bool(DisableMemoryFlag.Name),
- DisableStack: ctx.Bool(DisableStackFlag.Name),
- DisableStorage: ctx.Bool(DisableStorageFlag.Name),
- EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name),
- }
- var cfg vm.Config
- switch {
- case ctx.Bool(MachineFlag.Name):
- cfg.Tracer = logger.NewJSONLogger(config, os.Stderr)
+ path := ctx.Args().First()
- case ctx.Bool(DebugFlag.Name):
- cfg.Tracer = logger.NewStructLogger(config).Hooks()
+ // If path is provided, run the tests at that path.
+ if len(path) != 0 {
+ var (
+ collected = collectJSONFiles(path)
+ results []testResult
+ )
+ for _, fname := range collected {
+ r, err := runStateTest(ctx, fname)
+ if err != nil {
+ return err
+ }
+ results = append(results, r...)
+ }
+ report(ctx, results)
+ return nil
}
- // Load the test content from the input file
- if len(ctx.Args().First()) != 0 {
- return runStateTest(ctx, ctx.Args().First(), cfg, ctx.Bool(DumpFlag.Name), ctx.Bool(BenchFlag.Name))
- }
- // Read filenames from stdin and execute back-to-back
+ // Otherwise, read filenames from stdin and execute back-to-back.
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
fname := scanner.Text()
if len(fname) == 0 {
return nil
}
- if err := runStateTest(ctx, fname, cfg, ctx.Bool(DumpFlag.Name), ctx.Bool(BenchFlag.Name)); err != nil {
+ results, err := runStateTest(ctx, fname)
+ if err != nil {
return err
}
+ report(ctx, results)
}
return nil
}
-type stateTestCase struct {
- name string
- test tests.StateTest
- st tests.StateSubtest
-}
-
-// collectMatchedSubtests returns test cases which match against provided filtering CLI parameters
-func collectMatchedSubtests(ctx *cli.Context, testsByName map[string]tests.StateTest) []stateTestCase {
- var res []stateTestCase
- subtestName := ctx.String(testNameFlag.Name)
- if subtestName != "" {
- if subtest, ok := testsByName[subtestName]; ok {
- testsByName := make(map[string]tests.StateTest)
- testsByName[subtestName] = subtest
- }
- }
- idx := ctx.Int(idxFlag.Name)
- fork := ctx.String(forkFlag.Name)
-
- for key, test := range testsByName {
- for _, st := range test.Subtests() {
- if idx != -1 && st.Index != idx {
- continue
- }
- if fork != "" && st.Fork != fork {
- continue
- }
- res = append(res, stateTestCase{name: key, st: st, test: test})
- }
- }
- return res
-}
-
// runStateTest loads the state-test given by fname, and executes the test.
-func runStateTest(ctx *cli.Context, fname string, cfg vm.Config, dump bool, bench bool) error {
+func runStateTest(ctx *cli.Context, fname string) ([]testResult, error) {
src, err := os.ReadFile(fname)
if err != nil {
- return err
+ return nil, err
}
var testsByName map[string]tests.StateTest
if err := json.Unmarshal(src, &testsByName); err != nil {
- return err
+ return nil, fmt.Errorf("unable to read test file %s: %w", fname, err)
}
- matchingTests := collectMatchedSubtests(ctx, testsByName)
+ cfg := vm.Config{Tracer: tracerFromFlags(ctx)}
+ re, err := regexp.Compile(ctx.String(RunFlag.Name))
+ if err != nil {
+ return nil, fmt.Errorf("invalid regex -%s: %v", RunFlag.Name, err)
+ }
// Iterate over all the tests, run them and aggregate the results
- var results []StatetestResult
- for _, test := range matchingTests {
- // Run the test and aggregate the result
- result := &StatetestResult{Name: test.name, Fork: test.st.Fork, Pass: true}
- test.test.Run(test.st, cfg, false, rawdb.HashScheme, func(err error, tstate *tests.StateTestState) {
- var root common.Hash
- if tstate.StateDB != nil {
- root = tstate.StateDB.IntermediateRoot(false)
- result.Root = &root
- fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root)
- if dump { // Dump any state to aid debugging
- cpy, _ := state.New(root, tstate.StateDB.Database())
- dump := cpy.RawDump(nil)
- result.State = &dump
- }
- }
- if err != nil {
- // Test failed, mark as so
- result.Pass, result.Error = false, err.Error()
- }
- })
- if bench {
- _, stats, _ := timedExec(true, func() ([]byte, uint64, error) {
- _, _, gasUsed, _ := test.test.RunNoVerify(test.st, cfg, false, rawdb.HashScheme)
- return nil, gasUsed, nil
- })
- result.BenchStats = &stats
+ results := make([]testResult, 0, len(testsByName))
+ for key, test := range testsByName {
+ if !re.MatchString(key) {
+ continue
+ }
+ for i, st := range test.Subtests() {
+ if idx := ctx.Int(idxFlag.Name); idx != -1 && idx != i {
+ // If specific index requested, skip all tests that do not match.
+ continue
+ }
+ if fork := ctx.String(forkFlag.Name); fork != "" && st.Fork != fork {
+ // If specific fork requested, skip all tests that do not match.
+ continue
+ }
+ // Run the test and aggregate the result
+ result := &testResult{Name: key, Fork: st.Fork, Pass: true}
+ test.Run(st, cfg, false, rawdb.HashScheme, func(err error, state *tests.StateTestState) {
+ var root common.Hash
+ if state.StateDB != nil {
+ root = state.StateDB.IntermediateRoot(false)
+ result.Root = &root
+ fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root)
+ // Dump any state to aid debugging.
+ if ctx.Bool(DumpFlag.Name) {
+ result.State = dump(state.StateDB)
+ }
+ }
+ // Collect bench stats if requested.
+ if ctx.Bool(BenchFlag.Name) {
+ _, stats, _ := timedExec(true, func() ([]byte, uint64, error) {
+ _, _, gasUsed, _ := test.RunNoVerify(st, cfg, false, rawdb.HashScheme)
+ return nil, gasUsed, nil
+ })
+ result.Stats = &stats
+ }
+ if err != nil {
+ // Test failed, mark as so.
+ result.Pass, result.Error = false, err.Error()
+ return
+ }
+ })
+ results = append(results, *result)
}
- results = append(results, *result)
}
- out, _ := json.MarshalIndent(results, "", " ")
- fmt.Println(string(out))
- return nil
+ return results, nil
}
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 17773e4fa9..5c63e8c94d 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -50,7 +50,6 @@ import (
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/catalyst"
- "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/eth/gasprice"
@@ -1687,7 +1686,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
godebug.SetGCPercent(int(gogc))
if ctx.IsSet(SyncTargetFlag.Name) {
- cfg.SyncMode = downloader.FullSync // dev sync target forces full sync
+ cfg.SyncMode = ethconfig.FullSync // dev sync target forces full sync
} else if ctx.IsSet(SyncModeFlag.Name) {
if err = cfg.SyncMode.UnmarshalText([]byte(ctx.String(SyncModeFlag.Name))); err != nil {
Fatalf("invalid --syncmode flag: %v", err)
@@ -1758,7 +1757,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
}
if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 {
// If snap-sync is requested, this flag is also required
- if cfg.SyncMode == downloader.SnapSync {
+ if cfg.SyncMode == ethconfig.SnapSync {
if !ctx.Bool(SnapshotFlag.Name) {
log.Warn("Snap sync requested, enabling --snapshot")
}
@@ -1824,7 +1823,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if !ctx.IsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 1337
}
- cfg.SyncMode = downloader.FullSync
+ cfg.SyncMode = ethconfig.FullSync
// Create new developer account or reuse existing one
var (
developer accounts.Account
diff --git a/core/block_validator.go b/core/block_validator.go
index 59783a0407..5885df9ee2 100644
--- a/core/block_validator.go
+++ b/core/block_validator.go
@@ -93,7 +93,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
}
// The individual checks for blob validity (version-check + not empty)
- // happens in StateTransition.
+ // happens in state transition.
}
// Check blob gas usage.
diff --git a/core/blockchain.go b/core/blockchain.go
index c3da61b281..0fe4812626 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -1926,7 +1926,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
task := types.NewBlockWithHeader(context).WithBody(*block.Body())
// Run the stateless self-cross-validation
- crossStateRoot, crossReceiptRoot, err := ExecuteStateless(bc.chainConfig, task, witness)
+ crossStateRoot, crossReceiptRoot, err := ExecuteStateless(bc.chainConfig, bc.vmConfig, task, witness)
if err != nil {
return nil, fmt.Errorf("stateless self-validation failed: %v", err)
}
diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go
index 8f1da82a2b..cf72e98cb2 100644
--- a/core/blockchain_reader.go
+++ b/core/blockchain_reader.go
@@ -344,10 +344,7 @@ func (bc *BlockChain) stateRecoverable(root common.Hash) bool {
// ContractCodeWithPrefix retrieves a blob of data associated with a contract
// hash either from ephemeral in-memory cache, or from persistent storage.
-//
-// If the code doesn't exist in the in-memory cache, check the storage with
-// new code scheme.
-func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) ([]byte, error) {
+func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) []byte {
// TODO(rjl493456442) The associated account address is also required
// in Verkle scheme. Fix it once snap-sync is supported for Verkle.
return bc.statedb.ContractCodeWithPrefix(common.Address{}, hash)
diff --git a/core/chain_makers.go b/core/chain_makers.go
index e679a9e557..97c785d52b 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -349,25 +349,22 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
var requests [][]byte
if config.IsPrague(b.header.Number, b.header.Time) {
+ requests = [][]byte{}
// EIP-6110 deposits
var blockLogs []*types.Log
for _, r := range b.receipts {
blockLogs = append(blockLogs, r.Logs...)
}
- depositRequests, err := ParseDepositLogs(blockLogs, config)
- if err != nil {
+ if err := ParseDepositLogs(&requests, blockLogs, config); err != nil {
panic(fmt.Sprintf("failed to parse deposit log: %v", err))
}
- requests = append(requests, depositRequests)
// create EVM for system calls
blockContext := NewEVMBlockContext(b.header, cm, &b.header.Coinbase)
evm := vm.NewEVM(blockContext, statedb, cm.config, vm.Config{})
- // EIP-7002 withdrawals
- withdrawalRequests := ProcessWithdrawalQueue(evm)
- requests = append(requests, withdrawalRequests)
- // EIP-7251 consolidations
- consolidationRequests := ProcessConsolidationQueue(evm)
- requests = append(requests, consolidationRequests)
+ // EIP-7002
+ ProcessWithdrawalQueue(&requests, evm)
+ // EIP-7251
+ ProcessConsolidationQueue(&requests, evm)
}
if requests != nil {
reqHash := types.CalcRequestsHash(requests)
diff --git a/core/genesis.go b/core/genesis.go
index eff92084eb..85ef049ba6 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -472,9 +472,7 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
}
}
if conf.IsPrague(num, g.Timestamp) {
- emptyRequests := [][]byte{{0x00}, {0x01}, {0x02}}
- rhash := types.CalcRequestsHash(emptyRequests)
- head.RequestsHash = &rhash
+ head.RequestsHash = &types.EmptyRequestsHash
}
}
return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil))
diff --git a/core/genesis_test.go b/core/genesis_test.go
index 9eacf2024c..3ec87474e5 100644
--- a/core/genesis_test.go
+++ b/core/genesis_test.go
@@ -293,7 +293,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
},
}
- expected := common.FromHex("4a83dc39eb688dbcfaf581d60e82de18f875e38786ebce5833342011d6fef37b")
+ expected := common.FromHex("018d20eebb130b5e2b796465fe36aafab650650729a92435aec071bf2386f080")
got := genesis.ToBlock().Root().Bytes()
if !bytes.Equal(got, expected) {
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
diff --git a/core/state/database.go b/core/state/database.go
index 0d8acec35a..fff7f1519f 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -17,7 +17,6 @@
package state
import (
- "errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
@@ -55,12 +54,6 @@ type Database interface {
// OpenStorageTrie opens the storage trie of an account.
OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie Trie) (Trie, error)
- // ContractCode retrieves a particular contract's code.
- ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error)
-
- // ContractCodeSize retrieves a particular contracts code's size.
- ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error)
-
// PointCache returns the cache holding points used in verkle tree key computation
PointCache() *utils.PointCache
@@ -180,7 +173,7 @@ func NewDatabaseForTesting() *CachingDB {
// Reader returns a state reader associated with the specified state root.
func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
- var readers []Reader
+ var readers []StateReader
// Set up the state snapshot reader if available. This feature
// is optional and may be partially useful if it's not fully
@@ -188,7 +181,7 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
if db.snap != nil {
snap := db.snap.Snapshot(stateRoot)
if snap != nil {
- readers = append(readers, newStateReader(snap)) // snap reader is optional
+ readers = append(readers, newFlatReader(snap))
}
}
// Set up the trie reader, which is expected to always be available
@@ -199,7 +192,11 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
}
readers = append(readers, tr)
- return newMultiReader(readers...)
+ combined, err := newMultiStateReader(readers...)
+ if err != nil {
+ return nil, err
+ }
+ return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil
}
// OpenTrie opens the main account trie at a specific root hash.
@@ -229,45 +226,20 @@ func (db *CachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Addre
return tr, nil
}
-// ContractCode retrieves a particular contract's code.
-func (db *CachingDB) ContractCode(address common.Address, codeHash common.Hash) ([]byte, error) {
- code, _ := db.codeCache.Get(codeHash)
- if len(code) > 0 {
- return code, nil
- }
- code = rawdb.ReadCode(db.disk, codeHash)
- if len(code) > 0 {
- db.codeCache.Add(codeHash, code)
- db.codeSizeCache.Add(codeHash, len(code))
- return code, nil
- }
- return nil, errors.New("not found")
-}
-
// ContractCodeWithPrefix retrieves a particular contract's code. If the
// code can't be found in the cache, then check the existence with **new**
// db scheme.
-func (db *CachingDB) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) ([]byte, error) {
+func (db *CachingDB) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) []byte {
code, _ := db.codeCache.Get(codeHash)
if len(code) > 0 {
- return code, nil
+ return code
}
code = rawdb.ReadCodeWithPrefix(db.disk, codeHash)
if len(code) > 0 {
db.codeCache.Add(codeHash, code)
db.codeSizeCache.Add(codeHash, len(code))
- return code, nil
}
- return nil, errors.New("not found")
-}
-
-// ContractCodeSize retrieves a particular contracts code's size.
-func (db *CachingDB) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) {
- if cached, ok := db.codeSizeCache.Get(codeHash); ok {
- return cached, nil
- }
- code, err := db.ContractCode(addr, codeHash)
- return len(code), err
+ return code
}
// TrieDB retrieves any intermediate trie-node caching layer.
diff --git a/core/state/iterator.go b/core/state/iterator.go
index 83c552ca1a..5ea52c6183 100644
--- a/core/state/iterator.go
+++ b/core/state/iterator.go
@@ -136,10 +136,13 @@ func (it *nodeIterator) step() error {
}
if !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
it.codeHash = common.BytesToHash(account.CodeHash)
- it.code, err = it.state.db.ContractCode(address, common.BytesToHash(account.CodeHash))
+ it.code, err = it.state.reader.Code(address, common.BytesToHash(account.CodeHash))
if err != nil {
return fmt.Errorf("code %x: %v", account.CodeHash, err)
}
+ if len(it.code) == 0 {
+ return fmt.Errorf("code is not found: %x", account.CodeHash)
+ }
}
it.accountHash = it.stateIt.Parent()
return nil
diff --git a/core/state/reader.go b/core/state/reader.go
index 85842adde8..a0f15dfcc8 100644
--- a/core/state/reader.go
+++ b/core/state/reader.go
@@ -18,11 +18,13 @@ package state
import (
"errors"
- "maps"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/lru"
+ "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/utils"
@@ -30,9 +32,26 @@ import (
"github.com/ethereum/go-ethereum/triedb/database"
)
-// Reader defines the interface for accessing accounts and storage slots
+// ContractCodeReader defines the interface for accessing contract code.
+type ContractCodeReader interface {
+ // Code retrieves a particular contract's code.
+ //
+ // - Returns nil code along with nil error if the requested contract code
+ // doesn't exist
+ // - Returns an error only if an unexpected issue occurs
+ Code(addr common.Address, codeHash common.Hash) ([]byte, error)
+
+ // CodeSize retrieves a particular contracts code's size.
+ //
+ // - Returns zero code size along with nil error if the requested contract code
+ // doesn't exist
+ // - Returns an error only if an unexpected issue occurs
+ CodeSize(addr common.Address, codeHash common.Hash) (int, error)
+}
+
+// StateReader defines the interface for accessing accounts and storage slots
// associated with a specific state.
-type Reader interface {
+type StateReader interface {
// Account retrieves the account associated with a particular address.
//
// - Returns a nil account if it does not exist
@@ -47,32 +66,84 @@ type Reader interface {
// - Returns an error only if an unexpected issue occurs
// - The returned storage slot is safe to modify after the call
Storage(addr common.Address, slot common.Hash) (common.Hash, error)
-
- // Copy returns a deep-copied state reader.
- Copy() Reader
}
-// stateReader wraps a database state reader.
-type stateReader struct {
+// Reader defines the interface for accessing accounts, storage slots and contract
+// code associated with a specific state.
+type Reader interface {
+ ContractCodeReader
+ StateReader
+}
+
+// cachingCodeReader implements ContractCodeReader, accessing contract code either in
+// local key-value store or the shared code cache.
+type cachingCodeReader struct {
+ db ethdb.KeyValueReader
+
+ // These caches could be shared by multiple code reader instances,
+ // they are natively thread-safe.
+ codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
+ codeSizeCache *lru.Cache[common.Hash, int]
+}
+
+// newCachingCodeReader constructs the code reader.
+func newCachingCodeReader(db ethdb.KeyValueReader, codeCache *lru.SizeConstrainedCache[common.Hash, []byte], codeSizeCache *lru.Cache[common.Hash, int]) *cachingCodeReader {
+ return &cachingCodeReader{
+ db: db,
+ codeCache: codeCache,
+ codeSizeCache: codeSizeCache,
+ }
+}
+
+// Code implements ContractCodeReader, retrieving a particular contract's code.
+// If the contract code doesn't exist, no error will be returned.
+func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]byte, error) {
+ code, _ := r.codeCache.Get(codeHash)
+ if len(code) > 0 {
+ return code, nil
+ }
+ code = rawdb.ReadCode(r.db, codeHash)
+ if len(code) > 0 {
+ r.codeCache.Add(codeHash, code)
+ r.codeSizeCache.Add(codeHash, len(code))
+ }
+ return code, nil
+}
+
+// CodeSize implements ContractCodeReader, retrieving a particular contracts code's size.
+// If the contract code doesn't exist, no error will be returned.
+func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
+ if cached, ok := r.codeSizeCache.Get(codeHash); ok {
+ return cached, nil
+ }
+ code, err := r.Code(addr, codeHash)
+ if err != nil {
+ return 0, err
+ }
+ return len(code), nil
+}
+
+// flatReader wraps a database state reader.
+type flatReader struct {
reader database.StateReader
buff crypto.KeccakState
}
-// newStateReader constructs a state reader with on the given state root.
-func newStateReader(reader database.StateReader) *stateReader {
- return &stateReader{
+// newFlatReader constructs a state reader with on the given state root.
+func newFlatReader(reader database.StateReader) *flatReader {
+ return &flatReader{
reader: reader,
buff: crypto.NewKeccakState(),
}
}
-// Account implements Reader, retrieving the account specified by the address.
+// Account implements StateReader, retrieving the account specified by the address.
//
// An error will be returned if the associated snapshot is already stale or
// the requested account is not yet covered by the snapshot.
//
// The returned account might be nil if it's not existent.
-func (r *stateReader) Account(addr common.Address) (*types.StateAccount, error) {
+func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
account, err := r.reader.Account(crypto.HashData(r.buff, addr.Bytes()))
if err != nil {
return nil, err
@@ -95,14 +166,14 @@ func (r *stateReader) Account(addr common.Address) (*types.StateAccount, error)
return acct, nil
}
-// Storage implements Reader, retrieving the storage slot specified by the
+// Storage implements StateReader, retrieving the storage slot specified by the
// address and slot key.
//
// An error will be returned if the associated snapshot is already stale or
// the requested storage slot is not yet covered by the snapshot.
//
// The returned storage slot might be empty if it's not existent.
-func (r *stateReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
+func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
addrHash := crypto.HashData(r.buff, addr.Bytes())
slotHash := crypto.HashData(r.buff, key.Bytes())
ret, err := r.reader.Storage(addrHash, slotHash)
@@ -123,15 +194,7 @@ func (r *stateReader) Storage(addr common.Address, key common.Hash) (common.Hash
return value, nil
}
-// Copy implements Reader, returning a deep-copied snap reader.
-func (r *stateReader) Copy() Reader {
- return &stateReader{
- reader: r.reader,
- buff: crypto.NewKeccakState(),
- }
-}
-
-// trieReader implements the Reader interface, providing functions to access
+// trieReader implements the StateReader interface, providing functions to access
// state from the referenced trie.
type trieReader struct {
root common.Hash // State root which uniquely represent a state
@@ -167,7 +230,7 @@ func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCach
}, nil
}
-// Account implements Reader, retrieving the account specified by the address.
+// Account implements StateReader, retrieving the account specified by the address.
//
// An error will be returned if the trie state is corrupted. An nil account
// will be returned if it's not existent in the trie.
@@ -184,7 +247,7 @@ func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
return account, nil
}
-// Storage implements Reader, retrieving the storage slot specified by the
+// Storage implements StateReader, retrieving the storage slot specified by the
// address and slot key.
//
// An error will be returned if the trie state is corrupted. An empty storage
@@ -227,48 +290,32 @@ func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash,
return value, nil
}
-// Copy implements Reader, returning a deep-copied trie reader.
-func (r *trieReader) Copy() Reader {
- tries := make(map[common.Address]Trie)
- for addr, tr := range r.subTries {
- tries[addr] = mustCopyTrie(tr)
- }
- return &trieReader{
- root: r.root,
- db: r.db,
- buff: crypto.NewKeccakState(),
- mainTrie: mustCopyTrie(r.mainTrie),
- subRoots: maps.Clone(r.subRoots),
- subTries: tries,
- }
+// multiStateReader is the aggregation of a list of StateReader interface,
+// providing state access by leveraging all readers. The checking priority
+// is determined by the position in the reader list.
+type multiStateReader struct {
+ readers []StateReader // List of state readers, sorted by checking priority
}
-// multiReader is the aggregation of a list of Reader interface, providing state
-// access by leveraging all readers. The checking priority is determined by the
-// position in the reader list.
-type multiReader struct {
- readers []Reader // List of readers, sorted by checking priority
-}
-
-// newMultiReader constructs a multiReader instance with the given readers. The
-// priority among readers is assumed to be sorted. Note, it must contain at least
-// one reader for constructing a multiReader.
-func newMultiReader(readers ...Reader) (*multiReader, error) {
+// newMultiStateReader constructs a multiStateReader instance with the given
+// readers. The priority among readers is assumed to be sorted. Note, it must
+// contain at least one reader for constructing a multiStateReader.
+func newMultiStateReader(readers ...StateReader) (*multiStateReader, error) {
if len(readers) == 0 {
return nil, errors.New("empty reader set")
}
- return &multiReader{
+ return &multiStateReader{
readers: readers,
}, nil
}
-// Account implementing Reader interface, retrieving the account associated with
-// a particular address.
+// Account implementing StateReader interface, retrieving the account associated
+// with a particular address.
//
// - Returns a nil account if it does not exist
// - Returns an error only if an unexpected issue occurs
// - The returned account is safe to modify after the call
-func (r *multiReader) Account(addr common.Address) (*types.StateAccount, error) {
+func (r *multiStateReader) Account(addr common.Address) (*types.StateAccount, error) {
var errs []error
for _, reader := range r.readers {
acct, err := reader.Account(addr)
@@ -280,13 +327,13 @@ func (r *multiReader) Account(addr common.Address) (*types.StateAccount, error)
return nil, errors.Join(errs...)
}
-// Storage implementing Reader interface, retrieving the storage slot associated
-// with a particular account address and slot key.
+// Storage implementing StateReader interface, retrieving the storage slot
+// associated with a particular account address and slot key.
//
// - Returns an empty slot if it does not exist
// - Returns an error only if an unexpected issue occurs
// - The returned storage slot is safe to modify after the call
-func (r *multiReader) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
+func (r *multiStateReader) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
var errs []error
for _, reader := range r.readers {
slot, err := reader.Storage(addr, slot)
@@ -298,11 +345,16 @@ func (r *multiReader) Storage(addr common.Address, slot common.Hash) (common.Has
return common.Hash{}, errors.Join(errs...)
}
-// Copy implementing Reader interface, returning a deep-copied state reader.
-func (r *multiReader) Copy() Reader {
- var readers []Reader
- for _, reader := range r.readers {
- readers = append(readers, reader.Copy())
- }
- return &multiReader{readers: readers}
+// reader is the wrapper of ContractCodeReader and StateReader interface.
+type reader struct {
+ ContractCodeReader
+ StateReader
+}
+
+// newReader constructs a reader with the supplied code reader and state reader.
+func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
+ return &reader{
+ ContractCodeReader: codeReader,
+ StateReader: stateReader,
+ }
}
diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go
index 61552c73fe..e4b396b990 100644
--- a/core/state/snapshot/journal.go
+++ b/core/state/snapshot/journal.go
@@ -33,9 +33,11 @@ import (
"github.com/ethereum/go-ethereum/triedb"
)
-// 0: initial version
-// 1: destruct flag in diff layer is removed
-const journalVersion uint64 = 1
+const (
+ journalV0 uint64 = 0 // initial version
+ journalV1 uint64 = 1 // current version, with destruct flag (in diff layers) removed
+ journalCurrentVersion = journalV1
+)
// journalGenerator is a disk layer entry containing the generator progress marker.
type journalGenerator struct {
@@ -50,6 +52,11 @@ type journalGenerator struct {
Storage uint64
}
+// journalDestruct is an account deletion entry in a diffLayer's disk journal.
+type journalDestruct struct {
+ Hash common.Hash
+}
+
// journalAccount is an account entry in a diffLayer's disk journal.
type journalAccount struct {
Hash common.Hash
@@ -285,8 +292,8 @@ func iterateJournal(db ethdb.KeyValueReader, callback journalCallback) error {
log.Warn("Failed to resolve the journal version", "error", err)
return errors.New("failed to resolve journal version")
}
- if version != journalVersion {
- log.Warn("Discarded the snapshot journal with wrong version", "required", journalVersion, "got", version)
+ if version != journalV0 && version != journalCurrentVersion {
+ log.Warn("Discarded journal with wrong version", "required", journalCurrentVersion, "got", version)
return errors.New("wrong journal version")
}
// Secondly, resolve the disk layer root, ensure it's continuous
@@ -316,6 +323,36 @@ func iterateJournal(db ethdb.KeyValueReader, callback journalCallback) error {
}
return fmt.Errorf("load diff root: %v", err)
}
+ // If a legacy journal is detected, decode the destruct set from the stream.
+ // The destruct set has been deprecated. If the journal contains non-empty
+ // destruct set, then it is deemed incompatible.
+ //
+ // Since self-destruction has been deprecated following the cancun fork,
+ // the destruct set is expected to be nil for layers above the fork block.
+ // However, an exception occurs during contract deployment: pre-funded accounts
+ // may self-destruct, causing accounts with non-zero balances to be removed
+ // from the state. For example,
+ // https://etherscan.io/tx/0xa087333d83f0cd63b96bdafb686462e1622ce25f40bd499e03efb1051f31fe49).
+ //
+ // For nodes with a fully synced state, the legacy journal is likely compatible
+ // with the updated definition, eliminating the need for regeneration. Unfortunately,
+ // nodes performing a full sync of historical chain segments or encountering
+ // pre-funded account deletions may face incompatibilities, leading to automatic
+ // snapshot regeneration.
+ //
+ // This approach minimizes snapshot regeneration for Geth nodes upgrading from a
+ // legacy version that are already synced. The workaround can be safely removed
+ // after the next hard fork.
+ if version == journalV0 {
+ var destructs []journalDestruct
+ if err := r.Decode(&destructs); err != nil {
+ return fmt.Errorf("load diff destructs: %v", err)
+ }
+ if len(destructs) > 0 {
+ log.Warn("Incompatible legacy journal detected", "version", journalV0)
+ return fmt.Errorf("incompatible legacy journal detected")
+ }
+ }
if err := r.Decode(&accounts); err != nil {
return fmt.Errorf("load diff accounts: %v", err)
}
diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go
index 7466f12351..f0f6296433 100644
--- a/core/state/snapshot/snapshot.go
+++ b/core/state/snapshot/snapshot.go
@@ -664,7 +664,7 @@ func (t *Tree) Journal(root common.Hash) (common.Hash, error) {
// Firstly write out the metadata of journal
journal := new(bytes.Buffer)
- if err := rlp.Encode(journal, journalVersion); err != nil {
+ if err := rlp.Encode(journal, journalCurrentVersion); err != nil {
return common.Hash{}, err
}
diskroot := t.diskRoot()
diff --git a/core/state/state_object.go b/core/state/state_object.go
index b659bf7ff2..2d542e5005 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -510,10 +510,13 @@ func (s *stateObject) Code() []byte {
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
return nil
}
- code, err := s.db.db.ContractCode(s.address, common.BytesToHash(s.CodeHash()))
+ code, err := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))
if err != nil {
s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
}
+ if len(code) == 0 {
+ s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
+ }
s.code = code
return code
}
@@ -528,10 +531,13 @@ func (s *stateObject) CodeSize() int {
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
return 0
}
- size, err := s.db.db.ContractCodeSize(s.address, common.BytesToHash(s.CodeHash()))
+ size, err := s.db.reader.CodeSize(s.address, common.BytesToHash(s.CodeHash()))
if err != nil {
s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
}
+ if size == 0 {
+ s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
+ }
return size
}
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 9cc91c9332..b0603db7f0 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -650,10 +650,11 @@ func (s *StateDB) CreateContract(addr common.Address) {
// Snapshots of the copied state cannot be applied to the copy.
func (s *StateDB) Copy() *StateDB {
// Copy all the basic fields, initialize the memory ones
+ reader, _ := s.db.Reader(s.originalRoot) // impossible to fail
state := &StateDB{
db: s.db,
trie: mustCopyTrie(s.trie),
- reader: s.reader.Copy(),
+ reader: reader,
originalRoot: s.originalRoot,
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
stateObjectsDestruct: make(map[common.Address]*stateObject, len(s.stateObjectsDestruct)),
diff --git a/core/state/sync_test.go b/core/state/sync_test.go
index b2c75e72fe..efa56f8860 100644
--- a/core/state/sync_test.go
+++ b/core/state/sync_test.go
@@ -210,14 +210,18 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool, s
if err != nil {
t.Fatalf("state is not existent, %#x", srcRoot)
}
+ cReader, err := srcDb.Reader(srcRoot)
+ if err != nil {
+ t.Fatalf("state is not existent, %#x", srcRoot)
+ }
for len(nodeElements)+len(codeElements) > 0 {
var (
nodeResults = make([]trie.NodeSyncResult, len(nodeElements))
codeResults = make([]trie.CodeSyncResult, len(codeElements))
)
for i, element := range codeElements {
- data, err := srcDb.ContractCode(common.Address{}, element.code)
- if err != nil {
+ data, err := cReader.Code(common.Address{}, element.code)
+ if err != nil || len(data) == 0 {
t.Fatalf("failed to retrieve contract bytecode for hash %x", element.code)
}
codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data}
@@ -329,6 +333,10 @@ func testIterativeDelayedStateSync(t *testing.T, scheme string) {
if err != nil {
t.Fatalf("state is not existent, %#x", srcRoot)
}
+ cReader, err := srcDb.Reader(srcRoot)
+ if err != nil {
+ t.Fatalf("state is not existent, %#x", srcRoot)
+ }
for len(nodeElements)+len(codeElements) > 0 {
// Sync only half of the scheduled nodes
var nodeProcessed int
@@ -336,8 +344,8 @@ func testIterativeDelayedStateSync(t *testing.T, scheme string) {
if len(codeElements) > 0 {
codeResults := make([]trie.CodeSyncResult, len(codeElements)/2+1)
for i, element := range codeElements[:len(codeResults)] {
- data, err := srcDb.ContractCode(common.Address{}, element.code)
- if err != nil {
+ data, err := cReader.Code(common.Address{}, element.code)
+ if err != nil || len(data) == 0 {
t.Fatalf("failed to retrieve contract bytecode for %x", element.code)
}
codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data}
@@ -433,13 +441,17 @@ func testIterativeRandomStateSync(t *testing.T, count int, scheme string) {
if err != nil {
t.Fatalf("state is not existent, %#x", srcRoot)
}
+ cReader, err := srcDb.Reader(srcRoot)
+ if err != nil {
+ t.Fatalf("state is not existent, %#x", srcRoot)
+ }
for len(nodeQueue)+len(codeQueue) > 0 {
// Fetch all the queued nodes in a random order
if len(codeQueue) > 0 {
results := make([]trie.CodeSyncResult, 0, len(codeQueue))
for hash := range codeQueue {
- data, err := srcDb.ContractCode(common.Address{}, hash)
- if err != nil {
+ data, err := cReader.Code(common.Address{}, hash)
+ if err != nil || len(data) == 0 {
t.Fatalf("failed to retrieve node data for %x", hash)
}
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
@@ -526,6 +538,10 @@ func testIterativeRandomDelayedStateSync(t *testing.T, scheme string) {
if err != nil {
t.Fatalf("state is not existent, %#x", srcRoot)
}
+ cReader, err := srcDb.Reader(srcRoot)
+ if err != nil {
+ t.Fatalf("state is not existent, %#x", srcRoot)
+ }
for len(nodeQueue)+len(codeQueue) > 0 {
// Sync only half of the scheduled nodes, even those in random order
if len(codeQueue) > 0 {
@@ -533,8 +549,8 @@ func testIterativeRandomDelayedStateSync(t *testing.T, scheme string) {
for hash := range codeQueue {
delete(codeQueue, hash)
- data, err := srcDb.ContractCode(common.Address{}, hash)
- if err != nil {
+ data, err := cReader.Code(common.Address{}, hash)
+ if err != nil || len(data) == 0 {
t.Fatalf("failed to retrieve node data for %x", hash)
}
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
@@ -631,6 +647,10 @@ func testIncompleteStateSync(t *testing.T, scheme string) {
if err != nil {
t.Fatalf("state is not available %x", srcRoot)
}
+ cReader, err := srcDb.Reader(srcRoot)
+ if err != nil {
+ t.Fatalf("state is not existent, %#x", srcRoot)
+ }
nodeQueue := make(map[string]stateElement)
codeQueue := make(map[common.Hash]struct{})
paths, nodes, codes := sched.Missing(1)
@@ -649,8 +669,8 @@ func testIncompleteStateSync(t *testing.T, scheme string) {
if len(codeQueue) > 0 {
results := make([]trie.CodeSyncResult, 0, len(codeQueue))
for hash := range codeQueue {
- data, err := srcDb.ContractCode(common.Address{}, hash)
- if err != nil {
+ data, err := cReader.Code(common.Address{}, hash)
+ if err != nil || len(data) == 0 {
t.Fatalf("failed to retrieve node data for %x", hash)
}
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
@@ -713,6 +733,11 @@ func testIncompleteStateSync(t *testing.T, scheme string) {
// Sanity check that removing any node from the database is detected
for _, node := range addedCodes {
val := rawdb.ReadCode(dstDb, node)
+ if len(val) == 0 {
+ t.Logf("no code: %v", node)
+ } else {
+ t.Logf("has code: %v", node)
+ }
rawdb.DeleteCode(dstDb, node)
if err := checkStateConsistency(dstDb, ndb.Scheme(), srcRoot); err == nil {
t.Errorf("trie inconsistency not caught, missing: %x", node)
diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go
index 3b987bb289..805df5ef62 100644
--- a/core/state_prefetcher.go
+++ b/core/state_prefetcher.go
@@ -65,7 +65,10 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
return // Also invalid block, bail out
}
statedb.SetTxContext(tx.Hash(), i)
- if err := precacheTransaction(msg, gaspool, evm); err != nil {
+
+ // We attempt to apply a transaction. The goal is not to execute
+ // the transaction successfully, rather to warm up touched data slots.
+ if _, err := ApplyMessage(evm, msg, gaspool); err != nil {
return // Ugh, something went horribly wrong, bail out
}
// If we're pre-byzantium, pre-load trie nodes for the intermediate root
@@ -78,14 +81,3 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
statedb.IntermediateRoot(true)
}
}
-
-// precacheTransaction attempts to apply a transaction to the given state database
-// and uses the input parameters for its environment. The goal is not to execute
-// the transaction successfully, rather to warm up touched data slots.
-func precacheTransaction(msg *Message, gaspool *GasPool, evm *vm.EVM) error {
- // Update the evm with the new transaction context.
- evm.SetTxContext(NewEVMTxContext(msg))
- // Add addresses to access list if applicable
- _, err := ApplyMessage(evm, msg, gaspool)
- return err
-}
diff --git a/core/state_processor.go b/core/state_processor.go
index 1703377111..3eb83a673a 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc"
"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"
@@ -106,18 +107,15 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
// Read requests if Prague is enabled.
var requests [][]byte
if p.config.IsPrague(block.Number(), block.Time()) {
- // EIP-6110 deposits
- depositRequests, err := ParseDepositLogs(allLogs, p.config)
- if err != nil {
+ requests = [][]byte{}
+ // EIP-6110
+ if err := ParseDepositLogs(&requests, allLogs, p.config); err != nil {
return nil, err
}
- requests = append(requests, depositRequests)
- // EIP-7002 withdrawals
- withdrawalRequests := ProcessWithdrawalQueue(evm)
- requests = append(requests, withdrawalRequests)
- // EIP-7251 consolidations
- consolidationRequests := ProcessConsolidationQueue(evm)
- requests = append(requests, consolidationRequests)
+ // EIP-7002
+ ProcessWithdrawalQueue(&requests, evm)
+ // EIP-7251
+ ProcessConsolidationQueue(&requests, evm)
}
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
@@ -143,17 +141,11 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
defer func() { hooks.OnTxEnd(receipt, err) }()
}
}
-
- // Create a new context to be used in the EVM environment.
- txContext := NewEVMTxContext(msg)
- evm.SetTxContext(txContext)
-
// Apply the transaction to the current state (included in the env).
result, err := ApplyMessage(evm, msg, gp)
if err != nil {
return nil, err
}
-
// Update the state with pending changes.
var root []byte
if evm.ChainConfig().IsByzantium(blockNumber) {
@@ -221,9 +213,7 @@ func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *
// contract. This method is exported to be used in tests.
func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
if tracer := evm.Config.Tracer; tracer != nil {
- if tracer.OnSystemCallStart != nil {
- tracer.OnSystemCallStart()
- }
+ onSystemCallStart(tracer, evm.GetVMContext())
if tracer.OnSystemCallEnd != nil {
defer tracer.OnSystemCallEnd()
}
@@ -247,9 +237,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
// as per EIP-2935.
func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
if tracer := evm.Config.Tracer; tracer != nil {
- if tracer.OnSystemCallStart != nil {
- tracer.OnSystemCallStart()
- }
+ onSystemCallStart(tracer, evm.GetVMContext())
if tracer.OnSystemCallEnd != nil {
defer tracer.OnSystemCallEnd()
}
@@ -271,21 +259,19 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
// ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract.
// It returns the opaque request data returned by the contract.
-func ProcessWithdrawalQueue(evm *vm.EVM) []byte {
- return processRequestsSystemCall(evm, 0x01, params.WithdrawalQueueAddress)
+func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) {
+ processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress)
}
// ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract.
// It returns the opaque request data returned by the contract.
-func ProcessConsolidationQueue(evm *vm.EVM) []byte {
- return processRequestsSystemCall(evm, 0x02, params.ConsolidationQueueAddress)
+func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) {
+ processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress)
}
-func processRequestsSystemCall(evm *vm.EVM, requestType byte, addr common.Address) []byte {
+func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) {
if tracer := evm.Config.Tracer; tracer != nil {
- if tracer.OnSystemCallStart != nil {
- tracer.OnSystemCallStart()
- }
+ onSystemCallStart(tracer, evm.GetVMContext())
if tracer.OnSystemCallEnd != nil {
defer tracer.OnSystemCallEnd()
}
@@ -302,26 +288,40 @@ func processRequestsSystemCall(evm *vm.EVM, requestType byte, addr common.Addres
evm.StateDB.AddAddressToAccessList(addr)
ret, _, _ := evm.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560)
evm.StateDB.Finalise(true)
+ if len(ret) == 0 {
+ return // skip empty output
+ }
- // Create withdrawals requestsData with prefix 0x01
+ // Append prefixed requestsData to the requests list.
requestsData := make([]byte, len(ret)+1)
requestsData[0] = requestType
copy(requestsData[1:], ret)
- return requestsData
+ *requests = append(*requests, requestsData)
}
// ParseDepositLogs extracts the EIP-6110 deposit values from logs emitted by
// BeaconDepositContract.
-func ParseDepositLogs(logs []*types.Log, config *params.ChainConfig) ([]byte, error) {
+func ParseDepositLogs(requests *[][]byte, logs []*types.Log, config *params.ChainConfig) error {
deposits := make([]byte, 1) // note: first byte is 0x00 (== deposit request type)
for _, log := range logs {
if log.Address == config.DepositContractAddress {
request, err := types.DepositLogToRequest(log.Data)
if err != nil {
- return nil, fmt.Errorf("unable to parse deposit data: %v", err)
+ return fmt.Errorf("unable to parse deposit data: %v", err)
}
deposits = append(deposits, request...)
}
}
- return deposits, nil
+ if len(deposits) > 1 {
+ *requests = append(*requests, deposits)
+ }
+ return nil
+}
+
+func onSystemCallStart(tracer *tracing.Hooks, ctx *tracing.VMContext) {
+ if tracer.OnSystemCallStartV2 != nil {
+ tracer.OnSystemCallStartV2(ctx)
+ } else if tracer.OnSystemCallStart != nil {
+ tracer.OnSystemCallStart()
+ }
}
diff --git a/core/state_transition.go b/core/state_transition.go
index 4bd3c00167..ea7e3df2ff 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -187,10 +187,11 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
// indicates a core error meaning that the message would always fail for that particular
// state and would never be accepted within a block.
func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) {
- return NewStateTransition(evm, msg, gp).TransitionDb()
+ evm.SetTxContext(NewEVMTxContext(msg))
+ return newStateTransition(evm, msg, gp).execute()
}
-// StateTransition represents a state transition.
+// stateTransition represents a state transition.
//
// == The State Transitioning Model
//
@@ -212,7 +213,7 @@ func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, err
//
// 5. Run Script section
// 6. Derive new state root
-type StateTransition struct {
+type stateTransition struct {
gp *GasPool
msg *Message
gasRemaining uint64
@@ -221,9 +222,9 @@ type StateTransition struct {
evm *vm.EVM
}
-// NewStateTransition initialises and returns a new state transition object.
-func NewStateTransition(evm *vm.EVM, msg *Message, gp *GasPool) *StateTransition {
- return &StateTransition{
+// newStateTransition initialises and returns a new state transition object.
+func newStateTransition(evm *vm.EVM, msg *Message, gp *GasPool) *stateTransition {
+ return &stateTransition{
gp: gp,
evm: evm,
msg: msg,
@@ -232,14 +233,14 @@ func NewStateTransition(evm *vm.EVM, msg *Message, gp *GasPool) *StateTransition
}
// to returns the recipient of the message.
-func (st *StateTransition) to() common.Address {
+func (st *stateTransition) to() common.Address {
if st.msg == nil || st.msg.To == nil /* contract creation */ {
return common.Address{}
}
return *st.msg.To
}
-func (st *StateTransition) buyGas() error {
+func (st *stateTransition) buyGas() error {
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
mgval.Mul(mgval, st.msg.GasPrice)
balanceCheck := new(big.Int).Set(mgval)
@@ -283,7 +284,7 @@ func (st *StateTransition) buyGas() error {
return nil
}
-func (st *StateTransition) preCheck() error {
+func (st *stateTransition) preCheck() error {
// Only check transactions that are not fake
msg := st.msg
if !msg.SkipNonceChecks {
@@ -368,7 +369,7 @@ func (st *StateTransition) preCheck() error {
return st.buyGas()
}
-// TransitionDb will transition the state by applying the current message and
+// execute will transition the state by applying the current message and
// returning the evm execution result with following fields.
//
// - used gas: total gas used (including gas being refunded)
@@ -378,7 +379,7 @@ func (st *StateTransition) preCheck() error {
//
// However if any consensus issue encountered, return the error directly with
// nil evm execution result.
-func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
+func (st *stateTransition) execute() (*ExecutionResult, error) {
// First check this message satisfies all consensus rules before
// applying the message. The rules include these clauses
//
@@ -493,7 +494,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
}, nil
}
-func (st *StateTransition) refundGas(refundQuotient uint64) uint64 {
+func (st *stateTransition) refundGas(refundQuotient uint64) uint64 {
// Apply refund counter, capped to a refund quotient
refund := st.gasUsed() / refundQuotient
if refund > st.state.GetRefund() {
@@ -523,11 +524,11 @@ func (st *StateTransition) refundGas(refundQuotient uint64) uint64 {
}
// gasUsed returns the amount of gas used up by the state transition.
-func (st *StateTransition) gasUsed() uint64 {
+func (st *stateTransition) gasUsed() uint64 {
return st.initialGas - st.gasRemaining
}
// blobGasUsed returns the amount of blob gas used by the message.
-func (st *StateTransition) blobGasUsed() uint64 {
+func (st *stateTransition) blobGasUsed() uint64 {
return uint64(len(st.msg.BlobHashes) * params.BlobTxBlobGasPerBlob)
}
diff --git a/core/stateless.go b/core/stateless.go
index 5b37d5020e..d21a62b4a5 100644
--- a/core/stateless.go
+++ b/core/stateless.go
@@ -40,7 +40,7 @@ import (
// - It cannot be placed outside of core, because it needs to construct a dud headerchain
//
// TODO(karalabe): Would be nice to resolve both issues above somehow and move it.
-func ExecuteStateless(config *params.ChainConfig, block *types.Block, witness *stateless.Witness) (common.Hash, common.Hash, error) {
+func ExecuteStateless(config *params.ChainConfig, vmconfig vm.Config, block *types.Block, witness *stateless.Witness) (common.Hash, common.Hash, error) {
// Sanity check if the supplied block accidentally contains a set root or
// receipt hash. If so, be very loud, but still continue.
if block.Root() != (common.Hash{}) {
@@ -66,7 +66,7 @@ func ExecuteStateless(config *params.ChainConfig, block *types.Block, witness *s
validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block
// Run the stateless blocks processing and self-validate certain fields
- res, err := processor.Process(block, db, vm.Config{})
+ res, err := processor.Process(block, db, vmconfig)
if err != nil {
return common.Hash{}, common.Hash{}, err
}
diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go
index 0c5cae354b..728f15069b 100644
--- a/core/tracing/hooks.go
+++ b/core/tracing/hooks.go
@@ -55,9 +55,8 @@ type VMContext struct {
BlockNumber *big.Int
Time uint64
Random *common.Hash
- // Effective tx gas price
- GasPrice *big.Int
- StateDB StateDB
+ BaseFee *big.Int
+ StateDB StateDB
}
// BlockEvent is emitted upon tracing an incoming block.
@@ -146,6 +145,10 @@ type (
// will not be invoked.
OnSystemCallStartHook = func()
+ // OnSystemCallStartHookV2 is called when a system call is about to be executed. Refer
+ // to `OnSystemCallStartHook` for more information.
+ OnSystemCallStartHookV2 = func(vm *VMContext)
+
// OnSystemCallEndHook is called when a system call has finished executing. Today,
// this hook is invoked when the EIP-4788 system call is about to be executed to set the
// beacon block root.
@@ -181,14 +184,15 @@ type Hooks struct {
OnFault FaultHook
OnGasChange GasChangeHook
// Chain events
- OnBlockchainInit BlockchainInitHook
- OnClose CloseHook
- OnBlockStart BlockStartHook
- OnBlockEnd BlockEndHook
- OnSkippedBlock SkippedBlockHook
- OnGenesisBlock GenesisBlockHook
- OnSystemCallStart OnSystemCallStartHook
- OnSystemCallEnd OnSystemCallEndHook
+ OnBlockchainInit BlockchainInitHook
+ OnClose CloseHook
+ OnBlockStart BlockStartHook
+ OnBlockEnd BlockEndHook
+ OnSkippedBlock SkippedBlockHook
+ OnGenesisBlock GenesisBlockHook
+ OnSystemCallStart OnSystemCallStartHook
+ OnSystemCallStartV2 OnSystemCallStartHookV2
+ OnSystemCallEnd OnSystemCallEndHook
// State events
OnBalanceChange BalanceChangeHook
OnNonceChange NonceChangeHook
diff --git a/core/types/block.go b/core/types/block.go
index f20fc7d778..ad6c86398d 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -463,9 +463,11 @@ func CalcRequestsHash(requests [][]byte) common.Hash {
h1, h2 := sha256.New(), sha256.New()
var buf common.Hash
for _, item := range requests {
- h1.Reset()
- h1.Write(item)
- h2.Write(h1.Sum(buf[:0]))
+ if len(item) > 1 { // skip items with only requestType and no data.
+ h1.Reset()
+ h1.Write(item)
+ h2.Write(h1.Sum(buf[:0]))
+ }
}
h2.Sum(buf[:0])
return buf
diff --git a/core/types/hashes.go b/core/types/hashes.go
index 43e9130fd1..55506d63d0 100644
--- a/core/types/hashes.go
+++ b/core/types/hashes.go
@@ -41,6 +41,9 @@ var (
// EmptyWithdrawalsHash is the known hash of the empty withdrawal set.
EmptyWithdrawalsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
+ // EmptyRequestsHash is the known hash of an empty request set, sha256("").
+ EmptyRequestsHash = common.HexToHash("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
+
// EmptyVerkleHash is the known hash of an empty verkle trie.
EmptyVerkleHash = common.Hash{}
)
diff --git a/core/vm/evm.go b/core/vm/evm.go
index 34e5fa766b..07e4a272fa 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -605,7 +605,7 @@ func (evm *EVM) GetVMContext() *tracing.VMContext {
BlockNumber: evm.Context.BlockNumber,
Time: evm.Context.Time,
Random: evm.Context.Random,
- GasPrice: evm.TxContext.GasPrice,
+ BaseFee: evm.Context.BaseFee,
StateDB: evm.StateDB,
}
}
diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go
index 7d1345a57b..0e774a01c2 100644
--- a/core/vm/runtime/runtime_test.go
+++ b/core/vm/runtime/runtime_test.go
@@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/asm"
"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/core/vm/program"
@@ -670,17 +671,23 @@ func TestColdAccountAccessCost(t *testing.T) {
want: 7600,
},
} {
- tracer := logger.NewStructLogger(nil)
+ var step = 0
+ var have = uint64(0)
Execute(tc.code, nil, &Config{
EVMConfig: vm.Config{
- Tracer: tracer.Hooks(),
+ Tracer: &tracing.Hooks{
+ OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
+ // Uncomment to investigate failures:
+ //t.Logf("%d: %v %d", step, vm.OpCode(op).String(), cost)
+ if step == tc.step {
+ have = cost
+ }
+ step++
+ },
+ },
},
})
- have := tracer.StructLogs()[tc.step].GasCost
if want := tc.want; have != want {
- for ii, op := range tracer.StructLogs() {
- t.Logf("%d: %v %d", ii, op.OpName(), op.GasCost)
- }
t.Fatalf("testcase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want)
}
}
diff --git a/eth/backend.go b/eth/backend.go
index ccfe650f41..a3aa0a7b9b 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -424,17 +424,17 @@ func (s *Ethereum) Stop() error {
// SyncMode retrieves the current sync mode, either explicitly set, or derived
// from the chain status.
-func (s *Ethereum) SyncMode() downloader.SyncMode {
+func (s *Ethereum) SyncMode() ethconfig.SyncMode {
// If we're in snap sync mode, return that directly
if s.handler.snapSync.Load() {
- return downloader.SnapSync
+ return ethconfig.SnapSync
}
// We are probably in full sync, but we might have rewound to before the
// snap sync pivot, check if we should re-enable snap sync.
head := s.blockchain.CurrentBlock()
if pivot := rawdb.ReadLastPivotNumber(s.chainDb); pivot != nil {
if head.Number.Uint64() < *pivot {
- return downloader.SnapSync
+ return ethconfig.SnapSync
}
}
// We are in a full sync, but the associated head state is missing. To complete
@@ -442,8 +442,8 @@ func (s *Ethereum) SyncMode() downloader.SyncMode {
// persistent state is corrupted, just mismatch with the head block.
if !s.blockchain.HasState(head.Root) {
log.Info("Reenabled snap sync as chain is stateless")
- return downloader.SnapSync
+ return ethconfig.SnapSync
}
// Nope, we're really full syncing
- return downloader.FullSync
+ return ethconfig.FullSync
}
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
index 4779f9756b..3e45ad9e4f 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -31,8 +31,9 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth"
- "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/version"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner"
@@ -917,7 +918,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
// tries to make it import a block. That should be denied as pushing something
// into the database directly will conflict with the assumptions of snap sync
// that it has an empty db that it can fill itself.
- if api.eth.SyncMode() != downloader.FullSync {
+ if api.eth.SyncMode() != ethconfig.FullSync {
return api.delayPayloadImport(block), nil
}
if !api.eth.BlockChain().HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
@@ -995,7 +996,7 @@ func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, v
api.lastNewPayloadLock.Unlock()
log.Trace("Executing block statelessly", "number", block.Number(), "hash", params.BlockHash)
- stateRoot, receiptRoot, err := core.ExecuteStateless(api.eth.BlockChain().Config(), block, witness)
+ stateRoot, receiptRoot, err := core.ExecuteStateless(api.eth.BlockChain().Config(), vm.Config{}, block, witness)
if err != nil {
log.Warn("ExecuteStatelessPayload: execution failed", "err", err)
errorMsg := err.Error()
@@ -1030,7 +1031,7 @@ func (api *ConsensusAPI) delayPayloadImport(block *types.Block) engine.PayloadSt
// payload as non-integratable on top of the existing sync. We'll just
// have to rely on the beacon client to forcefully update the head with
// a forkchoice update request.
- if api.eth.SyncMode() == downloader.FullSync {
+ if api.eth.SyncMode() == ethconfig.FullSync {
// In full sync mode, failure to import a well-formed block can only mean
// that the parent state is missing and the syncer rejected extending the
// current cycle with the new payload.
diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go
index 3ac719c23e..e0a155f12b 100644
--- a/eth/catalyst/api_test.go
+++ b/eth/catalyst/api_test.go
@@ -40,7 +40,6 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/eth"
- "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/version"
"github.com/ethereum/go-ethereum/miner"
@@ -452,7 +451,7 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block)
}
mcfg := miner.DefaultConfig
- ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256, Miner: mcfg}
+ ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: ethconfig.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256, Miner: mcfg}
ethservice, err := eth.New(n, ethcfg)
if err != nil {
t.Fatal("can't create eth service:", err)
diff --git a/eth/catalyst/simulated_beacon_test.go b/eth/catalyst/simulated_beacon_test.go
index 7e9fd7b324..79d9ba738e 100644
--- a/eth/catalyst/simulated_beacon_test.go
+++ b/eth/catalyst/simulated_beacon_test.go
@@ -27,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
- "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node"
@@ -49,7 +48,7 @@ func startSimulatedBeaconEthService(t *testing.T, genesis *core.Genesis, period
t.Fatal("can't create node:", err)
}
- ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256, Miner: miner.DefaultConfig}
+ ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: ethconfig.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256, Miner: miner.DefaultConfig}
ethservice, err := eth.New(n, ethcfg)
if err != nil {
t.Fatal("can't create eth service:", err)
diff --git a/eth/catalyst/tester.go b/eth/catalyst/tester.go
index 0922ac0ba6..db2d638701 100644
--- a/eth/catalyst/tester.go
+++ b/eth/catalyst/tester.go
@@ -22,7 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/eth"
- "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
)
@@ -62,7 +62,7 @@ func (tester *FullSyncTester) Start() error {
// Trigger beacon sync with the provided block hash as trusted
// chain head.
- err := tester.backend.Downloader().BeaconDevSync(downloader.FullSync, tester.target, tester.closed)
+ err := tester.backend.Downloader().BeaconDevSync(ethconfig.FullSync, tester.target, tester.closed)
if err != nil {
log.Info("Failed to trigger beacon sync", "err", err)
}
diff --git a/eth/downloader/beaconsync.go b/eth/downloader/beaconsync.go
index e682536e07..c142ea7435 100644
--- a/eth/downloader/beaconsync.go
+++ b/eth/downloader/beaconsync.go
@@ -24,6 +24,7 @@ import (
"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/eth/ethconfig"
"github.com/ethereum/go-ethereum/log"
)
@@ -198,9 +199,9 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) {
var chainHead *types.Header
switch d.getMode() {
- case FullSync:
+ case ethconfig.FullSync:
chainHead = d.blockchain.CurrentBlock()
- case SnapSync:
+ case ethconfig.SnapSync:
chainHead = d.blockchain.CurrentSnapBlock()
default:
panic("unknown sync mode")
@@ -218,9 +219,9 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) {
}
var linked bool
switch d.getMode() {
- case FullSync:
+ case ethconfig.FullSync:
linked = d.blockchain.HasBlock(beaconTail.ParentHash, beaconTail.Number.Uint64()-1)
- case SnapSync:
+ case ethconfig.SnapSync:
linked = d.blockchain.HasFastBlock(beaconTail.ParentHash, beaconTail.Number.Uint64()-1)
default:
panic("unknown sync mode")
@@ -253,9 +254,9 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) {
var known bool
switch d.getMode() {
- case FullSync:
+ case ethconfig.FullSync:
known = d.blockchain.HasBlock(h.Hash(), n)
- case SnapSync:
+ case ethconfig.SnapSync:
known = d.blockchain.HasFastBlock(h.Hash(), n)
default:
panic("unknown sync mode")
diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go
index fadb68ef03..8ac5d2eb31 100644
--- a/eth/downloader/downloader.go
+++ b/eth/downloader/downloader.go
@@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/protocols/snap"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
@@ -69,6 +70,17 @@ var (
errNoPivotHeader = errors.New("pivot header is not found")
)
+// SyncMode defines the sync method of the downloader.
+// Deprecated: use ethconfig.SyncMode instead
+type SyncMode = ethconfig.SyncMode
+
+const (
+ // Deprecated: use ethconfig.FullSync
+ FullSync = ethconfig.FullSync
+ // Deprecated: use ethconfig.SnapSync
+ SnapSync = ethconfig.SnapSync
+)
+
// peerDropFn is a callback type for dropping a peer detected as malicious.
type peerDropFn func(id string)
@@ -230,9 +242,9 @@ func (d *Downloader) Progress() ethereum.SyncProgress {
current := uint64(0)
mode := d.getMode()
switch mode {
- case FullSync:
+ case ethconfig.FullSync:
current = d.blockchain.CurrentBlock().Number.Uint64()
- case SnapSync:
+ case ethconfig.SnapSync:
current = d.blockchain.CurrentSnapBlock().Number.Uint64()
default:
log.Error("Unknown downloader mode", "mode", mode)
@@ -326,7 +338,7 @@ func (d *Downloader) synchronise(mode SyncMode, beaconPing chan struct{}) error
if d.notified.CompareAndSwap(false, true) {
log.Info("Block synchronisation started")
}
- if mode == SnapSync {
+ if mode == ethconfig.SnapSync {
// Snap sync will directly modify the persistent state, making the entire
// trie database unusable until the state is fully synced. To prevent any
// subsequent state reads, explicitly disable the trie database and state
@@ -434,7 +446,7 @@ func (d *Downloader) syncToHead() (err error) {
// threshold (i.e. new chain). In that case we won't really snap sync
// anyway, but still need a valid pivot block to avoid some code hitting
// nil panics on access.
- if mode == SnapSync && pivot == nil {
+ if mode == ethconfig.SnapSync && pivot == nil {
pivot = d.blockchain.CurrentBlock()
}
height := latest.Number.Uint64()
@@ -452,7 +464,7 @@ func (d *Downloader) syncToHead() (err error) {
d.syncStatsLock.Unlock()
// Ensure our origin point is below any snap sync pivot point
- if mode == SnapSync {
+ if mode == ethconfig.SnapSync {
if height <= uint64(fsMinFullBlocks) {
origin = 0
} else {
@@ -466,10 +478,10 @@ func (d *Downloader) syncToHead() (err error) {
}
}
d.committed.Store(true)
- if mode == SnapSync && pivot.Number.Uint64() != 0 {
+ if mode == ethconfig.SnapSync && pivot.Number.Uint64() != 0 {
d.committed.Store(false)
}
- if mode == SnapSync {
+ if mode == ethconfig.SnapSync {
// Set the ancient data limitation. If we are running snap sync, all block
// data older than ancientLimit will be written to the ancient store. More
// recent data will be written to the active database and will wait for the
@@ -523,13 +535,13 @@ func (d *Downloader) syncToHead() (err error) {
func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during snap sync
func() error { return d.processHeaders(origin + 1) },
}
- if mode == SnapSync {
+ if mode == ethconfig.SnapSync {
d.pivotLock.Lock()
d.pivotHeader = pivot
d.pivotLock.Unlock()
fetchers = append(fetchers, func() error { return d.processSnapSyncContent() })
- } else if mode == FullSync {
+ } else if mode == ethconfig.FullSync {
fetchers = append(fetchers, func() error { return d.processFullSyncContent() })
}
return d.spawnSync(fetchers)
@@ -676,7 +688,7 @@ func (d *Downloader) processHeaders(origin uint64) error {
chunkHashes := hashes[:limit]
// In case of header only syncing, validate the chunk immediately
- if mode == SnapSync {
+ if mode == ethconfig.SnapSync {
// Although the received headers might be all valid, a legacy
// PoW/PoA sync must not accept post-merge headers. Make sure
// that any transition is rejected at this point.
diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go
index a2f916ebbc..6c9175a95a 100644
--- a/eth/downloader/queue.go
+++ b/eth/downloader/queue.go
@@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
+ "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
@@ -180,7 +181,7 @@ func (q *queue) Reset(blockCacheLimit int, thresholdInitialSize int) {
defer q.lock.Unlock()
q.closed = false
- q.mode = FullSync
+ q.mode = ethconfig.FullSync
q.headerHead = common.Hash{}
q.headerPendPool = make(map[string]*fetchRequest)
@@ -328,7 +329,7 @@ func (q *queue) Schedule(headers []*types.Header, hashes []common.Hash, from uin
q.blockTaskQueue.Push(header, -int64(header.Number.Uint64()))
}
// Queue for receipt retrieval
- if q.mode == SnapSync && !header.EmptyReceipts() {
+ if q.mode == ethconfig.SnapSync && !header.EmptyReceipts() {
if _, ok := q.receiptTaskPool[hash]; ok {
log.Warn("Header already scheduled for receipt fetch", "number", header.Number, "hash", hash)
} else {
@@ -523,7 +524,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
// we can ask the resultcache if this header is within the
// "prioritized" segment of blocks. If it is not, we need to throttle
- stale, throttle, item, err := q.resultCache.AddFetch(header, q.mode == SnapSync)
+ stale, throttle, item, err := q.resultCache.AddFetch(header, q.mode == ethconfig.SnapSync)
if stale {
// Don't put back in the task queue, this item has already been
// delivered upstream
diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go
index 8542bc97c4..6b75ab816f 100644
--- a/eth/ethconfig/config.go
+++ b/eth/ethconfig/config.go
@@ -29,7 +29,6 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
- "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
@@ -49,7 +48,7 @@ var FullNodeGPO = gasprice.Config{
// Defaults contains default settings for use on the Ethereum main net.
var Defaults = Config{
- SyncMode: downloader.SnapSync,
+ SyncMode: SnapSync,
NetworkId: 0, // enable auto configuration of networkID == chainID
TxLookupLimit: 2350000,
TransactionHistory: 2350000,
@@ -80,7 +79,7 @@ type Config struct {
// Network ID separates blockchains on the peer-to-peer networking level. When left
// zero, the chain ID is used as network ID.
NetworkId uint64
- SyncMode downloader.SyncMode
+ SyncMode SyncMode
// This can be set to list of enrtree:// URLs which will be queried for
// nodes to connect to.
diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go
index 0ec0eaddeb..8e954eaefb 100644
--- a/eth/ethconfig/gen_config.go
+++ b/eth/ethconfig/gen_config.go
@@ -9,7 +9,6 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
- "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/miner"
)
@@ -19,7 +18,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
type Config struct {
Genesis *core.Genesis `toml:",omitempty"`
NetworkId uint64
- SyncMode downloader.SyncMode
+ SyncMode SyncMode
EthDiscoveryURLs []string
SnapDiscoveryURLs []string
NoPruning bool
@@ -95,7 +94,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
type Config struct {
Genesis *core.Genesis `toml:",omitempty"`
NetworkId *uint64
- SyncMode *downloader.SyncMode
+ SyncMode *SyncMode
EthDiscoveryURLs []string
SnapDiscoveryURLs []string
NoPruning *bool
diff --git a/eth/downloader/modes.go b/eth/ethconfig/syncmode.go
similarity index 99%
rename from eth/downloader/modes.go
rename to eth/ethconfig/syncmode.go
index 9d8e1f313c..af5dbbb961 100644
--- a/eth/downloader/modes.go
+++ b/eth/ethconfig/syncmode.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 downloader
+package ethconfig
import "fmt"
diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go
index 5729f84278..9b24bfbf96 100644
--- a/eth/gasestimator/gasestimator.go
+++ b/eth/gasestimator/gasestimator.go
@@ -217,21 +217,19 @@ func execute(ctx context.Context, call *core.Message, opts *Options, gasLimit ui
func run(ctx context.Context, call *core.Message, opts *Options) (*core.ExecutionResult, error) {
// Assemble the call and the call context
var (
- msgContext = core.NewEVMTxContext(call)
evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil)
-
dirtyState = opts.State.Copy()
)
// Lower the basefee to 0 to avoid breaking EVM
// invariants (basefee < feecap).
- if msgContext.GasPrice.Sign() == 0 {
+ if call.GasPrice.Sign() == 0 {
evmContext.BaseFee = new(big.Int)
}
- if msgContext.BlobFeeCap != nil && msgContext.BlobFeeCap.BitLen() == 0 {
+ if call.BlobGasFeeCap != nil && call.BlobGasFeeCap.BitLen() == 0 {
evmContext.BlobBaseFee = new(big.Int)
}
evm := vm.NewEVM(evmContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
- evm.SetTxContext(msgContext)
+
// Monitor the outer context and interrupt the EVM upon cancellation. To avoid
// a dangling goroutine until the outer estimation finishes, create an internal
// context for the lifetime of this method call.
diff --git a/eth/handler.go b/eth/handler.go
index b28081eef0..583dc2835d 100644
--- a/eth/handler.go
+++ b/eth/handler.go
@@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/fetcher"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/eth/protocols/snap"
@@ -87,7 +88,7 @@ type handlerConfig struct {
Chain *core.BlockChain // Blockchain to serve data from
TxPool txPool // Transaction pool to propagate from
Network uint64 // Network identifier to advertise
- Sync downloader.SyncMode // Whether to snap or full sync
+ Sync ethconfig.SyncMode // Whether to snap or full sync
BloomCache uint64 // Megabytes to alloc for snap sync bloom
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
@@ -145,7 +146,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
handlerDoneCh: make(chan struct{}),
handlerStartCh: make(chan struct{}),
}
- if config.Sync == downloader.FullSync {
+ if config.Sync == ethconfig.FullSync {
// The database seems empty as the current block is the genesis. Yet the snap
// block is ahead, so snap sync was enabled for this node at a certain point.
// The scenarios where this can happen is
diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go
index 55f7da87dd..ce17345358 100644
--- a/eth/handler_eth_test.go
+++ b/eth/handler_eth_test.go
@@ -29,7 +29,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p"
@@ -109,7 +109,7 @@ func testForkIDSplit(t *testing.T, protocol uint) {
Chain: chainNoFork,
TxPool: newTestTxPool(),
Network: 1,
- Sync: downloader.FullSync,
+ Sync: ethconfig.FullSync,
BloomCache: 1,
})
ethProFork, _ = newHandler(&handlerConfig{
@@ -117,7 +117,7 @@ func testForkIDSplit(t *testing.T, protocol uint) {
Chain: chainProFork,
TxPool: newTestTxPool(),
Network: 1,
- Sync: downloader.FullSync,
+ Sync: ethconfig.FullSync,
BloomCache: 1,
})
)
diff --git a/eth/handler_test.go b/eth/handler_test.go
index 7b250df2e9..b63d3e8592 100644
--- a/eth/handler_test.go
+++ b/eth/handler_test.go
@@ -29,7 +29,7 @@ import (
"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/downloader"
+ "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
@@ -164,7 +164,7 @@ func newTestHandlerWithBlocks(blocks int) *testHandler {
Chain: chain,
TxPool: txpool,
Network: 1,
- Sync: downloader.SnapSync,
+ Sync: ethconfig.SnapSync,
BloomCache: 1,
})
handler.Start(1000)
diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go
index d36f9621b1..8164973c20 100644
--- a/eth/protocols/snap/handler.go
+++ b/eth/protocols/snap/handler.go
@@ -454,7 +454,7 @@ func ServiceGetByteCodesQuery(chain *core.BlockChain, req *GetByteCodesPacket) [
// Peers should not request the empty code, but if they do, at
// least sent them back a correct response without db lookups
codes = append(codes, []byte{})
- } else if blob, err := chain.ContractCodeWithPrefix(hash); err == nil {
+ } else if blob := chain.ContractCodeWithPrefix(hash); len(blob) > 0 {
codes = append(codes, blob)
bytes += uint64(len(blob))
}
diff --git a/eth/state_accessor.go b/eth/state_accessor.go
index 43432cff31..0749d73791 100644
--- a/eth/state_accessor.go
+++ b/eth/state_accessor.go
@@ -255,8 +255,6 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
}
// Assemble the transaction call message and return if the requested offset
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
- txContext := core.NewEVMTxContext(msg)
- evm.SetTxContext(txContext)
// Not yet the searched for transaction, execute on top of the current state
statedb.SetTxContext(tx.Hash(), idx)
diff --git a/eth/sync_test.go b/eth/sync_test.go
index 7ede0a82c5..57eea73790 100644
--- a/eth/sync_test.go
+++ b/eth/sync_test.go
@@ -20,7 +20,7 @@ import (
"testing"
"time"
- "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/eth/protocols/snap"
"github.com/ethereum/go-ethereum/p2p"
@@ -85,7 +85,7 @@ func testSnapSyncDisabling(t *testing.T, ethVer uint, snapVer uint) {
time.Sleep(250 * time.Millisecond)
// Check that snap sync was disabled
- if err := empty.handler.downloader.BeaconSync(downloader.SnapSync, full.chain.CurrentBlock(), nil); err != nil {
+ if err := empty.handler.downloader.BeaconSync(ethconfig.SnapSync, full.chain.CurrentBlock(), nil); err != nil {
t.Fatal("sync failed:", err)
}
empty.handler.enableSyncedFeatures()
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index a2c11e0fe2..c15c5c4eb2 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -546,11 +546,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
if err := ctx.Err(); err != nil {
return nil, err
}
- var (
- msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee())
- txContext = core.NewEVMTxContext(msg)
- )
- evm.SetTxContext(txContext)
+ msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
statedb.SetTxContext(tx.Hash(), i)
if _, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil {
log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err)
@@ -708,7 +704,6 @@ txloop:
// Generate the next state snapshot fast without tracing
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
statedb.SetTxContext(tx.Hash(), i)
- evm.SetTxContext(core.NewEVMTxContext(msg))
if _, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil {
failed = err
break txloop
@@ -792,12 +787,11 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
for i, tx := range block.Transactions() {
// Prepare the transaction for un-traced execution
var (
- msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee())
- txContext = core.NewEVMTxContext(msg)
- vmConf vm.Config
- dump *os.File
- writer *bufio.Writer
- err error
+ msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee())
+ vmConf vm.Config
+ dump *os.File
+ writer *bufio.Writer
+ err error
)
// If the transaction needs tracing, swap out the configs
if tx.Hash() == txHash || txHash == (common.Hash{}) {
@@ -820,7 +814,6 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
}
}
// Execute the transaction and flush any traces to disk
- evm.SetTxContext(txContext)
statedb.SetTxContext(tx.Hash(), i)
if vmConf.Tracer.OnTxStart != nil {
vmConf.Tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
@@ -1014,9 +1007,8 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor
return nil, err
}
}
- // The actual TxContext will be created as part of ApplyTransactionWithEVM.
- evm := vm.NewEVM(vmctx, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer.Hooks, NoBaseFee: true})
- evm.SetTxContext(vm.TxContext{GasPrice: message.GasPrice, BlobFeeCap: message.BlobGasFeeCap})
+ tracingStateDB := state.NewHookedState(statedb, tracer.Hooks)
+ evm := vm.NewEVM(vmctx, tracingStateDB, api.backend.ChainConfig(), vm.Config{Tracer: tracer.Hooks, NoBaseFee: true})
// Define a meaningful timeout of a single transaction trace
if config.Timeout != nil {
diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go
index 2b5cfa35c8..e786853ede 100644
--- a/eth/tracers/api_test.go
+++ b/eth/tracers/api_test.go
@@ -37,6 +37,7 @@ import (
"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"
@@ -177,8 +178,6 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
return tx, context, statedb, release, nil
}
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
- txContext := core.NewEVMTxContext(msg)
- evm.SetTxContext(txContext)
if _, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
}
@@ -187,6 +186,94 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash())
}
+type stateTracer struct {
+ Balance map[common.Address]*hexutil.Big
+ Nonce map[common.Address]hexutil.Uint64
+ Storage map[common.Address]map[common.Hash]common.Hash
+}
+
+func newStateTracer(ctx *Context, cfg json.RawMessage, chainCfg *params.ChainConfig) (*Tracer, error) {
+ t := &stateTracer{
+ Balance: make(map[common.Address]*hexutil.Big),
+ Nonce: make(map[common.Address]hexutil.Uint64),
+ Storage: make(map[common.Address]map[common.Hash]common.Hash),
+ }
+ return &Tracer{
+ GetResult: func() (json.RawMessage, error) {
+ return json.Marshal(t)
+ },
+ Hooks: &tracing.Hooks{
+ OnBalanceChange: func(addr common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) {
+ t.Balance[addr] = (*hexutil.Big)(new)
+ },
+ OnNonceChange: func(addr common.Address, prev, new uint64) {
+ t.Nonce[addr] = hexutil.Uint64(new)
+ },
+ OnStorageChange: func(addr common.Address, slot common.Hash, prev, new common.Hash) {
+ if t.Storage[addr] == nil {
+ t.Storage[addr] = make(map[common.Hash]common.Hash)
+ }
+ t.Storage[addr][slot] = new
+ },
+ },
+ }, nil
+}
+
+func TestStateHooks(t *testing.T) {
+ t.Parallel()
+
+ // Initialize test accounts
+ var (
+ key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ from = crypto.PubkeyToAddress(key.PublicKey)
+ to = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
+ genesis = &core.Genesis{
+ Config: params.TestChainConfig,
+ Alloc: types.GenesisAlloc{
+ from: {Balance: big.NewInt(params.Ether)},
+ to: {
+ Code: []byte{
+ byte(vm.PUSH1), 0x2a, // stack: [42]
+ byte(vm.PUSH1), 0x0, // stack: [0, 42]
+ byte(vm.SSTORE), // stack: []
+ byte(vm.STOP),
+ },
+ },
+ },
+ }
+ genBlocks = 2
+ signer = types.HomesteadSigner{}
+ nonce = uint64(0)
+ backend = newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) {
+ // Transfer from account[0] to account[1]
+ // value: 1000 wei
+ // fee: 0 wei
+ tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
+ Nonce: nonce,
+ To: &to,
+ Value: big.NewInt(1000),
+ Gas: params.TxGas,
+ GasPrice: b.BaseFee(),
+ Data: nil}),
+ signer, key)
+ b.AddTx(tx)
+ nonce++
+ })
+ )
+ defer backend.teardown()
+ DefaultDirectory.Register("stateTracer", newStateTracer, false)
+ api := NewAPI(backend)
+ tracer := "stateTracer"
+ res, err := api.TraceCall(context.Background(), ethapi.TransactionArgs{From: &from, To: &to, Value: (*hexutil.Big)(big.NewInt(1000))}, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber), &TraceCallConfig{TraceConfig: TraceConfig{Tracer: &tracer}})
+ if err != nil {
+ t.Fatalf("failed to trace call: %v", err)
+ }
+ expected := `{"Balance":{"0x00000000000000000000000000000000deadbeef":"0x3e8","0x71562b71999873db5b286df957af199ec94617f7":"0xde0975924ed6f90"},"Nonce":{"0x71562b71999873db5b286df957af199ec94617f7":"0x3"},"Storage":{"0x00000000000000000000000000000000deadbeef":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x000000000000000000000000000000000000000000000000000000000000002a"}}}`
+ if expected != fmt.Sprintf("%s", res) {
+ t.Fatalf("unexpected trace result: have %s want %s", res, expected)
+ }
+}
+
func TestTraceCall(t *testing.T) {
t.Parallel()
@@ -448,7 +535,7 @@ func TestTraceTransaction(t *testing.T) {
Gas: params.TxGas,
Failed: false,
ReturnValue: "",
- StructLogs: []logger.StructLogRes{},
+ StructLogs: []json.RawMessage{},
}) {
t.Error("Transaction tracing result is different")
}
diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go
index 869558c324..999ab211c0 100644
--- a/eth/tracers/internal/tracetest/calltrace_test.go
+++ b/eth/tracers/internal/tracetest/calltrace_test.go
@@ -133,7 +133,6 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
t.Fatalf("failed to prepare transaction for tracing: %v", err)
}
evm := vm.NewEVM(context, logState, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
- evm.SetTxContext(core.NewEVMTxContext(msg))
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil {
@@ -206,11 +205,6 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
b.Fatalf("failed to parse testcase input: %v", err)
}
signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time))
- origin, _ := signer.Sender(tx)
- txContext := vm.TxContext{
- Origin: origin,
- GasPrice: tx.GasPrice(),
- }
context := test.Context.toBlockContext(test.Genesis)
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
if err != nil {
@@ -222,19 +216,25 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
- tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), nil, test.Genesis.Config)
- if err != nil {
- b.Fatalf("failed to create call tracer: %v", err)
- }
- evm := vm.NewEVM(context, state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
- evm.SetTxContext(txContext)
+ evm := vm.NewEVM(context, state.StateDB, test.Genesis.Config, vm.Config{})
for i := 0; i < b.N; i++ {
snap := state.StateDB.Snapshot()
- st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
- if _, err = st.TransitionDb(); err != nil {
+ tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), nil, test.Genesis.Config)
+ if err != nil {
+ b.Fatalf("failed to create call tracer: %v", err)
+ }
+ evm.Config.Tracer = tracer.Hooks
+ if tracer.OnTxStart != nil {
+ tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
+ }
+ _, err = core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
+ if err != nil {
b.Fatalf("failed to execute transaction: %v", err)
}
+ if tracer.OnTxEnd != nil {
+ tracer.OnTxEnd(&types.Receipt{GasUsed: tx.Gas()}, nil)
+ }
if _, err = tracer.GetResult(); err != nil {
b.Fatal(err)
}
@@ -372,12 +372,7 @@ func TestInternals(t *testing.T) {
if err != nil {
t.Fatalf("test %v: failed to sign transaction: %v", tc.name, err)
}
- txContext := vm.TxContext{
- Origin: origin,
- GasPrice: tx.GasPrice(),
- }
evm := vm.NewEVM(context, logState, config, vm.Config{Tracer: tc.tracer.Hooks})
- evm.SetTxContext(txContext)
msg, err := core.TransactionToMessage(tx, signer, big.NewInt(0))
if err != nil {
t.Fatalf("test %v: failed to create message: %v", tc.name, err)
diff --git a/eth/tracers/internal/tracetest/flat_calltrace_test.go b/eth/tracers/internal/tracetest/flat_calltrace_test.go
index 1dbdc7caac..553eaf1b57 100644
--- a/eth/tracers/internal/tracetest/flat_calltrace_test.go
+++ b/eth/tracers/internal/tracetest/flat_calltrace_test.go
@@ -99,7 +99,6 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string
return fmt.Errorf("failed to prepare transaction for tracing: %v", err)
}
evm := vm.NewEVM(context, state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
- evm.SetTxContext(core.NewEVMTxContext(msg))
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil {
diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go
index b8dec51db1..ad3d75d8de 100644
--- a/eth/tracers/internal/tracetest/prestate_test.go
+++ b/eth/tracers/internal/tracetest/prestate_test.go
@@ -107,7 +107,6 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) {
t.Fatalf("failed to prepare transaction for tracing: %v", err)
}
evm := vm.NewEVM(context, state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
- evm.SetTxContext(core.NewEVMTxContext(msg))
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil {
diff --git a/eth/tracers/internal/tracetest/supply_test.go b/eth/tracers/internal/tracetest/supply_test.go
index 2391add91b..6f06b7c0d5 100644
--- a/eth/tracers/internal/tracetest/supply_test.go
+++ b/eth/tracers/internal/tracetest/supply_test.go
@@ -86,7 +86,7 @@ func TestSupplyOmittedFields(t *testing.T) {
expected := supplyInfo{
Number: 0,
- Hash: common.HexToHash("0xc02ee8ee5b54a40e43f0fa827d431e1bd4f217e941790dda10b2521d1925a20b"),
+ Hash: common.HexToHash("0x3055fc27d6b4a08eb07033a0d1ee755a4b2988086f28a6189eac1b507525eeb1"),
ParentHash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
}
actual := out[expected.Number]
diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go
index 35abd00017..227ea57226 100644
--- a/eth/tracers/js/goja.go
+++ b/eth/tracers/js/goja.go
@@ -260,7 +260,7 @@ func (t *jsTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from
t.activePrecompiles = vm.ActivePrecompiles(rules)
t.ctx["block"] = t.vm.ToValue(t.env.BlockNumber.Uint64())
t.ctx["gas"] = t.vm.ToValue(tx.Gas())
- gasPriceBig, err := t.toBig(t.vm, env.GasPrice.String())
+ gasPriceBig, err := t.toBig(t.vm, tx.EffectiveGasTipValue(env.BaseFee).String())
if err != nil {
t.err = err
return
diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go
index 54f628b5f3..faad1e7194 100644
--- a/eth/tracers/js/tracer_test.go
+++ b/eth/tracers/js/tracer_test.go
@@ -59,7 +59,7 @@ type vmContext struct {
}
func testCtx() *vmContext {
- return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
+ return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1), BaseFee: big.NewInt(0)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
}
func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte) (json.RawMessage, error) {
@@ -76,7 +76,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo
contract.Code = contractCode
}
- tracer.OnTxStart(evm.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit}), contract.Caller())
+ tracer.OnTxStart(evm.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit, GasPrice: vmctx.txCtx.GasPrice}), contract.Caller())
tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value.ToBig())
ret, err := evm.Interpreter().Run(contract, []byte{}, false)
tracer.OnExit(0, ret, startGas-contract.Gas, err, true)
diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go
index bdd4e07055..51b1512d03 100644
--- a/eth/tracers/logger/logger.go
+++ b/eth/tracers/logger/logger.go
@@ -21,6 +21,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "maps"
"math/big"
"strings"
"sync/atomic"
@@ -38,15 +39,6 @@ import (
// Storage represents a contract's storage.
type Storage map[common.Hash]common.Hash
-// Copy duplicates the current storage.
-func (s Storage) Copy() Storage {
- cpy := make(Storage, len(s))
- for key, value := range s {
- cpy[key] = value
- }
- return cpy
-}
-
// Config are the configuration options for structured logger the EVM
type Config struct {
EnableMemory bool // enable memory capture
@@ -54,15 +46,16 @@ type Config struct {
DisableStorage bool // disable storage capture
EnableReturnData bool // enable return data capture
Debug bool // print output during capture end
- Limit int // maximum length of output, but zero means unlimited
+ Limit int // maximum size of output, but zero means unlimited
+
// Chain overrides, can be used to execute a trace using future fork rules
Overrides *params.ChainConfig `json:"overrides,omitempty"`
}
//go:generate go run github.com/fjl/gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
-// StructLog is emitted to the EVM each cycle and lists information about the current internal state
-// prior to the execution of the statement.
+// StructLog is emitted to the EVM each cycle and lists information about the
+// current internal state prior to the execution of the statement.
type StructLog struct {
Pc uint64 `json:"pc"`
Op vm.OpCode `json:"op"`
@@ -102,29 +95,144 @@ func (s *StructLog) ErrorString() string {
return ""
}
+// WriteTo writes the human-readable log data into the supplied writer.
+func (s *StructLog) WriteTo(writer io.Writer) {
+ fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", s.Op, s.Pc, s.Gas, s.GasCost)
+ if s.Err != nil {
+ fmt.Fprintf(writer, " ERROR: %v", s.Err)
+ }
+ fmt.Fprintln(writer)
+
+ if len(s.Stack) > 0 {
+ fmt.Fprintln(writer, "Stack:")
+ for i := len(s.Stack) - 1; i >= 0; i-- {
+ fmt.Fprintf(writer, "%08d %s\n", len(s.Stack)-i-1, s.Stack[i].Hex())
+ }
+ }
+ if len(s.Memory) > 0 {
+ fmt.Fprintln(writer, "Memory:")
+ fmt.Fprint(writer, hex.Dump(s.Memory))
+ }
+ if len(s.Storage) > 0 {
+ fmt.Fprintln(writer, "Storage:")
+ for h, item := range s.Storage {
+ fmt.Fprintf(writer, "%x: %x\n", h, item)
+ }
+ }
+ if len(s.ReturnData) > 0 {
+ fmt.Fprintln(writer, "ReturnData:")
+ fmt.Fprint(writer, hex.Dump(s.ReturnData))
+ }
+ fmt.Fprintln(writer)
+}
+
+// structLogLegacy stores a structured log emitted by the EVM while replaying a
+// transaction in debug mode. It's the legacy format used in tracer. The differences
+// between the structLog json and the 'legacy' json are:
+//
+// op:
+// Legacy uses string (e.g. "SSTORE"), non-legacy uses a byte.
+// non-legacy has an 'opName' field containing the op name.
+//
+// gas, gasCost:
+// Legacy uses integers, non-legacy hex-strings
+//
+// memory:
+// Legacy uses a list of 64-char strings, each representing 32-byte chunks
+// of evm memory. Non-legacy just uses a string of hexdata, no chunking.
+//
+// storage:
+// Legacy has a storage field while non-legacy doesn't.
+type structLogLegacy struct {
+ Pc uint64 `json:"pc"`
+ Op string `json:"op"`
+ Gas uint64 `json:"gas"`
+ GasCost uint64 `json:"gasCost"`
+ Depth int `json:"depth"`
+ Error string `json:"error,omitempty"`
+ Stack *[]string `json:"stack,omitempty"`
+ ReturnData string `json:"returnData,omitempty"`
+ Memory *[]string `json:"memory,omitempty"`
+ Storage *map[string]string `json:"storage,omitempty"`
+ RefundCounter uint64 `json:"refund,omitempty"`
+}
+
+// toLegacyJSON converts the structLog to legacy json-encoded legacy form.
+func (s *StructLog) toLegacyJSON() json.RawMessage {
+ msg := structLogLegacy{
+ Pc: s.Pc,
+ Op: s.Op.String(),
+ Gas: s.Gas,
+ GasCost: s.GasCost,
+ Depth: s.Depth,
+ Error: s.ErrorString(),
+ RefundCounter: s.RefundCounter,
+ }
+ if s.Stack != nil {
+ stack := make([]string, len(s.Stack))
+ for i, stackValue := range s.Stack {
+ stack[i] = stackValue.Hex()
+ }
+ msg.Stack = &stack
+ }
+ if len(s.ReturnData) > 0 {
+ msg.ReturnData = hexutil.Bytes(s.ReturnData).String()
+ }
+ if s.Memory != nil {
+ memory := make([]string, 0, (len(s.Memory)+31)/32)
+ for i := 0; i+32 <= len(s.Memory); i += 32 {
+ memory = append(memory, fmt.Sprintf("%x", s.Memory[i:i+32]))
+ }
+ msg.Memory = &memory
+ }
+ if s.Storage != nil {
+ storage := make(map[string]string)
+ for i, storageValue := range s.Storage {
+ storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
+ }
+ msg.Storage = &storage
+ }
+ element, _ := json.Marshal(msg)
+ return element
+}
+
// StructLogger is an EVM state logger and implements EVMLogger.
//
// StructLogger can capture state based on the given Log configuration and also keeps
// a track record of modified storage which is used in reporting snapshots of the
// contract their storage.
+//
+// A StructLogger can either yield it's output immediately (streaming) or store for
+// later output.
type StructLogger struct {
cfg Config
env *tracing.VMContext
storage map[common.Address]Storage
- logs []StructLog
output []byte
err error
usedGas uint64
+ writer io.Writer // If set, the logger will stream instead of store logs
+ logs []json.RawMessage // buffer of json-encoded logs
+ resultSize int
+
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
}
-// NewStructLogger returns a new logger
+// NewStreamingStructLogger returns a new streaming logger.
+func NewStreamingStructLogger(cfg *Config, writer io.Writer) *StructLogger {
+ l := NewStructLogger(cfg)
+ l.writer = writer
+ return l
+}
+
+// NewStructLogger construct a new (non-streaming) struct logger.
func NewStructLogger(cfg *Config) *StructLogger {
logger := &StructLogger{
storage: make(map[common.Address]Storage),
+ logs: make([]json.RawMessage, 0),
}
if cfg != nil {
logger.cfg = *cfg
@@ -141,44 +249,36 @@ func (l *StructLogger) Hooks() *tracing.Hooks {
}
}
-// Reset clears the data held by the logger.
-func (l *StructLogger) Reset() {
- l.storage = make(map[common.Address]Storage)
- l.output = make([]byte, 0)
- l.logs = l.logs[:0]
- l.err = nil
-}
-
// OnOpcode logs a new structured log message and pushes it out to the environment
//
// OnOpcode also tracks SLOAD/SSTORE ops to track storage change.
func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
- // If tracing was interrupted, set the error and stop
+ // If tracing was interrupted, exit
if l.interrupt.Load() {
return
}
- // check if already accumulated the specified number of logs
- if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
+ // check if already accumulated the size of the response.
+ if l.cfg.Limit != 0 && l.resultSize > l.cfg.Limit {
return
}
-
- op := vm.OpCode(opcode)
- memory := scope.MemoryData()
- stack := scope.StackData()
- // Copy a snapshot of the current memory state to a new buffer
- var mem []byte
+ var (
+ op = vm.OpCode(opcode)
+ memory = scope.MemoryData()
+ contractAddr = scope.Address()
+ stack = scope.StackData()
+ stackLen = len(stack)
+ )
+ log := StructLog{pc, op, gas, cost, nil, len(memory), nil, nil, nil, depth, l.env.StateDB.GetRefund(), err}
if l.cfg.EnableMemory {
- mem = make([]byte, len(memory))
- copy(mem, memory)
+ log.Memory = memory
}
- // Copy a snapshot of the current stack state to a new buffer
- var stck []uint256.Int
if !l.cfg.DisableStack {
- stck = make([]uint256.Int, len(stack))
- copy(stck, stack)
+ log.Stack = scope.StackData()
}
- contractAddr := scope.Address()
- stackLen := len(stack)
+ if l.cfg.EnableReturnData {
+ log.ReturnData = rData
+ }
+
// Copy a snapshot of the current storage to a new container
var storage Storage
if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) {
@@ -194,7 +294,7 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope
value = l.env.StateDB.GetState(contractAddr, address)
)
l.storage[contractAddr][address] = value
- storage = l.storage[contractAddr].Copy()
+ storage = maps.Clone(l.storage[contractAddr])
} else if op == vm.SSTORE && stackLen >= 2 {
// capture SSTORE opcodes and record the written entry in the local storage.
var (
@@ -202,17 +302,19 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope
address = common.Hash(stack[stackLen-1].Bytes32())
)
l.storage[contractAddr][address] = value
- storage = l.storage[contractAddr].Copy()
+ storage = maps.Clone(l.storage[contractAddr])
}
}
- var rdata []byte
- if l.cfg.EnableReturnData {
- rdata = make([]byte, len(rData))
- copy(rdata, rData)
+ log.Storage = storage
+
+ // create a log
+ if l.writer == nil {
+ entry := log.toLegacyJSON()
+ l.resultSize += len(entry)
+ l.logs = append(l.logs, entry)
+ return
}
- // create a new snapshot of the EVM.
- log := StructLog{pc, op, gas, cost, mem, len(memory), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err}
- l.logs = append(l.logs, log)
+ log.WriteTo(l.writer)
}
// OnExit is called a call frame finishes processing.
@@ -246,7 +348,7 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
Gas: l.usedGas,
Failed: failed,
ReturnValue: returnVal,
- StructLogs: formatLogs(l.StructLogs()),
+ StructLogs: l.logs,
})
}
@@ -273,9 +375,6 @@ func (l *StructLogger) OnTxEnd(receipt *types.Receipt, err error) {
}
}
-// StructLogs returns the captured log entries.
-func (l *StructLogger) StructLogs() []StructLog { return l.logs }
-
// Error returns the VM error captured by the trace.
func (l *StructLogger) Error() error { return l.err }
@@ -283,49 +382,10 @@ func (l *StructLogger) Error() error { return l.err }
func (l *StructLogger) Output() []byte { return l.output }
// WriteTrace writes a formatted trace to the given writer
+// @deprecated
func WriteTrace(writer io.Writer, logs []StructLog) {
for _, log := range logs {
- fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", log.Op, log.Pc, log.Gas, log.GasCost)
- if log.Err != nil {
- fmt.Fprintf(writer, " ERROR: %v", log.Err)
- }
- fmt.Fprintln(writer)
-
- if len(log.Stack) > 0 {
- fmt.Fprintln(writer, "Stack:")
- for i := len(log.Stack) - 1; i >= 0; i-- {
- fmt.Fprintf(writer, "%08d %s\n", len(log.Stack)-i-1, log.Stack[i].Hex())
- }
- }
- if len(log.Memory) > 0 {
- fmt.Fprintln(writer, "Memory:")
- fmt.Fprint(writer, hex.Dump(log.Memory))
- }
- if len(log.Storage) > 0 {
- fmt.Fprintln(writer, "Storage:")
- for h, item := range log.Storage {
- fmt.Fprintf(writer, "%x: %x\n", h, item)
- }
- }
- if len(log.ReturnData) > 0 {
- fmt.Fprintln(writer, "ReturnData:")
- fmt.Fprint(writer, hex.Dump(log.ReturnData))
- }
- fmt.Fprintln(writer)
- }
-}
-
-// WriteLogs writes vm logs in a readable format to the given writer
-func WriteLogs(writer io.Writer, logs []*types.Log) {
- for _, log := range logs {
- fmt.Fprintf(writer, "LOG%d: %x bn=%d txi=%x\n", len(log.Topics), log.Address, log.BlockNumber, log.TxIndex)
-
- for i, topic := range log.Topics {
- fmt.Fprintf(writer, "%08d %x\n", i, topic)
- }
-
- fmt.Fprint(writer, hex.Dump(log.Data))
- fmt.Fprintln(writer)
+ log.WriteTo(writer)
}
}
@@ -425,65 +485,8 @@ func (t *mdLogger) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.O
// while replaying a transaction in debug mode as well as transaction
// execution status, the amount of gas used and the return value
type ExecutionResult struct {
- Gas uint64 `json:"gas"`
- Failed bool `json:"failed"`
- ReturnValue string `json:"returnValue"`
- StructLogs []StructLogRes `json:"structLogs"`
-}
-
-// StructLogRes stores a structured log emitted by the EVM while replaying a
-// transaction in debug mode
-type StructLogRes struct {
- Pc uint64 `json:"pc"`
- Op string `json:"op"`
- Gas uint64 `json:"gas"`
- GasCost uint64 `json:"gasCost"`
- Depth int `json:"depth"`
- Error string `json:"error,omitempty"`
- Stack *[]string `json:"stack,omitempty"`
- ReturnData string `json:"returnData,omitempty"`
- Memory *[]string `json:"memory,omitempty"`
- Storage *map[string]string `json:"storage,omitempty"`
- RefundCounter uint64 `json:"refund,omitempty"`
-}
-
-// formatLogs formats EVM returned structured logs for json output
-func formatLogs(logs []StructLog) []StructLogRes {
- formatted := make([]StructLogRes, len(logs))
- for index, trace := range logs {
- formatted[index] = StructLogRes{
- Pc: trace.Pc,
- Op: trace.Op.String(),
- Gas: trace.Gas,
- GasCost: trace.GasCost,
- Depth: trace.Depth,
- Error: trace.ErrorString(),
- RefundCounter: trace.RefundCounter,
- }
- if trace.Stack != nil {
- stack := make([]string, len(trace.Stack))
- for i, stackValue := range trace.Stack {
- stack[i] = stackValue.Hex()
- }
- formatted[index].Stack = &stack
- }
- if len(trace.ReturnData) > 0 {
- formatted[index].ReturnData = hexutil.Bytes(trace.ReturnData).String()
- }
- if trace.Memory != nil {
- memory := make([]string, 0, (len(trace.Memory)+31)/32)
- for i := 0; i+32 <= len(trace.Memory); i += 32 {
- memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
- }
- formatted[index].Memory = &memory
- }
- if trace.Storage != nil {
- storage := make(map[string]string)
- for i, storageValue := range trace.Storage {
- storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
- }
- formatted[index].Storage = &storage
- }
- }
- return formatted
+ Gas uint64 `json:"gas"`
+ Failed bool `json:"failed"`
+ ReturnValue string `json:"returnValue"`
+ StructLogs []json.RawMessage `json:"structLogs"`
}
diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go
index 72cc1bde25..13c36379e7 100644
--- a/eth/tracers/tracers_test.go
+++ b/eth/tracers/tracers_test.go
@@ -31,7 +31,7 @@ import (
"github.com/ethereum/go-ethereum/tests"
)
-func BenchmarkTransactionTrace(b *testing.B) {
+func BenchmarkTransactionTraceV2(b *testing.B) {
key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
from := crypto.PubkeyToAddress(key.PublicKey)
gas := uint64(1000000) // 1M gas
@@ -47,10 +47,6 @@ func BenchmarkTransactionTrace(b *testing.B) {
if err != nil {
b.Fatal(err)
}
- txContext := vm.TxContext{
- Origin: from,
- GasPrice: tx.GasPrice(),
- }
context := vm.BlockContext{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
@@ -82,15 +78,8 @@ func BenchmarkTransactionTrace(b *testing.B) {
state := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false, rawdb.HashScheme)
defer state.Close()
- // Create the tracer, the EVM environment and run it
- tracer := logger.NewStructLogger(&logger.Config{
- Debug: false,
- //DisableStorage: true,
- //EnableMemory: false,
- //EnableReturnData: false,
- })
- evm := vm.NewEVM(context, state.StateDB, params.AllEthashProtocolChanges, vm.Config{Tracer: tracer.Hooks()})
- evm.SetTxContext(txContext)
+ evm := vm.NewEVM(context, state.StateDB, params.AllEthashProtocolChanges, vm.Config{})
+
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
if err != nil {
b.Fatalf("failed to prepare transaction for tracing: %v", err)
@@ -99,18 +88,15 @@ func BenchmarkTransactionTrace(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- snap := state.StateDB.Snapshot()
+ tracer := logger.NewStructLogger(&logger.Config{Debug: false}).Hooks()
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
- st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
- res, err := st.TransitionDb()
+ evm.Config.Tracer = tracer
+
+ snap := state.StateDB.Snapshot()
+ _, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil {
b.Fatal(err)
}
- tracer.OnTxEnd(&types.Receipt{GasUsed: res.UsedGas}, nil)
state.StateDB.RevertToSnapshot(snap)
- if have, want := len(tracer.StructLogs()), 244752; have != want {
- b.Fatalf("trace wrong, want %d steps, have %d", want, have)
- }
- tracer.Reset()
}
}
diff --git a/ethclient/simulated/backend.go b/ethclient/simulated/backend.go
index 6e07aa68d0..65d44b9efa 100644
--- a/ethclient/simulated/backend.go
+++ b/ethclient/simulated/backend.go
@@ -26,7 +26,6 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/catalyst"
- "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/ethclient"
@@ -85,7 +84,7 @@ func NewBackend(alloc types.GenesisAlloc, options ...func(nodeConf *node.Config,
GasLimit: ethconfig.Defaults.Miner.GasCeil,
Alloc: alloc,
}
- ethConf.SyncMode = downloader.FullSync
+ ethConf.SyncMode = ethconfig.FullSync
ethConf.TxPool.NoLocals = true
for _, option := range options {
diff --git a/go.mod b/go.mod
index 1928f7b5c5..50c1a42e68 100644
--- a/go.mod
+++ b/go.mod
@@ -15,16 +15,16 @@ require (
github.com/cespare/cp v0.1.0
github.com/cloudflare/cloudflare-go v0.79.0
github.com/cockroachdb/pebble v1.1.2
- github.com/consensys/gnark-crypto v0.12.1
- github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c
- github.com/crate-crypto/go-kzg-4844 v1.0.0
+ github.com/consensys/gnark-crypto v0.14.0
+ github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a
+ github.com/crate-crypto/go-kzg-4844 v1.1.0
github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set/v2 v2.6.0
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3
github.com/ethereum/c-kzg-4844 v1.0.0
- github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9
+ github.com/ethereum/go-verkle v0.2.2
github.com/fatih/color v1.16.0
github.com/ferranbt/fastssz v0.1.2
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e
@@ -70,13 +70,13 @@ require (
github.com/urfave/cli/v2 v2.25.7
go.uber.org/automaxprocs v1.5.2
go.uber.org/zap v1.27.0
- golang.org/x/crypto v0.22.0
+ golang.org/x/crypto v0.26.0
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
- golang.org/x/sync v0.7.0
- golang.org/x/sys v0.26.0
- golang.org/x/text v0.14.0
+ golang.org/x/sync v0.10.0
+ golang.org/x/sys v0.28.0
+ golang.org/x/text v0.17.0
golang.org/x/time v0.5.0
- golang.org/x/tools v0.20.0
+ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d
google.golang.org/protobuf v1.34.2
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v2 v2.4.0
@@ -98,14 +98,14 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 // indirect
github.com/aws/smithy-go v1.15.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
- github.com/bits-and-blooms/bitset v1.13.0 // indirect
+ github.com/bits-and-blooms/bitset v1.17.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cockroachdb/errors v1.11.3 // indirect
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
- github.com/consensys/bavard v0.1.13 // indirect
+ github.com/consensys/bavard v0.1.22 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/deepmap/oapi-codegen v1.6.0 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect
@@ -145,7 +145,7 @@ require (
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
- github.com/rogpeppe/go-internal v1.9.0 // indirect
+ github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
@@ -153,7 +153,7 @@ require (
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.17.0 // indirect
- golang.org/x/net v0.24.0 // indirect
+ golang.org/x/net v0.25.0 // indirect
rsc.io/tmplfunc v0.0.3 // indirect
)
diff --git a/go.sum b/go.sum
index f3b591ce60..891a6ed426 100644
--- a/go.sum
+++ b/go.sum
@@ -90,8 +90,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
-github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
+github.com/bits-and-blooms/bitset v1.17.0 h1:1X2TS7aHz1ELcC0yU1y2stUs/0ig5oMU6STFZGrhvHI=
+github.com/bits-and-blooms/bitset v1.17.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
@@ -124,16 +124,16 @@ github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwP
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
-github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
-github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
-github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M=
-github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
+github.com/consensys/bavard v0.1.22 h1:Uw2CGvbXSZWhqK59X0VG/zOjpTFuOMcPLStrp1ihI0A=
+github.com/consensys/bavard v0.1.22/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs=
+github.com/consensys/gnark-crypto v0.14.0 h1:DDBdl4HaBtdQsq/wfMwJvZNE80sHidrK3Nfrefatm0E=
+github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I=
-github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs=
-github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI=
-github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc=
+github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg=
+github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM=
+github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4=
+github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -166,8 +166,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA=
github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
-github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 h1:8NfxH2iXvJ60YRB8ChToFTUzl8awsc3cJ8CbLjGIl/A=
-github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
+github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
+github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk=
@@ -261,8 +261,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -363,8 +363,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
-github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
-github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
+github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
+github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
@@ -483,8 +483,9 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -558,8 +559,8 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
-golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
-golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
+golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
+golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -634,8 +635,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
-golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
-golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
+golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -655,8 +656,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -720,8 +721,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
-golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -738,8 +739,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
+golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -791,8 +792,8 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
-golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/debug/flags.go b/internal/debug/flags.go
index 5e4f97e074..d013a13d41 100644
--- a/internal/debug/flags.go
+++ b/internal/debug/flags.go
@@ -136,8 +136,8 @@ var (
Category: flags.LoggingCategory,
}
traceFlag = &cli.StringFlag{
- Name: "trace",
- Usage: "Write execution trace to the given file",
+ Name: "go-execution-trace",
+ Usage: "Write Go execution trace to the given file",
Category: flags.LoggingCategory,
}
)
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index ded4db50aa..067d07ba7a 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -867,7 +867,6 @@ func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *s
if precompiles != nil {
evm.SetPrecompiles(precompiles)
}
- evm.SetTxContext(core.NewEVMTxContext(msg))
res, err := applyMessageWithEVM(ctx, evm, msg, timeout, gp)
// If an internal state error occurred, let that have precedence. Otherwise,
// a "trie root missing" type of error will masquerade as e.g. "insufficient gas"
@@ -1331,17 +1330,17 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
// Apply the transaction with the access list tracer
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
config := vm.Config{Tracer: tracer.Hooks(), NoBaseFee: true}
- vmenv := b.GetEVM(ctx, statedb, header, &config, nil)
+ evm := b.GetEVM(ctx, statedb, header, &config, nil)
+
// Lower the basefee to 0 to avoid breaking EVM
// invariants (basefee < feecap).
if msg.GasPrice.Sign() == 0 {
- vmenv.Context.BaseFee = new(big.Int)
+ evm.Context.BaseFee = new(big.Int)
}
if msg.BlobGasFeeCap != nil && msg.BlobGasFeeCap.BitLen() == 0 {
- vmenv.Context.BlobBaseFee = new(big.Int)
+ evm.Context.BlobBaseFee = new(big.Int)
}
- vmenv.SetTxContext(core.NewEVMTxContext(msg))
- res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit))
+ res, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit))
if err != nil {
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.ToTransaction(types.LegacyTxType).Hash(), err)
}
diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go
index 2161e1d5f4..f6647c7ba4 100644
--- a/internal/ethapi/simulate.go
+++ b/internal/ethapi/simulate.go
@@ -207,7 +207,6 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
tracer.reset(tx.Hash(), uint(i))
// EoA check is always skipped, even in validation mode.
msg := call.ToMessage(header.BaseFee, !sim.validate, true)
- evm.SetTxContext(core.NewEVMTxContext(msg))
result, err := applyMessageWithEVM(ctx, evm, msg, timeout, sim.gp)
if err != nil {
txErr := txValidationError(err)
diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-blob-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-blob-tx.json
index d315353ec6..df2d9349fb 100644
--- a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-blob-tx.json
+++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-blob-tx.json
@@ -2,7 +2,7 @@
{
"blobGasPrice": "0x1",
"blobGasUsed": "0x20000",
- "blockHash": "0x11e6318d77a45c01f89f76b56d36c6936c5250f4e2bd238cb7b09df73cf0cb7d",
+ "blockHash": "0x17124e31fb075a301b1d7d4135683b0a09fe4e6d453c54e2e734d5ee00744a49",
"blockNumber": "0x6",
"contractAddress": null,
"cumulativeGasUsed": "0x5208",
diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-contract-create-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-contract-create-tx.json
index f2e5ced2be..453e0abe8a 100644
--- a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-contract-create-tx.json
+++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-contract-create-tx.json
@@ -1,6 +1,6 @@
[
{
- "blockHash": "0x5526cd89bc188f20fd5e9bb50d8054dc5a51a81a74ed07eacf36a4a8b10de4b1",
+ "blockHash": "0xb3e447c77374fd285964cba692e96b1673a88a959726826b5b6e2dca15472b0a",
"blockNumber": "0x2",
"contractAddress": "0xae9bea628c4ce503dcfd7e305cab4e29e7476592",
"cumulativeGasUsed": "0xcf50",
diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-dynamic-fee-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-dynamic-fee-tx.json
index 71afd85e54..b01400e605 100644
--- a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-dynamic-fee-tx.json
+++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-dynamic-fee-tx.json
@@ -1,6 +1,6 @@
[
{
- "blockHash": "0x3e946aa9e252873af511b257d9d89a1bcafa54ce7c6a6442f8407ecdf81e288d",
+ "blockHash": "0x102e50de30318ee99a03a09db74387e79cad3165bf6840cc84249806a2a302f3",
"blockNumber": "0x4",
"contractAddress": null,
"cumulativeGasUsed": "0x538d",
diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-contract-call-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-contract-call-tx.json
index f089ac45ae..ae964b3d3b 100644
--- a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-contract-call-tx.json
+++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-contract-call-tx.json
@@ -1,6 +1,6 @@
[
{
- "blockHash": "0xc281d4299fc4e8ce5bba7ecb8deb50f5403d604c806b36aa887dfe2ff84c064f",
+ "blockHash": "0xcc6225bf39327429a3d869af71182d619a354155187d0b5a8ecd6a9309cffcaa",
"blockNumber": "0x3",
"contractAddress": null,
"cumulativeGasUsed": "0x5e28",
@@ -19,7 +19,7 @@
"blockNumber": "0x3",
"transactionHash": "0xeaf3921cbf03ba45bad4e6ab807b196ce3b2a0b5bacc355b6272fa96b11b4287",
"transactionIndex": "0x0",
- "blockHash": "0xc281d4299fc4e8ce5bba7ecb8deb50f5403d604c806b36aa887dfe2ff84c064f",
+ "blockHash": "0xcc6225bf39327429a3d869af71182d619a354155187d0b5a8ecd6a9309cffcaa",
"logIndex": "0x0",
"removed": false
}
diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-transfer-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-transfer-tx.json
index 8b69dddd66..7af5016079 100644
--- a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-transfer-tx.json
+++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-transfer-tx.json
@@ -1,6 +1,6 @@
[
{
- "blockHash": "0xda50d57d8802553b00bb8e4d777bd5c4114086941119ca04edb15429f4818ed9",
+ "blockHash": "0xe9bd1d8c303b1af5c704b9d78e62c54a34af47e0db04ac1389a5ef74a619b9da",
"blockNumber": "0x1",
"contractAddress": null,
"cumulativeGasUsed": "0x5208",
diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-tag-latest.json b/internal/ethapi/testdata/eth_getBlockReceipts-tag-latest.json
index d315353ec6..df2d9349fb 100644
--- a/internal/ethapi/testdata/eth_getBlockReceipts-tag-latest.json
+++ b/internal/ethapi/testdata/eth_getBlockReceipts-tag-latest.json
@@ -2,7 +2,7 @@
{
"blobGasPrice": "0x1",
"blobGasUsed": "0x20000",
- "blockHash": "0x11e6318d77a45c01f89f76b56d36c6936c5250f4e2bd238cb7b09df73cf0cb7d",
+ "blockHash": "0x17124e31fb075a301b1d7d4135683b0a09fe4e6d453c54e2e734d5ee00744a49",
"blockNumber": "0x6",
"contractAddress": null,
"cumulativeGasUsed": "0x5208",
diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-blob-tx.json b/internal/ethapi/testdata/eth_getTransactionReceipt-blob-tx.json
index 5debbd4447..8e0669d10a 100644
--- a/internal/ethapi/testdata/eth_getTransactionReceipt-blob-tx.json
+++ b/internal/ethapi/testdata/eth_getTransactionReceipt-blob-tx.json
@@ -1,7 +1,7 @@
{
"blobGasPrice": "0x1",
"blobGasUsed": "0x20000",
- "blockHash": "0x11e6318d77a45c01f89f76b56d36c6936c5250f4e2bd238cb7b09df73cf0cb7d",
+ "blockHash": "0x17124e31fb075a301b1d7d4135683b0a09fe4e6d453c54e2e734d5ee00744a49",
"blockNumber": "0x6",
"contractAddress": null,
"cumulativeGasUsed": "0x5208",
diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-tx.json b/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-tx.json
index 8cf2ead10f..a9391cb578 100644
--- a/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-tx.json
+++ b/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-tx.json
@@ -1,5 +1,5 @@
{
- "blockHash": "0x5526cd89bc188f20fd5e9bb50d8054dc5a51a81a74ed07eacf36a4a8b10de4b1",
+ "blockHash": "0xb3e447c77374fd285964cba692e96b1673a88a959726826b5b6e2dca15472b0a",
"blockNumber": "0x2",
"contractAddress": "0xae9bea628c4ce503dcfd7e305cab4e29e7476592",
"cumulativeGasUsed": "0xcf50",
diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-with-access-list.json b/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-with-access-list.json
index 34c318faca..49c06aad62 100644
--- a/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-with-access-list.json
+++ b/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-with-access-list.json
@@ -1,5 +1,5 @@
{
- "blockHash": "0xa04ad6be58c45fe483991b89416572bc50426b0de44b769757e95c704250f874",
+ "blockHash": "0x53bffe54375c0a31fe7bc0db7455db7d48278234c2400efa4d40d1c57cbe868d",
"blockNumber": "0x5",
"contractAddress": "0xfdaa97661a584d977b4d3abb5370766ff5b86a18",
"cumulativeGasUsed": "0xe01c",
diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-dynamic-tx-with-logs.json b/internal/ethapi/testdata/eth_getTransactionReceipt-dynamic-tx-with-logs.json
index 9f023ed6e3..13bd7bd12c 100644
--- a/internal/ethapi/testdata/eth_getTransactionReceipt-dynamic-tx-with-logs.json
+++ b/internal/ethapi/testdata/eth_getTransactionReceipt-dynamic-tx-with-logs.json
@@ -1,5 +1,5 @@
{
- "blockHash": "0x3e946aa9e252873af511b257d9d89a1bcafa54ce7c6a6442f8407ecdf81e288d",
+ "blockHash": "0x102e50de30318ee99a03a09db74387e79cad3165bf6840cc84249806a2a302f3",
"blockNumber": "0x4",
"contractAddress": null,
"cumulativeGasUsed": "0x538d",
diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-normal-transfer-tx.json b/internal/ethapi/testdata/eth_getTransactionReceipt-normal-transfer-tx.json
index f180a21977..779223af98 100644
--- a/internal/ethapi/testdata/eth_getTransactionReceipt-normal-transfer-tx.json
+++ b/internal/ethapi/testdata/eth_getTransactionReceipt-normal-transfer-tx.json
@@ -1,5 +1,5 @@
{
- "blockHash": "0xda50d57d8802553b00bb8e4d777bd5c4114086941119ca04edb15429f4818ed9",
+ "blockHash": "0xe9bd1d8c303b1af5c704b9d78e62c54a34af47e0db04ac1389a5ef74a619b9da",
"blockNumber": "0x1",
"contractAddress": null,
"cumulativeGasUsed": "0x5208",
diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-with-logs.json b/internal/ethapi/testdata/eth_getTransactionReceipt-with-logs.json
index 61aed4b7bd..1a1edb7887 100644
--- a/internal/ethapi/testdata/eth_getTransactionReceipt-with-logs.json
+++ b/internal/ethapi/testdata/eth_getTransactionReceipt-with-logs.json
@@ -1,5 +1,5 @@
{
- "blockHash": "0xc281d4299fc4e8ce5bba7ecb8deb50f5403d604c806b36aa887dfe2ff84c064f",
+ "blockHash": "0xcc6225bf39327429a3d869af71182d619a354155187d0b5a8ecd6a9309cffcaa",
"blockNumber": "0x3",
"contractAddress": null,
"cumulativeGasUsed": "0x5e28",
@@ -18,7 +18,7 @@
"blockNumber": "0x3",
"transactionHash": "0xeaf3921cbf03ba45bad4e6ab807b196ce3b2a0b5bacc355b6272fa96b11b4287",
"transactionIndex": "0x0",
- "blockHash": "0xc281d4299fc4e8ce5bba7ecb8deb50f5403d604c806b36aa887dfe2ff84c064f",
+ "blockHash": "0xcc6225bf39327429a3d869af71182d619a354155187d0b5a8ecd6a9309cffcaa",
"logIndex": "0x0",
"removed": false
}
diff --git a/miner/worker.go b/miner/worker.go
index aeb6cfcdc2..b5aa080025 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -121,18 +121,15 @@ func (miner *Miner) generateWork(params *generateParams, witness bool) *newPaylo
// Collect consensus-layer requests if Prague is enabled.
var requests [][]byte
if miner.chainConfig.IsPrague(work.header.Number, work.header.Time) {
+ requests = [][]byte{}
// EIP-6110 deposits
- depositRequests, err := core.ParseDepositLogs(allLogs, miner.chainConfig)
- if err != nil {
+ if err := core.ParseDepositLogs(&requests, allLogs, miner.chainConfig); err != nil {
return &newPayloadResult{err: err}
}
- requests = append(requests, depositRequests)
- // EIP-7002 withdrawals
- withdrawalRequests := core.ProcessWithdrawalQueue(work.evm)
- requests = append(requests, withdrawalRequests)
+ // EIP-7002
+ core.ProcessWithdrawalQueue(&requests, work.evm)
// EIP-7251 consolidations
- consolidationRequests := core.ProcessConsolidationQueue(work.evm)
- requests = append(requests, consolidationRequests)
+ core.ProcessConsolidationQueue(&requests, work.evm)
}
if requests != nil {
reqHash := types.CalcRequestsHash(requests)
diff --git a/oss-fuzz.sh b/oss-fuzz.sh
index 5e4aa1c253..1f222c433b 100644
--- a/oss-fuzz.sh
+++ b/oss-fuzz.sh
@@ -234,10 +234,6 @@ compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/secp256k1 \
compile_fuzzer github.com/ethereum/go-ethereum/eth/protocols/eth \
FuzzEthProtocolHandlers fuzz_eth_protocol_handlers \
- $repo/eth/protocols/eth/handler_test.go
+ $repo/eth/protocols/eth/handler_test.go,$repo/eth/protocols/eth/peer_test.go
-#compile_fuzzer tests/fuzzers/vflux FuzzClientPool fuzzClientPool
-#compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty
-#compile_fuzzer tests/fuzzers/les Fuzz fuzzLes
-
diff --git a/tests/state_test_util.go b/tests/state_test_util.go
index 446ffb40d5..6884ae7ed5 100644
--- a/tests/state_test_util.go
+++ b/tests/state_test_util.go
@@ -277,7 +277,6 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
}
// Prepare the EVM.
- txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase)
context.GetHash = vmTestBlockHash
context.BaseFee = baseFee
@@ -294,7 +293,6 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
context.BlobBaseFee = eip4844.CalcBlobFee(*t.json.Env.ExcessBlobGas)
}
evm := vm.NewEVM(context, st.StateDB, config, vmconfig)
- evm.SetTxContext(txContext)
if tracer := vmconfig.Tracer; tracer != nil && tracer.OnTxStart != nil {
tracer.OnTxStart(evm.GetVMContext(), nil, msg.From)
diff --git a/trie/proof.go b/trie/proof.go
index 40836fba33..2e527348bf 100644
--- a/trie/proof.go
+++ b/trie/proof.go
@@ -486,13 +486,11 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, keys [][]byte, valu
return false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values))
}
// Ensure the received batch is monotonic increasing and contains no deletions
- for i := 0; i < len(keys)-1; i++ {
- if bytes.Compare(keys[i], keys[i+1]) >= 0 {
+ for i := 0; i < len(keys); i++ {
+ if i < len(keys)-1 && bytes.Compare(keys[i], keys[i+1]) >= 0 {
return false, errors.New("range is not monotonically increasing")
}
- }
- for _, value := range values {
- if len(value) == 0 {
+ if len(values[i]) == 0 {
return false, errors.New("range contains deletion")
}
}
diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go
index b785e512b1..1d33f6c3e5 100644
--- a/trie/utils/verkle.go
+++ b/trie/utils/verkle.go
@@ -41,10 +41,10 @@ var (
zero = uint256.NewInt(0)
verkleNodeWidthLog2 = 8
headerStorageOffset = uint256.NewInt(64)
- mainStorageOffsetLshVerkleNodeWidth = new(uint256.Int).Lsh(uint256.NewInt(256), 31-uint(verkleNodeWidthLog2))
codeOffset = uint256.NewInt(128)
verkleNodeWidth = uint256.NewInt(256)
codeStorageDelta = uint256.NewInt(0).Sub(codeOffset, headerStorageOffset)
+ mainStorageOffsetLshVerkleNodeWidth = new(uint256.Int).Lsh(uint256.NewInt(1), 248-uint(verkleNodeWidthLog2))
index0Point *verkle.Point // pre-computed commitment of polynomial [2+256*64]
@@ -273,17 +273,9 @@ func StorageSlotKeyWithEvaluatedAddress(evaluated *verkle.Point, storageKey []by
}
func pointToHash(evaluated *verkle.Point, suffix byte) []byte {
- // The output of Byte() is big endian for banderwagon. This
- // introduces an imbalance in the tree, because hashes are
- // elements of a 253-bit field. This means more than half the
- // tree would be empty. To avoid this problem, use a little
- // endian commitment and chop the MSB.
- bytes := evaluated.Bytes()
- for i := 0; i < 16; i++ {
- bytes[31-i], bytes[i] = bytes[i], bytes[31-i]
- }
- bytes[31] = suffix
- return bytes[:]
+ retb := verkle.HashPointToBytes(evaluated)
+ retb[31] = suffix
+ return retb[:]
}
func evaluateAddressPoint(address []byte) *verkle.Point {
diff --git a/trie/verkle_test.go b/trie/verkle_test.go
index 4cd1717c0e..84eec2ed30 100644
--- a/trie/verkle_test.go
+++ b/trie/verkle_test.go
@@ -136,8 +136,8 @@ func TestVerkleRollBack(t *testing.T) {
}
}
- // ensure there is some code in the 2nd group
- keyOf2ndGroup := []byte{141, 124, 185, 236, 50, 22, 185, 39, 244, 47, 97, 209, 96, 235, 22, 13, 205, 38, 18, 201, 128, 223, 0, 59, 146, 199, 222, 119, 133, 13, 91, 0}
+ // ensure there is some code in the 2nd group of the 1st account
+ keyOf2ndGroup := utils.CodeChunkKeyWithEvaluatedAddress(tr.cache.Get(common.Address{1}.Bytes()), uint256.NewInt(128))
chunk, err := tr.root.Get(keyOf2ndGroup, nil)
if err != nil {
t.Fatalf("Failed to get account, %v", err)
diff --git a/triedb/database.go b/triedb/database.go
index d3eca57b54..b448d7cd07 100644
--- a/triedb/database.go
+++ b/triedb/database.go
@@ -60,6 +60,10 @@ type backend interface {
// An error will be returned if the specified state is not available.
NodeReader(root common.Hash) (database.NodeReader, error)
+ // StateReader returns a reader for accessing flat states within the specified
+ // state. An error will be returned if the specified state is not available.
+ StateReader(root common.Hash) (database.StateReader, error)
+
// Initialized returns an indicator if the state data is already initialized
// according to the state scheme.
Initialized(genesisRoot common.Hash) bool
@@ -122,6 +126,13 @@ func (db *Database) NodeReader(blockRoot common.Hash) (database.NodeReader, erro
return db.backend.NodeReader(blockRoot)
}
+// StateReader returns a reader that allows access to the state data associated
+// with the specified state. An error will be returned if the specified state is
+// not available.
+func (db *Database) StateReader(blockRoot common.Hash) (database.StateReader, error) {
+ return db.backend.StateReader(blockRoot)
+}
+
// Update performs a state transition by committing dirty nodes contained in the
// given set in order to update state from the specified parent to the specified
// root. The held pre-images accumulated up to this point will be flushed in case
diff --git a/triedb/hashdb/database.go b/triedb/hashdb/database.go
index 5de7805c31..fb718f4e74 100644
--- a/triedb/hashdb/database.go
+++ b/triedb/hashdb/database.go
@@ -635,3 +635,9 @@ func (reader *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]
blob, _ := reader.db.node(hash)
return blob, nil
}
+
+// StateReader returns a reader that allows access to the state data associated
+// with the specified state.
+func (db *Database) StateReader(root common.Hash) (database.StateReader, error) {
+ return nil, errors.New("not implemented")
+}
diff --git a/triedb/pathdb/buffer.go b/triedb/pathdb/buffer.go
index 3f1d0e90ee..68e136f193 100644
--- a/triedb/pathdb/buffer.go
+++ b/triedb/pathdb/buffer.go
@@ -33,40 +33,56 @@ import (
// must be checked before diving into disk (since it basically is not yet written
// data).
type buffer struct {
- layers uint64 // The number of diff layers aggregated inside
- limit uint64 // The maximum memory allowance in bytes
- nodes *nodeSet // Aggregated trie node set
+ layers uint64 // The number of diff layers aggregated inside
+ limit uint64 // The maximum memory allowance in bytes
+ nodes *nodeSet // Aggregated trie node set
+ states *stateSet // Aggregated state set
}
// newBuffer initializes the buffer with the provided states and trie nodes.
-func newBuffer(limit int, nodes *nodeSet, layers uint64) *buffer {
+func newBuffer(limit int, nodes *nodeSet, states *stateSet, layers uint64) *buffer {
// Don't panic for lazy users if any provided set is nil
if nodes == nil {
nodes = newNodeSet(nil)
}
+ if states == nil {
+ states = newStates(nil, nil)
+ }
return &buffer{
layers: layers,
limit: uint64(limit),
nodes: nodes,
+ states: states,
}
}
+// account retrieves the account blob with account address hash.
+func (b *buffer) account(hash common.Hash) ([]byte, bool) {
+ return b.states.account(hash)
+}
+
+// storage retrieves the storage slot with account address hash and slot key.
+func (b *buffer) storage(addrHash common.Hash, storageHash common.Hash) ([]byte, bool) {
+ return b.states.storage(addrHash, storageHash)
+}
+
// node retrieves the trie node with node path and its trie identifier.
func (b *buffer) node(owner common.Hash, path []byte) (*trienode.Node, bool) {
return b.nodes.node(owner, path)
}
// commit merges the provided states and trie nodes into the buffer.
-func (b *buffer) commit(nodes *nodeSet) *buffer {
+func (b *buffer) commit(nodes *nodeSet, states *stateSet) *buffer {
b.layers++
b.nodes.merge(nodes)
+ b.states.merge(states)
return b
}
-// revert is the reverse operation of commit. It also merges the provided states
+// revertTo is the reverse operation of commit. It also merges the provided states
// and trie nodes into the buffer. The key difference is that the provided state
// set should reverse the changes made by the most recent state transition.
-func (b *buffer) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) error {
+func (b *buffer) revertTo(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node, accounts map[common.Hash][]byte, storages map[common.Hash]map[common.Hash][]byte) error {
// Short circuit if no embedded state transition to revert
if b.layers == 0 {
return errStateUnrecoverable
@@ -78,7 +94,8 @@ func (b *buffer) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[strin
b.reset()
return nil
}
- b.nodes.revert(db, nodes)
+ b.nodes.revertTo(db, nodes)
+ b.states.revertTo(accounts, storages)
return nil
}
@@ -86,6 +103,7 @@ func (b *buffer) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[strin
func (b *buffer) reset() {
b.layers = 0
b.nodes.reset()
+ b.states.reset()
}
// empty returns an indicator if buffer is empty.
@@ -101,7 +119,7 @@ func (b *buffer) full() bool {
// size returns the approximate memory size of the held content.
func (b *buffer) size() uint64 {
- return b.nodes.size
+ return b.states.size + b.nodes.size
}
// flush persists the in-memory dirty trie node into the disk if the configured
diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go
index 48d46c7b08..cfbdb01c49 100644
--- a/triedb/pathdb/database.go
+++ b/triedb/pathdb/database.go
@@ -68,6 +68,24 @@ type layer interface {
// - no error will be returned if the requested node is not found in database.
node(owner common.Hash, path []byte, depth int) ([]byte, common.Hash, *nodeLoc, error)
+ // account directly retrieves the account RLP associated with a particular
+ // hash in the slim data format. An error will be returned if the read
+ // operation exits abnormally. Specifically, if the layer is already stale.
+ //
+ // Note:
+ // - the returned account is not a copy, please don't modify it.
+ // - no error will be returned if the requested account is not found in database.
+ account(hash common.Hash, depth int) ([]byte, error)
+
+ // storage directly retrieves the storage data associated with a particular hash,
+ // within a particular account. An error will be returned if the read operation
+ // exits abnormally. Specifically, if the layer is already stale.
+ //
+ // Note:
+ // - the returned storage data is not a copy, please don't modify it.
+ // - no error will be returned if the requested slot is not found in database.
+ storage(accountHash, storageHash common.Hash, depth int) ([]byte, error)
+
// rootHash returns the root hash for which this layer was made.
rootHash() common.Hash
@@ -130,17 +148,18 @@ var Defaults = &Config{
// ReadOnly is the config in order to open database in read only mode.
var ReadOnly = &Config{ReadOnly: true}
-// Database is a multiple-layered structure for maintaining in-memory trie nodes.
-// It consists of one persistent base layer backed by a key-value store, on top
-// of which arbitrarily many in-memory diff layers are stacked. The memory diffs
-// can form a tree with branching, but the disk layer is singleton and common to
-// all. If a reorg goes deeper than the disk layer, a batch of reverse diffs can
-// be applied to rollback. The deepest reorg that can be handled depends on the
-// amount of state histories tracked in the disk.
+// Database is a multiple-layered structure for maintaining in-memory states
+// along with its dirty trie nodes. It consists of one persistent base layer
+// backed by a key-value store, on top of which arbitrarily many in-memory diff
+// layers are stacked. The memory diffs can form a tree with branching, but the
+// disk layer is singleton and common to all. If a reorg goes deeper than the
+// disk layer, a batch of reverse diffs can be applied to rollback. The deepest
+// reorg that can be handled depends on the amount of state histories tracked
+// in the disk.
//
// At most one readable and writable database can be opened at the same time in
-// the whole system which ensures that only one database writer can operate disk
-// state. Unexpected open operations can cause the system to panic.
+// the whole system which ensures that only one database writer can operate the
+// persistent state. Unexpected open operations can cause the system to panic.
type Database struct {
// readOnly is the flag whether the mutation is allowed to be applied.
// It will be set automatically when the database is journaled during
@@ -358,7 +377,7 @@ func (db *Database) Enable(root common.Hash) error {
}
// Re-construct a new disk layer backed by persistent state
// with **empty clean cache and node buffer**.
- db.tree.reset(newDiskLayer(root, 0, db, nil, newBuffer(db.config.WriteBufferSize, nil, 0)))
+ db.tree.reset(newDiskLayer(root, 0, db, nil, newBuffer(db.config.WriteBufferSize, nil, nil, 0)))
// Re-enable the database as the final step.
db.waitSync = false
diff --git a/triedb/pathdb/database_test.go b/triedb/pathdb/database_test.go
index 61e0b0928e..648230df15 100644
--- a/triedb/pathdb/database_test.go
+++ b/triedb/pathdb/database_test.go
@@ -309,7 +309,7 @@ func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNode
delete(t.storages, addrHash)
}
}
- return root, ctx.nodes, NewStateSetWithOrigin(ctx.accountOrigin, ctx.storageOrigin)
+ return root, ctx.nodes, NewStateSetWithOrigin(ctx.accounts, ctx.storages, ctx.accountOrigin, ctx.storageOrigin)
}
// lastHash returns the latest root hash, or empty if nothing is cached.
diff --git a/triedb/pathdb/difflayer.go b/triedb/pathdb/difflayer.go
index ecc318614f..c06026b6ca 100644
--- a/triedb/pathdb/difflayer.go
+++ b/triedb/pathdb/difflayer.go
@@ -52,6 +52,7 @@ func newDiffLayer(parent layer, root common.Hash, id uint64, block uint64, nodes
states: states,
}
dirtyNodeWriteMeter.Mark(int64(nodes.size))
+ dirtyStateWriteMeter.Mark(int64(states.size))
log.Debug("Created new diff layer", "id", id, "block", block, "nodesize", common.StorageSize(nodes.size), "statesize", common.StorageSize(states.size))
return dl
}
@@ -96,6 +97,58 @@ func (dl *diffLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co
return dl.parent.node(owner, path, depth+1)
}
+// account directly retrieves the account RLP associated with a particular
+// hash in the slim data format.
+//
+// Note the returned account is not a copy, please don't modify it.
+func (dl *diffLayer) account(hash common.Hash, depth int) ([]byte, error) {
+ // Hold the lock, ensure the parent won't be changed during the
+ // state accessing.
+ dl.lock.RLock()
+ defer dl.lock.RUnlock()
+
+ if blob, found := dl.states.account(hash); found {
+ dirtyStateHitMeter.Mark(1)
+ dirtyStateHitDepthHist.Update(int64(depth))
+ dirtyStateReadMeter.Mark(int64(len(blob)))
+
+ if len(blob) == 0 {
+ stateAccountInexMeter.Mark(1)
+ } else {
+ stateAccountExistMeter.Mark(1)
+ }
+ return blob, nil
+ }
+ // Account is unknown to this layer, resolve from parent
+ return dl.parent.account(hash, depth+1)
+}
+
+// storage directly retrieves the storage data associated with a particular hash,
+// within a particular account.
+//
+// Note the returned storage slot is not a copy, please don't modify it.
+func (dl *diffLayer) storage(accountHash, storageHash common.Hash, depth int) ([]byte, error) {
+ // Hold the lock, ensure the parent won't be changed during the
+ // state accessing.
+ dl.lock.RLock()
+ defer dl.lock.RUnlock()
+
+ if blob, found := dl.states.storage(accountHash, storageHash); found {
+ dirtyStateHitMeter.Mark(1)
+ dirtyStateHitDepthHist.Update(int64(depth))
+ dirtyStateReadMeter.Mark(int64(len(blob)))
+
+ if len(blob) == 0 {
+ stateStorageInexMeter.Mark(1)
+ } else {
+ stateStorageExistMeter.Mark(1)
+ }
+ return blob, nil
+ }
+ // storage slot is unknown to this layer, resolve from parent
+ return dl.parent.storage(accountHash, storageHash, depth+1)
+}
+
// update implements the layer interface, creating a new layer on top of the
// existing layer tree with the specified data items.
func (dl *diffLayer) update(root common.Hash, id uint64, block uint64, nodes *nodeSet, states *StateSetWithOrigin) *diffLayer {
diff --git a/triedb/pathdb/difflayer_test.go b/triedb/pathdb/difflayer_test.go
index 61e8b4e064..7176d9964d 100644
--- a/triedb/pathdb/difflayer_test.go
+++ b/triedb/pathdb/difflayer_test.go
@@ -30,7 +30,7 @@ import (
func emptyLayer() *diskLayer {
return &diskLayer{
db: New(rawdb.NewMemoryDatabase(), nil, false),
- buffer: newBuffer(defaultBufferSize, nil, 0),
+ buffer: newBuffer(defaultBufferSize, nil, nil, 0),
}
}
@@ -76,7 +76,7 @@ func benchmarkSearch(b *testing.B, depth int, total int) {
nblob = common.CopyBytes(blob)
}
}
- return newDiffLayer(parent, common.Hash{}, 0, 0, newNodeSet(nodes), NewStateSetWithOrigin(nil, nil))
+ return newDiffLayer(parent, common.Hash{}, 0, 0, newNodeSet(nodes), NewStateSetWithOrigin(nil, nil, nil, nil))
}
var layer layer
layer = emptyLayer()
@@ -118,7 +118,7 @@ func BenchmarkPersist(b *testing.B) {
)
nodes[common.Hash{}][string(path)] = node
}
- return newDiffLayer(parent, common.Hash{}, 0, 0, newNodeSet(nodes), NewStateSetWithOrigin(nil, nil))
+ return newDiffLayer(parent, common.Hash{}, 0, 0, newNodeSet(nodes), NewStateSetWithOrigin(nil, nil, nil, nil))
}
for i := 0; i < b.N; i++ {
b.StopTimer()
@@ -156,7 +156,7 @@ func BenchmarkJournal(b *testing.B) {
)
nodes[common.Hash{}][string(path)] = node
}
- return newDiffLayer(parent, common.Hash{}, 0, 0, newNodeSet(nodes), new(StateSetWithOrigin))
+ return newDiffLayer(parent, common.Hash{}, 0, 0, newNodeSet(nodes), NewStateSetWithOrigin(nil, nil, nil, nil))
}
var layer layer
layer = emptyLayer()
diff --git a/triedb/pathdb/disklayer.go b/triedb/pathdb/disklayer.go
index edbe630968..003431b19b 100644
--- a/triedb/pathdb/disklayer.go
+++ b/triedb/pathdb/disklayer.go
@@ -17,6 +17,7 @@
package pathdb
import (
+ "errors"
"fmt"
"sync"
@@ -33,7 +34,7 @@ type diskLayer struct {
id uint64 // Immutable, corresponding state id
db *Database // Path-based trie database
nodes *fastcache.Cache // GC friendly memory cache of clean nodes
- buffer *buffer // Dirty buffer to aggregate writes of nodes
+ buffer *buffer // Dirty buffer to aggregate writes of nodes and states
stale bool // Signals that the layer became stale (state progressed)
lock sync.RWMutex // Lock used to protect stale flag
}
@@ -140,6 +141,75 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co
return blob, h.hash(blob), &nodeLoc{loc: locDiskLayer, depth: depth}, nil
}
+// account directly retrieves the account RLP associated with a particular
+// hash in the slim data format.
+//
+// Note the returned account is not a copy, please don't modify it.
+func (dl *diskLayer) account(hash common.Hash, depth int) ([]byte, error) {
+ dl.lock.RLock()
+ defer dl.lock.RUnlock()
+
+ if dl.stale {
+ return nil, errSnapshotStale
+ }
+ // Try to retrieve the account from the not-yet-written
+ // node buffer first. Note the buffer is lock free since
+ // it's impossible to mutate the buffer before tagging the
+ // layer as stale.
+ blob, found := dl.buffer.account(hash)
+ if found {
+ dirtyStateHitMeter.Mark(1)
+ dirtyStateReadMeter.Mark(int64(len(blob)))
+ dirtyStateHitDepthHist.Update(int64(depth))
+
+ if len(blob) == 0 {
+ stateAccountInexMeter.Mark(1)
+ } else {
+ stateAccountExistMeter.Mark(1)
+ }
+ return blob, nil
+ }
+ dirtyStateMissMeter.Mark(1)
+
+ // TODO(rjl493456442) support persistent state retrieval
+ return nil, errors.New("not supported")
+}
+
+// storage directly retrieves the storage data associated with a particular hash,
+// within a particular account.
+//
+// Note the returned account is not a copy, please don't modify it.
+func (dl *diskLayer) storage(accountHash, storageHash common.Hash, depth int) ([]byte, error) {
+ // Hold the lock, ensure the parent won't be changed during the
+ // state accessing.
+ dl.lock.RLock()
+ defer dl.lock.RUnlock()
+
+ if dl.stale {
+ return nil, errSnapshotStale
+ }
+ // Try to retrieve the storage slot from the not-yet-written
+ // node buffer first. Note the buffer is lock free since
+ // it's impossible to mutate the buffer before tagging the
+ // layer as stale.
+ if blob, found := dl.buffer.storage(accountHash, storageHash); found {
+ dirtyStateHitMeter.Mark(1)
+ dirtyStateReadMeter.Mark(int64(len(blob)))
+ dirtyStateHitDepthHist.Update(int64(depth))
+
+ if len(blob) == 0 {
+ stateStorageInexMeter.Mark(1)
+ } else {
+ stateStorageExistMeter.Mark(1)
+ }
+ return blob, nil
+ }
+ dirtyStateMissMeter.Mark(1)
+
+ // TODO(rjl493456442) support persistent state retrieval
+ return nil, errors.New("not supported")
+}
+
// update implements the layer interface, returning a new diff layer on top
// with the given state set.
func (dl *diskLayer) update(root common.Hash, id uint64, block uint64, nodes *nodeSet, states *StateSetWithOrigin) *diffLayer {
@@ -190,14 +260,14 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
// In a unique scenario where the ID of the oldest history object (after tail
// truncation) surpasses the persisted state ID, we take the necessary action
- // of forcibly committing the cached dirty nodes to ensure that the persisted
+ // of forcibly committing the cached dirty states to ensure that the persisted
// state ID remains higher.
if !force && rawdb.ReadPersistentStateID(dl.db.diskdb) < oldest {
force = true
}
- // Merge the trie nodes of the bottom-most diff layer into the buffer as the
- // combined layer.
- combined := dl.buffer.commit(bottom.nodes)
+ // Merge the trie nodes and flat states of the bottom-most diff layer into the
+ // buffer as the combined layer.
+ combined := dl.buffer.commit(bottom.nodes, bottom.states.stateSet)
if combined.full() || force {
if err := combined.flush(dl.db.diskdb, dl.db.freezer, dl.nodes, bottom.stateID()); err != nil {
return nil, err
@@ -225,6 +295,24 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
if dl.id == 0 {
return nil, fmt.Errorf("%w: zero state id", errStateUnrecoverable)
}
+ var (
+ buff = crypto.NewKeccakState()
+ hashes = make(map[common.Address]common.Hash)
+ accounts = make(map[common.Hash][]byte)
+ storages = make(map[common.Hash]map[common.Hash][]byte)
+ )
+ for addr, blob := range h.accounts {
+ hash := crypto.HashData(buff, addr.Bytes())
+ hashes[addr] = hash
+ accounts[hash] = blob
+ }
+ for addr, storage := range h.storages {
+ hash, ok := hashes[addr]
+ if !ok {
+ panic(fmt.Errorf("storage history with no account %x", addr))
+ }
+ storages[hash] = storage
+ }
// Apply the reverse state changes upon the current state. This must
// be done before holding the lock in order to access state in "this"
// layer.
@@ -244,7 +332,7 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
// needs to be reverted is not yet flushed and cached in node
// buffer, otherwise, manipulate persistent state directly.
if !dl.buffer.empty() {
- err := dl.buffer.revert(dl.db.diskdb, nodes)
+ err := dl.buffer.revertTo(dl.db.diskdb, nodes, accounts, storages)
if err != nil {
return nil, err
}
diff --git a/triedb/pathdb/journal.go b/triedb/pathdb/journal.go
index 70fa1fb9f8..779a262fdd 100644
--- a/triedb/pathdb/journal.go
+++ b/triedb/pathdb/journal.go
@@ -45,7 +45,8 @@ var (
//
// - Version 0: initial version
// - Version 1: storage.Incomplete field is removed
-const journalVersion uint64 = 1
+// - Version 2: add post-modification state values
+const journalVersion uint64 = 2
// loadJournal tries to parse the layer journal from the disk.
func (db *Database) loadJournal(diskRoot common.Hash) (layer, error) {
@@ -108,7 +109,7 @@ func (db *Database) loadLayers() layer {
log.Info("Failed to load journal, discard it", "err", err)
}
// Return single layer with persistent state.
- return newDiskLayer(root, rawdb.ReadPersistentStateID(db.diskdb), db, nil, newBuffer(db.config.WriteBufferSize, nil, 0))
+ return newDiskLayer(root, rawdb.ReadPersistentStateID(db.diskdb), db, nil, newBuffer(db.config.WriteBufferSize, nil, nil, 0))
}
// loadDiskLayer reads the binary blob from the layer journal, reconstructing
@@ -135,7 +136,12 @@ func (db *Database) loadDiskLayer(r *rlp.Stream) (layer, error) {
if err := nodes.decode(r); err != nil {
return nil, err
}
- return newDiskLayer(root, id, db, nil, newBuffer(db.config.WriteBufferSize, &nodes, id-stored)), nil
+ // Resolve flat state sets in aggregated buffer
+ var states stateSet
+ if err := states.decode(r); err != nil {
+ return nil, err
+ }
+ return newDiskLayer(root, id, db, nil, newBuffer(db.config.WriteBufferSize, &nodes, &states, id-stored)), nil
}
// loadDiffLayer reads the next sections of a layer journal, reconstructing a new
@@ -189,6 +195,10 @@ func (dl *diskLayer) journal(w io.Writer) error {
if err := dl.buffer.nodes.encode(w); err != nil {
return err
}
+ // Step four, write the accumulated flat states into the journal
+ if err := dl.buffer.states.encode(w); err != nil {
+ return err
+ }
log.Debug("Journaled pathdb disk layer", "root", dl.root)
return nil
}
diff --git a/triedb/pathdb/metrics.go b/triedb/pathdb/metrics.go
index 8897cc4450..1a2559e38b 100644
--- a/triedb/pathdb/metrics.go
+++ b/triedb/pathdb/metrics.go
@@ -30,10 +30,21 @@ var (
dirtyNodeWriteMeter = metrics.NewRegisteredMeter("pathdb/dirty/node/write", nil)
dirtyNodeHitDepthHist = metrics.NewRegisteredHistogram("pathdb/dirty/node/depth", nil, metrics.NewExpDecaySample(1028, 0.015))
- cleanFalseMeter = metrics.NewRegisteredMeter("pathdb/clean/false", nil)
- dirtyFalseMeter = metrics.NewRegisteredMeter("pathdb/dirty/false", nil)
- diskFalseMeter = metrics.NewRegisteredMeter("pathdb/disk/false", nil)
- diffFalseMeter = metrics.NewRegisteredMeter("pathdb/diff/false", nil)
+ stateAccountInexMeter = metrics.NewRegisteredMeter("pathdb/state/account/inex/total", nil)
+ stateStorageInexMeter = metrics.NewRegisteredMeter("pathdb/state/storage/inex/total", nil)
+ stateAccountExistMeter = metrics.NewRegisteredMeter("pathdb/state/account/exist/total", nil)
+ stateStorageExistMeter = metrics.NewRegisteredMeter("pathdb/state/storage/exist/total", nil)
+
+ dirtyStateHitMeter = metrics.NewRegisteredMeter("pathdb/dirty/state/hit", nil)
+ dirtyStateMissMeter = metrics.NewRegisteredMeter("pathdb/dirty/state/miss", nil)
+ dirtyStateReadMeter = metrics.NewRegisteredMeter("pathdb/dirty/state/read", nil)
+ dirtyStateWriteMeter = metrics.NewRegisteredMeter("pathdb/dirty/state/write", nil)
+ dirtyStateHitDepthHist = metrics.NewRegisteredHistogram("pathdb/dirty/state/depth", nil, metrics.NewExpDecaySample(1028, 0.015))
+
+ nodeCleanFalseMeter = metrics.NewRegisteredMeter("pathdb/clean/false", nil)
+ nodeDirtyFalseMeter = metrics.NewRegisteredMeter("pathdb/dirty/false", nil)
+ nodeDiskFalseMeter = metrics.NewRegisteredMeter("pathdb/disk/false", nil)
+ nodeDiffFalseMeter = metrics.NewRegisteredMeter("pathdb/diff/false", nil)
commitTimeTimer = metrics.NewRegisteredTimer("pathdb/commit/time", nil)
commitNodesMeter = metrics.NewRegisteredMeter("pathdb/commit/nodes", nil)
@@ -41,6 +52,10 @@ var (
gcTrieNodeMeter = metrics.NewRegisteredMeter("pathdb/gc/node/count", nil)
gcTrieNodeBytesMeter = metrics.NewRegisteredMeter("pathdb/gc/node/bytes", nil)
+ gcAccountMeter = metrics.NewRegisteredMeter("pathdb/gc/account/count", nil)
+ gcAccountBytesMeter = metrics.NewRegisteredMeter("pathdb/gc/account/bytes", nil)
+ gcStorageMeter = metrics.NewRegisteredMeter("pathdb/gc/storage/count", nil)
+ gcStorageBytesMeter = metrics.NewRegisteredMeter("pathdb/gc/storage/bytes", nil)
historyBuildTimeMeter = metrics.NewRegisteredTimer("pathdb/history/time", nil)
historyDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/data", nil)
diff --git a/triedb/pathdb/nodes.go b/triedb/pathdb/nodes.go
index ade669512e..dee8c872ac 100644
--- a/triedb/pathdb/nodes.go
+++ b/triedb/pathdb/nodes.go
@@ -131,9 +131,9 @@ func (s *nodeSet) merge(set *nodeSet) {
s.updateSize(delta)
}
-// revert merges the provided trie nodes into the set. This should reverse the
+// revertTo merges the provided trie nodes into the set. This should reverse the
// changes made by the most recent state transition.
-func (s *nodeSet) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) {
+func (s *nodeSet) revertTo(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) {
var delta int64
for owner, subset := range nodes {
current, ok := s.nodes[owner]
diff --git a/triedb/pathdb/reader.go b/triedb/pathdb/reader.go
index 2ca4a0205b..4bba813b14 100644
--- a/triedb/pathdb/reader.go
+++ b/triedb/pathdb/reader.go
@@ -21,7 +21,9 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/triedb/database"
)
@@ -66,13 +68,13 @@ func (r *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte,
// is not found.
switch loc.loc {
case locCleanCache:
- cleanFalseMeter.Mark(1)
+ nodeCleanFalseMeter.Mark(1)
case locDirtyCache:
- dirtyFalseMeter.Mark(1)
+ nodeDirtyFalseMeter.Mark(1)
case locDiffLayer:
- diffFalseMeter.Mark(1)
+ nodeDiffFalseMeter.Mark(1)
case locDiskLayer:
- diskFalseMeter.Mark(1)
+ nodeDiskFalseMeter.Mark(1)
}
blobHex := "nil"
if len(blob) > 0 {
@@ -84,6 +86,39 @@ func (r *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte,
return blob, nil
}
+// Account directly retrieves the account associated with a particular hash in
+// the slim data format. An error will be returned if the read operation exits
+// abnormally. Specifically, if the layer is already stale.
+//
+// Note:
+// - the returned account object is safe to modify
+// - no error will be returned if the requested account is not found in database
+func (r *reader) Account(hash common.Hash) (*types.SlimAccount, error) {
+ blob, err := r.layer.account(hash, 0)
+ if err != nil {
+ return nil, err
+ }
+ if len(blob) == 0 {
+ return nil, nil
+ }
+ account := new(types.SlimAccount)
+ if err := rlp.DecodeBytes(blob, account); err != nil {
+ panic(err)
+ }
+ return account, nil
+}
+
+// Storage directly retrieves the storage data associated with a particular hash,
+// within a particular account. An error will be returned if the read operation
+// exits abnormally. Specifically, if the layer is already stale.
+//
+// Note:
+// - the returned storage data is not a copy, please don't modify it
+// - no error will be returned if the requested slot is not found in database
+func (r *reader) Storage(accountHash, storageHash common.Hash) ([]byte, error) {
+ return r.layer.storage(accountHash, storageHash, 0)
+}
+
// NodeReader retrieves a layer belonging to the given state root.
func (db *Database) NodeReader(root common.Hash) (database.NodeReader, error) {
layer := db.tree.get(root)
@@ -92,3 +127,13 @@ func (db *Database) NodeReader(root common.Hash) (database.NodeReader, error) {
}
return &reader{layer: layer, noHashCheck: db.isVerkle}, nil
}
+
+// StateReader returns a reader that allows access to the state data associated
+// with the specified state.
+func (db *Database) StateReader(root common.Hash) (database.StateReader, error) {
+ layer := db.tree.get(root)
+ if layer == nil {
+ return nil, fmt.Errorf("state %#x is not available", root)
+ }
+ return &reader{layer: layer}, nil
+}
diff --git a/triedb/pathdb/states.go b/triedb/pathdb/states.go
index da8befab95..e1611b3ebc 100644
--- a/triedb/pathdb/states.go
+++ b/triedb/pathdb/states.go
@@ -19,10 +19,15 @@ package pathdb
import (
"fmt"
"io"
+ "slices"
+ "sync"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp"
+ "golang.org/x/exp/maps"
)
// counter helps in tracking items and their corresponding sizes.
@@ -43,9 +48,373 @@ func (c *counter) report(count metrics.Meter, size metrics.Meter) {
size.Mark(int64(c.size))
}
+// stateSet represents a collection of state modifications associated with a
+// transition (e.g., a block execution) or multiple aggregated transitions.
+//
+// A stateSet can only reside within a diffLayer or the buffer of a diskLayer,
+// serving as the envelope for the set. Lock protection is not required for
+// accessing or mutating the account set and storage set, as the associated
+// envelope is always marked as stale before any mutation is applied. Any
+// subsequent state access will be denied due to the stale flag. Therefore,
+// state access and mutation won't happen at the same time with guarantee.
+type stateSet struct {
+ accountData map[common.Hash][]byte // Keyed accounts for direct retrieval (nil means deleted)
+ storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrieval. one per account (nil means deleted)
+ size uint64 // Memory size of the state data (accountData and storageData)
+
+ accountListSorted []common.Hash // List of account for iteration. If it exists, it's sorted, otherwise it's nil
+ storageListSorted map[common.Hash][]common.Hash // List of storage slots for iterated retrievals, one per account. Any existing lists are sorted if non-nil
+
+ // Lock for guarding the two lists above. These lists might be accessed
+ // concurrently and lock protection is essential to avoid concurrent
+ // slice or map read/write.
+ listLock sync.RWMutex
+}
+
+// newStates constructs the state set with the provided account and storage data.
+func newStates(accounts map[common.Hash][]byte, storages map[common.Hash]map[common.Hash][]byte) *stateSet {
+ // Don't panic for the lazy callers, initialize the nil maps instead.
+ if accounts == nil {
+ accounts = make(map[common.Hash][]byte)
+ }
+ if storages == nil {
+ storages = make(map[common.Hash]map[common.Hash][]byte)
+ }
+ s := &stateSet{
+ accountData: accounts,
+ storageData: storages,
+ storageListSorted: make(map[common.Hash][]common.Hash),
+ }
+ s.size = s.check()
+ return s
+}
+
+// account returns the account data associated with the specified address hash.
+func (s *stateSet) account(hash common.Hash) ([]byte, bool) {
+ // If the account is known locally, return it
+ if data, ok := s.accountData[hash]; ok {
+ return data, true
+ }
+ return nil, false // account is unknown in this set
+}
+
+// storage returns the storage slot associated with the specified address hash
+// and storage key hash.
+func (s *stateSet) storage(accountHash, storageHash common.Hash) ([]byte, bool) {
+ // If the account is known locally, try to resolve the slot locally
+ if storage, ok := s.storageData[accountHash]; ok {
+ if data, ok := storage[storageHash]; ok {
+ return data, true
+ }
+ }
+ return nil, false // storage is unknown in this set
+}
+
+// check sanitizes accounts and storage slots to ensure the data validity.
+// Additionally, it computes the total memory size occupied by the maps.
+func (s *stateSet) check() uint64 {
+ var size int
+ for _, blob := range s.accountData {
+ size += common.HashLength + len(blob)
+ }
+ for accountHash, slots := range s.storageData {
+ if slots == nil {
+ panic(fmt.Sprintf("storage %#x nil", accountHash)) // nil slots is not permitted
+ }
+ for _, blob := range slots {
+ size += 2*common.HashLength + len(blob)
+ }
+ }
+ return uint64(size)
+}
+
+// accountList returns a sorted list of all accounts in this state set, including
+// the deleted ones.
+//
+// Note, the returned slice is not a copy, so do not modify it.
+//
+// nolint:unused
+func (s *stateSet) accountList() []common.Hash {
+ // If an old list already exists, return it
+ s.listLock.RLock()
+ list := s.accountListSorted
+ s.listLock.RUnlock()
+
+ if list != nil {
+ return list
+ }
+ // No old sorted account list exists, generate a new one. It's possible that
+ // multiple threads waiting for the write lock may regenerate the list
+ // multiple times, which is acceptable.
+ s.listLock.Lock()
+ defer s.listLock.Unlock()
+
+ list = maps.Keys(s.accountData)
+ slices.SortFunc(list, common.Hash.Cmp)
+ s.accountListSorted = list
+ return list
+}
+
+// StorageList returns a sorted list of all storage slot hashes in this state set
+// for the given account. The returned list will include the hash of deleted
+// storage slot.
+//
+// Note, the returned slice is not a copy, so do not modify it.
+//
+// nolint:unused
+func (s *stateSet) storageList(accountHash common.Hash) []common.Hash {
+ s.listLock.RLock()
+ if _, ok := s.storageData[accountHash]; !ok {
+ // Account not tracked by this layer
+ s.listLock.RUnlock()
+ return nil
+ }
+ // If an old list already exists, return it
+ if list, exist := s.storageListSorted[accountHash]; exist {
+ s.listLock.RUnlock()
+ return list // the cached list can't be nil
+ }
+ s.listLock.RUnlock()
+
+ // No old sorted account list exists, generate a new one. It's possible that
+ // multiple threads waiting for the write lock may regenerate the list
+ // multiple times, which is acceptable.
+ s.listLock.Lock()
+ defer s.listLock.Unlock()
+
+ list := maps.Keys(s.storageData[accountHash])
+ slices.SortFunc(list, common.Hash.Cmp)
+ s.storageListSorted[accountHash] = list
+ return list
+}
+
+// clearLists invalidates the cached account list and storage lists.
+func (s *stateSet) clearLists() {
+ s.listLock.Lock()
+ defer s.listLock.Unlock()
+
+ s.accountListSorted = nil
+ s.storageListSorted = make(map[common.Hash][]common.Hash)
+}
+
+// merge integrates the accounts and storages from the external set into the
+// local set, ensuring the combined set reflects the combined state of both.
+//
+// The stateSet supplied as parameter set will not be mutated by this operation,
+// as it may still be referenced by other layers.
+func (s *stateSet) merge(other *stateSet) {
+ var (
+ delta int
+ accountOverwrites counter
+ storageOverwrites counter
+ )
+ // Apply the updated account data
+ for accountHash, data := range other.accountData {
+ if origin, ok := s.accountData[accountHash]; ok {
+ delta += len(data) - len(origin)
+ accountOverwrites.add(common.HashLength + len(origin))
+ } else {
+ delta += common.HashLength + len(data)
+ }
+ s.accountData[accountHash] = data
+ }
+ // Apply all the updated storage slots (individually)
+ for accountHash, storage := range other.storageData {
+ // If storage didn't exist in the set, overwrite blindly
+ if _, ok := s.storageData[accountHash]; !ok {
+ // To prevent potential concurrent map read/write issues, allocate a
+ // new map for the storage instead of claiming it directly from the
+ // passed external set. Even after merging, the slots belonging to the
+ // external state set remain accessible, so ownership of the map should
+ // not be taken, and any mutation on it should be avoided.
+ slots := make(map[common.Hash][]byte, len(storage))
+ for storageHash, data := range storage {
+ slots[storageHash] = data
+ delta += 2*common.HashLength + len(data)
+ }
+ s.storageData[accountHash] = slots
+ continue
+ }
+ // Storage exists in both local and external set, merge the slots
+ slots := s.storageData[accountHash]
+ for storageHash, data := range storage {
+ if origin, ok := slots[storageHash]; ok {
+ delta += len(data) - len(origin)
+ storageOverwrites.add(2*common.HashLength + len(origin))
+ } else {
+ delta += 2*common.HashLength + len(data)
+ }
+ slots[storageHash] = data
+ }
+ }
+ accountOverwrites.report(gcAccountMeter, gcAccountBytesMeter)
+ storageOverwrites.report(gcStorageMeter, gcStorageBytesMeter)
+ s.clearLists()
+ s.updateSize(delta)
+}
+
+// revertTo takes the original value of accounts and storages as input and reverts
+// the latest state transition applied on the state set.
+//
+// Notably, this operation may result in the set containing more entries after a
+// revert. For example, if account x did not exist and was created during transition
+// w, reverting w will retain an x=nil entry in the set. And also if account x along
+// with its storage slots was deleted in the transition w, reverting w will retain
+// a list of additional storage slots with their original value.
+func (s *stateSet) revertTo(accountOrigin map[common.Hash][]byte, storageOrigin map[common.Hash]map[common.Hash][]byte) {
+ var delta int // size tracking
+ for addrHash, blob := range accountOrigin {
+ data, ok := s.accountData[addrHash]
+ if !ok {
+ panic(fmt.Sprintf("non-existent account for reverting, %x", addrHash))
+ }
+ if len(data) == 0 && len(blob) == 0 {
+ panic(fmt.Sprintf("invalid account mutation (null to null), %x", addrHash))
+ }
+ delta += len(blob) - len(data)
+ s.accountData[addrHash] = blob
+ }
+ // Overwrite the storage data with original value blindly
+ for addrHash, storage := range storageOrigin {
+ slots := s.storageData[addrHash]
+ if len(slots) == 0 {
+ panic(fmt.Sprintf("non-existent storage set for reverting, %x", addrHash))
+ }
+ for storageHash, blob := range storage {
+ data, ok := slots[storageHash]
+ if !ok {
+ panic(fmt.Sprintf("non-existent storage slot for reverting, %x-%x", addrHash, storageHash))
+ }
+ if len(blob) == 0 && len(data) == 0 {
+ panic(fmt.Sprintf("invalid storage slot mutation (null to null), %x-%x", addrHash, storageHash))
+ }
+ delta += len(blob) - len(data)
+ slots[storageHash] = blob
+ }
+ }
+ s.clearLists()
+ s.updateSize(delta)
+}
+
+// updateSize updates the total cache size by the given delta.
+func (s *stateSet) updateSize(delta int) {
+ size := int64(s.size) + int64(delta)
+ if size >= 0 {
+ s.size = uint64(size)
+ return
+ }
+ log.Error("Stateset size underflow", "prev", common.StorageSize(s.size), "delta", common.StorageSize(delta))
+ s.size = 0
+}
+
+// encode serializes the content of state set into the provided writer.
+func (s *stateSet) encode(w io.Writer) error {
+ // Encode accounts
+ type accounts struct {
+ AddrHashes []common.Hash
+ Accounts [][]byte
+ }
+ var enc accounts
+ for addrHash, blob := range s.accountData {
+ enc.AddrHashes = append(enc.AddrHashes, addrHash)
+ enc.Accounts = append(enc.Accounts, blob)
+ }
+ if err := rlp.Encode(w, enc); err != nil {
+ return err
+ }
+ // Encode storages
+ type Storage struct {
+ AddrHash common.Hash
+ Keys []common.Hash
+ Vals [][]byte
+ }
+ storages := make([]Storage, 0, len(s.storageData))
+ for addrHash, slots := range s.storageData {
+ keys := make([]common.Hash, 0, len(slots))
+ vals := make([][]byte, 0, len(slots))
+ for key, val := range slots {
+ keys = append(keys, key)
+ vals = append(vals, val)
+ }
+ storages = append(storages, Storage{
+ AddrHash: addrHash,
+ Keys: keys,
+ Vals: vals,
+ })
+ }
+ return rlp.Encode(w, storages)
+}
+
+// decode deserializes the content from the rlp stream into the state set.
+func (s *stateSet) decode(r *rlp.Stream) error {
+ type accounts struct {
+ AddrHashes []common.Hash
+ Accounts [][]byte
+ }
+ var (
+ dec accounts
+ accountSet = make(map[common.Hash][]byte)
+ )
+ if err := r.Decode(&dec); err != nil {
+ return fmt.Errorf("load diff accounts: %v", err)
+ }
+ for i := 0; i < len(dec.AddrHashes); i++ {
+ accountSet[dec.AddrHashes[i]] = dec.Accounts[i]
+ }
+ s.accountData = accountSet
+
+ // Decode storages
+ type storage struct {
+ AddrHash common.Hash
+ Keys []common.Hash
+ Vals [][]byte
+ }
+ var (
+ storages []storage
+ storageSet = make(map[common.Hash]map[common.Hash][]byte)
+ )
+ if err := r.Decode(&storages); err != nil {
+ return fmt.Errorf("load diff storage: %v", err)
+ }
+ for _, entry := range storages {
+ storageSet[entry.AddrHash] = make(map[common.Hash][]byte, len(entry.Keys))
+ for i := 0; i < len(entry.Keys); i++ {
+ storageSet[entry.AddrHash][entry.Keys[i]] = entry.Vals[i]
+ }
+ }
+ s.storageData = storageSet
+ s.storageListSorted = make(map[common.Hash][]common.Hash)
+
+ s.size = s.check()
+ return nil
+}
+
+// reset clears all cached state data, including any optional sorted lists that
+// may have been generated.
+func (s *stateSet) reset() {
+ s.accountData = make(map[common.Hash][]byte)
+ s.storageData = make(map[common.Hash]map[common.Hash][]byte)
+ s.size = 0
+ s.accountListSorted = nil
+ s.storageListSorted = make(map[common.Hash][]common.Hash)
+}
+
+// dbsize returns the approximate size for db write.
+//
+// nolint:unused
+func (s *stateSet) dbsize() int {
+ m := len(s.accountData) * len(rawdb.SnapshotAccountPrefix)
+ for _, slots := range s.storageData {
+ m += len(slots) * len(rawdb.SnapshotStoragePrefix)
+ }
+ return m + int(s.size)
+}
+
// StateSetWithOrigin wraps the state set with additional original values of the
// mutated states.
type StateSetWithOrigin struct {
+ *stateSet
+
// AccountOrigin represents the account data before the state transition,
// corresponding to both the accountData and destructSet. It's keyed by the
// account address. The nil value means the account was not present before.
@@ -62,7 +431,7 @@ type StateSetWithOrigin struct {
}
// NewStateSetWithOrigin constructs the state set with the provided data.
-func NewStateSetWithOrigin(accountOrigin map[common.Address][]byte, storageOrigin map[common.Address]map[common.Hash][]byte) *StateSetWithOrigin {
+func NewStateSetWithOrigin(accounts map[common.Hash][]byte, storages map[common.Hash]map[common.Hash][]byte, accountOrigin map[common.Address][]byte, storageOrigin map[common.Address]map[common.Hash][]byte) *StateSetWithOrigin {
// Don't panic for the lazy callers, initialize the nil maps instead.
if accountOrigin == nil {
accountOrigin = make(map[common.Address][]byte)
@@ -82,15 +451,21 @@ func NewStateSetWithOrigin(accountOrigin map[common.Address][]byte, storageOrigi
size += 2*common.HashLength + len(data)
}
}
+ set := newStates(accounts, storages)
return &StateSetWithOrigin{
+ stateSet: set,
accountOrigin: accountOrigin,
storageOrigin: storageOrigin,
- size: uint64(size),
+ size: set.size + uint64(size),
}
}
// encode serializes the content of state set into the provided writer.
func (s *StateSetWithOrigin) encode(w io.Writer) error {
+ // Encode state set
+ if err := s.stateSet.encode(w); err != nil {
+ return err
+ }
// Encode accounts
type Accounts struct {
Addresses []common.Address
@@ -108,7 +483,7 @@ func (s *StateSetWithOrigin) encode(w io.Writer) error {
type Storage struct {
Address common.Address
Keys []common.Hash
- Blobs [][]byte
+ Vals [][]byte
}
storages := make([]Storage, 0, len(s.storageOrigin))
for address, slots := range s.storageOrigin {
@@ -118,13 +493,19 @@ func (s *StateSetWithOrigin) encode(w io.Writer) error {
keys = append(keys, key)
vals = append(vals, val)
}
- storages = append(storages, Storage{Address: address, Keys: keys, Blobs: vals})
+ storages = append(storages, Storage{Address: address, Keys: keys, Vals: vals})
}
return rlp.Encode(w, storages)
}
// decode deserializes the content from the rlp stream into the state set.
func (s *StateSetWithOrigin) decode(r *rlp.Stream) error {
+ if s.stateSet == nil {
+ s.stateSet = &stateSet{}
+ }
+ if err := s.stateSet.decode(r); err != nil {
+ return err
+ }
// Decode account origin
type Accounts struct {
Addresses []common.Address
@@ -146,7 +527,7 @@ func (s *StateSetWithOrigin) decode(r *rlp.Stream) error {
type Storage struct {
Address common.Address
Keys []common.Hash
- Blobs [][]byte
+ Vals [][]byte
}
var (
storages []Storage
@@ -158,7 +539,7 @@ func (s *StateSetWithOrigin) decode(r *rlp.Stream) error {
for _, storage := range storages {
storageSet[storage.Address] = make(map[common.Hash][]byte)
for i := 0; i < len(storage.Keys); i++ {
- storageSet[storage.Address][storage.Keys[i]] = storage.Blobs[i]
+ storageSet[storage.Address][storage.Keys[i]] = storage.Vals[i]
}
}
s.storageOrigin = storageSet
diff --git a/triedb/pathdb/states_test.go b/triedb/pathdb/states_test.go
new file mode 100644
index 0000000000..4557fa958d
--- /dev/null
+++ b/triedb/pathdb/states_test.go
@@ -0,0 +1,453 @@
+// Copyright 2024 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 pathdb
+
+import (
+ "bytes"
+ "reflect"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+func TestStatesMerge(t *testing.T) {
+ a := newStates(
+ map[common.Hash][]byte{
+ common.Hash{0xa}: {0xa0},
+ common.Hash{0xb}: {0xb0},
+ common.Hash{0xc}: {0xc0},
+ },
+ map[common.Hash]map[common.Hash][]byte{
+ common.Hash{0xa}: {
+ common.Hash{0x1}: {0x10},
+ common.Hash{0x2}: {0x20},
+ },
+ common.Hash{0xb}: {
+ common.Hash{0x1}: {0x10},
+ },
+ common.Hash{0xc}: {
+ common.Hash{0x1}: {0x10},
+ },
+ },
+ )
+ b := newStates(
+ map[common.Hash][]byte{
+ common.Hash{0xa}: {0xa1},
+ common.Hash{0xb}: {0xb1},
+ common.Hash{0xc}: nil, // delete account
+ },
+ map[common.Hash]map[common.Hash][]byte{
+ common.Hash{0xa}: {
+ common.Hash{0x1}: {0x11},
+ common.Hash{0x2}: nil, // delete slot
+ common.Hash{0x3}: {0x31},
+ },
+ common.Hash{0xb}: {
+ common.Hash{0x1}: {0x11},
+ },
+ common.Hash{0xc}: {
+ common.Hash{0x1}: nil, // delete slot
+ },
+ },
+ )
+ a.merge(b)
+
+ blob, exist := a.account(common.Hash{0xa})
+ if !exist || !bytes.Equal(blob, []byte{0xa1}) {
+ t.Error("Unexpected value for account a")
+ }
+ blob, exist = a.account(common.Hash{0xb})
+ if !exist || !bytes.Equal(blob, []byte{0xb1}) {
+ t.Error("Unexpected value for account b")
+ }
+ blob, exist = a.account(common.Hash{0xc})
+ if !exist || len(blob) != 0 {
+ t.Error("Unexpected value for account c")
+ }
+ // unknown account
+ blob, exist = a.account(common.Hash{0xd})
+ if exist || len(blob) != 0 {
+ t.Error("Unexpected value for account d")
+ }
+
+ blob, exist = a.storage(common.Hash{0xa}, common.Hash{0x1})
+ if !exist || !bytes.Equal(blob, []byte{0x11}) {
+ t.Error("Unexpected value for a's storage")
+ }
+ blob, exist = a.storage(common.Hash{0xa}, common.Hash{0x2})
+ if !exist || len(blob) != 0 {
+ t.Error("Unexpected value for a's storage")
+ }
+ blob, exist = a.storage(common.Hash{0xa}, common.Hash{0x3})
+ if !exist || !bytes.Equal(blob, []byte{0x31}) {
+ t.Error("Unexpected value for a's storage")
+ }
+ blob, exist = a.storage(common.Hash{0xb}, common.Hash{0x1})
+ if !exist || !bytes.Equal(blob, []byte{0x11}) {
+ t.Error("Unexpected value for b's storage")
+ }
+ blob, exist = a.storage(common.Hash{0xc}, common.Hash{0x1})
+ if !exist || len(blob) != 0 {
+ t.Error("Unexpected value for c's storage")
+ }
+
+ // unknown storage slots
+ blob, exist = a.storage(common.Hash{0xd}, common.Hash{0x1})
+ if exist || len(blob) != 0 {
+ t.Error("Unexpected value for d's storage")
+ }
+}
+
+func TestStatesRevert(t *testing.T) {
+ a := newStates(
+ map[common.Hash][]byte{
+ common.Hash{0xa}: {0xa0},
+ common.Hash{0xb}: {0xb0},
+ common.Hash{0xc}: {0xc0},
+ },
+ map[common.Hash]map[common.Hash][]byte{
+ common.Hash{0xa}: {
+ common.Hash{0x1}: {0x10},
+ common.Hash{0x2}: {0x20},
+ },
+ common.Hash{0xb}: {
+ common.Hash{0x1}: {0x10},
+ },
+ common.Hash{0xc}: {
+ common.Hash{0x1}: {0x10},
+ },
+ },
+ )
+ b := newStates(
+ map[common.Hash][]byte{
+ common.Hash{0xa}: {0xa1},
+ common.Hash{0xb}: {0xb1},
+ common.Hash{0xc}: nil,
+ },
+ map[common.Hash]map[common.Hash][]byte{
+ common.Hash{0xa}: {
+ common.Hash{0x1}: {0x11},
+ common.Hash{0x2}: nil,
+ common.Hash{0x3}: {0x31},
+ },
+ common.Hash{0xb}: {
+ common.Hash{0x1}: {0x11},
+ },
+ common.Hash{0xc}: {
+ common.Hash{0x1}: nil,
+ },
+ },
+ )
+ a.merge(b)
+ a.revertTo(
+ map[common.Hash][]byte{
+ common.Hash{0xa}: {0xa0},
+ common.Hash{0xb}: {0xb0},
+ common.Hash{0xc}: {0xc0},
+ },
+ map[common.Hash]map[common.Hash][]byte{
+ common.Hash{0xa}: {
+ common.Hash{0x1}: {0x10},
+ common.Hash{0x2}: {0x20},
+ common.Hash{0x3}: nil,
+ },
+ common.Hash{0xb}: {
+ common.Hash{0x1}: {0x10},
+ },
+ common.Hash{0xc}: {
+ common.Hash{0x1}: {0x10},
+ },
+ },
+ )
+
+ blob, exist := a.account(common.Hash{0xa})
+ if !exist || !bytes.Equal(blob, []byte{0xa0}) {
+ t.Error("Unexpected value for account a")
+ }
+ blob, exist = a.account(common.Hash{0xb})
+ if !exist || !bytes.Equal(blob, []byte{0xb0}) {
+ t.Error("Unexpected value for account b")
+ }
+ blob, exist = a.account(common.Hash{0xc})
+ if !exist || !bytes.Equal(blob, []byte{0xc0}) {
+ t.Error("Unexpected value for account c")
+ }
+ // unknown account
+ blob, exist = a.account(common.Hash{0xd})
+ if exist || len(blob) != 0 {
+ t.Error("Unexpected value for account d")
+ }
+
+ blob, exist = a.storage(common.Hash{0xa}, common.Hash{0x1})
+ if !exist || !bytes.Equal(blob, []byte{0x10}) {
+ t.Error("Unexpected value for a's storage")
+ }
+ blob, exist = a.storage(common.Hash{0xa}, common.Hash{0x2})
+ if !exist || !bytes.Equal(blob, []byte{0x20}) {
+ t.Error("Unexpected value for a's storage")
+ }
+ blob, exist = a.storage(common.Hash{0xa}, common.Hash{0x3})
+ if !exist || len(blob) != 0 {
+ t.Error("Unexpected value for a's storage")
+ }
+ blob, exist = a.storage(common.Hash{0xb}, common.Hash{0x1})
+ if !exist || !bytes.Equal(blob, []byte{0x10}) {
+ t.Error("Unexpected value for b's storage")
+ }
+ blob, exist = a.storage(common.Hash{0xc}, common.Hash{0x1})
+ if !exist || !bytes.Equal(blob, []byte{0x10}) {
+ t.Error("Unexpected value for c's storage")
+ }
+ // unknown storage slots
+ blob, exist = a.storage(common.Hash{0xd}, common.Hash{0x1})
+ if exist || len(blob) != 0 {
+ t.Error("Unexpected value for d's storage")
+ }
+}
+
+// TestStateRevertAccountNullMarker tests the scenario that account x did not exist
+// before and was created during transition w, reverting w will retain an x=nil
+// entry in the set.
+func TestStateRevertAccountNullMarker(t *testing.T) {
+ a := newStates(nil, nil) // empty initial state
+ b := newStates(
+ map[common.Hash][]byte{
+ common.Hash{0xa}: {0xa},
+ },
+ nil,
+ )
+ a.merge(b) // create account 0xa
+ a.revertTo(
+ map[common.Hash][]byte{
+ common.Hash{0xa}: nil,
+ },
+ nil,
+ ) // revert the transition b
+
+ blob, exist := a.account(common.Hash{0xa})
+ if !exist {
+ t.Fatal("null marker is not found")
+ }
+ if len(blob) != 0 {
+ t.Fatalf("Unexpected value for account, %v", blob)
+ }
+}
+
+// TestStateRevertStorageNullMarker tests the scenario that slot x did not exist
+// before and was created during transition w, reverting w will retain an x=nil
+// entry in the set.
+func TestStateRevertStorageNullMarker(t *testing.T) {
+ a := newStates(map[common.Hash][]byte{
+ common.Hash{0xa}: {0xa},
+ }, nil) // initial state with account 0xa
+
+ b := newStates(
+ nil,
+ map[common.Hash]map[common.Hash][]byte{
+ common.Hash{0xa}: {
+ common.Hash{0x1}: {0x1},
+ },
+ },
+ )
+ a.merge(b) // create slot 0x1
+ a.revertTo(
+ nil,
+ map[common.Hash]map[common.Hash][]byte{
+ common.Hash{0xa}: {
+ common.Hash{0x1}: nil,
+ },
+ },
+ ) // revert the transition b
+
+ blob, exist := a.storage(common.Hash{0xa}, common.Hash{0x1})
+ if !exist {
+ t.Fatal("null marker is not found")
+ }
+ if len(blob) != 0 {
+ t.Fatalf("Unexpected value for storage slot, %v", blob)
+ }
+}
+
+func TestStatesEncode(t *testing.T) {
+ s := newStates(
+ map[common.Hash][]byte{
+ common.Hash{0x1}: {0x1},
+ },
+ map[common.Hash]map[common.Hash][]byte{
+ common.Hash{0x1}: {
+ common.Hash{0x1}: {0x1},
+ },
+ },
+ )
+ buf := bytes.NewBuffer(nil)
+ if err := s.encode(buf); err != nil {
+ t.Fatalf("Failed to encode states, %v", err)
+ }
+ var dec stateSet
+ if err := dec.decode(rlp.NewStream(buf, 0)); err != nil {
+ t.Fatalf("Failed to decode states, %v", err)
+ }
+ if !reflect.DeepEqual(s.accountData, dec.accountData) {
+ t.Fatal("Unexpected account data")
+ }
+ if !reflect.DeepEqual(s.storageData, dec.storageData) {
+ t.Fatal("Unexpected storage data")
+ }
+}
+
+func TestStateWithOriginEncode(t *testing.T) {
+ s := NewStateSetWithOrigin(
+ map[common.Hash][]byte{
+ common.Hash{0x1}: {0x1},
+ },
+ map[common.Hash]map[common.Hash][]byte{
+ common.Hash{0x1}: {
+ common.Hash{0x1}: {0x1},
+ },
+ },
+ map[common.Address][]byte{
+ common.Address{0x1}: {0x1},
+ },
+ map[common.Address]map[common.Hash][]byte{
+ common.Address{0x1}: {
+ common.Hash{0x1}: {0x1},
+ },
+ },
+ )
+ buf := bytes.NewBuffer(nil)
+ if err := s.encode(buf); err != nil {
+ t.Fatalf("Failed to encode states, %v", err)
+ }
+ var dec StateSetWithOrigin
+ if err := dec.decode(rlp.NewStream(buf, 0)); err != nil {
+ t.Fatalf("Failed to decode states, %v", err)
+ }
+ if !reflect.DeepEqual(s.accountData, dec.accountData) {
+ t.Fatal("Unexpected account data")
+ }
+ if !reflect.DeepEqual(s.storageData, dec.storageData) {
+ t.Fatal("Unexpected storage data")
+ }
+ if !reflect.DeepEqual(s.accountOrigin, dec.accountOrigin) {
+ t.Fatal("Unexpected account origin data")
+ }
+ if !reflect.DeepEqual(s.storageOrigin, dec.storageOrigin) {
+ t.Fatal("Unexpected storage origin data")
+ }
+}
+
+func TestStateSizeTracking(t *testing.T) {
+ expSizeA := 3*(common.HashLength+1) + /* account data */
+ 2*(2*common.HashLength+1) + /* storage data of 0xa */
+ 2*common.HashLength + 3 + /* storage data of 0xb */
+ 2*common.HashLength + 1 /* storage data of 0xc */
+
+ a := newStates(
+ map[common.Hash][]byte{
+ common.Hash{0xa}: {0xa0}, // common.HashLength+1
+ common.Hash{0xb}: {0xb0}, // common.HashLength+1
+ common.Hash{0xc}: {0xc0}, // common.HashLength+1
+ },
+ map[common.Hash]map[common.Hash][]byte{
+ common.Hash{0xa}: {
+ common.Hash{0x1}: {0x10}, // 2*common.HashLength+1
+ common.Hash{0x2}: {0x20}, // 2*common.HashLength+1
+ },
+ common.Hash{0xb}: {
+ common.Hash{0x1}: {0x10, 0x11, 0x12}, // 2*common.HashLength+3
+ },
+ common.Hash{0xc}: {
+ common.Hash{0x1}: {0x10}, // 2*common.HashLength+1
+ },
+ },
+ )
+ if a.size != uint64(expSizeA) {
+ t.Fatalf("Unexpected size, want: %d, got: %d", expSizeA, a.size)
+ }
+
+ expSizeB := common.HashLength + 2 + common.HashLength + 3 + common.HashLength + /* account data */
+ 2*common.HashLength + 3 + 2*common.HashLength + 2 + /* storage data of 0xa */
+ 2*common.HashLength + 2 + 2*common.HashLength + 2 + /* storage data of 0xb */
+ 3*2*common.HashLength /* storage data of 0xc */
+ b := newStates(
+ map[common.Hash][]byte{
+ common.Hash{0xa}: {0xa1, 0xa1}, // common.HashLength+2
+ common.Hash{0xb}: {0xb1, 0xb1, 0xb1}, // common.HashLength+3
+ common.Hash{0xc}: nil, // common.HashLength, account deletion
+ },
+ map[common.Hash]map[common.Hash][]byte{
+ common.Hash{0xa}: {
+ common.Hash{0x1}: {0x11, 0x11, 0x11}, // 2*common.HashLength+3
+ common.Hash{0x3}: {0x31, 0x31}, // 2*common.HashLength+2, slot creation
+ },
+ common.Hash{0xb}: {
+ common.Hash{0x1}: {0x11, 0x11}, // 2*common.HashLength+2
+ common.Hash{0x2}: {0x22, 0x22}, // 2*common.HashLength+2, slot creation
+ },
+ // The storage of 0xc is entirely removed
+ common.Hash{0xc}: {
+ common.Hash{0x1}: nil, // 2*common.HashLength, slot deletion
+ common.Hash{0x2}: nil, // 2*common.HashLength, slot deletion
+ common.Hash{0x3}: nil, // 2*common.HashLength, slot deletion
+ },
+ },
+ )
+ if b.size != uint64(expSizeB) {
+ t.Fatalf("Unexpected size, want: %d, got: %d", expSizeB, b.size)
+ }
+
+ a.merge(b)
+ mergeSize := expSizeA + 1 /* account a data change */ + 2 /* account b data change */ - 1 /* account c data change */
+ mergeSize += 2*common.HashLength + 2 + 2 /* storage a change */
+ mergeSize += 2*common.HashLength + 2 - 1 /* storage b change */
+ mergeSize += 2*2*common.HashLength - 1 /* storage data removal of 0xc */
+
+ if a.size != uint64(mergeSize) {
+ t.Fatalf("Unexpected size, want: %d, got: %d", mergeSize, a.size)
+ }
+
+ // Revert the set to original status
+ a.revertTo(
+ map[common.Hash][]byte{
+ common.Hash{0xa}: {0xa0},
+ common.Hash{0xb}: {0xb0},
+ common.Hash{0xc}: {0xc0},
+ },
+ map[common.Hash]map[common.Hash][]byte{
+ common.Hash{0xa}: {
+ common.Hash{0x1}: {0x10},
+ common.Hash{0x2}: {0x20},
+ common.Hash{0x3}: nil, // revert slot creation
+ },
+ common.Hash{0xb}: {
+ common.Hash{0x1}: {0x10, 0x11, 0x12},
+ common.Hash{0x2}: nil, // revert slot creation
+ },
+ common.Hash{0xc}: {
+ common.Hash{0x1}: {0x10},
+ common.Hash{0x2}: {0x20}, // resurrected slot
+ common.Hash{0x3}: {0x30}, // resurrected slot
+ },
+ },
+ )
+ revertSize := expSizeA + 2*common.HashLength + 2*common.HashLength // delete-marker of a.3 and b.2 slot
+ revertSize += 2 * (2*common.HashLength + 1) // resurrected slot, c.2, c.3
+ if a.size != uint64(revertSize) {
+ t.Fatalf("Unexpected size, want: %d, got: %d", revertSize, a.size)
+ }
+}
diff --git a/triedb/states.go b/triedb/states.go
index 0b03f2b9f3..fa432e0704 100644
--- a/triedb/states.go
+++ b/triedb/states.go
@@ -45,5 +45,5 @@ func (set *StateSet) internal() *pathdb.StateSetWithOrigin {
if set == nil {
return nil
}
- return pathdb.NewStateSetWithOrigin(set.AccountsOrigin, set.StoragesOrigin)
+ return pathdb.NewStateSetWithOrigin(set.Accounts, set.Storages, set.AccountsOrigin, set.StoragesOrigin)
}