Merge pull request #15 from streamingfast/extended-tracer-sei

Extended tracer sei
This commit is contained in:
Steven Landers 2024-03-19 10:28:23 -04:00 committed by GitHub
commit 6587b72359
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
107 changed files with 3284 additions and 1356 deletions

View file

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "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/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/filters"
@ -681,7 +682,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
// Set infinite balance to the fake caller account. // Set infinite balance to the fake caller account.
from := stateDB.(*state.StateDB).GetOrNewStateObject(call.From) from := stateDB.(*state.StateDB).GetOrNewStateObject(call.From)
from.SetBalance(math.MaxBig256) from.SetBalance(math.MaxBig256, tracing.BalanceChangeUnspecified)
// Execute the call. // Execute the call.
msg := &core.Message{ msg := &core.Message{

View file

@ -27,7 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
estate "github.com/ethereum/go-ethereum/core/state" estate "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -52,7 +52,7 @@ func blockTestCmd(ctx *cli.Context) error {
return errors.New("path-to-test argument required") return errors.New("path-to-test argument required")
} }
var tracer vm.EVMLogger var tracer *tracing.Hooks
// Configure the EVM logger // Configure the EVM logger
if ctx.Bool(MachineFlag.Name) { if ctx.Bool(MachineFlag.Name) {
tracer = logger.NewJSONLogger(&logger.Config{ tracer = logger.NewJSONLogger(&logger.Config{

View file

@ -17,7 +17,9 @@
package t8ntool package t8ntool
import ( import (
"encoding/json"
"fmt" "fmt"
"io"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -28,9 +30,11 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "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/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers/directory"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -117,7 +121,7 @@ type rejectedTx struct {
// Apply applies a set of transactions to a pre-state // Apply applies a set of transactions to a pre-state
func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
txIt txIterator, miningReward int64, txIt txIterator, miningReward int64,
getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error)) (*state.StateDB, *ExecutionResult, []byte, error) { getTracerFn func(txIndex int, txHash common.Hash) (*directory.Tracer, io.WriteCloser, error)) (*state.StateDB, *ExecutionResult, []byte, error) {
// Capture errors for BLOCKHASH operation, if we haven't been supplied the // Capture errors for BLOCKHASH operation, if we haven't been supplied the
// required blockhashes // required blockhashes
var hashError error var hashError error
@ -220,11 +224,13 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
} }
blobGasUsed += txBlobGas blobGasUsed += txBlobGas
} }
tracer, err := getTracerFn(txIndex, tx.Hash()) tracer, traceOutput, err := getTracerFn(txIndex, tx.Hash())
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
vmConfig.Tracer = tracer if tracer != nil {
vmConfig.Tracer = tracer.Hooks
}
statedb.SetTxContext(tx.Hash(), txIndex) statedb.SetTxContext(tx.Hash(), txIndex)
var ( var (
@ -234,6 +240,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
) )
evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig)
if tracer != nil && tracer.OnTxStart != nil {
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
}
// (ret []byte, usedGas uint64, failed bool, err error) // (ret []byte, usedGas uint64, failed bool, err error)
msgResult, err := core.ApplyMessage(evm, msg, gaspool) msgResult, err := core.ApplyMessage(evm, msg, gaspool)
if err != nil { if err != nil {
@ -241,6 +250,14 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err) log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err)
rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()})
gaspool.SetGas(prevGas) gaspool.SetGas(prevGas)
if tracer != nil {
if tracer.OnTxEnd != nil {
tracer.OnTxEnd(nil, err)
}
if err := writeTraceResult(tracer, traceOutput); err != nil {
log.Warn("Error writing tracer output", "err", err)
}
}
continue continue
} }
includedTxs = append(includedTxs, tx) includedTxs = append(includedTxs, tx)
@ -282,6 +299,12 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
//receipt.BlockNumber //receipt.BlockNumber
receipt.TransactionIndex = uint(txIndex) receipt.TransactionIndex = uint(txIndex)
receipts = append(receipts, receipt) receipts = append(receipts, receipt)
if tracer != nil {
if tracer.Hooks.OnTxEnd != nil {
tracer.Hooks.OnTxEnd(receipt, nil)
}
writeTraceResult(tracer, traceOutput)
}
} }
txIndex++ txIndex++
@ -307,15 +330,15 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
reward.Sub(reward, new(big.Int).SetUint64(ommer.Delta)) reward.Sub(reward, new(big.Int).SetUint64(ommer.Delta))
reward.Mul(reward, blockReward) reward.Mul(reward, blockReward)
reward.Div(reward, big.NewInt(8)) reward.Div(reward, big.NewInt(8))
statedb.AddBalance(ommer.Address, reward) statedb.AddBalance(ommer.Address, reward, tracing.BalanceIncreaseRewardMineUncle)
} }
statedb.AddBalance(pre.Env.Coinbase, minerReward) statedb.AddBalance(pre.Env.Coinbase, minerReward, tracing.BalanceIncreaseRewardMineBlock)
} }
// Apply withdrawals // Apply withdrawals
for _, w := range pre.Env.Withdrawals { for _, w := range pre.Env.Withdrawals {
// Amount is in gwei, turn into wei // Amount is in gwei, turn into wei
amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei)) amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei))
statedb.AddBalance(w.Address, amount) statedb.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal)
} }
// Commit block // Commit block
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber)) root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber))
@ -358,7 +381,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB
for addr, a := range accounts { for addr, a := range accounts {
statedb.SetCode(addr, a.Code) statedb.SetCode(addr, a.Code)
statedb.SetNonce(addr, a.Nonce) statedb.SetNonce(addr, a.Nonce)
statedb.SetBalance(addr, a.Balance) statedb.SetBalance(addr, a.Balance, tracing.BalanceIncreaseGenesisBalance)
for k, v := range a.Storage { for k, v := range a.Storage {
statedb.SetState(addr, k, v) statedb.SetState(addr, k, v)
} }
@ -395,3 +418,16 @@ func calcDifficulty(config *params.ChainConfig, number, currentTime, parentTime
} }
return ethash.CalcDifficulty(config, currentTime, parent) return ethash.CalcDifficulty(config, currentTime, parent)
} }
func writeTraceResult(tracer *directory.Tracer, f io.WriteCloser) error {
defer f.Close()
result, err := tracer.GetResult()
if err != nil || result == nil {
return err
}
err = json.NewEncoder(f).Encode(result)
if err != nil {
return err
}
return nil
}

View file

@ -30,6 +30,14 @@ var (
Name: "trace", Name: "trace",
Usage: "Output full trace logs to files <txhash>.jsonl", Usage: "Output full trace logs to files <txhash>.jsonl",
} }
TraceTracerFlag = &cli.StringFlag{
Name: "trace.tracer",
Usage: "Configures the use of a custom tracer, e.g native or js tracers. Examples are callTracer and 4byteTracer. These tracers emit results into files as trace-<txIndex>-<txHash>.json",
}
TraceTracerConfigFlag = &cli.StringFlag{
Name: "trace.jsonconfig",
Usage: "The configurations for the custom tracer specified by --trace.tracer. If provided, must be in JSON format",
}
TraceDisableMemoryFlag = &cli.BoolFlag{ TraceDisableMemoryFlag = &cli.BoolFlag{
Name: "trace.nomemory", Name: "trace.nomemory",
Value: true, Value: true,

View file

@ -20,6 +20,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"math/big" "math/big"
"os" "os"
"path" "path"
@ -33,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/directory"
"github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -87,12 +89,7 @@ func Transition(ctx *cli.Context) error {
glogger.Verbosity(slog.Level(ctx.Int(VerbosityFlag.Name))) glogger.Verbosity(slog.Level(ctx.Int(VerbosityFlag.Name)))
log.SetDefault(log.NewLogger(glogger)) log.SetDefault(log.NewLogger(glogger))
var ( var getTracer = func(txIndex int, txHash common.Hash) (*directory.Tracer, io.WriteCloser, error) { return nil, nil, nil }
err error
tracer vm.EVMLogger
)
var getTracer func(txIndex int, txHash common.Hash) (vm.EVMLogger, error)
baseDir, err := createBasedir(ctx) baseDir, err := createBasedir(ctx)
if err != nil { if err != nil {
return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err)) return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err))
@ -124,20 +121,35 @@ func Transition(ctx *cli.Context) error {
prevFile.Close() prevFile.Close()
} }
}() }()
getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { getTracer = func(txIndex int, txHash common.Hash) (*directory.Tracer, io.WriteCloser, error) {
if prevFile != nil {
prevFile.Close()
}
traceFile, err := os.Create(path.Join(baseDir, fmt.Sprintf("trace-%d-%v.jsonl", txIndex, txHash.String()))) traceFile, err := os.Create(path.Join(baseDir, fmt.Sprintf("trace-%d-%v.jsonl", txIndex, txHash.String())))
if err != nil { if err != nil {
return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err))
} }
prevFile = traceFile logger := logger.NewJSONLogger(logConfig, traceFile)
return logger.NewJSONLogger(logConfig, traceFile), nil tracer := &directory.Tracer{
Hooks: logger,
// jsonLogger streams out result to file.
GetResult: func() (json.RawMessage, error) { return nil, nil },
Stop: func(err error) {},
}
return tracer, traceFile, nil
} }
} else { } else if ctx.IsSet(TraceTracerFlag.Name) {
getTracer = func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error) { var config json.RawMessage
return nil, nil if ctx.IsSet(TraceTracerConfigFlag.Name) {
config = []byte(ctx.String(TraceTracerConfigFlag.Name))
}
getTracer = func(txIndex int, txHash common.Hash) (*directory.Tracer, io.WriteCloser, error) {
traceFile, err := os.Create(path.Join(baseDir, fmt.Sprintf("trace-%d-%v.json", txIndex, txHash.String())))
if err != nil {
return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err))
}
tracer, err := directory.DefaultDirectory.New(ctx.String(TraceTracerFlag.Name), nil, config)
if err != nil {
return nil, nil, NewError(ErrorConfig, fmt.Errorf("failed instantiating tracer: %w", err))
}
return tracer, traceFile, nil
} }
} }
// We need to load three things: alloc, env and transactions. May be either in // We need to load three things: alloc, env and transactions. May be either in
@ -176,9 +188,7 @@ func Transition(ctx *cli.Context) error {
} }
prestate.Env = *inputData.Env prestate.Env = *inputData.Env
vmConfig := vm.Config{ vmConfig := vm.Config{}
Tracer: tracer,
}
// Construct the chainconfig // Construct the chainconfig
var chainConfig *params.ChainConfig var chainConfig *params.ChainConfig
if cConf, extraEips, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil { if cConf, extraEips, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil {

View file

@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/core/vm/runtime" "github.com/ethereum/go-ethereum/core/vm/runtime"
"github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/eth/tracers/logger"
@ -116,7 +117,7 @@ func runCmd(ctx *cli.Context) error {
} }
var ( var (
tracer vm.EVMLogger tracer *tracing.Hooks
debugLogger *logger.StructLogger debugLogger *logger.StructLogger
statedb *state.StateDB statedb *state.StateDB
chainConfig *params.ChainConfig chainConfig *params.ChainConfig
@ -130,7 +131,7 @@ func runCmd(ctx *cli.Context) error {
tracer = logger.NewJSONLogger(logconfig, os.Stdout) tracer = logger.NewJSONLogger(logconfig, os.Stdout)
} else if ctx.Bool(DebugFlag.Name) { } else if ctx.Bool(DebugFlag.Name) {
debugLogger = logger.NewStructLogger(logconfig) debugLogger = logger.NewStructLogger(logconfig)
tracer = debugLogger tracer = debugLogger.Hooks()
} else { } else {
debugLogger = logger.NewStructLogger(logconfig) debugLogger = logger.NewStructLogger(logconfig)
} }

View file

@ -64,7 +64,7 @@ func stateTestCmd(ctx *cli.Context) error {
cfg.Tracer = logger.NewJSONLogger(config, os.Stderr) cfg.Tracer = logger.NewJSONLogger(config, os.Stderr)
case ctx.Bool(DebugFlag.Name): case ctx.Bool(DebugFlag.Name):
cfg.Tracer = logger.NewStructLogger(config) cfg.Tracer = logger.NewStructLogger(config).Hooks()
} }
// Load the test content from the input file // Load the test content from the input file
if len(ctx.Args().First()) != 0 { if len(ctx.Args().First()) != 0 {

View file

@ -17,9 +17,12 @@
package main package main
import ( import (
"bufio"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
@ -321,6 +324,107 @@ func TestT8n(t *testing.T) {
} }
} }
func lineIterator(path string) func() (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return func() (string, error) { return err.Error(), err }
}
scanner := bufio.NewScanner(strings.NewReader(string(data)))
return func() (string, error) {
if scanner.Scan() {
return scanner.Text(), nil
}
if err := scanner.Err(); err != nil {
return "", err
}
return "", io.EOF // scanner gobbles io.EOF, but we want it
}
}
// TestT8nTracing is a test that checks the tracing-output from t8n.
func TestT8nTracing(t *testing.T) {
t.Parallel()
tt := new(testT8n)
tt.TestCmd = cmdtest.NewTestCmd(t, tt)
for i, tc := range []struct {
base string
input t8nInput
expExitCode int
extraArgs []string
expectedTraces []string
}{
{
base: "./testdata/31",
input: t8nInput{
"alloc.json", "txs.json", "env.json", "Cancun", "",
},
extraArgs: []string{"--trace"},
expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl"},
},
{
base: "./testdata/31",
input: t8nInput{
"alloc.json", "txs.json", "env.json", "Cancun", "",
},
extraArgs: []string{"--trace.tracer", `
{
result: function(){
return "hello world"
},
fault: function(){}
}`},
expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json"},
},
} {
args := []string{"t8n"}
args = append(args, tc.input.get(tc.base)...)
// Place the output somewhere we can find it
outdir := t.TempDir()
args = append(args, "--output.basedir", outdir)
args = append(args, tc.extraArgs...)
var qArgs []string // quoted args for debugging purposes
for _, arg := range args {
if len(arg) == 0 {
qArgs = append(qArgs, `""`)
} else {
qArgs = append(qArgs, arg)
}
}
tt.Logf("args: %v\n", strings.Join(qArgs, " "))
tt.Run("evm-test", args...)
t.Log(string(tt.Output()))
// Compare the expected traces
for _, traceFile := range tc.expectedTraces {
haveFn := lineIterator(filepath.Join(outdir, traceFile))
wantFn := lineIterator(filepath.Join(tc.base, traceFile))
for line := 0; ; line++ {
want, wErr := wantFn()
have, hErr := haveFn()
if want != have {
t.Fatalf("test %d, trace %v, line %d\nwant: %v\nhave: %v\n",
i, traceFile, line, want, have)
}
if wErr != nil && hErr != nil {
break
}
if wErr != nil {
t.Fatal(wErr)
}
if hErr != nil {
t.Fatal(hErr)
}
t.Logf("%v\n", want)
}
}
if have, want := tt.ExitStatus(), tc.expExitCode; have != want {
t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want)
}
}
}
type t9nInput struct { type t9nInput struct {
inTxs string inTxs string
stFork string stFork string

1
cmd/evm/testdata/31/README.md vendored Normal file
View file

@ -0,0 +1 @@
This test does some EVM execution, and can be used to test the tracers and trace-outputs.

16
cmd/evm/testdata/31/alloc.json vendored Normal file
View file

@ -0,0 +1,16 @@
{
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
"balance" : "0x016345785d8a0000",
"code" : "0x",
"nonce" : "0x00",
"storage" : {
}
},
"0x1111111111111111111111111111111111111111" : {
"balance" : "0x1",
"code" : "0x604060406040604000",
"nonce" : "0x00",
"storage" : {
}
}
}

20
cmd/evm/testdata/31/env.json vendored Normal file
View file

@ -0,0 +1,20 @@
{
"currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
"currentNumber" : "0x01",
"currentTimestamp" : "0x03e8",
"currentGasLimit" : "0x1000000000",
"previousHash" : "0xe4e2a30b340bec696242b67584264f878600dce98354ae0b6328740fd4ff18da",
"currentDataGasUsed" : "0x2000",
"parentTimestamp" : "0x00",
"parentDifficulty" : "0x00",
"parentUncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"parentBeaconBlockRoot" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"currentRandom" : "0x0000000000000000000000000000000000000000000000000000000000020000",
"withdrawals" : [
],
"parentBaseFee" : "0x08",
"parentGasUsed" : "0x00",
"parentGasLimit" : "0x1000000000",
"parentExcessBlobGas" : "0x1000",
"parentBlobGasUsed" : "0x2000"
}

View file

@ -0,0 +1,6 @@
{"pc":0,"op":96,"gas":"0x13498","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":2,"op":96,"gas":"0x13495","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":4,"op":96,"gas":"0x13492","gasCost":"0x3","memSize":0,"stack":["0x40","0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":6,"op":96,"gas":"0x1348f","gasCost":"0x3","memSize":0,"stack":["0x40","0x40","0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":8,"op":0,"gas":"0x1348c","gasCost":"0x0","memSize":0,"stack":["0x40","0x40","0x40","0x40"],"depth":1,"refund":0,"opName":"STOP"}
{"output":"","gasUsed":"0xc"}

14
cmd/evm/testdata/31/txs.json vendored Normal file
View file

@ -0,0 +1,14 @@
[
{
"gas": "0x186a0",
"gasPrice": "0x600",
"input": "0x",
"nonce": "0x0",
"to": "0x1111111111111111111111111111111111111111",
"value": "0x1",
"v" : "0x0",
"r" : "0x0",
"s" : "0x0",
"secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"
}
]

View file

@ -97,6 +97,8 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
utils.MetricsInfluxDBBucketFlag, utils.MetricsInfluxDBBucketFlag,
utils.MetricsInfluxDBOrganizationFlag, utils.MetricsInfluxDBOrganizationFlag,
utils.TxLookupLimitFlag, utils.TxLookupLimitFlag,
utils.VMTraceFlag,
utils.VMTraceConfigFlag,
utils.TransactionHistoryFlag, utils.TransactionHistoryFlag,
utils.StateHistoryFlag, utils.StateHistoryFlag,
}, utils.DatabaseFlags), }, utils.DatabaseFlags),

View file

@ -177,6 +177,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
v := ctx.Uint64(utils.OverrideVerkle.Name) v := ctx.Uint64(utils.OverrideVerkle.Name)
cfg.Eth.OverrideVerkle = &v cfg.Eth.OverrideVerkle = &v
} }
backend, eth := utils.RegisterEthService(stack, &cfg.Eth) backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
// Create gauge with geth system and build information // Create gauge with geth system and build information

View file

@ -43,6 +43,7 @@ import (
// Force-load the tracer engines to trigger registration // Force-load the tracer engines to trigger registration
_ "github.com/ethereum/go-ethereum/eth/tracers/js" _ "github.com/ethereum/go-ethereum/eth/tracers/js"
_ "github.com/ethereum/go-ethereum/eth/tracers/live"
_ "github.com/ethereum/go-ethereum/eth/tracers/native" _ "github.com/ethereum/go-ethereum/eth/tracers/native"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -136,6 +137,8 @@ var (
utils.DeveloperGasLimitFlag, utils.DeveloperGasLimitFlag,
utils.DeveloperPeriodFlag, utils.DeveloperPeriodFlag,
utils.VMEnableDebugFlag, utils.VMEnableDebugFlag,
utils.VMTraceFlag,
utils.VMTraceConfigFlag,
utils.NetworkIdFlag, utils.NetworkIdFlag,
utils.EthStatsURLFlag, utils.EthStatsURLFlag,
utils.NoCompactionFlag, utils.NoCompactionFlag,

View file

@ -21,6 +21,7 @@ import (
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"encoding/hex" "encoding/hex"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"math" "math"
@ -51,6 +52,7 @@ import (
"github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/directory/live"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/remotedb" "github.com/ethereum/go-ethereum/ethdb/remotedb"
"github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/ethstats"
@ -496,7 +498,16 @@ var (
Usage: "Record information useful for VM and contract debugging", Usage: "Record information useful for VM and contract debugging",
Category: flags.VMCategory, Category: flags.VMCategory,
} }
VMTraceFlag = &cli.StringFlag{
Name: "vmtrace",
Usage: "Name of tracer which should record internal VM operations (costly)",
Category: flags.VMCategory,
}
VMTraceConfigFlag = &cli.StringFlag{
Name: "vmtrace.config",
Usage: "Tracer configuration (JSON)",
Category: flags.VMCategory,
}
// API options. // API options.
RPCGlobalGasCapFlag = &cli.Uint64Flag{ RPCGlobalGasCapFlag = &cli.Uint64Flag{
Name: "rpc.gascap", Name: "rpc.gascap",
@ -1838,6 +1849,18 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if err := kzg4844.UseCKZG(ctx.String(CryptoKZGFlag.Name) == "ckzg"); err != nil { if err := kzg4844.UseCKZG(ctx.String(CryptoKZGFlag.Name) == "ckzg"); err != nil {
Fatalf("Failed to set KZG library implementation to %s: %v", ctx.String(CryptoKZGFlag.Name), err) Fatalf("Failed to set KZG library implementation to %s: %v", ctx.String(CryptoKZGFlag.Name), err)
} }
// VM tracing config.
if ctx.IsSet(VMTraceFlag.Name) {
if name := ctx.String(VMTraceFlag.Name); name != "" {
var config string
if ctx.IsSet(VMTraceConfigFlag.Name) {
config = ctx.String(VMTraceConfigFlag.Name)
}
cfg.VMTrace = name
cfg.VMTraceConfig = config
}
}
} }
// SetDNSDiscoveryDefaults configures DNS discovery with the given URL if // SetDNSDiscoveryDefaults configures DNS discovery with the given URL if
@ -2116,12 +2139,25 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
cache.TrieDirtyLimit = ctx.Int(CacheFlag.Name) * ctx.Int(CacheGCFlag.Name) / 100 cache.TrieDirtyLimit = ctx.Int(CacheFlag.Name) * ctx.Int(CacheGCFlag.Name) / 100
} }
vmcfg := vm.Config{EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name)} vmcfg := vm.Config{EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name)}
if ctx.IsSet(VMTraceFlag.Name) {
if name := ctx.String(VMTraceFlag.Name); name != "" {
var config json.RawMessage
if ctx.IsSet(VMTraceConfigFlag.Name) {
config = json.RawMessage(ctx.String(VMTraceConfigFlag.Name))
}
t, err := live.Directory.New(name, config)
if err != nil {
Fatalf("Failed to create tracer %q: %v", name, err)
}
vmcfg.Tracer = t
}
}
// Disable transaction indexing/unindexing by default. // Disable transaction indexing/unindexing by default.
chain, err := core.NewBlockChain(chainDb, cache, gspec, nil, engine, vmcfg, nil, nil) chain, err := core.NewBlockChain(chainDb, cache, gspec, nil, engine, vmcfg, nil, nil)
if err != nil { if err != nil {
Fatalf("Can't create BlockChain: %v", err) Fatalf("Can't create BlockChain: %v", err)
} }
return chain, chainDb return chain, chainDb
} }

View file

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -347,9 +348,9 @@ func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.H
} }
// Finalize implements consensus.Engine and processes withdrawals on top. // Finalize implements consensus.Engine and processes withdrawals on top.
func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, stateDB vm.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) {
if !beacon.IsPoSHeader(header) { if !beacon.IsPoSHeader(header) {
beacon.ethone.Finalize(chain, header, state, txs, uncles, nil) beacon.ethone.Finalize(chain, header, stateDB, txs, uncles, nil)
return return
} }
// Withdrawals processing. // Withdrawals processing.
@ -357,7 +358,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
// Convert amount from gwei to wei. // Convert amount from gwei to wei.
amount := new(big.Int).SetUint64(w.Amount) amount := new(big.Int).SetUint64(w.Amount)
amount = amount.Mul(amount, big.NewInt(params.GWei)) amount = amount.Mul(amount, big.NewInt(params.GWei))
state.AddBalance(w.Address, amount) stateDB.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal)
} }
// No block reward which is issued by consensus layer instead. // No block reward which is issued by consensus layer instead.
} }

View file

@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -569,7 +570,7 @@ var (
// AccumulateRewards credits the coinbase of the given block with the mining // AccumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for // reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded. // included uncles. The coinbase of each uncle block is also rewarded.
func accumulateRewards(config *params.ChainConfig, state vm.StateDB, header *types.Header, uncles []*types.Header) { func accumulateRewards(config *params.ChainConfig, stateDB vm.StateDB, header *types.Header, uncles []*types.Header) {
// Select the correct block reward based on chain progression // Select the correct block reward based on chain progression
blockReward := FrontierBlockReward blockReward := FrontierBlockReward
if config.IsByzantium(header.Number) { if config.IsByzantium(header.Number) {
@ -586,10 +587,10 @@ func accumulateRewards(config *params.ChainConfig, state vm.StateDB, header *typ
r.Sub(r, header.Number) r.Sub(r, header.Number)
r.Mul(r, blockReward) r.Mul(r, blockReward)
r.Div(r, big8) r.Div(r, big8)
state.AddBalance(uncle.Coinbase, r) stateDB.AddBalance(uncle.Coinbase, r, tracing.BalanceIncreaseRewardMineUncle)
r.Div(blockReward, big32) r.Div(blockReward, big32)
reward.Add(reward, r) reward.Add(reward, r)
} }
state.AddBalance(header.Coinbase, reward) stateDB.AddBalance(header.Coinbase, reward, tracing.BalanceIncreaseRewardMineBlock)
} }

View file

@ -21,6 +21,7 @@ import (
"errors" "errors"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -80,7 +81,7 @@ func ApplyDAOHardFork(statedb vm.StateDB) {
// Move every DAO account and extra-balance account funds into the refund contract // Move every DAO account and extra-balance account funds into the refund contract
for _, addr := range params.DAODrainList() { for _, addr := range params.DAODrainList() {
statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr)) statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr), tracing.BalanceIncreaseDaoContract)
statedb.SetBalance(addr, new(big.Int)) statedb.SetBalance(addr, new(big.Int), tracing.BalanceDecreaseDaoAccount)
} }
} }

View file

@ -37,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
@ -185,6 +186,13 @@ func DefaultCacheConfigWithScheme(scheme string) *CacheConfig {
return &config return &config
} }
// txLookup is wrapper over transaction lookup along with the corresponding
// transaction object.
type txLookup struct {
lookup *rawdb.LegacyTxLookupEntry
transaction *types.Transaction
}
// BlockChain represents the canonical chain given a database with a genesis // BlockChain represents the canonical chain given a database with a genesis
// block. The Blockchain manages chain imports, reverts, chain reorganisations. // block. The Blockchain manages chain imports, reverts, chain reorganisations.
// //
@ -258,6 +266,7 @@ type BlockChain struct {
processor Processor // Block transaction processor interface processor Processor // Block transaction processor interface
forker *ForkChoice forker *ForkChoice
vmConfig vm.Config vmConfig vm.Config
logger *tracing.Hooks
} }
// NewBlockChain returns a fully initialised block chain using information // NewBlockChain returns a fully initialised block chain using information
@ -301,6 +310,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks), futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks),
engine: engine, engine: engine,
vmConfig: vmConfig, vmConfig: vmConfig,
logger: vmConfig.Tracer,
} }
bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit)) bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit))
bc.forker = NewForkChoice(bc, shouldPreserve) bc.forker = NewForkChoice(bc, shouldPreserve)
@ -427,6 +437,25 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
} }
} }
if bc.logger != nil && bc.logger.OnBlockchainInit != nil {
bc.logger.OnBlockchainInit(chainConfig)
}
if bc.logger != nil && bc.logger.OnGenesisBlock != nil {
if block := bc.CurrentBlock(); block.Number.Uint64() == 0 {
alloc, err := getGenesisState(bc.db, block.Hash())
if err != nil {
return nil, fmt.Errorf("failed to get genesis state: %w", err)
}
if alloc == nil {
return nil, fmt.Errorf("live blockchain tracer requires genesis alloc to be set")
}
bc.logger.OnGenesisBlock(bc.genesisBlock, alloc)
}
}
// Load any existing snapshot, regenerating it if loading failed // Load any existing snapshot, regenerating it if loading failed
if bc.cacheConfig.SnapshotLimit > 0 { if bc.cacheConfig.SnapshotLimit > 0 {
// If the chain was rewound past the snapshot persistent layer (causing // If the chain was rewound past the snapshot persistent layer (causing
@ -463,7 +492,8 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
} }
rawdb.WriteChainConfig(db, genesisHash, chainConfig) rawdb.WriteChainConfig(db, genesisHash, chainConfig)
} }
// Start tx indexer/unindexer if required.
// Start tx indexer if it's enabled.
if txLookupLimit != nil { if txLookupLimit != nil {
bc.txLookupLimit = *txLookupLimit bc.txLookupLimit = *txLookupLimit
@ -1765,6 +1795,14 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
return it.index, err return it.index, err
} }
stats.processed++ stats.processed++
if bc.logger != nil && bc.logger.OnSkippedBlock != nil {
bc.logger.OnSkippedBlock(tracing.BlockEvent{
Block: block,
TD: bc.GetTd(block.ParentHash(), block.NumberU64()-1),
Finalized: bc.CurrentFinalBlock(),
Safe: bc.CurrentSafeBlock(),
})
}
// We can assume that logs are empty here, since the only way for consecutive // We can assume that logs are empty here, since the only way for consecutive
// Clique blocks to have the same state is if there are no transactions. // Clique blocks to have the same state is if there are no transactions.
@ -1782,6 +1820,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
if err != nil { if err != nil {
return it.index, err return it.index, err
} }
statedb.SetLogger(bc.logger)
// Enable prefetching to pull in trie node paths while processing transactions // Enable prefetching to pull in trie node paths while processing transactions
statedb.StartPrefetcher("chain") statedb.StartPrefetcher("chain")
@ -1795,7 +1834,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps) throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps)
go func(start time.Time, followup *types.Block, throwaway *state.StateDB) { go func(start time.Time, followup *types.Block, throwaway *state.StateDB) {
bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, &followupInterrupt) vmCfg := bc.vmConfig
vmCfg.Tracer = nil
bc.prefetcher.Prefetch(followup, throwaway, vmCfg, &followupInterrupt)
blockPrefetchExecuteTimer.Update(time.Since(start)) blockPrefetchExecuteTimer.Update(time.Since(start))
if followupInterrupt.Load() { if followupInterrupt.Load() {
@ -1805,68 +1846,15 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
} }
} }
// Process block using the parent state as reference point // The traced section of block import.
pstart := time.Now() res, err := bc.processBlock(block, statedb, start, setHead)
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
if err != nil {
bc.reportBlock(block, receipts, err)
followupInterrupt.Store(true)
return it.index, err
}
ptime := time.Since(pstart)
vstart := time.Now()
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil {
bc.reportBlock(block, receipts, err)
followupInterrupt.Store(true)
return it.index, err
}
vtime := time.Since(vstart)
proctime := time.Since(start) // processing + validation
// Update the metrics touched during block processing and validation
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing)
storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing)
snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete(in processing)
snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete(in processing)
accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation)
storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation)
accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation)
storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation)
triehash := statedb.AccountHashes + statedb.StorageHashes // The time spent on tries hashing
trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update
trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read
trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read
blockExecutionTimer.Update(ptime - trieRead) // The time spent on EVM processing
blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation
// Write the block to the chain and get the status.
var (
wstart = time.Now()
status WriteStatus
)
if !setHead {
// Don't set the head, only insert the block
err = bc.writeBlockWithState(block, receipts, statedb)
} else {
status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false)
}
followupInterrupt.Store(true) followupInterrupt.Store(true)
if err != nil { if err != nil {
return it.index, err return it.index, err
} }
// Update the metrics touched during block commit
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them
snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them
triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits)
blockInsertTimer.UpdateSince(start)
// Report the import stats before returning the various results // Report the import stats before returning the various results
stats.processed++ stats.processed++
stats.usedGas += usedGas stats.usedGas += res.usedGas
var snapDiffItems, snapBufItems common.StorageSize var snapDiffItems, snapBufItems common.StorageSize
if bc.snaps != nil { if bc.snaps != nil {
@ -1878,11 +1866,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
if !setHead { if !setHead {
// After merge we expect few side chains. Simply count // After merge we expect few side chains. Simply count
// all blocks the CL gives us for GC processing time // all blocks the CL gives us for GC processing time
bc.gcproc += proctime bc.gcproc += res.procTime
return it.index, nil // Direct block insertion of a single block return it.index, nil // Direct block insertion of a single block
} }
switch status { switch res.status {
case CanonStatTy: case CanonStatTy:
log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(), log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(),
"uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(),
@ -1892,7 +1879,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
lastCanon = block lastCanon = block
// Only count canonical blocks for GC processing time // Only count canonical blocks for GC processing time
bc.gcproc += proctime bc.gcproc += res.procTime
case SideStatTy: case SideStatTy:
log.Debug("Inserted forked block", "number", block.Number(), "hash", block.Hash(), log.Debug("Inserted forked block", "number", block.Number(), "hash", block.Hash(),
@ -1929,6 +1916,91 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
return it.index, err return it.index, err
} }
// blockProcessingResult is a summary of block processing
// used for updating the stats.
type blockProcessingResult struct {
usedGas uint64
procTime time.Duration
status WriteStatus
}
// processBlock executes and validates the given block. If there was no error
// it writes the block and associated state to database.
func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, start time.Time, setHead bool) (_ *blockProcessingResult, blockEndErr error) {
if bc.logger != nil && bc.logger.OnBlockStart != nil {
td := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
bc.logger.OnBlockStart(tracing.BlockEvent{
Block: block,
TD: td,
Finalized: bc.CurrentFinalBlock(),
Safe: bc.CurrentSafeBlock(),
})
}
if bc.logger != nil && bc.logger.OnBlockEnd != nil {
defer func() {
bc.logger.OnBlockEnd(blockEndErr)
}()
}
// Process block using the parent state as reference point
pstart := time.Now()
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
if err != nil {
bc.reportBlock(block, receipts, err)
return nil, err
}
ptime := time.Since(pstart)
vstart := time.Now()
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil {
bc.reportBlock(block, receipts, err)
return nil, err
}
vtime := time.Since(vstart)
proctime := time.Since(start) // processing + validation
// Update the metrics touched during block processing and validation
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing)
storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing)
snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete(in processing)
snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete(in processing)
accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation)
storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation)
accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation)
storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation)
triehash := statedb.AccountHashes + statedb.StorageHashes // The time spent on tries hashing
trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update
trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read
trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read
blockExecutionTimer.Update(ptime - trieRead) // The time spent on EVM processing
blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation
// Write the block to the chain and get the status.
var (
wstart = time.Now()
status WriteStatus
)
if !setHead {
// Don't set the head, only insert the block
err = bc.writeBlockWithState(block, receipts, statedb)
} else {
status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false)
}
if err != nil {
return nil, err
}
// Update the metrics touched during block commit
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them
snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them
triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits)
blockInsertTimer.UpdateSince(start)
return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil
}
// insertSideChain is called when an import batch hits upon a pruned ancestor // insertSideChain is called when an import batch hits upon a pruned ancestor
// error, which happens when a sidechain with a sufficiently old fork-block is // error, which happens when a sidechain with a sufficiently old fork-block is
// found. // found.

View file

@ -4682,7 +4682,7 @@ func TestEIP3651(t *testing.T) {
b.AddTx(tx) b.AddTx(tx)
}) })
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr)}, nil, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr).Hooks()}, nil, nil)
if err != nil { if err != nil {
t.Fatalf("failed to create tester chain: %v", err) t.Fatalf("failed to create tester chain: %v", err)
} }

View file

@ -406,6 +406,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
// then generate chain on top. // then generate chain on top.
func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts) { func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts) {
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
triedb := trie.NewDatabase(db, trie.HashDefaults) triedb := trie.NewDatabase(db, trie.HashDefaults)
defer triedb.Close() defer triedb.Close()
_, err := genesis.Commit(db, triedb) _, err := genesis.Commit(db, triedb)

View file

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
) )
@ -135,6 +136,6 @@ func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool {
// Transfer subtracts amount from sender and adds amount to recipient using the given Db // Transfer subtracts amount from sender and adds amount to recipient using the given Db
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {
db.SubBalance(sender, amount) db.SubBalance(sender, amount, tracing.BalanceChangeTransfer)
db.AddBalance(recipient, amount) db.AddBalance(recipient, amount, tracing.BalanceChangeTransfer)
} }

View file

@ -18,7 +18,6 @@ package core
import ( import (
"bytes" "bytes"
"encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -30,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "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/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
@ -41,10 +41,15 @@ import (
) )
//go:generate go run github.com/fjl/gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go //go:generate go run github.com/fjl/gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go
//go:generate go run github.com/fjl/gencodec -type GenesisAccount -field-override genesisAccountMarshaling -out gen_genesis_account.go
var errGenesisNoConfig = errors.New("genesis has no chain configuration") var errGenesisNoConfig = errors.New("genesis has no chain configuration")
// use types.GenesisAccount instead (Deprecation not ported from latest Geth for now).
type GenesisAccount = types.Account
// use types.GenesisAlloc instead (Deprecation not ported from latest Geth for now).
type GenesisAlloc = types.GenesisAlloc
// Genesis specifies the header fields, state of a genesis block. It also defines hard // Genesis specifies the header fields, state of a genesis block. It also defines hard
// fork switch-over blocks through the chain configuration. // fork switch-over blocks through the chain configuration.
type Genesis struct { type Genesis struct {
@ -106,23 +111,8 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) {
return &genesis, nil return &genesis, nil
} }
// GenesisAlloc specifies the initial state that is part of the genesis block. // hashAlloc computes the state root according to the genesis specification.
type GenesisAlloc map[common.Address]GenesisAccount func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
func (ga *GenesisAlloc) UnmarshalJSON(data []byte) error {
m := make(map[common.UnprefixedAddress]GenesisAccount)
if err := json.Unmarshal(data, &m); err != nil {
return err
}
*ga = make(GenesisAlloc)
for addr, a := range m {
(*ga)[common.Address(addr)] = a
}
return nil
}
// hash computes the state root according to the genesis specification.
func (ga *GenesisAlloc) hash(isVerkle bool) (common.Hash, error) {
// If a genesis-time verkle trie is requested, create a trie config // If a genesis-time verkle trie is requested, create a trie config
// with the verkle trie enabled so that the tree can be initialized // with the verkle trie enabled so that the tree can be initialized
// as such. // as such.
@ -142,7 +132,7 @@ func (ga *GenesisAlloc) hash(isVerkle bool) (common.Hash, error) {
} }
for addr, account := range *ga { for addr, account := range *ga {
if account.Balance != nil { if account.Balance != nil {
statedb.AddBalance(addr, account.Balance) statedb.AddBalance(addr, account.Balance, tracing.BalanceIncreaseGenesisBalance)
} }
statedb.SetCode(addr, account.Code) statedb.SetCode(addr, account.Code)
statedb.SetNonce(addr, account.Nonce) statedb.SetNonce(addr, account.Nonce)
@ -153,17 +143,19 @@ func (ga *GenesisAlloc) hash(isVerkle bool) (common.Hash, error) {
return statedb.Commit(0, false) return statedb.Commit(0, false)
} }
// flush is very similar with hash, but the main difference is all the generated // flushAlloc is very similar with hash, but the main difference is all the generated
// states will be persisted into the given database. Also, the genesis state // states will be persisted into the given database. Also, the genesis state
// specification will be flushed as well. // specification will be flushed as well.
func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhash common.Hash) error { func flushAlloc(ga *types.GenesisAlloc, db ethdb.Database, triedb *trie.Database, blockhash common.Hash) error {
statedb, err := state.New(types.EmptyRootHash, state.NewDatabaseWithNodeDB(db, triedb), nil) statedb, err := state.New(types.EmptyRootHash, state.NewDatabaseWithNodeDB(db, triedb), nil)
if err != nil { if err != nil {
return err return err
} }
for addr, account := range *ga { for addr, account := range *ga {
if account.Balance != nil { if account.Balance != nil {
statedb.AddBalance(addr, account.Balance) // This is not actually logged via tracer because OnGenesisBlock
// already captures the allocations.
statedb.AddBalance(addr, account.Balance, tracing.BalanceIncreaseGenesisBalance)
} }
statedb.SetCode(addr, account.Code) statedb.SetCode(addr, account.Code)
statedb.SetNonce(addr, account.Nonce) statedb.SetNonce(addr, account.Nonce)
@ -190,13 +182,37 @@ func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhas
return nil return nil
} }
// GenesisAccount is an account in the state of the genesis block. func getGenesisState(db ethdb.Database, blockhash common.Hash) (alloc GenesisAlloc, err error) {
type GenesisAccount struct { blob := rawdb.ReadGenesisStateSpec(db, blockhash)
Code []byte `json:"code,omitempty"` if len(blob) != 0 {
Storage map[common.Hash]common.Hash `json:"storage,omitempty"` if err := alloc.UnmarshalJSON(blob); err != nil {
Balance *big.Int `json:"balance" gencodec:"required"` return nil, err
Nonce uint64 `json:"nonce,omitempty"` }
PrivateKey []byte `json:"secretKey,omitempty"` // for tests
return alloc, nil
}
// Genesis allocation is missing and there are several possibilities:
// the node is legacy which doesn't persist the genesis allocation or
// the persisted allocation is just lost.
// - supported networks(mainnet, testnets), recover with defined allocations
// - private network, can't recover
var genesis *Genesis
switch blockhash {
case params.MainnetGenesisHash:
genesis = DefaultGenesisBlock()
case params.GoerliGenesisHash:
genesis = DefaultGoerliGenesisBlock()
case params.SepoliaGenesisHash:
genesis = DefaultSepoliaGenesisBlock()
case params.HoleskyGenesisHash:
genesis = DefaultHoleskyGenesisBlock()
}
if genesis != nil {
return genesis.Alloc, nil
}
return nil, nil
} }
// field type overrides for gencodec // field type overrides for gencodec
@ -214,34 +230,6 @@ type genesisSpecMarshaling struct {
BlobGasUsed *math.HexOrDecimal64 BlobGasUsed *math.HexOrDecimal64
} }
type genesisAccountMarshaling struct {
Code hexutil.Bytes
Balance *math.HexOrDecimal256
Nonce math.HexOrDecimal64
Storage map[storageJSON]storageJSON
PrivateKey hexutil.Bytes
}
// storageJSON represents a 256 bit byte array, but allows less than 256 bits when
// unmarshaling from hex.
type storageJSON common.Hash
func (h *storageJSON) UnmarshalText(text []byte) error {
text = bytes.TrimPrefix(text, []byte("0x"))
if len(text) > 64 {
return fmt.Errorf("too many hex characters in storage key/value %q", text)
}
offset := len(h) - len(text)/2 // pad on the left
if _, err := hex.Decode(h[offset:], text); err != nil {
return fmt.Errorf("invalid hex storage key/value %q", text)
}
return nil
}
func (h storageJSON) MarshalText() ([]byte, error) {
return hexutil.Bytes(h[:]).MarshalText()
}
// GenesisMismatchError is raised when trying to overwrite an existing // GenesisMismatchError is raised when trying to overwrite an existing
// genesis block with an incompatible one. // genesis block with an incompatible one.
type GenesisMismatchError struct { type GenesisMismatchError struct {
@ -298,6 +286,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen
} else { } else {
log.Info("Writing custom genesis block") log.Info("Writing custom genesis block")
} }
applyOverrides(genesis.Config) applyOverrides(genesis.Config)
block, err := genesis.Commit(db, triedb) block, err := genesis.Commit(db, triedb)
if err != nil { if err != nil {
@ -429,7 +418,7 @@ func (g *Genesis) IsVerkle() bool {
// ToBlock returns the genesis block according to genesis specification. // ToBlock returns the genesis block according to genesis specification.
func (g *Genesis) ToBlock() *types.Block { func (g *Genesis) ToBlock() *types.Block {
root, err := g.Alloc.hash(g.IsVerkle()) root, err := hashAlloc(&g.Alloc, g.IsVerkle())
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -506,7 +495,7 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database) (*types.Block
// All the checks has passed, flush the states derived from the genesis // All the checks has passed, flush the states derived from the genesis
// specification as well as the specification itself into the provided // specification as well as the specification itself into the provided
// database. // database.
if err := g.Alloc.flush(db, triedb, block.Hash()); err != nil { if err := flushAlloc(&g.Alloc, db, triedb, block.Hash()); err != nil {
return nil, err return nil, err
} }
rawdb.WriteTd(db, block.Hash(), block.NumberU64(), block.Difficulty()) rawdb.WriteTd(db, block.Hash(), block.NumberU64(), block.Difficulty())
@ -522,6 +511,8 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database) (*types.Block
// MustCommit writes the genesis block and state to db, panicking on error. // MustCommit writes the genesis block and state to db, panicking on error.
// The block is committed as the canonical head block. // The block is committed as the canonical head block.
// Note the state changes will be committed in hash-based scheme, use Commit
// if path-scheme is preferred.
func (g *Genesis) MustCommit(db ethdb.Database, triedb *trie.Database) *types.Block { func (g *Genesis) MustCommit(db ethdb.Database, triedb *trie.Database) *types.Block {
block, err := g.Commit(db, triedb) block, err := g.Commit(db, triedb)
if err != nil { if err != nil {

View file

@ -232,7 +232,7 @@ func TestReadWriteGenesisAlloc(t *testing.T) {
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
{2}: {Balance: big.NewInt(2), Storage: map[common.Hash]common.Hash{{2}: {2}}}, {2}: {Balance: big.NewInt(2), Storage: map[common.Hash]common.Hash{{2}: {2}}},
} }
hash, _ = alloc.hash(false) hash, _ = hashAlloc(alloc, false)
) )
blob, _ := json.Marshal(alloc) blob, _ := json.Marshal(alloc)
rawdb.WriteGenesisStateSpec(db, hash, blob) rawdb.WriteGenesisStateSpec(db, hash, blob)

View file

@ -24,6 +24,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
@ -243,6 +244,9 @@ func (s *stateObject) SetState(key, value common.Hash) {
key: key, key: key,
prevalue: prev, prevalue: prev,
}) })
if s.db.logger != nil && s.db.logger.OnStorageChange != nil {
s.db.logger.OnStorageChange(s.address, key, prev, value)
}
s.setState(key, value) s.setState(key, value)
} }
@ -405,7 +409,7 @@ func (s *stateObject) commit() (*trienode.NodeSet, error) {
// AddBalance adds amount to s's balance. // AddBalance adds amount to s's balance.
// It is used to add funds to the destination account of a transfer. // It is used to add funds to the destination account of a transfer.
func (s *stateObject) AddBalance(amount *big.Int) { func (s *stateObject) AddBalance(amount *big.Int, reason tracing.BalanceChangeReason) {
// EIP161: We must check emptiness for the objects such that the account // EIP161: We must check emptiness for the objects such that the account
// clearing (0,0,0 objects) can take effect. // clearing (0,0,0 objects) can take effect.
if amount.Sign() == 0 { if amount.Sign() == 0 {
@ -414,23 +418,26 @@ func (s *stateObject) AddBalance(amount *big.Int) {
} }
return return
} }
s.SetBalance(new(big.Int).Add(s.Balance(), amount)) s.SetBalance(new(big.Int).Add(s.Balance(), amount), reason)
} }
// SubBalance removes amount from s's balance. // SubBalance removes amount from s's balance.
// It is used to remove funds from the origin account of a transfer. // It is used to remove funds from the origin account of a transfer.
func (s *stateObject) SubBalance(amount *big.Int) { func (s *stateObject) SubBalance(amount *big.Int, reason tracing.BalanceChangeReason) {
if amount.Sign() == 0 { if amount.Sign() == 0 {
return return
} }
s.SetBalance(new(big.Int).Sub(s.Balance(), amount)) s.SetBalance(new(big.Int).Sub(s.Balance(), amount), reason)
} }
func (s *stateObject) SetBalance(amount *big.Int) { func (s *stateObject) SetBalance(amount *big.Int, reason tracing.BalanceChangeReason) {
s.db.journal.append(balanceChange{ s.db.journal.append(balanceChange{
account: &s.address, account: &s.address,
prev: new(big.Int).Set(s.data.Balance), prev: new(big.Int).Set(s.data.Balance),
}) })
if s.db.logger != nil && s.db.logger.OnBalanceChange != nil {
s.db.logger.OnBalanceChange(s.address, s.Balance(), amount, reason)
}
s.setBalance(amount) s.setBalance(amount)
} }
@ -508,6 +515,9 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
prevhash: s.CodeHash(), prevhash: s.CodeHash(),
prevcode: prevcode, prevcode: prevcode,
}) })
if s.db.logger != nil && s.db.logger.OnCodeChange != nil {
s.db.logger.OnCodeChange(s.address, common.BytesToHash(s.CodeHash()), prevcode, codeHash, code)
}
s.setCode(codeHash, code) s.setCode(codeHash, code)
} }
@ -522,6 +532,9 @@ func (s *stateObject) SetNonce(nonce uint64) {
account: &s.address, account: &s.address,
prev: s.data.Nonce, prev: s.data.Nonce,
}) })
if s.db.logger != nil && s.db.logger.OnNonceChange != nil {
s.db.logger.OnNonceChange(s.address, s.data.Nonce, nonce)
}
s.setNonce(nonce) s.setNonce(nonce)
} }

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
@ -49,11 +50,11 @@ func TestDump(t *testing.T) {
// generate a few entries // generate a few entries
obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01})) obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01}))
obj1.AddBalance(big.NewInt(22)) obj1.AddBalance(big.NewInt(22), tracing.BalanceChangeUnspecified)
obj2 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02})) obj2 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02}))
obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3})
obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02})) obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02}))
obj3.SetBalance(big.NewInt(44)) obj3.SetBalance(big.NewInt(44), tracing.BalanceChangeUnspecified)
// write some of them to the trie // write some of them to the trie
s.state.updateStateObject(obj1) s.state.updateStateObject(obj1)
@ -106,13 +107,13 @@ func TestIterativeDump(t *testing.T) {
// generate a few entries // generate a few entries
obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01})) obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01}))
obj1.AddBalance(big.NewInt(22)) obj1.AddBalance(big.NewInt(22), tracing.BalanceChangeUnspecified)
obj2 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02})) obj2 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02}))
obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3})
obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02})) obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02}))
obj3.SetBalance(big.NewInt(44)) obj3.SetBalance(big.NewInt(44), tracing.BalanceChangeUnspecified)
obj4 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x00})) obj4 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x00}))
obj4.AddBalance(big.NewInt(1337)) obj4.AddBalance(big.NewInt(1337), tracing.BalanceChangeUnspecified)
// write some of them to the trie // write some of them to the trie
s.state.updateStateObject(obj1) s.state.updateStateObject(obj1)
@ -208,7 +209,7 @@ func TestSnapshot2(t *testing.T) {
// db, trie are already non-empty values // db, trie are already non-empty values
so0 := state.getStateObject(stateobjaddr0) so0 := state.getStateObject(stateobjaddr0)
so0.SetBalance(big.NewInt(42)) so0.SetBalance(big.NewInt(42), tracing.BalanceChangeUnspecified)
so0.SetNonce(43) so0.SetNonce(43)
so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'}) so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
so0.selfDestructed = false so0.selfDestructed = false
@ -220,7 +221,7 @@ func TestSnapshot2(t *testing.T) {
// and one with deleted == true // and one with deleted == true
so1 := state.getStateObject(stateobjaddr1) so1 := state.getStateObject(stateobjaddr1)
so1.SetBalance(big.NewInt(52)) so1.SetBalance(big.NewInt(52), tracing.BalanceChangeUnspecified)
so1.SetNonce(53) so1.SetNonce(53)
so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'}) so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'})
so1.selfDestructed = true so1.selfDestructed = true

View file

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -64,6 +65,7 @@ type StateDB struct {
prefetcher *triePrefetcher prefetcher *triePrefetcher
trie Trie trie Trie
hasher crypto.KeccakState hasher crypto.KeccakState
logger *tracing.Hooks
snaps *snapshot.Tree // Nil if snapshot is not available snaps *snapshot.Tree // Nil if snapshot is not available
snap snapshot.Snapshot // Nil if snapshot is not available snap snapshot.Snapshot // Nil if snapshot is not available
@ -173,6 +175,11 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
return sdb, nil return sdb, nil
} }
// SetLogger sets the logger for account update hooks.
func (s *StateDB) SetLogger(l *tracing.Hooks) {
s.logger = l
}
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
// state trie concurrently while the state is mutated so that when we reach the // state trie concurrently while the state is mutated so that when we reach the
// commit phase, most of the needed data is already hot. // commit phase, most of the needed data is already hot.
@ -213,6 +220,9 @@ func (s *StateDB) AddLog(log *types.Log) {
log.TxHash = s.thash log.TxHash = s.thash
log.TxIndex = uint(s.txIndex) log.TxIndex = uint(s.txIndex)
log.Index = s.logSize log.Index = s.logSize
if s.logger != nil && s.logger.OnLog != nil {
s.logger.OnLog(log)
}
s.logs[s.thash] = append(s.logs[s.thash], log) s.logs[s.thash] = append(s.logs[s.thash], log)
s.logSize++ s.logSize++
} }
@ -374,25 +384,25 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool {
*/ */
// AddBalance adds amount to the account associated with addr. // AddBalance adds amount to the account associated with addr.
func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) { func (s *StateDB) AddBalance(addr common.Address, amount *big.Int, reason tracing.BalanceChangeReason) {
stateObject := s.GetOrNewStateObject(addr) stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil { if stateObject != nil {
stateObject.AddBalance(amount) stateObject.AddBalance(amount, reason)
} }
} }
// SubBalance subtracts amount from the account associated with addr. // SubBalance subtracts amount from the account associated with addr.
func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) { func (s *StateDB) SubBalance(addr common.Address, amount *big.Int, reason tracing.BalanceChangeReason) {
stateObject := s.GetOrNewStateObject(addr) stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil { if stateObject != nil {
stateObject.SubBalance(amount) stateObject.SubBalance(amount, reason)
} }
} }
func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) { func (s *StateDB) SetBalance(addr common.Address, amount *big.Int, reason tracing.BalanceChangeReason) {
stateObject := s.GetOrNewStateObject(addr) stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil { if stateObject != nil {
stateObject.SetBalance(amount) stateObject.SetBalance(amount, reason)
} }
} }
@ -448,11 +458,18 @@ func (s *StateDB) SelfDestruct(addr common.Address) {
if stateObject == nil { if stateObject == nil {
return return
} }
var (
prev = stateObject.Balance()
n = new(big.Int)
)
s.journal.append(selfDestructChange{ s.journal.append(selfDestructChange{
account: &addr, account: &addr,
prev: stateObject.selfDestructed, prev: stateObject.selfDestructed,
prevbalance: new(big.Int).Set(stateObject.Balance()), prevbalance: new(big.Int).Set(stateObject.Balance()),
}) })
if s.logger != nil && s.logger.OnBalanceChange != nil && prev.Sign() > 0 {
s.logger.OnBalanceChange(addr, prev, n, tracing.BalanceDecreaseSelfdestruct)
}
stateObject.markSelfdestructed() stateObject.markSelfdestructed()
stateObject.data.Balance = new(big.Int) stateObject.data.Balance = new(big.Int)
} }
@ -835,6 +852,10 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
obj.deleted = true obj.deleted = true
// If ether was sent to account post-selfdestruct it is burnt.
if bal := obj.Balance(); s.logger != nil && s.logger.OnBalanceChange != nil && obj.selfDestructed && bal.Sign() != 0 {
s.logger.OnBalanceChange(obj.address, bal, new(big.Int), tracing.BalanceDecreaseSelfdestructBurn)
}
// We need to maintain account deletions explicitly (will remain // We need to maintain account deletions explicitly (will remain
// set indefinitely). Note only the first occurred self-destruct // set indefinitely). Note only the first occurred self-destruct
// event is tracked. // event is tracked.

View file

@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
@ -60,7 +61,7 @@ func newStateTestAction(addr common.Address, r *rand.Rand, index int) testAction
{ {
name: "SetBalance", name: "SetBalance",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {
s.SetBalance(addr, big.NewInt(a.args[0])) s.SetBalance(addr, big.NewInt(a.args[0]), tracing.BalanceChangeUnspecified)
}, },
args: make([]int64, 1), args: make([]int64, 1),
}, },

View file

@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
@ -56,7 +57,7 @@ func TestUpdateLeaks(t *testing.T) {
// Update it with some accounts // Update it with some accounts
for i := byte(0); i < 255; i++ { for i := byte(0); i < 255; i++ {
addr := common.BytesToAddress([]byte{i}) addr := common.BytesToAddress([]byte{i})
state.AddBalance(addr, big.NewInt(int64(11*i))) state.AddBalance(addr, big.NewInt(int64(11*i)), tracing.BalanceChangeUnspecified)
state.SetNonce(addr, uint64(42*i)) state.SetNonce(addr, uint64(42*i))
if i%2 == 0 { if i%2 == 0 {
state.SetState(addr, common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i})) state.SetState(addr, common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i}))
@ -91,7 +92,7 @@ func TestIntermediateLeaks(t *testing.T) {
finalState, _ := New(types.EmptyRootHash, NewDatabaseWithNodeDB(finalDb, finalNdb), nil) finalState, _ := New(types.EmptyRootHash, NewDatabaseWithNodeDB(finalDb, finalNdb), nil)
modify := func(state *StateDB, addr common.Address, i, tweak byte) { modify := func(state *StateDB, addr common.Address, i, tweak byte) {
state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak))) state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak)), tracing.BalanceChangeUnspecified)
state.SetNonce(addr, uint64(42*i+tweak)) state.SetNonce(addr, uint64(42*i+tweak))
if i%2 == 0 { if i%2 == 0 {
state.SetState(addr, common.Hash{i, i, i, 0}, common.Hash{}) state.SetState(addr, common.Hash{i, i, i, 0}, common.Hash{})
@ -167,7 +168,7 @@ func TestCopy(t *testing.T) {
for i := byte(0); i < 255; i++ { for i := byte(0); i < 255; i++ {
obj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i})) obj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(big.NewInt(int64(i))) obj.AddBalance(big.NewInt(int64(i)), tracing.BalanceChangeUnspecified)
orig.updateStateObject(obj) orig.updateStateObject(obj)
} }
orig.Finalise(false) orig.Finalise(false)
@ -184,9 +185,9 @@ func TestCopy(t *testing.T) {
copyObj := copy.(*StateDB).GetOrNewStateObject(common.BytesToAddress([]byte{i})) copyObj := copy.(*StateDB).GetOrNewStateObject(common.BytesToAddress([]byte{i}))
ccopyObj := ccopy.(*StateDB).GetOrNewStateObject(common.BytesToAddress([]byte{i})) ccopyObj := ccopy.(*StateDB).GetOrNewStateObject(common.BytesToAddress([]byte{i}))
origObj.AddBalance(big.NewInt(2 * int64(i))) origObj.AddBalance(big.NewInt(2*int64(i)), tracing.BalanceChangeUnspecified)
copyObj.AddBalance(big.NewInt(3 * int64(i))) copyObj.AddBalance(big.NewInt(3*int64(i)), tracing.BalanceChangeUnspecified)
ccopyObj.AddBalance(big.NewInt(4 * int64(i))) ccopyObj.AddBalance(big.NewInt(4*int64(i)), tracing.BalanceChangeUnspecified)
orig.updateStateObject(origObj) orig.updateStateObject(origObj)
copy.(*StateDB).updateStateObject(copyObj) copy.(*StateDB).updateStateObject(copyObj)
@ -266,14 +267,14 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
{ {
name: "SetBalance", name: "SetBalance",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {
s.SetBalance(addr, big.NewInt(a.args[0])) s.SetBalance(addr, big.NewInt(a.args[0]), tracing.BalanceChangeUnspecified)
}, },
args: make([]int64, 1), args: make([]int64, 1),
}, },
{ {
name: "AddBalance", name: "AddBalance",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {
s.AddBalance(addr, big.NewInt(a.args[0])) s.AddBalance(addr, big.NewInt(a.args[0]), tracing.BalanceChangeUnspecified)
}, },
args: make([]int64, 1), args: make([]int64, 1),
}, },
@ -536,7 +537,7 @@ func TestTouchDelete(t *testing.T) {
s.state, _ = New(root, s.state.db, s.state.snaps) s.state, _ = New(root, s.state.db, s.state.snaps)
snapshot := s.state.Snapshot() snapshot := s.state.Snapshot()
s.state.AddBalance(common.Address{}, new(big.Int)) s.state.AddBalance(common.Address{}, new(big.Int), tracing.BalanceChangeUnspecified)
if len(s.state.journal.dirties) != 1 { if len(s.state.journal.dirties) != 1 {
t.Fatal("expected one dirty state object") t.Fatal("expected one dirty state object")
@ -552,12 +553,12 @@ func TestTouchDelete(t *testing.T) {
func TestCopyOfCopy(t *testing.T) { func TestCopyOfCopy(t *testing.T) {
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
addr := common.HexToAddress("aaaa") addr := common.HexToAddress("aaaa")
state.SetBalance(addr, big.NewInt(42)) state.SetBalance(addr, big.NewInt(42), tracing.BalanceChangeUnspecified)
if got := state.Copy().GetBalance(addr).Uint64(); got != 42 { if got := state.Copy().(*StateDB).GetBalance(addr).Uint64(); got != 42 {
t.Fatalf("1st copy fail, expected 42, got %v", got) t.Fatalf("1st copy fail, expected 42, got %v", got)
} }
if got := state.Copy().Copy().GetBalance(addr).Uint64(); got != 42 { if got := state.Copy().(*StateDB).Copy().(*StateDB).GetBalance(addr).Uint64(); got != 42 {
t.Fatalf("2nd copy fail, expected 42, got %v", got) t.Fatalf("2nd copy fail, expected 42, got %v", got)
} }
} }
@ -575,9 +576,9 @@ func TestCopyCommitCopy(t *testing.T) {
skey := common.HexToHash("aaa") skey := common.HexToHash("aaa")
sval := common.HexToHash("bbb") sval := common.HexToHash("bbb")
state.SetBalance(addr, big.NewInt(42)) // Change the account trie state.SetBalance(addr, big.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetCode(addr, []byte("hello")) // Change an external metadata
state.SetState(addr, skey, sval) // Change the storage trie state.SetState(addr, skey, sval) // Change the storage trie
if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
@ -592,7 +593,7 @@ func TestCopyCommitCopy(t *testing.T) {
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{}) t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
} }
// Copy the non-committed state database and check pre/post commit balance // Copy the non-committed state database and check pre/post commit balance
copyOne := state.Copy() copyOne := state.Copy().(*StateDB)
if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
t.Fatalf("first copy pre-commit balance mismatch: have %v, want %v", balance, 42) t.Fatalf("first copy pre-commit balance mismatch: have %v, want %v", balance, 42)
} }
@ -606,7 +607,7 @@ func TestCopyCommitCopy(t *testing.T) {
t.Fatalf("first copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{}) t.Fatalf("first copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{})
} }
// Copy the copy and check the balance once more // Copy the copy and check the balance once more
copyTwo := copyOne.Copy() copyTwo := copyOne.Copy().(*StateDB)
if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
t.Fatalf("second copy balance mismatch: have %v, want %v", balance, 42) t.Fatalf("second copy balance mismatch: have %v, want %v", balance, 42)
} }
@ -648,9 +649,9 @@ func TestCopyCopyCommitCopy(t *testing.T) {
skey := common.HexToHash("aaa") skey := common.HexToHash("aaa")
sval := common.HexToHash("bbb") sval := common.HexToHash("bbb")
state.SetBalance(addr, big.NewInt(42)) // Change the account trie state.SetBalance(addr, big.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetCode(addr, []byte("hello")) // Change an external metadata
state.SetState(addr, skey, sval) // Change the storage trie state.SetState(addr, skey, sval) // Change the storage trie
if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
@ -665,7 +666,7 @@ func TestCopyCopyCommitCopy(t *testing.T) {
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{}) t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
} }
// Copy the non-committed state database and check pre/post commit balance // Copy the non-committed state database and check pre/post commit balance
copyOne := state.Copy() copyOne := state.Copy().(*StateDB)
if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
t.Fatalf("first copy balance mismatch: have %v, want %v", balance, 42) t.Fatalf("first copy balance mismatch: have %v, want %v", balance, 42)
} }
@ -679,7 +680,7 @@ func TestCopyCopyCommitCopy(t *testing.T) {
t.Fatalf("first copy committed storage slot mismatch: have %x, want %x", val, common.Hash{}) t.Fatalf("first copy committed storage slot mismatch: have %x, want %x", val, common.Hash{})
} }
// Copy the copy and check the balance once more // Copy the copy and check the balance once more
copyTwo := copyOne.Copy() copyTwo := copyOne.Copy().(*StateDB)
if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
t.Fatalf("second copy pre-commit balance mismatch: have %v, want %v", balance, 42) t.Fatalf("second copy pre-commit balance mismatch: have %v, want %v", balance, 42)
} }
@ -693,7 +694,7 @@ func TestCopyCopyCommitCopy(t *testing.T) {
t.Fatalf("second copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{}) t.Fatalf("second copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{})
} }
// Copy the copy-copy and check the balance once more // Copy the copy-copy and check the balance once more
copyThree := copyTwo.Copy() copyThree := copyTwo.Copy().(*StateDB)
if balance := copyThree.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { if balance := copyThree.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
t.Fatalf("third copy balance mismatch: have %v, want %v", balance, 42) t.Fatalf("third copy balance mismatch: have %v, want %v", balance, 42)
} }
@ -717,9 +718,9 @@ func TestCommitCopy(t *testing.T) {
skey := common.HexToHash("aaa") skey := common.HexToHash("aaa")
sval := common.HexToHash("bbb") sval := common.HexToHash("bbb")
state.SetBalance(addr, big.NewInt(42)) // Change the account trie state.SetBalance(addr, big.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetCode(addr, []byte("hello")) // Change an external metadata
state.SetState(addr, skey, sval) // Change the storage trie state.SetState(addr, skey, sval) // Change the storage trie
if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
@ -735,7 +736,7 @@ func TestCommitCopy(t *testing.T) {
} }
// Copy the committed state database, the copied one is not functional. // Copy the committed state database, the copied one is not functional.
state.Commit(0, true) state.Commit(0, true)
copied := state.Copy() copied := state.Copy().(*StateDB)
if balance := copied.GetBalance(addr); balance.Cmp(big.NewInt(0)) != 0 { if balance := copied.GetBalance(addr); balance.Cmp(big.NewInt(0)) != 0 {
t.Fatalf("unexpected balance: have %v", balance) t.Fatalf("unexpected balance: have %v", balance)
} }
@ -766,7 +767,7 @@ func TestDeleteCreateRevert(t *testing.T) {
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
addr := common.BytesToAddress([]byte("so")) addr := common.BytesToAddress([]byte("so"))
state.SetBalance(addr, big.NewInt(1)) state.SetBalance(addr, big.NewInt(1), tracing.BalanceChangeUnspecified)
root, _ := state.Commit(0, false) root, _ := state.Commit(0, false)
state, _ = New(root, state.db, state.snaps) state, _ = New(root, state.db, state.snaps)
@ -776,7 +777,7 @@ func TestDeleteCreateRevert(t *testing.T) {
state.Finalise(true) state.Finalise(true)
id := state.Snapshot() id := state.Snapshot()
state.SetBalance(addr, big.NewInt(2)) state.SetBalance(addr, big.NewInt(2), tracing.BalanceChangeUnspecified)
state.RevertToSnapshot(id) state.RevertToSnapshot(id)
// Commit the entire state and make sure we don't crash and have the correct state // Commit the entire state and make sure we don't crash and have the correct state
@ -818,10 +819,10 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
state, _ := New(types.EmptyRootHash, db, nil) state, _ := New(types.EmptyRootHash, db, nil)
addr := common.BytesToAddress([]byte("so")) addr := common.BytesToAddress([]byte("so"))
{ {
state.SetBalance(addr, big.NewInt(1)) state.SetBalance(addr, big.NewInt(1), tracing.BalanceChangeUnspecified)
state.SetCode(addr, []byte{1, 2, 3}) state.SetCode(addr, []byte{1, 2, 3})
a2 := common.BytesToAddress([]byte("another")) a2 := common.BytesToAddress([]byte("another"))
state.SetBalance(a2, big.NewInt(100)) state.SetBalance(a2, big.NewInt(100), tracing.BalanceChangeUnspecified)
state.SetCode(a2, []byte{1, 2, 4}) state.SetCode(a2, []byte{1, 2, 4})
root, _ = state.Commit(0, false) root, _ = state.Commit(0, false)
t.Logf("root: %x", root) t.Logf("root: %x", root)
@ -846,7 +847,7 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
t.Errorf("expected %d, got %d", exp, got) t.Errorf("expected %d, got %d", exp, got)
} }
// Modify the state // Modify the state
state.SetBalance(addr, big.NewInt(2)) state.SetBalance(addr, big.NewInt(2), tracing.BalanceChangeUnspecified)
root, err := state.Commit(0, false) root, err := state.Commit(0, false)
if err == nil { if err == nil {
t.Fatalf("expected error, got root :%x", root) t.Fatalf("expected error, got root :%x", root)
@ -1096,7 +1097,7 @@ func TestStateDBTransientStorage(t *testing.T) {
// set transient state and then copy the statedb and ensure that // set transient state and then copy the statedb and ensure that
// the transient state is copied // the transient state is copied
state.SetTransientState(addr, key, value) state.SetTransientState(addr, key, value)
cpy := state.Copy() cpy := state.Copy().(*StateDB)
if got := cpy.GetTransientState(addr, key); got != value { if got := cpy.GetTransientState(addr, key); got != value {
t.Fatalf("transient storage mismatch: have %x, want %x", got, value) t.Fatalf("transient storage mismatch: have %x, want %x", got, value)
} }
@ -1114,13 +1115,13 @@ func TestResetObject(t *testing.T) {
slotB = common.HexToHash("0x2") slotB = common.HexToHash("0x2")
) )
// Initialize account with balance and storage in first transaction. // Initialize account with balance and storage in first transaction.
state.SetBalance(addr, big.NewInt(1)) state.SetBalance(addr, big.NewInt(1), tracing.BalanceChangeUnspecified)
state.SetState(addr, slotA, common.BytesToHash([]byte{0x1})) state.SetState(addr, slotA, common.BytesToHash([]byte{0x1}))
state.IntermediateRoot(true) state.IntermediateRoot(true)
// Reset account and mutate balance and storages // Reset account and mutate balance and storages
state.CreateAccount(addr) state.CreateAccount(addr)
state.SetBalance(addr, big.NewInt(2)) state.SetBalance(addr, big.NewInt(2), tracing.BalanceChangeUnspecified)
state.SetState(addr, slotB, common.BytesToHash([]byte{0x2})) state.SetState(addr, slotB, common.BytesToHash([]byte{0x2}))
root, _ := state.Commit(0, true) root, _ := state.Commit(0, true)
@ -1146,7 +1147,7 @@ func TestDeleteStorage(t *testing.T) {
addr = common.HexToAddress("0x1") addr = common.HexToAddress("0x1")
) )
// Initialize account and populate storage // Initialize account and populate storage
state.SetBalance(addr, big.NewInt(1)) state.SetBalance(addr, big.NewInt(1), tracing.BalanceChangeUnspecified)
state.CreateAccount(addr) state.CreateAccount(addr)
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
slot := common.Hash(uint256.NewInt(uint64(i)).Bytes32()) slot := common.Hash(uint256.NewInt(uint64(i)).Bytes32())

View file

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
@ -60,7 +61,7 @@ func makeTestState(scheme string) (ethdb.Database, Database, *trie.Database, com
obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i})) obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
acc := &testAccount{address: common.BytesToAddress([]byte{i})} acc := &testAccount{address: common.BytesToAddress([]byte{i})}
obj.AddBalance(big.NewInt(int64(11 * i))) obj.AddBalance(big.NewInt(int64(11*i)), tracing.BalanceChangeUnspecified)
acc.balance = big.NewInt(int64(11 * i)) acc.balance = big.NewInt(int64(11 * i))
obj.SetNonce(uint64(42 * i)) obj.SetNonce(uint64(42 * i))

View file

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
@ -34,9 +35,9 @@ func filledStateDB() *StateDB {
skey := common.HexToHash("aaa") skey := common.HexToHash("aaa")
sval := common.HexToHash("bbb") sval := common.HexToHash("bbb")
state.SetBalance(addr, big.NewInt(42)) // Change the account trie state.SetBalance(addr, big.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetCode(addr, []byte("hello")) // Change an external metadata
state.SetState(addr, skey, sval) // Change the storage trie state.SetState(addr, skey, sval) // Change the storage trie
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
sk := common.BigToHash(big.NewInt(int64(i))) sk := common.BigToHash(big.NewInt(int64(i)))
state.SetState(addr, sk, sk) // Change the storage trie state.SetState(addr, sk, sk) // Change the storage trie

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -66,6 +67,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb vm.StateDB, cfg vm.
allLogs []*types.Log allLogs []*types.Log
gp = new(GasPool).AddGas(block.GasLimit()) gp = new(GasPool).AddGas(block.GasLimit())
) )
// Mutate the block and state according to any hard-fork specs // Mutate the block and state according to any hard-fork specs
if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
misc.ApplyDAOHardFork(statedb) misc.ApplyDAOHardFork(statedb)
@ -85,7 +87,8 @@ func (p *StateProcessor) Process(block *types.Block, statedb vm.StateDB, cfg vm.
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
} }
statedb.SetTxContext(tx.Hash(), i) statedb.SetTxContext(tx.Hash(), i)
receipt, err := applyTransaction(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv)
receipt, err := ApplyTransactionWithEVM(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv)
if err != nil { if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
} }
@ -103,7 +106,25 @@ func (p *StateProcessor) Process(block *types.Block, statedb vm.StateDB, cfg vm.
return receipts, allLogs, *usedGas, nil return receipts, allLogs, *usedGas, nil
} }
func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, statedb vm.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) { // ApplyTransactionWithEVM attempts to apply a transaction to the given state database
// and uses the input parameters for its environment similar to ApplyTransaction. However,
// this method takes an already created EVM instance as input.
func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPool, statedb vm.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, err error) {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnTxStart != nil {
evm.Config.Tracer.OnTxStart(&tracing.VMContext{
ChainConfig: evm.ChainConfig(),
StateDB: statedb,
BlockNumber: evm.Context.BlockNumber,
Time: evm.Context.Time,
Coinbase: evm.Context.Coinbase,
Random: evm.Context.Random,
}, tx, msg.From)
if evm.Config.Tracer.OnTxEnd != nil {
defer func() {
evm.Config.Tracer.OnTxEnd(receipt, err)
}()
}
}
// Create a new context to be used in the EVM environment. // Create a new context to be used in the EVM environment.
txContext := NewEVMTxContext(msg) txContext := NewEVMTxContext(msg)
evm.Reset(txContext, statedb) evm.Reset(txContext, statedb)
@ -125,7 +146,7 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta
// Create a new receipt for the transaction, storing the intermediate root and gas used // Create a new receipt for the transaction, storing the intermediate root and gas used
// by the tx. // by the tx.
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas} receipt = &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas}
if result.Failed() { if result.Failed() {
receipt.Status = types.ReceiptStatusFailed receipt.Status = types.ReceiptStatusFailed
} else { } else {
@ -166,7 +187,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
blockContext := NewEVMBlockContext(header, bc, author) blockContext := NewEVMBlockContext(header, bc, author)
txContext := NewEVMTxContext(msg) txContext := NewEVMTxContext(msg)
vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg) vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg)
return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) return ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
} }
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root // ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
cmath "github.com/ethereum/go-ethereum/common/math" cmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -139,6 +140,8 @@ type Message struct {
AccessList types.AccessList AccessList types.AccessList
BlobGasFeeCap *big.Int BlobGasFeeCap *big.Int
BlobHashes []common.Hash BlobHashes []common.Hash
// Distinguishes type-2 txes
DynamicFee bool
// When SkipAccountChecks is true, the message nonce is not checked against the // When SkipAccountChecks is true, the message nonce is not checked against the
// account nonce in state. It also disables checking that the sender is an EOA. // account nonce in state. It also disables checking that the sender is an EOA.
@ -161,6 +164,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
SkipAccountChecks: false, SkipAccountChecks: false,
BlobHashes: tx.BlobHashes(), BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(), BlobGasFeeCap: tx.BlobGasFeeCap(),
DynamicFee: tx.Type() == 2 || tx.Type() == 3,
} }
// If baseFee provided, set gasPrice to effectiveGasPrice. // If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil { if baseFee != nil {
@ -258,10 +262,15 @@ func (st *StateTransition) buyGas() error {
if err := st.gp.SubGas(st.msg.GasLimit); err != nil { if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
return err return err
} }
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil {
st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance)
}
st.gasRemaining += st.msg.GasLimit st.gasRemaining += st.msg.GasLimit
st.initialGas = st.msg.GasLimit st.initialGas = st.msg.GasLimit
st.state.SubBalance(st.msg.From, mgval) st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
return nil return nil
} }
@ -369,13 +378,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
return nil, err return nil, err
} }
if tracer := st.evm.Config.Tracer; tracer != nil {
tracer.CaptureTxStart(st.initialGas)
defer func() {
tracer.CaptureTxEnd(st.gasRemaining)
}()
}
var ( var (
msg = st.msg msg = st.msg
sender = vm.AccountRef(msg.From) sender = vm.AccountRef(msg.From)
@ -391,6 +393,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if st.gasRemaining < gas { if st.gasRemaining < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas) return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
} }
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
t.OnGasChange(st.gasRemaining, st.gasRemaining-gas, tracing.GasChangeTxIntrinsicGas)
}
st.gasRemaining -= gas st.gasRemaining -= gas
// Check clause 6 // Check clause 6
@ -440,7 +445,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
} else { } else {
fee := new(big.Int).SetUint64(st.gasUsed()) fee := new(big.Int).SetUint64(st.gasUsed())
fee.Mul(fee, effectiveTip) fee.Mul(fee, effectiveTip)
st.state.AddBalance(st.evm.Context.Coinbase, fee) st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
} }
return &ExecutionResult{ return &ExecutionResult{
@ -457,11 +462,20 @@ func (st *StateTransition) refundGas(refundQuotient uint64) uint64 {
if refund > st.state.GetRefund() { if refund > st.state.GetRefund() {
refund = st.state.GetRefund() refund = st.state.GetRefund()
} }
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, st.gasRemaining+refund, tracing.GasChangeTxRefunds)
}
st.gasRemaining += refund st.gasRemaining += refund
// Return ETH for remaining gas, exchanged at the original rate. // Return ETH for remaining gas, exchanged at the original rate.
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gasRemaining), st.msg.GasPrice) remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gasRemaining), st.msg.GasPrice)
st.state.AddBalance(st.msg.From, remaining) st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned)
}
// Also return remaining gas to the block gas counter so it is // Also return remaining gas to the block gas counter so it is
// available for the next transaction. // available for the next transaction.

275
core/tracing/hooks.go Normal file
View file

@ -0,0 +1,275 @@
// 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 <http://www.gnu.org/licenses/>.
package tracing
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
// OpContext provides the context at which the opcode is being
// executed in, including the memory, stack and various contract-level information.
type OpContext interface {
MemoryData() []byte
StackData() []uint256.Int
Caller() common.Address
Address() common.Address
CallValue() *big.Int
CallInput() []byte
}
// StateDB gives tracers access to the whole state.
type StateDB interface {
GetBalance(common.Address) *big.Int
GetNonce(common.Address) uint64
GetCode(common.Address) []byte
GetState(common.Address, common.Hash) common.Hash
Exist(common.Address) bool
GetRefund() uint64
}
// VMContext provides the context for the EVM execution.
type VMContext struct {
Coinbase common.Address
BlockNumber *big.Int
Time uint64
Random *common.Hash
// Effective tx gas price
GasPrice *big.Int
ChainConfig *params.ChainConfig
StateDB StateDB
}
// BlockEvent is emitted upon tracing an incoming block.
// It contains the block as well as consensus related information.
type BlockEvent struct {
Block *types.Block
TD *big.Int
Finalized *types.Header
Safe *types.Header
}
type (
/*
- VM events -
*/
// TxStartHook is called before the execution of a transaction starts.
// Call simulations don't come with a valid signature. `from` field
// to be used for address of the caller.
TxStartHook = func(vm *VMContext, tx *types.Transaction, from common.Address)
// TxEndHook is called after the execution of a transaction ends.
TxEndHook = func(receipt *types.Receipt, err error)
// EnterHook is invoked when the processing of a message starts.
EnterHook = func(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
// ExitHook is invoked when the processing of a message ends.
// `revert` is true when there was an error during the execution.
// Exceptionally, before the homestead hardfork a contract creation that
// ran out of gas when attempting to persist the code to database did not
// count as a call failure and did not cause a revert of the call. This will
// be indicated by `reverted == false` and `err == ErrCodeStoreOutOfGas`.
ExitHook = func(depth int, output []byte, gasUsed uint64, err error, reverted bool)
// OpcodeHook is invoked just prior to the execution of an opcode.
OpcodeHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, rData []byte, depth int, err error)
// FaultHook is invoked when an error occurs during the execution of an opcode.
FaultHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, depth int, err error)
// GasChangeHook is invoked when the gas changes.
GasChangeHook = func(old, new uint64, reason GasChangeReason)
/*
- Chain events -
*/
// BlockchainInitHook is called when the blockchain is initialized.
BlockchainInitHook = func(chainConfig *params.ChainConfig)
// BlockStartHook is called before executing `block`.
// `td` is the total difficulty prior to `block`.
BlockStartHook = func(event BlockEvent)
// BlockEndHook is called after executing a block.
BlockEndHook = func(err error)
// SkippedBlockHook indicates a block was skipped during processing
// due to it being known previously. This can happen e.g. when recovering
// from a crash.
SkippedBlockHook = func(event BlockEvent)
// GenesisBlockHook is called when the genesis block is being processed.
GenesisBlockHook = func(genesis *types.Block, alloc types.GenesisAlloc)
/*
- State events -
*/
// BalanceChangeHook is called when the balance of an account changes.
BalanceChangeHook = func(addr common.Address, prev, new *big.Int, reason BalanceChangeReason)
// NonceChangeHook is called when the nonce of an account changes.
NonceChangeHook = func(addr common.Address, prev, new uint64)
// CodeChangeHook is called when the code of an account changes.
CodeChangeHook = func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte)
// StorageChangeHook is called when the storage of an account changes.
StorageChangeHook = func(addr common.Address, slot common.Hash, prev, new common.Hash)
// LogHook is called when a log is emitted.
LogHook = func(log *types.Log)
)
type Hooks struct {
// VM events
OnTxStart TxStartHook
OnTxEnd TxEndHook
OnEnter EnterHook
OnExit ExitHook
OnOpcode OpcodeHook
OnFault FaultHook
OnGasChange GasChangeHook
// Chain events
OnBlockchainInit BlockchainInitHook
OnBlockStart BlockStartHook
OnBlockEnd BlockEndHook
OnSkippedBlock SkippedBlockHook
OnGenesisBlock GenesisBlockHook
// State events
OnBalanceChange BalanceChangeHook
OnNonceChange NonceChangeHook
OnCodeChange CodeChangeHook
OnStorageChange StorageChangeHook
OnLog LogHook
}
// BalanceChangeReason is used to indicate the reason for a balance change, useful
// for tracing and reporting.
type BalanceChangeReason byte
const (
BalanceChangeUnspecified BalanceChangeReason = 0
// Issuance
// BalanceIncreaseRewardMineUncle is a reward for mining an uncle block.
BalanceIncreaseRewardMineUncle BalanceChangeReason = 1
// BalanceIncreaseRewardMineBlock is a reward for mining a block.
BalanceIncreaseRewardMineBlock BalanceChangeReason = 2
// BalanceIncreaseWithdrawal is ether withdrawn from the beacon chain.
BalanceIncreaseWithdrawal BalanceChangeReason = 3
// BalanceIncreaseGenesisBalance is ether allocated at the genesis block.
BalanceIncreaseGenesisBalance BalanceChangeReason = 4
// Transaction fees
// BalanceIncreaseRewardTransactionFee is the transaction tip increasing block builder's balance.
BalanceIncreaseRewardTransactionFee BalanceChangeReason = 5
// BalanceDecreaseGasBuy is spent to purchase gas for execution a transaction.
// Part of this gas will be burnt as per EIP-1559 rules.
BalanceDecreaseGasBuy BalanceChangeReason = 6
// BalanceIncreaseGasReturn is ether returned for unused gas at the end of execution.
BalanceIncreaseGasReturn BalanceChangeReason = 7
// DAO fork
// BalanceIncreaseDaoContract is ether sent to the DAO refund contract.
BalanceIncreaseDaoContract BalanceChangeReason = 8
// BalanceDecreaseDaoAccount is ether taken from a DAO account to be moved to the refund contract.
BalanceDecreaseDaoAccount BalanceChangeReason = 9
// BalanceChangeTransfer is ether transferred via a call.
// it is a decrease for the sender and an increase for the recipient.
BalanceChangeTransfer BalanceChangeReason = 10
// BalanceChangeTouchAccount is a transfer of zero value. It is only there to
// touch-create an account.
BalanceChangeTouchAccount BalanceChangeReason = 11
// BalanceIncreaseSelfdestruct is added to the recipient as indicated by a selfdestructing account.
BalanceIncreaseSelfdestruct BalanceChangeReason = 12
// BalanceDecreaseSelfdestruct is deducted from a contract due to self-destruct.
BalanceDecreaseSelfdestruct BalanceChangeReason = 13
// BalanceDecreaseSelfdestructBurn is ether that is sent to an already self-destructed
// account within the same tx (captured at end of tx).
// Note it doesn't account for a self-destruct which appoints itself as recipient.
BalanceDecreaseSelfdestructBurn BalanceChangeReason = 14
)
// GasChangeReason is used to indicate the reason for a gas change, useful
// for tracing and reporting.
//
// There is essentially two types of gas changes, those that can be emitted once per transaction
// and those that can be emitted on a call basis, so possibly multiple times per transaction.
//
// They can be recognized easily by their name, those that start with `GasChangeTx` are emitted
// once per transaction, while those that start with `GasChangeCall` are emitted on a call basis.
type GasChangeReason byte
const (
GasChangeUnspecified GasChangeReason = 0
// GasChangeTxInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only
// one such gas change per transaction.
GasChangeTxInitialBalance GasChangeReason = 1
// GasChangeTxIntrinsicGas is the amount of gas that will be charged for the intrinsic cost of the transaction, there is
// always exactly one of those per transaction.
GasChangeTxIntrinsicGas GasChangeReason = 2
// GasChangeTxRefunds is the sum of all refunds which happened during the tx execution (e.g. storage slot being cleared)
// this generates an increase in gas. There is at most one of such gas change per transaction.
GasChangeTxRefunds GasChangeReason = 3
// GasChangeTxLeftOverReturned is the amount of gas left over at the end of transaction's execution that will be returned
// to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas
// left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller.
// There is at most one of such gas change per transaction.
GasChangeTxLeftOverReturned GasChangeReason = 4
// GasChangeCallInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only
// one such gas change per call.
GasChangeCallInitialBalance GasChangeReason = 5
// GasChangeCallLeftOverReturned is the amount of gas left over that will be returned to the caller, this change will always
// be a negative change as we "drain" left over gas towards 0. If there was no gas left at the end of execution, no such even
// will be emitted.
GasChangeCallLeftOverReturned GasChangeReason = 6
// GasChangeCallLeftOverRefunded is the amount of gas that will be refunded to the call after the child call execution it
// executed completed. This value is always positive as we are giving gas back to the you, the left over gas of the child.
// If there was no gas left to be refunded, no such even will be emitted.
GasChangeCallLeftOverRefunded GasChangeReason = 7
// GasChangeCallContractCreation is the amount of gas that will be burned for a CREATE.
GasChangeCallContractCreation GasChangeReason = 8
// GasChangeContractCreation is the amount of gas that will be burned for a CREATE2.
GasChangeCallContractCreation2 GasChangeReason = 9
// GasChangeCallCodeStorage is the amount of gas that will be charged for code storage.
GasChangeCallCodeStorage GasChangeReason = 10
// GasChangeCallOpCode is the amount of gas that will be charged for an opcode executed by the EVM, exact opcode that was
// performed can be check by `OnOpcode` handling.
GasChangeCallOpCode GasChangeReason = 11
// GasChangeCallPrecompiledContract is the amount of gas that will be charged for a precompiled contract execution.
GasChangeCallPrecompiledContract GasChangeReason = 12
// GasChangeCallStorageColdAccess is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules.
GasChangeCallStorageColdAccess GasChangeReason = 13
// GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert.
GasChangeCallFailedExecution GasChangeReason = 14
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
// it will be "manually" tracked by a direct emit of the gas change event.
GasChangeIgnored GasChangeReason = 0xFF
)

View file

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
@ -513,17 +514,17 @@ func TestOpenDrops(t *testing.T) {
// Create a blob pool out of the pre-seeded data // Create a blob pool out of the pre-seeded data
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
statedb.AddBalance(crypto.PubkeyToAddress(gapper.PublicKey), big.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(gapper.PublicKey), big.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(dangler.PublicKey), big.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(dangler.PublicKey), big.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(filler.PublicKey), big.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(filler.PublicKey), big.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.SetNonce(crypto.PubkeyToAddress(filler.PublicKey), 3) statedb.SetNonce(crypto.PubkeyToAddress(filler.PublicKey), 3)
statedb.AddBalance(crypto.PubkeyToAddress(overlapper.PublicKey), big.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(overlapper.PublicKey), big.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.SetNonce(crypto.PubkeyToAddress(overlapper.PublicKey), 2) statedb.SetNonce(crypto.PubkeyToAddress(overlapper.PublicKey), 2)
statedb.AddBalance(crypto.PubkeyToAddress(underpayer.PublicKey), big.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(underpayer.PublicKey), big.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(outpricer.PublicKey), big.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(outpricer.PublicKey), big.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(exceeder.PublicKey), big.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(exceeder.PublicKey), big.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(overdrafter.PublicKey), big.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(overdrafter.PublicKey), big.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(overcapper.PublicKey), big.NewInt(10000000)) statedb.AddBalance(crypto.PubkeyToAddress(overcapper.PublicKey), big.NewInt(10000000), tracing.BalanceChangeUnspecified)
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
@ -638,7 +639,7 @@ func TestOpenIndex(t *testing.T) {
// Create a blob pool out of the pre-seeded data // Create a blob pool out of the pre-seeded data
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
statedb.AddBalance(addr, big.NewInt(1_000_000_000)) statedb.AddBalance(addr, big.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
@ -738,9 +739,9 @@ func TestOpenHeap(t *testing.T) {
// Create a blob pool out of the pre-seeded data // Create a blob pool out of the pre-seeded data
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
statedb.AddBalance(addr1, big.NewInt(1_000_000_000)) statedb.AddBalance(addr1, big.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr2, big.NewInt(1_000_000_000)) statedb.AddBalance(addr2, big.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr3, big.NewInt(1_000_000_000)) statedb.AddBalance(addr3, big.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
@ -818,9 +819,9 @@ func TestOpenCap(t *testing.T) {
for _, datacap := range []uint64{2 * (txAvgSize + blobSize), 100 * (txAvgSize + blobSize)} { for _, datacap := range []uint64{2 * (txAvgSize + blobSize), 100 * (txAvgSize + blobSize)} {
// Create a blob pool out of the pre-seeded data, but cap it to 2 blob transaction // Create a blob pool out of the pre-seeded data, but cap it to 2 blob transaction
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
statedb.AddBalance(addr1, big.NewInt(1_000_000_000)) statedb.AddBalance(addr1, big.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr2, big.NewInt(1_000_000_000)) statedb.AddBalance(addr2, big.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr3, big.NewInt(1_000_000_000)) statedb.AddBalance(addr3, big.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
@ -1211,7 +1212,7 @@ func TestAdd(t *testing.T) {
addrs[acc] = crypto.PubkeyToAddress(keys[acc].PublicKey) addrs[acc] = crypto.PubkeyToAddress(keys[acc].PublicKey)
// Seed the state database with this acocunt // Seed the state database with this acocunt
statedb.AddBalance(addrs[acc], new(big.Int).SetUint64(seed.balance)) statedb.AddBalance(addrs[acc], new(big.Int).SetUint64(seed.balance), tracing.BalanceChangeUnspecified)
statedb.SetNonce(addrs[acc], seed.nonce) statedb.SetNonce(addrs[acc], seed.nonce)
// Sign the seed transactions and store them in the data store // Sign the seed transactions and store them in the data store

View file

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "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/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
@ -49,7 +50,7 @@ func fillPool(t testing.TB, pool *LegacyPool) {
nonExecutableTxs := types.Transactions{} nonExecutableTxs := types.Transactions{}
for i := 0; i < 384; i++ { for i := 0; i < 384; i++ {
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(10000000000)) pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(10000000000), tracing.BalanceChangeUnspecified)
// Add executable ones // Add executable ones
for j := 0; j < int(pool.config.AccountSlots); j++ { for j := 0; j < int(pool.config.AccountSlots); j++ {
executableTxs = append(executableTxs, pricedTransaction(uint64(j), 100000, big.NewInt(300), key)) executableTxs = append(executableTxs, pricedTransaction(uint64(j), 100000, big.NewInt(300), key))
@ -91,7 +92,7 @@ func TestTransactionFutureAttack(t *testing.T) {
// Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops // Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops
{ {
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000)) pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000), tracing.BalanceChangeUnspecified)
futureTxs := types.Transactions{} futureTxs := types.Transactions{}
for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ { for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ {
futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 100000, big.NewInt(500), key)) futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 100000, big.NewInt(500), key))
@ -128,7 +129,7 @@ func TestTransactionFuture1559(t *testing.T) {
// Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops // Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops
{ {
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000)) pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000), tracing.BalanceChangeUnspecified)
futureTxs := types.Transactions{} futureTxs := types.Transactions{}
for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ { for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ {
futureTxs = append(futureTxs, dynamicFeeTx(1000+uint64(j), 100000, big.NewInt(200), big.NewInt(101), key)) futureTxs = append(futureTxs, dynamicFeeTx(1000+uint64(j), 100000, big.NewInt(200), big.NewInt(101), key))
@ -182,7 +183,7 @@ func TestTransactionZAttack(t *testing.T) {
for j := 0; j < int(pool.config.GlobalQueue); j++ { for j := 0; j < int(pool.config.GlobalQueue); j++ {
futureTxs := types.Transactions{} futureTxs := types.Transactions{}
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000)) pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000), tracing.BalanceChangeUnspecified)
futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 21000, big.NewInt(500), key)) futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 21000, big.NewInt(500), key))
pool.addRemotesSync(futureTxs) pool.addRemotesSync(futureTxs)
} }
@ -190,7 +191,7 @@ func TestTransactionZAttack(t *testing.T) {
overDraftTxs := types.Transactions{} overDraftTxs := types.Transactions{}
{ {
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000)) pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000), tracing.BalanceChangeUnspecified)
for j := 0; j < int(pool.config.GlobalSlots); j++ { for j := 0; j < int(pool.config.GlobalSlots); j++ {
overDraftTxs = append(overDraftTxs, pricedValuedTransaction(uint64(j), 600000000000, 21000, big.NewInt(500), key)) overDraftTxs = append(overDraftTxs, pricedValuedTransaction(uint64(j), 600000000000, 21000, big.NewInt(500), key))
} }
@ -227,7 +228,7 @@ func BenchmarkFutureAttack(b *testing.B) {
fillPool(b, pool) fillPool(b, pool)
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000)) pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000), tracing.BalanceChangeUnspecified)
futureTxs := types.Transactions{} futureTxs := types.Transactions{}
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {

View file

@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
@ -256,7 +257,7 @@ func (c *testChain) State() (vm.StateDB, error) {
c.statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) c.statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
// simulate that the new head block included tx0 and tx1 // simulate that the new head block included tx0 and tx1
c.statedb.SetNonce(c.address, 2) c.statedb.SetNonce(c.address, 2)
c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether)) c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether), tracing.BalanceChangeUnspecified)
*c.trigger = false *c.trigger = false
} }
return stdb, nil return stdb, nil
@ -276,7 +277,7 @@ func TestStateChangeDuringReset(t *testing.T) {
) )
// setup pool with 2 transaction in it // setup pool with 2 transaction in it
statedb.SetBalance(address, new(big.Int).SetUint64(params.Ether)) statedb.SetBalance(address, new(big.Int).SetUint64(params.Ether), tracing.BalanceChangeUnspecified)
blockchain := &testChain{newTestBlockChain(params.TestChainConfig, 1000000000, statedb, new(event.Feed)), address, &trigger} blockchain := &testChain{newTestBlockChain(params.TestChainConfig, 1000000000, statedb, new(event.Feed)), address, &trigger}
tx0 := transaction(0, 100000, key) tx0 := transaction(0, 100000, key)
@ -310,7 +311,7 @@ func TestStateChangeDuringReset(t *testing.T) {
func testAddBalance(pool *LegacyPool, addr common.Address, amount *big.Int) { func testAddBalance(pool *LegacyPool, addr common.Address, amount *big.Int) {
pool.mu.Lock() pool.mu.Lock()
pool.currentState.AddBalance(addr, amount) pool.currentState.AddBalance(addr, amount, tracing.BalanceChangeUnspecified)
pool.mu.Unlock() pool.mu.Unlock()
} }
@ -471,7 +472,7 @@ func TestChainFork(t *testing.T) {
addr := crypto.PubkeyToAddress(key.PublicKey) addr := crypto.PubkeyToAddress(key.PublicKey)
resetState := func() { resetState := func() {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
statedb.AddBalance(addr, big.NewInt(100000000000000)) statedb.AddBalance(addr, big.NewInt(100000000000000), tracing.BalanceChangeUnspecified)
pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed)) pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed))
<-pool.requestReset(nil, nil) <-pool.requestReset(nil, nil)
@ -500,7 +501,7 @@ func TestDoubleNonce(t *testing.T) {
addr := crypto.PubkeyToAddress(key.PublicKey) addr := crypto.PubkeyToAddress(key.PublicKey)
resetState := func() { resetState := func() {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
statedb.AddBalance(addr, big.NewInt(100000000000000)) statedb.AddBalance(addr, big.NewInt(100000000000000), tracing.BalanceChangeUnspecified)
pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed)) pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed))
<-pool.requestReset(nil, nil) <-pool.requestReset(nil, nil)
@ -2663,7 +2664,7 @@ func BenchmarkMultiAccountBatchInsert(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
account := crypto.PubkeyToAddress(key.PublicKey) account := crypto.PubkeyToAddress(key.PublicKey)
pool.currentState.AddBalance(account, big.NewInt(1000000)) pool.currentState.AddBalance(account, big.NewInt(1000000), tracing.BalanceChangeUnspecified)
tx := transaction(uint64(0), 100000, key) tx := transaction(uint64(0), 100000, key)
batches[i] = tx batches[i] = tx
} }

View file

@ -35,7 +35,7 @@ type noncer struct {
// newNoncer creates a new virtual state database to track the pool nonces. // newNoncer creates a new virtual state database to track the pool nonces.
func newNoncer(statedb vm.StateDB) *noncer { func newNoncer(statedb vm.StateDB) *noncer {
return &noncer{ return &noncer{
fallback: statedb.Copy(), fallback: statedb.Copy().(vm.StateDB),
nonces: make(map[common.Address]uint64), nonces: make(map[common.Address]uint64),
} }
} }

87
core/types/account.go Normal file
View file

@ -0,0 +1,87 @@
// 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 <http://www.gnu.org/licenses/>.
package types
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
)
//go:generate go run github.com/fjl/gencodec -type Account -field-override accountMarshaling -out gen_account.go
// Account represents an Ethereum account and its attached data.
// This type is used to specify accounts in the genesis block state, and
// is also useful for JSON encoding/decoding of accounts.
type Account struct {
Code []byte `json:"code,omitempty"`
Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
Balance *big.Int `json:"balance" gencodec:"required"`
Nonce uint64 `json:"nonce,omitempty"`
// used in tests
PrivateKey []byte `json:"secretKey,omitempty"`
}
type accountMarshaling struct {
Code hexutil.Bytes
Balance *math.HexOrDecimal256
Nonce math.HexOrDecimal64
Storage map[storageJSON]storageJSON
PrivateKey hexutil.Bytes
}
// storageJSON represents a 256 bit byte array, but allows less than 256 bits when
// unmarshaling from hex.
type storageJSON common.Hash
func (h *storageJSON) UnmarshalText(text []byte) error {
text = bytes.TrimPrefix(text, []byte("0x"))
if len(text) > 64 {
return fmt.Errorf("too many hex characters in storage key/value %q", text)
}
offset := len(h) - len(text)/2 // pad on the left
if _, err := hex.Decode(h[offset:], text); err != nil {
return fmt.Errorf("invalid hex storage key/value %q", text)
}
return nil
}
func (h storageJSON) MarshalText() ([]byte, error) {
return hexutil.Bytes(h[:]).MarshalText()
}
// GenesisAlloc specifies the initial state of a genesis block.
type GenesisAlloc map[common.Address]Account
func (ga *GenesisAlloc) UnmarshalJSON(data []byte) error {
m := make(map[common.UnprefixedAddress]Account)
if err := json.Unmarshal(data, &m); err != nil {
return err
}
*ga = make(GenesisAlloc)
for addr, a := range m {
(*ga)[common.Address(addr)] = a
}
return nil
}

View file

@ -1,6 +1,6 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT. // Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package core package types
import ( import (
"encoding/json" "encoding/json"
@ -12,62 +12,62 @@ import (
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
) )
var _ = (*genesisAccountMarshaling)(nil) var _ = (*accountMarshaling)(nil)
// MarshalJSON marshals as JSON. // MarshalJSON marshals as JSON.
func (g GenesisAccount) MarshalJSON() ([]byte, error) { func (a Account) MarshalJSON() ([]byte, error) {
type GenesisAccount struct { type Account struct {
Code hexutil.Bytes `json:"code,omitempty"` Code hexutil.Bytes `json:"code,omitempty"`
Storage map[storageJSON]storageJSON `json:"storage,omitempty"` Storage map[storageJSON]storageJSON `json:"storage,omitempty"`
Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"` Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"`
Nonce math.HexOrDecimal64 `json:"nonce,omitempty"` Nonce math.HexOrDecimal64 `json:"nonce,omitempty"`
PrivateKey hexutil.Bytes `json:"secretKey,omitempty"` PrivateKey hexutil.Bytes `json:"secretKey,omitempty"`
} }
var enc GenesisAccount var enc Account
enc.Code = g.Code enc.Code = a.Code
if g.Storage != nil { if a.Storage != nil {
enc.Storage = make(map[storageJSON]storageJSON, len(g.Storage)) enc.Storage = make(map[storageJSON]storageJSON, len(a.Storage))
for k, v := range g.Storage { for k, v := range a.Storage {
enc.Storage[storageJSON(k)] = storageJSON(v) enc.Storage[storageJSON(k)] = storageJSON(v)
} }
} }
enc.Balance = (*math.HexOrDecimal256)(g.Balance) enc.Balance = (*math.HexOrDecimal256)(a.Balance)
enc.Nonce = math.HexOrDecimal64(g.Nonce) enc.Nonce = math.HexOrDecimal64(a.Nonce)
enc.PrivateKey = g.PrivateKey enc.PrivateKey = a.PrivateKey
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON. // UnmarshalJSON unmarshals from JSON.
func (g *GenesisAccount) UnmarshalJSON(input []byte) error { func (a *Account) UnmarshalJSON(input []byte) error {
type GenesisAccount struct { type Account struct {
Code *hexutil.Bytes `json:"code,omitempty"` Code *hexutil.Bytes `json:"code,omitempty"`
Storage map[storageJSON]storageJSON `json:"storage,omitempty"` Storage map[storageJSON]storageJSON `json:"storage,omitempty"`
Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"` Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"`
Nonce *math.HexOrDecimal64 `json:"nonce,omitempty"` Nonce *math.HexOrDecimal64 `json:"nonce,omitempty"`
PrivateKey *hexutil.Bytes `json:"secretKey,omitempty"` PrivateKey *hexutil.Bytes `json:"secretKey,omitempty"`
} }
var dec GenesisAccount var dec Account
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
return err return err
} }
if dec.Code != nil { if dec.Code != nil {
g.Code = *dec.Code a.Code = *dec.Code
} }
if dec.Storage != nil { if dec.Storage != nil {
g.Storage = make(map[common.Hash]common.Hash, len(dec.Storage)) a.Storage = make(map[common.Hash]common.Hash, len(dec.Storage))
for k, v := range dec.Storage { for k, v := range dec.Storage {
g.Storage[common.Hash(k)] = common.Hash(v) a.Storage[common.Hash(k)] = common.Hash(v)
} }
} }
if dec.Balance == nil { if dec.Balance == nil {
return errors.New("missing required field 'balance' for GenesisAccount") return errors.New("missing required field 'balance' for Account")
} }
g.Balance = (*big.Int)(dec.Balance) a.Balance = (*big.Int)(dec.Balance)
if dec.Nonce != nil { if dec.Nonce != nil {
g.Nonce = uint64(*dec.Nonce) a.Nonce = uint64(*dec.Nonce)
} }
if dec.PrivateKey != nil { if dec.PrivateKey != nil {
g.PrivateKey = *dec.PrivateKey a.PrivateKey = *dec.PrivateKey
} }
return nil return nil
} }

View file

@ -20,6 +20,7 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -159,14 +160,28 @@ func (c *Contract) Caller() common.Address {
} }
// UseGas attempts the use gas and subtracts it and returns true on success // UseGas attempts the use gas and subtracts it and returns true on success
func (c *Contract) UseGas(gas uint64) (ok bool) { func (c *Contract) UseGas(gas uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) (ok bool) {
if c.Gas < gas { if c.Gas < gas {
return false return false
} }
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored {
logger.OnGasChange(c.Gas, c.Gas-gas, reason)
}
c.Gas -= gas c.Gas -= gas
return true return true
} }
// RefundGas refunds gas to the contract
func (c *Contract) RefundGas(gas uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) {
if gas == 0 {
return
}
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored {
logger.OnGasChange(c.Gas, c.Gas+gas, reason)
}
c.Gas += gas
}
// Address returns the contracts address // Address returns the contracts address
func (c *Contract) Address() common.Address { func (c *Contract) Address() common.Address {
return c.self.Address() return c.self.Address()

View file

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/blake2b" "github.com/ethereum/go-ethereum/crypto/blake2b"
bls12381 "github.com/ethereum/go-ethereum/crypto/bls12381" bls12381 "github.com/ethereum/go-ethereum/crypto/bls12381"
@ -43,7 +44,7 @@ type PrecompiledContract interface {
} }
type DynamicGasPrecompiledContract interface { type DynamicGasPrecompiledContract interface {
RunAndCalculateGas(evm *EVM, sender common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int) (ret []byte, remainingGas uint64, err error) // Run runs the precompiled contract and calculate gas dynamically RunAndCalculateGas(evm *EVM, sender common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, logger *tracing.Hooks) (ret []byte, remainingGas uint64, err error) // Run runs the precompiled contract and calculate gas dynamically
} }
// PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum // PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum
@ -172,14 +173,17 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
// - the returned bytes, // - the returned bytes,
// - the _remaining_ gas, // - the _remaining_ gas,
// - any error that occurred // - any error that occurred
func RunPrecompiledContract(p PrecompiledContract, evm *EVM, sender common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int) (ret []byte, remainingGas uint64, err error) { func RunPrecompiledContract(p PrecompiledContract, evm *EVM, sender common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, logger *tracing.Hooks) (ret []byte, remainingGas uint64, err error) {
if dp, ok := p.(DynamicGasPrecompiledContract); ok { if dp, ok := p.(DynamicGasPrecompiledContract); ok {
return dp.RunAndCalculateGas(evm, sender, callingContract, input, suppliedGas, value) return dp.RunAndCalculateGas(evm, sender, callingContract, input, suppliedGas, value, logger)
} }
gasCost := p.RequiredGas(input) gasCost := p.RequiredGas(input)
if suppliedGas < gasCost { if suppliedGas < gasCost {
return nil, 0, ErrOutOfGas return nil, 0, ErrOutOfGas
} }
if logger != nil && logger.OnGasChange != nil {
logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract)
}
suppliedGas -= gasCost suppliedGas -= gasCost
output, err := p.Run(evm, sender, input, value) output, err := p.Run(evm, sender, input, value)
return output, suppliedGas, err return output, suppliedGas, err

View file

@ -37,7 +37,7 @@ func FuzzPrecompiledContracts(f *testing.F) {
return return
} }
inWant := string(input) inWant := string(input)
vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, input, gas, nil) vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, input, gas, nil, nil)
if inHave := string(input); inWant != inHave { if inHave := string(input); inWant != inHave {
t.Errorf("Precompiled %v modified input data", a) t.Errorf("Precompiled %v modified input data", a)
} }

View file

@ -99,7 +99,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
if res, _, err := vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, in, gas, nil); err != nil { if res, _, err := vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, in, gas, nil, nil); err != nil {
t.Error(err) t.Error(err)
} else if common.Bytes2Hex(res) != test.Expected { } else if common.Bytes2Hex(res) != test.Expected {
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
@ -121,7 +121,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
gas := p.RequiredGas(in) - 1 gas := p.RequiredGas(in) - 1
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
_, _, err := vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, in, gas, nil) _, _, err := vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, in, gas, nil, nil)
if err.Error() != "out of gas" { if err.Error() != "out of gas" {
t.Errorf("Expected error [out of gas], got [%v]", err) t.Errorf("Expected error [out of gas], got [%v]", err)
} }
@ -138,7 +138,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(test.Name, func(t *testing.T) { t.Run(test.Name, func(t *testing.T) {
_, _, err := vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, in, gas, nil) _, _, err := vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, in, gas, nil, nil)
if err.Error() != test.ExpectedError { if err.Error() != test.ExpectedError {
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
} }
@ -170,7 +170,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
bench.ResetTimer() bench.ResetTimer()
for i := 0; i < bench.N; i++ { for i := 0; i < bench.N; i++ {
copy(data, in) copy(data, in)
res, _, err = vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, data, reqGas, nil) res, _, err = vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, data, reqGas, nil, nil)
} }
bench.StopTimer() bench.StopTimer()
elapsed := uint64(time.Since(start)) elapsed := uint64(time.Since(start))

View file

@ -19,6 +19,7 @@ package vm
import ( import (
"errors" "errors"
"fmt" "fmt"
"math"
) )
// List evm execution errors // List evm execution errors
@ -71,3 +72,125 @@ type ErrInvalidOpCode struct {
} }
func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) } func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) }
// rpcError is the same interface as the one defined in rpc/errors.go
// but we do not want to depend on rpc package here so we redefine it.
//
// It's used to ensure that the VMError implements the RPC error interface.
type rpcError interface {
Error() string // returns the message
ErrorCode() int // returns the code
}
var _ rpcError = (*VMError)(nil)
// VMError wraps a VM error with an additional stable error code. The error
// field is the original error that caused the VM error and must be one of the
// VM error defined at the top of this file.
//
// If the error is not one of the known error above, the error code will be
// set to VMErrorCodeUnknown.
type VMError struct {
error
code int
}
func VMErrorFromErr(err error) error {
if err == nil {
return nil
}
return &VMError{
error: err,
code: vmErrorCodeFromErr(err),
}
}
func (e *VMError) Error() string {
return e.error.Error()
}
func (e *VMError) Unwrap() error {
return e.error
}
func (e *VMError) ErrorCode() int {
return e.code
}
const (
// We start the error code at 1 so that we can use 0 later for some possible extension. There
// is no unspecified value for the code today because it should always be set to a valid value
// that could be VMErrorCodeUnknown if the error is not mapped to a known error code.
VMErrorCodeOutOfGas = 1 + iota
VMErrorCodeCodeStoreOutOfGas
VMErrorCodeDepth
VMErrorCodeInsufficientBalance
VMErrorCodeContractAddressCollision
VMErrorCodeExecutionReverted
VMErrorCodeMaxInitCodeSizeExceeded
VMErrorCodeMaxCodeSizeExceeded
VMErrorCodeInvalidJump
VMErrorCodeWriteProtection
VMErrorCodeReturnDataOutOfBounds
VMErrorCodeGasUintOverflow
VMErrorCodeInvalidCode
VMErrorCodeNonceUintOverflow
VMErrorCodeStackUnderflow
VMErrorCodeStackOverflow
VMErrorCodeInvalidOpCode
// VMErrorCodeUnknown explicitly marks an error as unknown, this is useful when error is converted
// from an actual `error` in which case if the mapping is not known, we can use this value to indicate that.
VMErrorCodeUnknown = math.MaxInt - 1
)
func vmErrorCodeFromErr(err error) int {
switch {
case errors.Is(err, ErrOutOfGas):
return VMErrorCodeOutOfGas
case errors.Is(err, ErrCodeStoreOutOfGas):
return VMErrorCodeCodeStoreOutOfGas
case errors.Is(err, ErrDepth):
return VMErrorCodeDepth
case errors.Is(err, ErrInsufficientBalance):
return VMErrorCodeInsufficientBalance
case errors.Is(err, ErrContractAddressCollision):
return VMErrorCodeContractAddressCollision
case errors.Is(err, ErrExecutionReverted):
return VMErrorCodeExecutionReverted
case errors.Is(err, ErrMaxInitCodeSizeExceeded):
return VMErrorCodeMaxInitCodeSizeExceeded
case errors.Is(err, ErrMaxCodeSizeExceeded):
return VMErrorCodeMaxCodeSizeExceeded
case errors.Is(err, ErrInvalidJump):
return VMErrorCodeInvalidJump
case errors.Is(err, ErrWriteProtection):
return VMErrorCodeWriteProtection
case errors.Is(err, ErrReturnDataOutOfBounds):
return VMErrorCodeReturnDataOutOfBounds
case errors.Is(err, ErrGasUintOverflow):
return VMErrorCodeGasUintOverflow
case errors.Is(err, ErrInvalidCode):
return VMErrorCodeInvalidCode
case errors.Is(err, ErrNonceUintOverflow):
return VMErrorCodeNonceUintOverflow
default:
// Dynamic errors
if v := (*ErrStackUnderflow)(nil); errors.As(err, &v) {
return VMErrorCodeStackUnderflow
}
if v := (*ErrStackOverflow)(nil); errors.As(err, &v) {
return VMErrorCodeStackOverflow
}
if v := (*ErrInvalidOpCode)(nil); errors.As(err, &v) {
return VMErrorCodeInvalidOpCode
}
return VMErrorCodeUnknown
}
}

View file

@ -17,10 +17,12 @@
package vm package vm
import ( import (
"errors"
"math/big" "math/big"
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -185,6 +187,13 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
// the necessary steps to create accounts and reverses the state in case of an // the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer. // execution error or failed value transfer.
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
// Capture the tracer start/end events in debug mode
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, CALL, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) {
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
}(gas)
}
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
@ -195,44 +204,18 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
} }
snapshot := evm.StateDB.Snapshot() snapshot := evm.StateDB.Snapshot()
p, isPrecompile := evm.precompile(addr) p, isPrecompile := evm.precompile(addr)
debug := evm.Config.Tracer != nil
if !evm.StateDB.Exist(addr) { if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer // Calling a non-existing account, don't do anything.
if debug {
if evm.depth == 0 {
evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
evm.Config.Tracer.CaptureEnd(ret, 0, nil)
} else {
evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value)
evm.Config.Tracer.CaptureExit(ret, 0, nil)
}
}
return nil, gas, nil return nil, gas, nil
} }
evm.StateDB.CreateAccount(addr) evm.StateDB.CreateAccount(addr)
} }
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
// Capture the tracer start/end events in debug mode
if debug {
if evm.depth == 0 {
evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
defer func(startGas uint64) { // Lazy evaluation of the parameters
evm.Config.Tracer.CaptureEnd(ret, startGas-gas, err)
}(gas)
} else {
// Handle tracer events for entering and exiting a call frame
evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas)
}
}
if isPrecompile { if isPrecompile {
ret, gas, err = RunPrecompiledContract(p, evm, caller.Address(), caller.Address(), input, gas, value) ret, gas, err = RunPrecompiledContract(p, evm, caller.Address(), caller.Address(), input, gas, value, evm.Config.Tracer)
} else { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only. // The contract is a scoped environment for this execution context only.
@ -250,11 +233,15 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
} }
} }
// When an error was returned by the EVM or when setting the creation code // When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally // above we revert to the snapshot and consume any gas remaining. Additionally,
// when we're in homestead this also counts for code storage gas errors. // when we're in homestead this also counts for code storage gas errors.
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
}
gas = 0 gas = 0
} }
// TODO: consider clearing up unused snapshots: // TODO: consider clearing up unused snapshots:
@ -272,6 +259,13 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// CallCode differs from Call in the sense that it executes the given address' // CallCode differs from Call in the sense that it executes the given address'
// code with the caller as context. // code with the caller as context.
func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, CALLCODE, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) {
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
}(gas)
}
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
@ -285,17 +279,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
} }
var snapshot = evm.StateDB.Snapshot() var snapshot = evm.StateDB.Snapshot()
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
evm.Config.Tracer.CaptureEnter(CALLCODE, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas)
}
// It is allowed to call precompiles, even via delegatecall // It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile { if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, evm, caller.Address(), caller.Address(), input, gas, value) ret, gas, err = RunPrecompiledContract(p, evm, caller.Address(), caller.Address(), input, gas, value, evm.Config.Tracer)
} else { } else {
addrCopy := addr addrCopy := addr
// Initialise a new contract and set the code that is to be used by the EVM. // Initialise a new contract and set the code that is to be used by the EVM.
@ -308,6 +294,10 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
}
gas = 0 gas = 0
} }
} }
@ -320,30 +310,29 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
// DelegateCall differs from CallCode in the sense that it executes the given address' // DelegateCall differs from CallCode in the sense that it executes the given address'
// code with the caller as context and the caller is set to the caller of the caller. // code with the caller as context and the caller is set to the caller of the caller.
func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
var snapshot = evm.StateDB.Snapshot()
// Invoke tracer hooks that signal entering/exiting a call frame // Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil { if evm.Config.Tracer != nil {
// NOTE: caller must, at all times be a contract. It should never happen // NOTE: caller must, at all times be a contract. It should never happen
// that caller is something other than a Contract. // that caller is something other than a Contract.
parent := caller.(*Contract) parent := caller.(*Contract)
// DELEGATECALL inherits value from parent call // DELEGATECALL inherits value from parent call
evm.Config.Tracer.CaptureEnter(DELEGATECALL, caller.Address(), addr, input, gas, parent.value) evm.captureBegin(evm.depth, DELEGATECALL, caller.Address(), addr, input, gas, parent.value)
defer func(startGas uint64) { defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
}(gas) }(gas)
} }
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
var snapshot = evm.StateDB.Snapshot()
// It is allowed to call precompiles, even via delegatecall // It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile { if p, isPrecompile := evm.precompile(addr); isPrecompile {
// NOTE: caller must, at all times be a contract. It should never happen // NOTE: caller must, at all times be a contract. It should never happen
// that caller is something other than a Contract. // that caller is something other than a Contract.
parent := caller.(*Contract) parent := caller.(*Contract)
ret, gas, err = RunPrecompiledContract(p, evm, parent.CallerAddress, caller.Address(), input, gas, nil) ret, gas, err = RunPrecompiledContract(p, evm, parent.CallerAddress, caller.Address(), input, gas, nil, evm.Config.Tracer)
} else { } else {
addrCopy := addr addrCopy := addr
// Initialise a new contract and make initialise the delegate values // Initialise a new contract and make initialise the delegate values
@ -355,6 +344,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
}
gas = 0 gas = 0
} }
} }
@ -366,6 +358,13 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
// Opcodes that attempt to perform such modifications will result in exceptions // Opcodes that attempt to perform such modifications will result in exceptions
// instead of performing the modifications. // instead of performing the modifications.
func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, STATICCALL, caller.Address(), addr, input, gas, nil)
defer func(startGas uint64) {
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
}(gas)
}
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
@ -381,18 +380,10 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,
// but is the correct thing to do and matters on other networks, in tests, and potential // but is the correct thing to do and matters on other networks, in tests, and potential
// future scenarios // future scenarios
evm.StateDB.AddBalance(addr, big0) evm.StateDB.AddBalance(addr, new(big.Int), tracing.BalanceChangeTouchAccount)
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
evm.Config.Tracer.CaptureEnter(STATICCALL, caller.Address(), addr, input, gas, nil)
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas)
}
if p, isPrecompile := evm.precompile(addr); isPrecompile { if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, evm, caller.Address(), caller.Address(), input, gas, nil) ret, gas, err = RunPrecompiledContract(p, evm, caller.Address(), caller.Address(), input, gas, nil, evm.Config.Tracer)
} else { } else {
// At this point, we use a copy of address. If we don't, the go compiler will // At this point, we use a copy of address. If we don't, the go compiler will
// leak the 'contract' to the outer scope, and make allocation for 'contract' // leak the 'contract' to the outer scope, and make allocation for 'contract'
@ -411,6 +402,10 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
}
gas = 0 gas = 0
} }
} }
@ -430,7 +425,13 @@ func (c *codeAndHash) Hash() common.Hash {
} }
// create creates a new contract using code as deployment code. // create creates a new contract using code as deployment code.
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) { func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) {
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, typ, caller.Address(), address, codeAndHash.code, gas, value)
defer func(startGas uint64) {
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
}(gas)
}
// Depth check execution. Fail if we're trying to execute above the // Depth check execution. Fail if we're trying to execute above the
// limit. // limit.
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
@ -452,6 +453,10 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// Ensure there's no existing contract already at the designated address // Ensure there's no existing contract already at the designated address
contractHash := evm.StateDB.GetCodeHash(address) contractHash := evm.StateDB.GetCodeHash(address)
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) { if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
}
return nil, common.Address{}, 0, ErrContractAddressCollision return nil, common.Address{}, 0, ErrContractAddressCollision
} }
// Create a new account on the state // Create a new account on the state
@ -467,15 +472,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
contract := NewContract(caller, AccountRef(address), value, gas) contract := NewContract(caller, AccountRef(address), value, gas)
contract.SetCodeOptionalHash(&address, codeAndHash) contract.SetCodeOptionalHash(&address, codeAndHash)
if evm.Config.Tracer != nil { ret, err = evm.interpreter.Run(contract, nil, false)
if evm.depth == 0 {
evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
} else {
evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value)
}
}
ret, err := evm.interpreter.Run(contract, nil, false)
// Check whether the max code size has been exceeded, assign err if the case. // Check whether the max code size has been exceeded, assign err if the case.
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
@ -493,7 +490,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// by the error checking condition below. // by the error checking condition below.
if err == nil { if err == nil {
createDataGas := uint64(len(ret)) * params.CreateDataGas createDataGas := uint64(len(ret)) * params.CreateDataGas
if contract.UseGas(createDataGas) { if contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
evm.StateDB.SetCode(address, ret) evm.StateDB.SetCode(address, ret)
} else { } else {
err = ErrCodeStoreOutOfGas err = ErrCodeStoreOutOfGas
@ -501,22 +498,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
} }
// When an error was returned by the EVM or when setting the creation code // When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally // above we revert to the snapshot and consume any gas remaining. Additionally,
// when we're in homestead this also counts for code storage gas errors. // when we're in homestead this also counts for code storage gas errors.
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
contract.UseGas(contract.Gas) contract.UseGas(contract.Gas, evm.Config.Tracer, tracing.GasChangeCallFailedExecution)
} }
} }
if evm.Config.Tracer != nil {
if evm.depth == 0 {
evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, err)
} else {
evm.Config.Tracer.CaptureExit(ret, gas-contract.Gas, err)
}
}
return ret, address, contract.Gas, err return ret, address, contract.Gas, err
} }
@ -538,3 +528,44 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *
// ChainConfig returns the environment's chain configuration // ChainConfig returns the environment's chain configuration
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to common.Address, input []byte, startGas uint64, value *big.Int) {
tracer := evm.Config.Tracer
if tracer.OnEnter != nil {
tracer.OnEnter(depth, byte(typ), from, to, input, startGas, value)
}
if tracer.OnGasChange != nil {
tracer.OnGasChange(0, startGas, tracing.GasChangeCallInitialBalance)
}
}
func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret []byte, err error) {
tracer := evm.Config.Tracer
if leftOverGas != 0 && tracer.OnGasChange != nil {
tracer.OnGasChange(leftOverGas, 0, tracing.GasChangeCallLeftOverReturned)
}
var reverted bool
if err != nil {
reverted = true
}
if !evm.chainRules.IsHomestead && errors.Is(err, ErrCodeStoreOutOfGas) {
reverted = false
}
if tracer.OnExit != nil {
tracer.OnExit(depth, ret, startGas-leftOverGas, VMErrorFromErr(err), reverted)
}
}
// GetVMContext provides context about the block being executed as well as state
// to the tracers.
func (evm *EVM) GetVMContext() *tracing.VMContext {
return &tracing.VMContext{
Coinbase: evm.Context.Coinbase,
BlockNumber: evm.Context.BlockNumber,
Time: evm.Context.Time,
Random: evm.Context.Random,
GasPrice: evm.TxContext.GasPrice,
ChainConfig: evm.ChainConfig(),
StateDB: evm.StateDB,
}
}

View file

@ -18,6 +18,7 @@ package vm
import ( import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -247,7 +248,6 @@ func OpKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
if evm.Config.EnablePreimageRecording { if evm.Config.EnablePreimageRecording {
evm.StateDB.AddPreimage(interpreter.hasherBuf, data) evm.StateDB.AddPreimage(interpreter.hasherBuf, data)
} }
size.SetBytes(interpreter.hasherBuf[:]) size.SetBytes(interpreter.hasherBuf[:])
return nil, nil return nil, nil
} }
@ -591,7 +591,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
// reuse size int for stackvalue // reuse size int for stackvalue
stackvalue := size stackvalue := size
scope.Contract.UseGas(gas) scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation)
//TODO: use uint256.Int instead of converting with toBig() //TODO: use uint256.Int instead of converting with toBig()
var bigVal = big0 var bigVal = big0
if !value.IsZero() { if !value.IsZero() {
@ -611,7 +611,8 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
stackvalue.SetBytes(addr.Bytes()) stackvalue.SetBytes(addr.Bytes())
} }
scope.Stack.push(&stackvalue) scope.Stack.push(&stackvalue)
scope.Contract.Gas += returnGas
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if suberr == ErrExecutionReverted { if suberr == ErrExecutionReverted {
interpreter.returnData = res // set REVERT data to return data buffer interpreter.returnData = res // set REVERT data to return data buffer
@ -634,7 +635,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
) )
// Apply EIP150 // Apply EIP150
gas -= gas / 64 gas -= gas / 64
scope.Contract.UseGas(gas) scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
// reuse size int for stackvalue // reuse size int for stackvalue
stackvalue := size stackvalue := size
//TODO: use uint256.Int instead of converting with toBig() //TODO: use uint256.Int instead of converting with toBig()
@ -651,7 +652,8 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
stackvalue.SetBytes(addr.Bytes()) stackvalue.SetBytes(addr.Bytes())
} }
scope.Stack.push(&stackvalue) scope.Stack.push(&stackvalue)
scope.Contract.Gas += returnGas
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if suberr == ErrExecutionReverted { if suberr == ErrExecutionReverted {
interpreter.returnData = res // set REVERT data to return data buffer interpreter.returnData = res // set REVERT data to return data buffer
@ -696,7 +698,8 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
if err == nil || err == ErrExecutionReverted { if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
scope.Contract.Gas += returnGas
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
interpreter.returnData = ret interpreter.returnData = ret
return ret, nil return ret, nil
@ -731,7 +734,8 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
if err == nil || err == ErrExecutionReverted { if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
scope.Contract.Gas += returnGas
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
interpreter.returnData = ret interpreter.returnData = ret
return ret, nil return ret, nil
@ -759,7 +763,8 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
if err == nil || err == ErrExecutionReverted { if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
scope.Contract.Gas += returnGas
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
interpreter.returnData = ret interpreter.returnData = ret
return ret, nil return ret, nil
@ -787,7 +792,8 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
if err == nil || err == ErrExecutionReverted { if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
scope.Contract.Gas += returnGas
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
interpreter.returnData = ret interpreter.returnData = ret
return ret, nil return ret, nil
@ -822,11 +828,15 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
} }
beneficiary := scope.Stack.pop() beneficiary := scope.Stack.pop()
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct)
interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address()) interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address())
if tracer := interpreter.evm.Config.Tracer; tracer != nil { if tracer := interpreter.evm.Config.Tracer; tracer != nil {
tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) if tracer.OnEnter != nil {
tracer.CaptureExit([]byte{}, 0, nil) tracer.OnEnter(interpreter.evm.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance)
}
if tracer.OnExit != nil {
tracer.OnExit(interpreter.evm.depth, []byte{}, 0, nil, false)
}
} }
return nil, errStopToken return nil, errStopToken
} }
@ -837,12 +847,16 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon
} }
beneficiary := scope.Stack.pop() beneficiary := scope.Stack.pop()
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance) interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance, tracing.BalanceDecreaseSelfdestruct)
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct)
interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address()) interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address())
if tracer := interpreter.evm.Config.Tracer; tracer != nil { if tracer := interpreter.evm.Config.Tracer; tracer != nil {
tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) if tracer.OnEnter != nil {
tracer.CaptureExit([]byte{}, 0, nil) tracer.OnEnter(interpreter.evm.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance)
}
if tracer.OnExit != nil {
tracer.OnExit(interpreter.evm.depth, []byte{}, 0, nil, false)
}
} }
return nil, errStopToken return nil, errStopToken
} }

View file

@ -20,6 +20,7 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -28,8 +29,8 @@ import (
type StateDB interface { type StateDB interface {
CreateAccount(common.Address) CreateAccount(common.Address)
SubBalance(common.Address, *big.Int) SubBalance(common.Address, *big.Int, tracing.BalanceChangeReason)
AddBalance(common.Address, *big.Int) AddBalance(common.Address, *big.Int, tracing.BalanceChangeReason)
GetBalance(common.Address) *big.Int GetBalance(common.Address) *big.Int
GetNonce(common.Address) uint64 GetNonce(common.Address) uint64
@ -82,7 +83,7 @@ type StateDB interface {
// new methods // new methods
Error() error Error() error
GetStorageRoot(common.Address) common.Hash GetStorageRoot(common.Address) common.Hash
SetBalance(common.Address, *big.Int) SetBalance(common.Address, *big.Int, tracing.BalanceChangeReason)
SetStorage(common.Address, map[common.Hash]common.Hash) SetStorage(common.Address, map[common.Hash]common.Hash)
Finalise(deleteEmptyObjects bool) Finalise(deleteEmptyObjects bool)
Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error)
@ -92,6 +93,7 @@ type StateDB interface {
GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash) []*types.Log GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash) []*types.Log
TxIndex() int TxIndex() int
Preimages() map[common.Hash][]byte Preimages() map[common.Hash][]byte
SetLogger(logger *tracing.Hooks)
SetEVM(evm *EVM) SetEVM(evm *EVM)
} }

View file

@ -17,18 +17,22 @@
package vm package vm
import ( import (
"math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/holiman/uint256"
) )
// Config are the configuration options for the Interpreter // Config are the configuration options for the Interpreter
type Config struct { type Config struct {
Tracer EVMLogger // Opcode logger Tracer *tracing.Hooks
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
ExtraEips []int // Additional EIPS that are to be enabled ExtraEips []int // Additional EIPS that are to be enabled
} }
// ScopeContext contains the things that are per-call, such as stack and memory, // ScopeContext contains the things that are per-call, such as stack and memory,
@ -39,6 +43,45 @@ type ScopeContext struct {
Contract *Contract Contract *Contract
} }
// MemoryData returns the underlying memory slice. Callers must not modify the contents
// of the returned data.
func (ctx *ScopeContext) MemoryData() []byte {
if ctx.Memory == nil {
return nil
}
return ctx.Memory.Data()
}
// MemoryData returns the stack data. Callers must not modify the contents
// of the returned data.
func (ctx *ScopeContext) StackData() []uint256.Int {
if ctx.Stack == nil {
return nil
}
return ctx.Stack.Data()
}
// Caller returns the current caller.
func (ctx *ScopeContext) Caller() common.Address {
return ctx.Contract.Caller()
}
// Address returns the address where this scope of execution is taking place.
func (ctx *ScopeContext) Address() common.Address {
return ctx.Contract.Address()
}
// CallValue returns the value supplied with this call.
func (ctx *ScopeContext) CallValue() *big.Int {
return ctx.Contract.Value()
}
// CallInput returns the input/calldata with this call. Callers must not modify
// the contents of the returned data.
func (ctx *ScopeContext) CallInput() []byte {
return ctx.Contract.Input
}
// EVMInterpreter represents an EVM interpreter // EVMInterpreter represents an EVM interpreter
type EVMInterpreter struct { type EVMInterpreter struct {
evm *EVM evm *EVM
@ -146,8 +189,8 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
res []byte // result of the opcode execution function res []byte // result of the opcode execution function
debug = in.evm.Config.Tracer != nil debug = in.evm.Config.Tracer != nil
) )
// Don't move this deferred function, it's placed before the capturestate-deferred method, // Don't move this deferred function, it's placed before the OnOpcode-deferred method,
// so that it get's executed _after_: the capturestate needs the stacks before // so that it gets executed _after_: the OnOpcode needs the stacks before
// they are returned to the pools // they are returned to the pools
defer func() { defer func() {
returnStack(stack) returnStack(stack)
@ -155,13 +198,15 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
contract.Input = input contract.Input = input
if debug { if debug {
defer func() { defer func() { // this deferred method handles exit-with-error
if err != nil { if err == nil {
if !logged { return
in.evm.Config.Tracer.CaptureState(pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) }
} else { if !logged && in.evm.Config.Tracer.OnOpcode != nil {
in.evm.Config.Tracer.CaptureFault(pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err) in.evm.Config.Tracer.OnOpcode(pcCopy, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
} }
if logged && in.evm.Config.Tracer.OnFault != nil {
in.evm.Config.Tracer.OnFault(pcCopy, byte(op), gasCopy, cost, callContext, in.evm.depth, VMErrorFromErr(err))
} }
}() }()
} }
@ -185,9 +230,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
} else if sLen > operation.maxStack { } else if sLen > operation.maxStack {
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
} }
if !contract.UseGas(cost) { if !contract.UseGas(cost, in.evm.Config.Tracer, tracing.GasChangeIgnored) {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
if operation.dynamicGas != nil { if operation.dynamicGas != nil {
// All ops with a dynamic memory usage also has a dynamic gas cost. // All ops with a dynamic memory usage also has a dynamic gas cost.
var memorySize uint64 var memorySize uint64
@ -211,21 +257,33 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
var dynamicCost uint64 var dynamicCost uint64
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
cost += dynamicCost // for tracing cost += dynamicCost // for tracing
if err != nil || !contract.UseGas(dynamicCost) { if err != nil || !contract.UseGas(dynamicCost, in.evm.Config.Tracer, tracing.GasChangeIgnored) {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
// Do tracing before memory expansion // Do tracing before memory expansion
if debug { if debug {
in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) if in.evm.Config.Tracer.OnGasChange != nil {
logged = true in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode)
}
if in.evm.Config.Tracer.OnOpcode != nil {
in.evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
logged = true
}
} }
if memorySize > 0 { if memorySize > 0 {
mem.Resize(memorySize) mem.Resize(memorySize)
} }
} else if debug { } else if debug {
in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) if in.evm.Config.Tracer.OnGasChange != nil {
logged = true in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode)
}
if in.evm.Config.Tracer.OnOpcode != nil {
in.evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
logged = true
}
} }
// execute the operation // execute the operation
res, err = operation.execute(&pc, in, callContext) res, err = operation.execute(&pc, in, callContext)
if err != nil { if err != nil {

View file

@ -1,43 +0,0 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package vm
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
// EVMLogger is used to collect execution traces from an EVM transaction
// execution. CaptureState is called for each step of the VM with the
// current VM state.
// Note that reference types are actual VM data structures; make copies
// if you need to retain them beyond the current call.
type EVMLogger interface {
// Transaction level
CaptureTxStart(gasLimit uint64)
CaptureTxEnd(restGas uint64)
// Top call frame
CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
CaptureEnd(output []byte, gasUsed uint64, err error)
// Rest of call frames
CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
CaptureExit(output []byte, gasUsed uint64, err error)
// Opcode level
CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
}

View file

@ -21,6 +21,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -169,7 +170,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
evm.StateDB.AddAddressToAccessList(addr) evm.StateDB.AddAddressToAccessList(addr)
// Charge the remaining difference here already, to correctly calculate available // Charge the remaining difference here already, to correctly calculate available
// gas for call // gas for call
if !contract.UseGas(coldCost) { if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return 0, ErrOutOfGas return 0, ErrOutOfGas
} }
} }

View file

@ -122,6 +122,9 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
sender = vm.AccountRef(cfg.Origin) sender = vm.AccountRef(cfg.Origin)
rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time) rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time)
) )
if cfg.EVMConfig.Tracer != nil {
cfg.EVMConfig.Tracer.OnTxStart(vmenv.GetVMContext(), types.NewTx(&types.LegacyTx{To: &address, Data: input, Value: cfg.Value, Gas: cfg.GasLimit}), cfg.Origin)
}
// Execute the preparatory steps for state transition which includes: // Execute the preparatory steps for state transition which includes:
// - prepare accessList(post-berlin) // - prepare accessList(post-berlin)
// - reset transient storage(eip 1153) // - reset transient storage(eip 1153)
@ -155,6 +158,9 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
sender = vm.AccountRef(cfg.Origin) sender = vm.AccountRef(cfg.Origin)
rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time) rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time)
) )
if cfg.EVMConfig.Tracer != nil {
cfg.EVMConfig.Tracer.OnTxStart(vmenv.GetVMContext(), types.NewTx(&types.LegacyTx{Data: input, Value: cfg.Value, Gas: cfg.GasLimit}), cfg.Origin)
}
// Execute the preparatory steps for state transition which includes: // Execute the preparatory steps for state transition which includes:
// - prepare accessList(post-berlin) // - prepare accessList(post-berlin)
// - reset transient storage(eip 1153) // - reset transient storage(eip 1153)
@ -183,6 +189,9 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
statedb = cfg.State statedb = cfg.State
rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time) rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time)
) )
if cfg.EVMConfig.Tracer != nil {
cfg.EVMConfig.Tracer.OnTxStart(vmenv.GetVMContext(), types.NewTx(&types.LegacyTx{To: &address, Data: input, Value: cfg.Value, Gas: cfg.GasLimit}), cfg.Origin)
}
// Execute the preparatory steps for state transition which includes: // Execute the preparatory steps for state transition which includes:
// - prepare accessList(post-berlin) // - prepare accessList(post-berlin)
// - reset transient storage(eip 1153) // - reset transient storage(eip 1153)

View file

@ -32,7 +32,7 @@ import (
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers/directory"
"github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -330,12 +330,12 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode
cfg.State, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) cfg.State, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
cfg.GasLimit = gas cfg.GasLimit = gas
if len(tracerCode) > 0 { if len(tracerCode) > 0 {
tracer, err := tracers.DefaultDirectory.New(tracerCode, new(tracers.Context), nil) tracer, err := directory.DefaultDirectory.New(tracerCode, new(directory.Context), nil)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
cfg.EVMConfig = vm.Config{ cfg.EVMConfig = vm.Config{
Tracer: tracer, Tracer: tracer.Hooks,
} }
} }
var ( var (
@ -510,7 +510,7 @@ func TestEip2929Cases(t *testing.T) {
code, ops) code, ops)
Execute(code, nil, &Config{ Execute(code, nil, &Config{
EVMConfig: vm.Config{ EVMConfig: vm.Config{
Tracer: logger.NewMarkdownLogger(nil, os.Stdout), Tracer: logger.NewMarkdownLogger(nil, os.Stdout).Hooks(),
ExtraEips: []int{2929}, ExtraEips: []int{2929},
}, },
}) })
@ -663,7 +663,7 @@ func TestColdAccountAccessCost(t *testing.T) {
tracer := logger.NewStructLogger(nil) tracer := logger.NewStructLogger(nil)
Execute(tc.code, nil, &Config{ Execute(tc.code, nil, &Config{
EVMConfig: vm.Config{ EVMConfig: vm.Config{
Tracer: tracer, Tracer: tracer.Hooks(),
}, },
}) })
have := tracer.StructLogs()[tc.step].GasCost have := tracer.StructLogs()[tc.step].GasCost
@ -811,7 +811,7 @@ func TestRuntimeJSTracer(t *testing.T) {
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
byte(vm.RETURN), byte(vm.RETURN),
} }
depressedCode := []byte{ suicideCode := []byte{
byte(vm.PUSH1), 0xaa, byte(vm.PUSH1), 0xaa,
byte(vm.SELFDESTRUCT), byte(vm.SELFDESTRUCT),
} }
@ -824,9 +824,9 @@ func TestRuntimeJSTracer(t *testing.T) {
statedb.SetCode(common.HexToAddress("0xcc"), calleeCode) statedb.SetCode(common.HexToAddress("0xcc"), calleeCode)
statedb.SetCode(common.HexToAddress("0xdd"), calleeCode) statedb.SetCode(common.HexToAddress("0xdd"), calleeCode)
statedb.SetCode(common.HexToAddress("0xee"), calleeCode) statedb.SetCode(common.HexToAddress("0xee"), calleeCode)
statedb.SetCode(common.HexToAddress("0xff"), depressedCode) statedb.SetCode(common.HexToAddress("0xff"), suicideCode)
tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil) tracer, err := directory.DefaultDirectory.New(jsTracer, new(directory.Context), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -834,7 +834,7 @@ func TestRuntimeJSTracer(t *testing.T) {
GasLimit: 1000000, GasLimit: 1000000,
State: statedb, State: statedb,
EVMConfig: vm.Config{ EVMConfig: vm.Config{
Tracer: tracer, Tracer: tracer.Hooks,
}}) }})
if err != nil { if err != nil {
t.Fatal("didn't expect error", err) t.Fatal("didn't expect error", err)
@ -861,14 +861,14 @@ func TestJSTracerCreateTx(t *testing.T) {
code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)} code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)}
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil) tracer, err := directory.DefaultDirectory.New(jsTracer, new(directory.Context), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, _, _, err = Create(code, &Config{ _, _, _, err = Create(code, &Config{
State: statedb, State: statedb,
EVMConfig: vm.Config{ EVMConfig: vm.Config{
Tracer: tracer, Tracer: tracer.Hooks,
}}) }})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View file

@ -411,6 +411,6 @@ func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, re
return b.eth.stateAtBlock(ctx, block, reexec, base, readOnly, preferDisk) return b.eth.stateAtBlock(ctx, block, reexec, base, readOnly, preferDisk)
} }
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, vm.StateDB, tracers.StateReleaseFunc, error) { func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, vm.StateDB, tracers.StateReleaseFunc, error) {
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec) return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
} }

View file

@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "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/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
@ -73,7 +74,7 @@ func TestAccountRange(t *testing.T) {
hash := common.HexToHash(fmt.Sprintf("%x", i)) hash := common.HexToHash(fmt.Sprintf("%x", i))
addr := common.BytesToAddress(crypto.Keccak256Hash(hash.Bytes()).Bytes()) addr := common.BytesToAddress(crypto.Keccak256Hash(hash.Bytes()).Bytes())
addrs[i] = addr addrs[i] = addr
sdb.SetBalance(addrs[i], big.NewInt(1)) sdb.SetBalance(addrs[i], big.NewInt(1), tracing.BalanceChangeUnspecified)
if _, ok := m[addr]; ok { if _, ok := m[addr]; ok {
t.Fatalf("bad") t.Fatalf("bad")
} else { } else {

View file

@ -18,6 +18,7 @@
package eth package eth
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
@ -44,6 +45,7 @@ import (
"github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/eth/protocols/snap"
"github.com/ethereum/go-ethereum/eth/tracers/directory/live"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/lib/ethapi" "github.com/ethereum/go-ethereum/lib/ethapi"
@ -205,6 +207,17 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
StateScheme: scheme, StateScheme: scheme,
} }
) )
if config.VMTrace != "" {
var traceConfig json.RawMessage
if config.VMTraceConfig != "" {
traceConfig = json.RawMessage(config.VMTraceConfig)
}
t, err := live.Directory.New(config.VMTrace, traceConfig)
if err != nil {
return nil, fmt.Errorf("Failed to create tracer %s: %v", config.VMTrace, err)
}
vmConfig.Tracer = t
}
// Override the chain config with provided settings. // Override the chain config with provided settings.
var overrides core.ChainOverrides var overrides core.ChainOverrides
if config.OverrideCancun != nil { if config.OverrideCancun != nil {

View file

@ -141,6 +141,10 @@ type Config struct {
// Enables tracking of SHA3 preimages in the VM // Enables tracking of SHA3 preimages in the VM
EnablePreimageRecording bool EnablePreimageRecording bool
// Enables VM tracing
VMTrace string
VMTraceConfig string
// Miscellaneous options // Miscellaneous options
DocRoot string `toml:"-"` DocRoot string `toml:"-"`

View file

@ -213,6 +213,7 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true}) evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
) )
dirtyState.SetEVM(evm) dirtyState.SetEVM(evm)
// Monitor the outer context and interrupt the EVM upon cancellation. To avoid // Monitor the outer context and interrupt the EVM upon cancellation. To avoid
// a dangling goroutine until the outer estimation finishes, create an internal // a dangling goroutine until the outer estimation finishes, create an internal
// context for the lifetime of this method call. // context for the lifetime of this method call.

View file

@ -216,7 +216,7 @@ func (eth *Ethereum) stateAtBlock(ctx context.Context, block *types.Block, reexe
} }
// stateAtTransaction returns the execution environment of a certain transaction. // stateAtTransaction returns the execution environment of a certain transaction.
func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, vm.StateDB, tracers.StateReleaseFunc, error) { func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, vm.StateDB, tracers.StateReleaseFunc, error) {
// Short circuit if it's genesis block. // Short circuit if it's genesis block.
if block.NumberU64() == 0 { if block.NumberU64() == 0 {
return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis") return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis")
@ -243,7 +243,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
txContext := core.NewEVMTxContext(msg) txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
if idx == txIndex { if idx == txIndex {
return msg, context, statedb, release, nil return tx, context, statedb, release, nil
} }
// Not yet the searched for transaction, execute on top of the current state // Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{}) vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{})

View file

@ -22,6 +22,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"math/big"
"os" "os"
"runtime" "runtime"
"sync" "sync"
@ -35,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/directory"
"github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/lib/ethapi" "github.com/ethereum/go-ethereum/lib/ethapi"
@ -86,7 +88,7 @@ type Backend interface {
Engine() consensus.Engine Engine() consensus.Engine
ChainDb() ethdb.Database ChainDb() ethdb.Database
StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base vm.StateDB, readOnly bool, preferDisk bool) (vm.StateDB, StateReleaseFunc, error) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base vm.StateDB, readOnly bool, preferDisk bool) (vm.StateDB, StateReleaseFunc, error)
StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, vm.StateDB, StateReleaseFunc, error) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, vm.StateDB, StateReleaseFunc, error)
} }
// API is the collection of tracing APIs exposed over the private debugging endpoint. // API is the collection of tracing APIs exposed over the private debugging endpoint.
@ -226,7 +228,7 @@ func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, conf
} }
sub := notifier.CreateSubscription() sub := notifier.CreateSubscription()
resCh := api.traceChain(from, to, config, notifier.Closed()) resCh := api.traceChain(from, to, config, sub.Err())
go func() { go func() {
for result := range resCh { for result := range resCh {
notifier.Notify(sub.ID, result) notifier.Notify(sub.ID, result)
@ -240,7 +242,7 @@ func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, conf
// the end block but excludes the start one. The return value will be one item per // the end block but excludes the start one. The return value will be one item per
// transaction, dependent on the requested tracer. // transaction, dependent on the requested tracer.
// The tracing procedure should be aborted in case the closed signal is received. // The tracing procedure should be aborted in case the closed signal is received.
func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed <-chan interface{}) chan *blockTraceResult { func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed <-chan error) chan *blockTraceResult {
reexec := defaultTraceReexec reexec := defaultTraceReexec
if config != nil && config.Reexec != nil { if config != nil && config.Reexec != nil {
reexec = *config.Reexec reexec = *config.Reexec
@ -271,20 +273,18 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
// Trace all the transactions contained within // Trace all the transactions contained within
for i, tx := range task.block.Transactions() { for i, tx := range task.block.Transactions() {
msg, _ := core.TransactionToMessage(tx, signer, task.block.BaseFee()) msg, _ := core.TransactionToMessage(tx, signer, task.block.BaseFee())
txctx := &Context{ txctx := &directory.Context{
BlockHash: task.block.Hash(), BlockHash: task.block.Hash(),
BlockNumber: task.block.Number(), BlockNumber: task.block.Number(),
TxIndex: i, TxIndex: i,
TxHash: tx.Hash(), TxHash: tx.Hash(),
} }
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) res, err := api.traceTx(ctx, tx, msg, txctx, blockCtx, task.statedb, config)
if err != nil { if err != nil {
task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()}
log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err)
break break
} }
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
task.statedb.Finalise(api.backend.ChainConfig().IsEIP158(task.block.Number()))
task.results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res} task.results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res}
} }
// Tracing state is used up, queue it for de-referencing. Note the // Tracing state is used up, queue it for de-referencing. Note the
@ -386,7 +386,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
// Send the block over to the concurrent tracers (if not in the fast-forward phase) // Send the block over to the concurrent tracers (if not in the fast-forward phase)
txs := next.Transactions() txs := next.Transactions()
select { select {
case taskCh <- &blockTraceTask{statedb: statedb.Copy(), block: next, release: release, results: make([]*txTraceResult, len(txs))}: case taskCh <- &blockTraceTask{statedb: statedb.Copy().(vm.StateDB), block: next, release: release, results: make([]*txTraceResult, len(txs))}:
case <-closed: case <-closed:
tracker.releaseState(number, release) tracker.releaseState(number, release)
return return
@ -590,7 +590,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
// process that generates states in one thread and traces txes // process that generates states in one thread and traces txes
// in separate worker threads. // in separate worker threads.
if config != nil && config.Tracer != nil && *config.Tracer != "" { if config != nil && config.Tracer != nil && *config.Tracer != "" {
if isJS := DefaultDirectory.IsJS(*config.Tracer); isJS { if isJS := directory.DefaultDirectory.IsJS(*config.Tracer); isJS {
return api.traceBlockParallel(ctx, block, statedb, config) return api.traceBlockParallel(ctx, block, statedb, config)
} }
} }
@ -598,7 +598,6 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
var ( var (
txs = block.Transactions() txs = block.Transactions()
blockHash = block.Hash() blockHash = block.Hash()
is158 = api.backend.ChainConfig().IsEIP158(block.Number())
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
results = make([]*txTraceResult, len(txs)) results = make([]*txTraceResult, len(txs))
@ -606,20 +605,17 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
for i, tx := range txs { for i, tx := range txs {
// Generate the next state snapshot fast without tracing // Generate the next state snapshot fast without tracing
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
txctx := &Context{ txctx := &directory.Context{
BlockHash: blockHash, BlockHash: blockHash,
BlockNumber: block.Number(), BlockNumber: block.Number(),
TxIndex: i, TxIndex: i,
TxHash: tx.Hash(), TxHash: tx.Hash(),
} }
res, err := api.traceTx(ctx, msg, txctx, blockCtx, statedb, config) res, err := api.traceTx(ctx, tx, msg, txctx, blockCtx, statedb, config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res} results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res}
// Finalize the state so any modifications are written to the trie
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
statedb.Finalise(is158)
} }
return results, nil return results, nil
} }
@ -632,7 +628,6 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
var ( var (
txs = block.Transactions() txs = block.Transactions()
blockHash = block.Hash() blockHash = block.Hash()
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
results = make([]*txTraceResult, len(txs)) results = make([]*txTraceResult, len(txs))
pend sync.WaitGroup pend sync.WaitGroup
@ -649,13 +644,18 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
// Fetch and execute the next transaction trace tasks // Fetch and execute the next transaction trace tasks
for task := range jobs { for task := range jobs {
msg, _ := core.TransactionToMessage(txs[task.index], signer, block.BaseFee()) msg, _ := core.TransactionToMessage(txs[task.index], signer, block.BaseFee())
txctx := &Context{ txctx := &directory.Context{
BlockHash: blockHash, BlockHash: blockHash,
BlockNumber: block.Number(), BlockNumber: block.Number(),
TxIndex: task.index, TxIndex: task.index,
TxHash: txs[task.index].Hash(), TxHash: txs[task.index].Hash(),
} }
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) // Reconstruct the block context for each transaction
// as the GetHash function of BlockContext is not safe for
// concurrent use.
// See: https://github.com/ethereum/go-ethereum/issues/29114
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
res, err := api.traceTx(ctx, txs[task.index], msg, txctx, blockCtx, task.statedb, config)
if err != nil { if err != nil {
results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()} results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()}
continue continue
@ -667,10 +667,11 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
// Feed the transactions into the tracers and return // Feed the transactions into the tracers and return
var failed error var failed error
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
txloop: txloop:
for i, tx := range txs { for i, tx := range txs {
// Send the trace task over for execution // Send the trace task over for execution
task := &txTraceTask{statedb: statedb.Copy(), index: i} task := &txTraceTask{statedb: statedb.Copy().(vm.StateDB), index: i}
select { select {
case <-ctx.Done(): case <-ctx.Done():
failed = ctx.Err() failed = ctx.Err()
@ -789,7 +790,9 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
// Execute the transaction and flush any traces to disk // Execute the transaction and flush any traces to disk
vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf) vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf)
statedb.SetTxContext(tx.Hash(), i) statedb.SetTxContext(tx.Hash(), i)
_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)) vmConf.Tracer.OnTxStart(vmenv.GetVMContext(), tx, msg.From)
vmRet, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit))
vmConf.Tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, err)
if writer != nil { if writer != nil {
writer.Flush() writer.Flush()
} }
@ -846,19 +849,23 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
if err != nil { if err != nil {
return nil, err return nil, err
} }
msg, vmctx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec) tx, vmctx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer release() defer release()
msg, err := core.TransactionToMessage(tx, types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()), block.BaseFee())
if err != nil {
return nil, err
}
txctx := &Context{ txctx := &directory.Context{
BlockHash: blockHash, BlockHash: blockHash,
BlockNumber: block.Number(), BlockNumber: block.Number(),
TxIndex: int(index), TxIndex: int(index),
TxHash: hash, TxHash: hash,
} }
return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) return api.traceTx(ctx, tx, msg, txctx, vmctx, statedb, config)
} }
// TraceCall lets you trace a given eth_call. It collects the structured logs // TraceCall lets you trace a given eth_call. It collects the structured logs
@ -919,40 +926,44 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
config.BlockOverrides.Apply(&vmctx) config.BlockOverrides.Apply(&vmctx)
} }
// Execute the trace // Execute the trace
msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee()) msg, err := args.ToMessage(api.backend.RPCGasCap(), vmctx.BaseFee)
if err != nil {
return nil, err
}
tx, err := argsToTransaction(api.backend, &args, msg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var traceConfig *TraceConfig var traceConfig *TraceConfig
if config != nil { if config != nil {
traceConfig = &config.TraceConfig traceConfig = &config.TraceConfig
} }
return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) return api.traceTx(ctx, tx, msg, new(directory.Context), vmctx, statedb, traceConfig)
} }
// traceTx configures a new tracer according to the provided configuration, and // traceTx configures a new tracer according to the provided configuration, and
// executes the given message in the provided environment. The return value will // executes the given message in the provided environment. The return value will
// be tracer dependent. // be tracer dependent.
func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb vm.StateDB, config *TraceConfig) (interface{}, error) { func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *core.Message, txctx *directory.Context, vmctx vm.BlockContext, statedb vm.StateDB, config *TraceConfig) (interface{}, error) {
var ( var (
tracer Tracer tracer *directory.Tracer
err error err error
timeout = defaultTraceTimeout timeout = defaultTraceTimeout
txContext = core.NewEVMTxContext(message) usedGas uint64
) )
if config == nil { if config == nil {
config = &TraceConfig{} config = &TraceConfig{}
} }
// Default tracer is the struct logger // Default tracer is the struct logger
tracer = logger.NewStructLogger(config.Config) tracer = logger.NewStructLogger(config.Config).Tracer()
if config.Tracer != nil { if config.Tracer != nil {
tracer, err = DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig) tracer, err = directory.DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true}) vmenv := vm.NewEVM(vmctx, vm.TxContext{GasPrice: big.NewInt(0)}, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer.Hooks, NoBaseFee: true})
statedb.SetLogger(tracer.Hooks)
// Define a meaningful timeout of a single transaction trace // Define a meaningful timeout of a single transaction trace
if config.Timeout != nil { if config.Timeout != nil {
@ -973,7 +984,8 @@ func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Conte
// Call Prepare to clear out the statedb access list // Call Prepare to clear out the statedb access list
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex) statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit)); err != nil { _, err = core.ApplyTransactionWithEVM(message, api.backend.ChainConfig(), new(core.GasPool).AddGas(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, tx, &usedGas, vmenv)
if err != nil {
return nil, fmt.Errorf("tracing failed: %w", err) return nil, fmt.Errorf("tracing failed: %w", err)
} }
return tracer.GetResult() return tracer.GetResult()
@ -1038,3 +1050,54 @@ func overrideConfig(original *params.ChainConfig, override *params.ChainConfig)
return copy, canon return copy, canon
} }
// argsToTransaction produces a Transaction object given call arguments.
// The `msg` field must be converted from the same arguments.
func argsToTransaction(b Backend, args *ethapi.TransactionArgs, msg *core.Message) (*types.Transaction, error) {
chainID := b.ChainConfig().ChainID
if args.ChainID != nil {
if have := (*big.Int)(args.ChainID); have.Cmp(chainID) != 0 {
return nil, fmt.Errorf("chainId does not match node's (have=%v, want=%v)", have, chainID)
}
}
var data types.TxData
switch {
case args.MaxFeePerGas != nil:
al := types.AccessList{}
if args.AccessList != nil {
al = *args.AccessList
}
data = &types.DynamicFeeTx{
To: args.To,
ChainID: chainID,
Nonce: msg.Nonce,
Gas: msg.GasLimit,
GasFeeCap: msg.GasFeeCap,
GasTipCap: msg.GasTipCap,
Value: msg.Value,
Data: msg.Data,
AccessList: al,
}
case args.AccessList != nil:
data = &types.AccessListTx{
To: args.To,
ChainID: chainID,
Nonce: msg.Nonce,
Gas: msg.GasLimit,
GasPrice: msg.GasPrice,
Value: msg.Value,
Data: msg.Data,
AccessList: *args.AccessList,
}
default:
data = &types.LegacyTx{
To: args.To,
Nonce: msg.Nonce,
Gas: msg.GasLimit,
GasPrice: msg.GasPrice,
Value: msg.Value,
Data: msg.Data,
}
}
return types.NewTx(data), nil
}

View file

@ -154,7 +154,7 @@ func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reex
return statedb, release, nil return statedb, release, nil
} }
func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, vm.StateDB, StateReleaseFunc, error) { func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, vm.StateDB, StateReleaseFunc, error) {
parent := b.chain.GetBlock(block.ParentHash(), block.NumberU64()-1) parent := b.chain.GetBlock(block.ParentHash(), block.NumberU64()-1)
if parent == nil { if parent == nil {
return nil, vm.BlockContext{}, nil, nil, errBlockNotFound return nil, vm.BlockContext{}, nil, nil, errBlockNotFound
@ -173,7 +173,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
txContext := core.NewEVMTxContext(msg) txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(block.Header(), b.chain, nil) context := core.NewEVMBlockContext(block.Header(), b.chain, nil)
if idx == txIndex { if idx == txIndex {
return msg, context, statedb, release, nil return tx, context, statedb, release, nil
} }
vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{}) vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{})
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
@ -191,7 +191,7 @@ func TestTraceCall(t *testing.T) {
accounts := newAccounts(3) accounts := newAccounts(3)
genesis := &core.Genesis{ genesis := &core.Genesis{
Config: params.TestChainConfig, Config: params.TestChainConfig,
Alloc: core.GenesisAlloc{ Alloc: types.GenesisAlloc{
accounts[0].addr: {Balance: big.NewInt(params.Ether)}, accounts[0].addr: {Balance: big.NewInt(params.Ether)},
accounts[1].addr: {Balance: big.NewInt(params.Ether)}, accounts[1].addr: {Balance: big.NewInt(params.Ether)},
accounts[2].addr: {Balance: big.NewInt(params.Ether)}, accounts[2].addr: {Balance: big.NewInt(params.Ether)},
@ -409,7 +409,7 @@ func TestTraceTransaction(t *testing.T) {
accounts := newAccounts(2) accounts := newAccounts(2)
genesis := &core.Genesis{ genesis := &core.Genesis{
Config: params.TestChainConfig, Config: params.TestChainConfig,
Alloc: core.GenesisAlloc{ Alloc: types.GenesisAlloc{
accounts[0].addr: {Balance: big.NewInt(params.Ether)}, accounts[0].addr: {Balance: big.NewInt(params.Ether)},
accounts[1].addr: {Balance: big.NewInt(params.Ether)}, accounts[1].addr: {Balance: big.NewInt(params.Ether)},
}, },
@ -464,7 +464,7 @@ func TestTraceBlock(t *testing.T) {
accounts := newAccounts(3) accounts := newAccounts(3)
genesis := &core.Genesis{ genesis := &core.Genesis{
Config: params.TestChainConfig, Config: params.TestChainConfig,
Alloc: core.GenesisAlloc{ Alloc: types.GenesisAlloc{
accounts[0].addr: {Balance: big.NewInt(params.Ether)}, accounts[0].addr: {Balance: big.NewInt(params.Ether)},
accounts[1].addr: {Balance: big.NewInt(params.Ether)}, accounts[1].addr: {Balance: big.NewInt(params.Ether)},
accounts[2].addr: {Balance: big.NewInt(params.Ether)}, accounts[2].addr: {Balance: big.NewInt(params.Ether)},
@ -554,7 +554,7 @@ func TestTracingWithOverrides(t *testing.T) {
storageAccount := common.Address{0x13, 37} storageAccount := common.Address{0x13, 37}
genesis := &core.Genesis{ genesis := &core.Genesis{
Config: params.TestChainConfig, Config: params.TestChainConfig,
Alloc: core.GenesisAlloc{ Alloc: types.GenesisAlloc{
accounts[0].addr: {Balance: big.NewInt(params.Ether)}, accounts[0].addr: {Balance: big.NewInt(params.Ether)},
accounts[1].addr: {Balance: big.NewInt(params.Ether)}, accounts[1].addr: {Balance: big.NewInt(params.Ether)},
accounts[2].addr: {Balance: big.NewInt(params.Ether)}, accounts[2].addr: {Balance: big.NewInt(params.Ether)},
@ -665,7 +665,6 @@ func TestTracingWithOverrides(t *testing.T) {
From: &accounts[0].addr, From: &accounts[0].addr,
// BLOCKNUMBER PUSH1 MSTORE // BLOCKNUMBER PUSH1 MSTORE
Input: newRPCBytes(common.Hex2Bytes("4360005260206000f3")), Input: newRPCBytes(common.Hex2Bytes("4360005260206000f3")),
//&hexutil.Bytes{0x43}, // blocknumber
}, },
config: &TraceCallConfig{ config: &TraceCallConfig{
BlockOverrides: &ethapi.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))}, BlockOverrides: &ethapi.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))},
@ -923,7 +922,7 @@ func TestTraceChain(t *testing.T) {
accounts := newAccounts(3) accounts := newAccounts(3)
genesis := &core.Genesis{ genesis := &core.Genesis{
Config: params.TestChainConfig, Config: params.TestChainConfig,
Alloc: core.GenesisAlloc{ Alloc: types.GenesisAlloc{
accounts[0].addr: {Balance: big.NewInt(params.Ether)}, accounts[0].addr: {Balance: big.NewInt(params.Ether)},
accounts[1].addr: {Balance: big.NewInt(params.Ether)}, accounts[1].addr: {Balance: big.NewInt(params.Ether)},
accounts[2].addr: {Balance: big.NewInt(params.Ether)}, accounts[2].addr: {Balance: big.NewInt(params.Ether)},

View file

@ -0,0 +1,31 @@
package live
import (
"encoding/json"
"errors"
"github.com/ethereum/go-ethereum/core/tracing"
)
type ctorFunc func(config json.RawMessage) (*tracing.Hooks, error)
// Directory is the collection of tracers which can be used
// during normal block import operations.
var Directory = directory{elems: make(map[string]ctorFunc)}
type directory struct {
elems map[string]ctorFunc
}
// Register registers a tracer constructor by name.
func (d *directory) Register(name string, f ctorFunc) {
d.elems[name] = f
}
// New instantiates a tracer by name.
func (d *directory) New(name string, config json.RawMessage) (*tracing.Hooks, error) {
if f, ok := d.elems[name]; ok {
return f(config)
}
return nil, errors.New("not found")
}

View file

@ -0,0 +1,97 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package directory
import (
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
)
func init() {
DefaultDirectory.Register("noopTracer", newNoopTracer, false)
}
// NoopTracer is a go implementation of the Tracer interface which
// performs no action. It's mostly useful for testing purposes.
type NoopTracer struct{}
// newNoopTracer returns a new noop tracer.
func newNoopTracer(ctx *Context, _ json.RawMessage) (*Tracer, error) {
t := &NoopTracer{}
return &Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
OnGasChange: t.OnGasChange,
OnBalanceChange: t.OnBalanceChange,
OnNonceChange: t.OnNonceChange,
OnCodeChange: t.OnCodeChange,
OnStorageChange: t.OnStorageChange,
OnLog: t.OnLog,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil
}
func (t *NoopTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
}
func (t *NoopTracer) OnFault(pc uint64, op byte, gas, cost uint64, _ tracing.OpContext, depth int, err error) {
}
func (t *NoopTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {}
func (t *NoopTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}
func (t *NoopTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
}
func (*NoopTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
}
func (*NoopTracer) OnTxEnd(receipt *types.Receipt, err error) {}
func (*NoopTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) {
}
func (*NoopTracer) OnNonceChange(a common.Address, prev, new uint64) {}
func (*NoopTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
}
func (*NoopTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {}
func (*NoopTracer) OnLog(log *types.Log) {}
// GetResult returns an empty json object.
func (t *NoopTracer) GetResult() (json.RawMessage, error) {
return json.RawMessage(`{}`), nil
}
// Stop terminates execution of the tracer at the first opportune moment.
func (t *NoopTracer) Stop(err error) {
}

View file

@ -14,17 +14,17 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package tracers is a manager for transaction tracing engines. // Package directory provides functionality to lookup tracers by name.
package tracers // It also includes utility functions that are imported by the other
// tracing packages.
package directory
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/tracing"
) )
// Context contains some contextual infos for a transaction execution that is not // Context contains some contextual infos for a transaction execution that is not
@ -36,17 +36,19 @@ type Context struct {
TxHash common.Hash // Hash of the transaction being traced (zero if dangling call) TxHash common.Hash // Hash of the transaction being traced (zero if dangling call)
} }
// Tracer interface extends vm.EVMLogger and additionally // The set of methods that must be exposed by a tracer
// allows collecting the tracing result. // for it to be available through the RPC interface.
type Tracer interface { // This involves a method to retrieve results and one to
vm.EVMLogger // stop tracing.
GetResult() (json.RawMessage, error) type Tracer struct {
*tracing.Hooks
GetResult func() (json.RawMessage, error)
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
Stop(err error) Stop func(err error)
} }
type ctorFn func(*Context, json.RawMessage) (Tracer, error) type ctorFn func(*Context, json.RawMessage) (*Tracer, error)
type jsCtorFn func(string, *Context, json.RawMessage) (Tracer, error) type jsCtorFn func(string, *Context, json.RawMessage) (*Tracer, error)
type elem struct { type elem struct {
ctor ctorFn ctor ctorFn
@ -79,7 +81,7 @@ func (d *directory) RegisterJSEval(f jsCtorFn) {
// New returns a new instance of a tracer, by iterating through the // New returns a new instance of a tracer, by iterating through the
// registered lookups. Name is either name of an existing tracer // registered lookups. Name is either name of an existing tracer
// or an arbitrary JS code. // or an arbitrary JS code.
func (d *directory) New(name string, ctx *Context, cfg json.RawMessage) (Tracer, error) { func (d *directory) New(name string, ctx *Context, cfg json.RawMessage) (*Tracer, error) {
if elem, ok := d.elems[name]; ok { if elem, ok := d.elems[name]; ok {
return elem.ctor(ctx, cfg) return elem.ctor(ctx, cfg)
} }
@ -97,27 +99,3 @@ func (d *directory) IsJS(name string) bool {
// JS eval will execute JS code // JS eval will execute JS code
return true return true
} }
const (
memoryPadLimit = 1024 * 1024
)
// GetMemoryCopyPadded returns offset + size as a new slice.
// It zero-pads the slice if it extends beyond memory bounds.
func GetMemoryCopyPadded(m *vm.Memory, offset, size int64) ([]byte, error) {
if offset < 0 || size < 0 {
return nil, errors.New("offset or size must not be negative")
}
if int(offset+size) < m.Len() { // slice fully inside memory
return m.GetCopy(offset, size), nil
}
paddingNeeded := int(offset+size) - m.Len()
if paddingNeeded > memoryPadLimit {
return nil, fmt.Errorf("reached limit for padding memory slice: %d", paddingNeeded)
}
cpy := make([]byte, size)
if overlap := int64(m.Len()) - offset; overlap > 0 {
copy(cpy, m.GetPtr(offset, overlap))
}
return cpy, nil
}

View file

@ -0,0 +1,80 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package directory
import (
"errors"
"fmt"
"github.com/holiman/uint256"
)
const (
memoryPadLimit = 1024 * 1024
)
// GetMemoryCopyPadded returns offset + size as a new slice.
// It zero-pads the slice if it extends beyond memory bounds.
func GetMemoryCopyPadded(m []byte, offset, size int64) ([]byte, error) {
if offset < 0 || size < 0 {
return nil, errors.New("offset or size must not be negative")
}
length := int64(len(m))
if offset+size < length { // slice fully inside memory
return memoryCopy(m, offset, size), nil
}
paddingNeeded := offset + size - length
if paddingNeeded > memoryPadLimit {
return nil, fmt.Errorf("reached limit for padding memory slice: %d", paddingNeeded)
}
cpy := make([]byte, size)
if overlap := length - offset; overlap > 0 {
copy(cpy, MemoryPtr(m, offset, overlap))
}
return cpy, nil
}
func memoryCopy(m []byte, offset, size int64) (cpy []byte) {
if size == 0 {
return nil
}
if len(m) > int(offset) {
cpy = make([]byte, size)
copy(cpy, m[offset:offset+size])
return
}
return
}
func MemoryPtr(m []byte, offset, size int64) []byte {
if size == 0 {
return nil
}
if len(m) > int(offset) {
return m[offset : offset+size]
}
return nil
}
// Back returns the n'th item in stack
func StackBack(st []uint256.Int, n int) *uint256.Int {
return &st[len(st)-n-1]
}

View file

@ -0,0 +1,60 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package directory
import (
"testing"
"github.com/ethereum/go-ethereum/core/vm"
)
func TestMemCopying(t *testing.T) {
for i, tc := range []struct {
memsize int64
offset int64
size int64
wantErr string
wantSize int
}{
{0, 0, 100, "", 100}, // Should pad up to 100
{0, 100, 0, "", 0}, // No need to pad (0 size)
{100, 50, 100, "", 100}, // Should pad 100-150
{100, 50, 5, "", 5}, // Wanted range fully within memory
{100, -50, 0, "offset or size must not be negative", 0}, // Error
{0, 1, 1024*1024 + 1, "reached limit for padding memory slice: 1048578", 0}, // Error
{10, 0, 1024*1024 + 100, "reached limit for padding memory slice: 1048666", 0}, // Error
} {
mem := vm.NewMemory()
mem.Resize(uint64(tc.memsize))
cpy, err := GetMemoryCopyPadded(mem.Data(), tc.offset, tc.size)
if want := tc.wantErr; want != "" {
if err == nil {
t.Fatalf("test %d: want '%v' have no error", i, want)
}
if have := err.Error(); want != have {
t.Fatalf("test %d: want '%v' have '%v'", i, want, have)
}
continue
}
if err != nil {
t.Fatalf("test %d: unexpected error: %v", i, err)
}
if want, have := tc.wantSize, len(cpy); have != want {
t.Fatalf("test %d: want %v have %v", i, want, have)
}
}
}

View file

@ -0,0 +1,10 @@
# Filling test cases
To fill test cases for the built-in tracers, the `makeTest.js` script can be used. Given a transaction on a dev/test network, `makeTest.js` will fetch its prestate and then traces with the given configuration.
In the Geth console do:
```terminal
let tx = '0x...'
loadScript('makeTest.js')
makeTest(tx, { tracer: 'callTracer' })
```

View file

@ -18,6 +18,7 @@ package tracetest
import ( import (
"encoding/json" "encoding/json"
"fmt"
"math/big" "math/big"
"os" "os"
"path/filepath" "path/filepath"
@ -31,7 +32,8 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers/directory"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
@ -122,12 +124,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
} }
// Configure a blockchain with the given prestate // Configure a blockchain with the given prestate
var ( var (
signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time)) 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 = vm.BlockContext{ context = vm.BlockContext{
CanTransfer: core.CanTransfer, CanTransfer: core.CanTransfer,
Transfer: core.Transfer, Transfer: core.Transfer,
@ -142,19 +139,23 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
) )
triedb.Close() triedb.Close()
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) tracer, err := directory.DefaultDirectory.New(tracerName, new(directory.Context), test.TracerConfig)
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer})
msg, err := core.TransactionToMessage(tx, signer, nil) statedb.SetLogger(tracer.Hooks)
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
if err != nil { if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err) t.Fatalf("failed to prepare transaction for tracing: %v", err)
} }
evm := vm.NewEVM(context, core.NewEVMTxContext(msg), statedb, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())) vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil { if err != nil {
t.Fatalf("failed to execute transaction: %v", err) t.Fatalf("failed to execute transaction: %v", err)
} }
tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
// Retrieve the trace result and compare against the expected. // Retrieve the trace result and compare against the expected.
res, err := tracer.GetResult() res, err := tracer.GetResult()
if err != nil { if err != nil {
@ -222,10 +223,6 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
b.Fatalf("failed to parse testcase input: %v", err) 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)) signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time))
msg, err := core.TransactionToMessage(tx, signer, nil)
if err != nil {
b.Fatalf("failed to prepare transaction for tracing: %v", err)
}
origin, _ := signer.Sender(tx) origin, _ := signer.Sender(tx)
txContext := vm.TxContext{ txContext := vm.TxContext{
Origin: origin, Origin: origin,
@ -240,17 +237,21 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
Difficulty: (*big.Int)(test.Context.Difficulty), Difficulty: (*big.Int)(test.Context.Difficulty),
GasLimit: uint64(test.Context.GasLimit), GasLimit: uint64(test.Context.GasLimit),
} }
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
if err != nil {
b.Fatalf("failed to prepare transaction for tracing: %v", err)
}
triedb, _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme) triedb, _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme)
defer triedb.Close() defer triedb.Close()
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), nil) tracer, err := directory.DefaultDirectory.New(tracerName, new(directory.Context), nil)
if err != nil { if err != nil {
b.Fatalf("failed to create call tracer: %v", err) b.Fatalf("failed to create call tracer: %v", err)
} }
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer}) evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
snap := statedb.Snapshot() snap := statedb.Snapshot()
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if _, err = st.TransitionDb(); err != nil { if _, err = st.TransitionDb(); err != nil {
@ -265,13 +266,13 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
func TestInternals(t *testing.T) { func TestInternals(t *testing.T) {
var ( var (
config = params.MainnetChainConfig
to = common.HexToAddress("0x00000000000000000000000000000000deadbeef") to = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
origin = common.HexToAddress("0x00000000000000000000000000000000feed") originHex = "0x71562b71999873db5b286df957af199ec94617f7"
txContext = vm.TxContext{ origin = common.HexToAddress(originHex)
Origin: origin, signer = types.LatestSigner(config)
GasPrice: big.NewInt(1), key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
} context = vm.BlockContext{
context = vm.BlockContext{
CanTransfer: core.CanTransfer, CanTransfer: core.CanTransfer,
Transfer: core.Transfer, Transfer: core.Transfer,
Coinbase: common.Address{}, Coinbase: common.Address{},
@ -279,10 +280,11 @@ func TestInternals(t *testing.T) {
Time: 5, Time: 5,
Difficulty: big.NewInt(0x30000), Difficulty: big.NewInt(0x30000),
GasLimit: uint64(6000000), GasLimit: uint64(6000000),
BaseFee: new(big.Int),
} }
) )
mkTracer := func(name string, cfg json.RawMessage) tracers.Tracer { mkTracer := func(name string, cfg json.RawMessage) *directory.Tracer {
tr, err := tracers.DefaultDirectory.New(name, nil, cfg) tr, err := directory.DefaultDirectory.New(name, nil, cfg)
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }
@ -292,7 +294,7 @@ func TestInternals(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
name string name string
code []byte code []byte
tracer tracers.Tracer tracer *directory.Tracer
want string want string
}{ }{
{ {
@ -306,13 +308,13 @@ func TestInternals(t *testing.T) {
byte(vm.CALL), byte(vm.CALL),
}, },
tracer: mkTracer("callTracer", nil), tracer: mkTracer("callTracer", nil),
want: `{"from":"0x000000000000000000000000000000000000feed","gas":"0x13880","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0xe01a","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`, want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0xe01a","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`, originHex),
}, },
{ {
name: "Stack depletion in LOG0", name: "Stack depletion in LOG0",
code: []byte{byte(vm.LOG3)}, code: []byte{byte(vm.LOG3)},
tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)),
want: `{"from":"0x000000000000000000000000000000000000feed","gas":"0x13880","gasUsed":"0x13880","to":"0x00000000000000000000000000000000deadbeef","input":"0x","error":"stack underflow (0 \u003c=\u003e 5)","value":"0x0","type":"CALL"}`, want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x13880","to":"0x00000000000000000000000000000000deadbeef","input":"0x","error":"stack underflow (0 \u003c=\u003e 5)","value":"0x0","type":"CALL"}`, originHex),
}, },
{ {
name: "Mem expansion in LOG0", name: "Mem expansion in LOG0",
@ -325,7 +327,7 @@ func TestInternals(t *testing.T) {
byte(vm.LOG0), byte(vm.LOG0),
}, },
tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)),
want: `{"from":"0x000000000000000000000000000000000000feed","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","position":"0x0"}],"value":"0x0","type":"CALL"}`, want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","position":"0x0"}],"value":"0x0","type":"CALL"}`, originHex),
}, },
{ {
// Leads to OOM on the prestate tracer // Leads to OOM on the prestate tracer
@ -344,7 +346,7 @@ func TestInternals(t *testing.T) {
byte(vm.LOG0), byte(vm.LOG0),
}, },
tracer: mkTracer("prestateTracer", nil), tracer: mkTracer("prestateTracer", nil),
want: `{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x000000000000000000000000000000000000feed":{"balance":"0x1c6bf52647880"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600164ffffffffff60016000f560ff6000a0"}}`, want: fmt.Sprintf(`{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600164ffffffffff60016000f560ff6000a0"},"%s":{"balance":"0x1c6bf52634000"}}`, originHex),
}, },
{ {
// CREATE2 which requires padding memory by prestate tracer // CREATE2 which requires padding memory by prestate tracer
@ -363,36 +365,45 @@ func TestInternals(t *testing.T) {
byte(vm.LOG0), byte(vm.LOG0),
}, },
tracer: mkTracer("prestateTracer", nil), tracer: mkTracer("prestateTracer", nil),
want: `{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x000000000000000000000000000000000000feed":{"balance":"0x1c6bf52647880"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600160ff60016000f560ff6000a0"},"0x91ff9a805d36f54e3e272e230f3e3f5c1b330804":{"balance":"0x0"}}`, want: fmt.Sprintf(`{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600160ff60016000f560ff6000a0"},"%s":{"balance":"0x1c6bf52634000"}}`, originHex),
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
triedb, _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), triedb, _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(),
core.GenesisAlloc{ types.GenesisAlloc{
to: core.GenesisAccount{ to: types.Account{
Code: tc.code, Code: tc.code,
}, },
origin: core.GenesisAccount{ origin: types.Account{
Balance: big.NewInt(500000000000000), Balance: big.NewInt(500000000000000),
}, },
}, false, rawdb.HashScheme) }, false, rawdb.HashScheme)
defer triedb.Close() defer triedb.Close()
statedb.SetLogger(tc.tracer.Hooks)
evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Tracer: tc.tracer}) tx, err := types.SignNewTx(key, signer, &types.LegacyTx{
msg := &core.Message{ To: &to,
To: &to, Value: big.NewInt(0),
From: origin, Gas: 80000,
Value: big.NewInt(0), GasPrice: big.NewInt(1),
GasLimit: 80000, })
GasPrice: big.NewInt(0), if err != nil {
GasFeeCap: big.NewInt(0), t.Fatalf("test %v: failed to sign transaction: %v", tc.name, err)
GasTipCap: big.NewInt(0),
SkipAccountChecks: false,
} }
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(msg.GasLimit)) txContext := vm.TxContext{
if _, err := st.TransitionDb(); err != nil { Origin: origin,
GasPrice: tx.GasPrice(),
}
evm := vm.NewEVM(context, txContext, statedb, config, vm.Config{Tracer: tc.tracer.Hooks})
msg, err := core.TransactionToMessage(tx, signer, big.NewInt(0))
if err != nil {
t.Fatalf("test %v: failed to create message: %v", tc.name, err)
}
tc.tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil {
t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err) t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err)
} }
tc.tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
// Retrieve the trace result and compare against the expected // Retrieve the trace result and compare against the expected
res, err := tc.tracer.GetResult() res, err := tc.tracer.GetResult()
if err != nil { if err != nil {

View file

@ -16,11 +16,9 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/directory"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
// Force-load the native, to trigger registration
"github.com/ethereum/go-ethereum/eth/tracers"
) )
// flatCallTrace is the result of a callTracerParity run. // flatCallTrace is the result of a callTracerParity run.
@ -86,11 +84,6 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string
return fmt.Errorf("failed to parse testcase input: %v", err) return fmt.Errorf("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)) 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 := vm.BlockContext{ context := vm.BlockContext{
CanTransfer: core.CanTransfer, CanTransfer: core.CanTransfer,
Transfer: core.Transfer, Transfer: core.Transfer,
@ -104,21 +97,23 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string
defer triedb.Close() defer triedb.Close()
// Create the tracer, the EVM environment and run it // Create the tracer, the EVM environment and run it
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) tracer, err := directory.DefaultDirectory.New(tracerName, new(directory.Context), test.TracerConfig)
if err != nil { if err != nil {
return fmt.Errorf("failed to create call tracer: %v", err) return fmt.Errorf("failed to create call tracer: %v", err)
} }
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer})
msg, err := core.TransactionToMessage(tx, signer, nil) statedb.SetLogger(tracer.Hooks)
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
if err != nil { if err != nil {
return fmt.Errorf("failed to prepare transaction for tracing: %v", err) return fmt.Errorf("failed to prepare transaction for tracing: %v", err)
} }
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) evm := vm.NewEVM(context, core.NewEVMTxContext(msg), statedb, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
if _, err = st.TransitionDb(); err != nil { vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil {
return fmt.Errorf("failed to execute transaction: %v", err) return fmt.Errorf("failed to execute transaction: %v", err)
} }
tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
// Retrieve the trace result and compare against the etalon // Retrieve the trace result and compare against the etalon
res, err := tracer.GetResult() res, err := tracer.GetResult()
@ -130,7 +125,7 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string
return fmt.Errorf("failed to unmarshal trace result: %v", err) return fmt.Errorf("failed to unmarshal trace result: %v", err)
} }
if !jsonEqualFlat(ret, test.Result) { if !jsonEqualFlat(ret, test.Result) {
t.Logf("tracer name: %s", tracerName) t.Logf("test %s failed", filename)
// uncomment this for easier debugging // uncomment this for easier debugging
// have, _ := json.MarshalIndent(ret, "", " ") // have, _ := json.MarshalIndent(ret, "", " ")

View file

@ -0,0 +1,48 @@
// makeTest generates a test for the configured tracer by running
// a prestate reassembled and a call trace run, assembling all the
// gathered information into a test case.
var makeTest = function(tx, traceConfig) {
// Generate the genesis block from the block, transaction and prestate data
var block = eth.getBlock(eth.getTransaction(tx).blockHash);
var genesis = eth.getBlock(block.parentHash);
delete genesis.gasUsed;
delete genesis.logsBloom;
delete genesis.parentHash;
delete genesis.receiptsRoot;
delete genesis.sha3Uncles;
delete genesis.size;
delete genesis.transactions;
delete genesis.transactionsRoot;
delete genesis.uncles;
genesis.gasLimit = genesis.gasLimit.toString();
genesis.number = genesis.number.toString();
genesis.timestamp = genesis.timestamp.toString();
genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer"});
for (var key in genesis.alloc) {
var nonce = genesis.alloc[key].nonce;
if (nonce) {
genesis.alloc[key].nonce = nonce.toString();
}
}
genesis.config = admin.nodeInfo.protocols.eth.config;
// Generate the call trace and produce the test input
var result = debug.traceTransaction(tx, traceConfig);
delete result.time;
console.log(JSON.stringify({
genesis: genesis,
context: {
number: block.number.toString(),
difficulty: block.difficulty,
timestamp: block.timestamp.toString(),
gasLimit: block.gasLimit.toString(),
miner: block.miner,
},
input: eth.getRawTransaction(tx),
result: result,
}, null, 2));
}

View file

@ -25,11 +25,12 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers/directory"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
) )
@ -92,12 +93,7 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) {
} }
// Configure a blockchain with the given prestate // Configure a blockchain with the given prestate
var ( var (
signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time)) 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 = vm.BlockContext{ context = vm.BlockContext{
CanTransfer: core.CanTransfer, CanTransfer: core.CanTransfer,
Transfer: core.Transfer, Transfer: core.Transfer,
@ -112,19 +108,28 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) {
) )
defer triedb.Close() defer triedb.Close()
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) if test.Genesis.ExcessBlobGas != nil && test.Genesis.BlobGasUsed != nil {
excessBlobGas := eip4844.CalcExcessBlobGas(*test.Genesis.ExcessBlobGas, *test.Genesis.BlobGasUsed)
context.BlobBaseFee = eip4844.CalcBlobFee(excessBlobGas)
}
tracer, err := directory.DefaultDirectory.New(tracerName, new(directory.Context), test.TracerConfig)
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer})
msg, err := core.TransactionToMessage(tx, signer, nil) statedb.SetLogger(tracer.Hooks)
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
if err != nil { if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err) t.Fatalf("failed to prepare transaction for tracing: %v", err)
} }
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) evm := vm.NewEVM(context, core.NewEVMTxContext(msg), statedb, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
if _, err = st.TransitionDb(); err != nil { tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil {
t.Fatalf("failed to execute transaction: %v", err) t.Fatalf("failed to execute transaction: %v", err)
} }
tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
// Retrieve the trace result and compare against the expected // Retrieve the trace result and compare against the expected
res, err := tracer.GetResult() res, err := tracer.GetResult()
if err != nil { if err != nil {

View file

@ -56,6 +56,16 @@
"value": "0x0", "value": "0x0",
"gas": "0x1f97e", "gas": "0x1f97e",
"gasUsed": "0x72de", "gasUsed": "0x72de",
"input": "0x2e1a7d4d00000000000000000000000000000000000000000000000014d1120d7b160000" "input": "0x2e1a7d4d00000000000000000000000000000000000000000000000014d1120d7b160000",
"calls": [{
"from":"0x6c06b16512b332e6cd8293a2974872674716ce18",
"gas":"0x8fc",
"gasUsed":"0x0",
"to":"0x66fdfd05e46126a07465ad24e40cc0597bc1ef31",
"input":"0x",
"error":"insufficient balance for transfer",
"value":"0x14d1120d7b160000",
"type":"CALL"
}]
} }
} }

View file

@ -63,12 +63,27 @@
"address": "0x5f8a7e007172ba80afbff1b15f800eb0b260f224" "address": "0x5f8a7e007172ba80afbff1b15f800eb0b260f224"
}, },
"traceAddress": [], "traceAddress": [],
"subtraces": 0, "subtraces": 1,
"transactionPosition": 74, "transactionPosition": 74,
"transactionHash": "0x5ef60b27ac971c22a7d484e546e50093ca62300c8986d165154e47773764b6a4", "transactionHash": "0x5ef60b27ac971c22a7d484e546e50093ca62300c8986d165154e47773764b6a4",
"blockNumber": 1555279, "blockNumber": 1555279,
"blockHash": "0xd6c98d1b87dfa92a210d99bad2873adaf0c9e51fe43addc63fd9cca03a5c6f46", "blockHash": "0xd6c98d1b87dfa92a210d99bad2873adaf0c9e51fe43addc63fd9cca03a5c6f46",
"time": "209.346µs" "time": "209.346µs"
},
{
"action": {
"balance": "0x0",
"callType": "callcode",
"from": "0x5f8a7e007172ba80afbff1b15f800eb0b260f224",
"gas": "0xaf64",
"to": "0x0000000000000000000000000000000000000004",
"value": "0x13"
},
"error": "insufficient balance for transfer",
"result": {},
"subtraces": 0,
"traceAddress": [0],
"type": "call"
} }
] ]
} }

View file

@ -64,9 +64,23 @@
"gasUsed": "0x72de", "gasUsed": "0x72de",
"output": "0x" "output": "0x"
}, },
"subtraces": 0, "subtraces": 1,
"traceAddress": [], "traceAddress": [],
"type": "call" "type": "call"
},
{
"action": {
"callType": "call",
"from": "0x6c06b16512b332e6cd8293a2974872674716ce18",
"gas": "0x8fc",
"to": "0x66fdfd05e46126a07465ad24e40cc0597bc1ef31",
"value": "0x14d1120d7b160000"
},
"error": "insufficient balance for transfer",
"result": {},
"subtraces": 0,
"traceAddress": [0],
"type": "call"
} }
] ]
} }

View file

@ -70,12 +70,25 @@
"output": "0x" "output": "0x"
}, },
"traceAddress": [], "traceAddress": [],
"subtraces": 0, "subtraces": 1,
"transactionPosition": 26, "transactionPosition": 26,
"transactionHash": "0xcb1090fa85d2a3da8326b75333e92b3dca89963c895d9c981bfdaa64643135e4", "transactionHash": "0xcb1090fa85d2a3da8326b75333e92b3dca89963c895d9c981bfdaa64643135e4",
"blockNumber": 839247, "blockNumber": 839247,
"blockHash": "0xce7ff7d84ca97f0f89d6065e2c12409a795c9f607cdb14aef0713cad5d7e311c", "blockHash": "0xce7ff7d84ca97f0f89d6065e2c12409a795c9f607cdb14aef0713cad5d7e311c",
"time": "182.267µs" "time": "182.267µs"
},
{
"action": {
"from": "0x76554b33410b6d90b7dc889bfed0451ad195f27e",
"gas": "0x25a18",
"init": "0x0000000000000000000000000000000000000000000000000000000000000000",
"value": "0xa"
},
"error": "insufficient balance for transfer",
"result": {},
"subtraces": 0,
"traceAddress": [0],
"type": "create"
} }
] ]
} }

View file

@ -63,13 +63,26 @@
"address": "0x1d99a1a3efa9181f540f9e24fa6e4e08eb7844ca" "address": "0x1d99a1a3efa9181f540f9e24fa6e4e08eb7844ca"
}, },
"traceAddress": [], "traceAddress": [],
"subtraces": 1, "subtraces": 2,
"transactionPosition": 14, "transactionPosition": 14,
"transactionHash": "0xdd76f02407e2f8329303ba688e111cae4f7008ad0d14d6e42c5698424ea36d79", "transactionHash": "0xdd76f02407e2f8329303ba688e111cae4f7008ad0d14d6e42c5698424ea36d79",
"blockNumber": 1555146, "blockNumber": 1555146,
"blockHash": "0xafb4f1dd27b9054c805acb81a88ed04384788cb31d84164c21874935c81e5c7e", "blockHash": "0xafb4f1dd27b9054c805acb81a88ed04384788cb31d84164c21874935c81e5c7e",
"time": "187.145µs" "time": "187.145µs"
}, },
{
"action": {
"from": "0x1d99a1a3efa9181f540f9e24fa6e4e08eb7844ca",
"gas": "0x50ac",
"init": "0x5a",
"value": "0x1"
},
"error": "insufficient balance for transfer",
"result": {},
"subtraces": 0,
"traceAddress": [0],
"type": "create"
},
{ {
"type": "suicide", "type": "suicide",
"action": { "action": {
@ -79,7 +92,7 @@
}, },
"result": null, "result": null,
"traceAddress": [ "traceAddress": [
0 1
], ],
"subtraces": 0, "subtraces": 0,
"transactionPosition": 14, "transactionPosition": 14,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,63 @@
{
"genesis": {
"baseFeePerGas": "7",
"blobGasUsed": "0",
"difficulty": "0",
"excessBlobGas": "36306944",
"extraData": "0xd983010e00846765746888676f312e32312e308664617277696e",
"gasLimit": "15639172",
"hash": "0xc682259fda061bb9ce8ccb491d5b2d436cb73daf04e1025dd116d045ce4ad28c",
"miner": "0x0000000000000000000000000000000000000000",
"mixHash": "0xae1a5ba939a4c9ac38aabeff361169fb55a6fc2c9511457e0be6eff9514faec0",
"nonce": "0x0000000000000000",
"number": "315",
"parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"stateRoot": "0x577f42ab21ccfd946511c57869ace0bdf7c217c36f02b7cd3459df0ed1cffc1a",
"timestamp": "1709626771",
"totalDifficulty": "1",
"withdrawals": [],
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"alloc": {
"0x0000000000000000000000000000000000000000": {
"balance": "0x272e0528"
},
"0x0c2c51a0990aee1d73c1228de158688341557508": {
"balance": "0xde0b6b3a7640000"
}
},
"config": {
"chainId": 1337,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"muirGlacierBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"arrowGlacierBlock": 0,
"grayGlacierBlock": 0,
"shanghaiTime": 0,
"cancunTime": 0,
"terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true
}
},
"context": {
"number": "316",
"difficulty": "0",
"timestamp": "1709626785",
"gasLimit": "15654443",
"miner": "0x0000000000000000000000000000000000000000"
},
"input": "0x03f8b1820539806485174876e800825208940c2c51a0990aee1d73c1228de1586883415575088080c083020000f842a00100c9fbdf97f747e85847b4f3fff408f89c26842f77c882858bf2c89923849aa00138e3896f3c27f2389147507f8bcec52028b0efca6ee842ed83c9158873943880a0dbac3f97a532c9b00e6239b29036245a5bfbb96940b9d848634661abee98b945a03eec8525f261c2e79798f7b45a5d6ccaefa24576d53ba5023e919b86841c0675",
"result": {
"0x0000000000000000000000000000000000000000": { "balance": "0x272e0528" },
"0x0c2c51a0990aee1d73c1228de158688341557508": {
"balance": "0xde0b6b3a7640000"
}
}
}

View file

@ -0,0 +1,62 @@
{
"genesis": {
"baseFeePerGas": "875000000",
"difficulty": "0",
"extraData": "0xd983010d05846765746888676f312e32312e318664617277696e",
"gasLimit": "11511229",
"hash": "0xd462585c6c5a3b3bf14850ebcde71b6615b9aaf6541403f9a0457212dd0502e0",
"miner": "0x0000000000000000000000000000000000000000",
"mixHash": "0xfa51e868d6a7c0728f18800e4cc8d4cc1c87430cc9975e947eb6c9c03599b4e2",
"nonce": "0x0000000000000000",
"number": "1",
"stateRoot": "0xd2ebe0a7f3572ffe3e5b4c78147376d3fca767f236e4dd23f9151acfec7cb0d1",
"timestamp": "1699617692",
"totalDifficulty": "0",
"withdrawals": [],
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"alloc": {
"0x0000000000000000000000000000000000000000": {
"balance": "0x5208"
},
"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": {
"balance": "0x8ac7230489e80000"
}
},
"config": {
"chainId": 1337,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"muirGlacierBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"arrowGlacierBlock": 0,
"grayGlacierBlock": 0,
"shanghaiTime": 0,
"terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true,
"isDev": true
}
},
"context": {
"number": "2",
"difficulty": "0",
"timestamp": "1699617847",
"gasLimit": "11522469",
"miner": "0x0000000000000000000000000000000000000000"
},
"input": "0x02f902b48205398084b2d05e0085011b1f3f8083031ca88080b90258608060405234801561001057600080fd5b5060405161001d906100e3565b604051809103906000f080158015610039573d6000803e3d6000fd5b50600080546001600160a01b0319166001600160a01b039290921691821781556040517fc66247bafd1305823857fb4c3e651e684d918df8554ef560bbbcb025fdd017039190a26000546040516360fe47b160e01b8152600560048201526001600160a01b03909116906360fe47b190602401600060405180830381600087803b1580156100c657600080fd5b505af11580156100da573d6000803e3d6000fd5b505050506100ef565b60ca8061018e83390190565b6091806100fd6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806380de699314602d575b600080fd5b600054603f906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f3fea2646970667358221220dab781465e7f4cf20304cc388130a763508e20edd25b4bc8ea8f57743a0de8da64736f6c634300081700336080604052348015600f57600080fd5b5060ac8061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806360fe47b11460375780636d4ce63c146049575b600080fd5b60476042366004605e565b600055565b005b60005460405190815260200160405180910390f35b600060208284031215606f57600080fd5b503591905056fea264697066735822122049e09da6320793487d58eaa7b97f802618a062cbc35f08ca1ce92c17349141f864736f6c63430008170033c080a01d4fce93ad08bf413052645721f20e6136830cf5a2759fa57e76a134e90899a7a0399a72832d52118991dc04c4f9e1c0fec3d5e441ad7d4b055f0cf03130d8f815",
"result": {
"0x0000000000000000000000000000000000000000": {
"balance": "0x5208"
},
"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": {
"balance": "0x8ac7230489e80000"
}
}
}

View file

@ -0,0 +1,64 @@
{
"genesis": {
"baseFeePerGas": "7",
"difficulty": "2",
"extraData": "0xd983010d0e846765746888676f312e32312e318664617277696e0000000000001713699f05f79a59abec177c7a87b90ceda79b72ff5edc9197dd7627a447cde45b079bbc3765a236cdf680e2d4d2247135d0e6bb6fd92b50638b92504ddb274f00",
"gasLimit": "30000000",
"hash": "0x6ad5258175c66f4e883d238a92a08428d8ebcbeac631ab7b972634cc05effab3",
"miner": "0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0x0000000000000000",
"number": "39137",
"stateRoot": "0x715f00df764dbadd4863247a215ac44b5420beafde3ec458b15db7aafa89be0c",
"timestamp": "1709022192",
"totalDifficulty": "78275",
"alloc": {
"0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0": {
"balance": "0x10f06447a8d44dba190",
"nonce": "2"
},
"0x82211934c340b29561381392348d48413e15adc8": {
"balance": "0x6abd7a808913ed2",
"nonce": "64"
}
},
"config": {
"chainId": 12345,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"muirGlacierBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"arrowGlacierBlock": 0,
"grayGlacierBlock": 0,
"clique": {
"period": 5,
"epoch": 30000
}
}
},
"context": {
"number": "39138",
"difficulty": "2",
"timestamp": "1709022197",
"gasLimit": "30000000",
"miner": "0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0"
},
"input": "0x02f902af823039408459682f008459682f088302b3538080b90254608060405234801561001057600080fd5b50610234806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806309ce9ccb1461003b5780633fb5c1cb14610059575b600080fd5b610043610075565b60405161005091906100e2565b60405180910390f35b610073600480360381019061006e919061012e565b61007b565b005b60005481565b80600081905550600a8111156100c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906101de565b60405180910390fd5b50565b6000819050919050565b6100dc816100c9565b82525050565b60006020820190506100f760008301846100d3565b92915050565b600080fd5b61010b816100c9565b811461011657600080fd5b50565b60008135905061012881610102565b92915050565b600060208284031215610144576101436100fd565b5b600061015284828501610119565b91505092915050565b600082825260208201905092915050565b7f4e756d6265722069732067726561746572207468616e2031302c207472616e7360008201527f616374696f6e2072657665727465642e00000000000000000000000000000000602082015250565b60006101c860308361015b565b91506101d38261016c565b604082019050919050565b600060208201905081810360008301526101f7816101bb565b905091905056fea264697066735822122069018995fecf03bda91a88b6eafe41641709dee8b4a706fe301c8a569fe8c1b364736f6c63430008130033c001a0a8cf4729b7e4664687abb3e2559853d7d489eb441519be2a17493061fb4c3a03a04b5a904ba8a6e59c6c40049c4d14a73233aeb8a45b38403199f304630dc0d453",
"result": {
"0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0": {
"balance": "0x10f06447a8d44dba190",
"nonce": 2
},
"0x82211934c340b29561381392348d48413e15adc8": {
"balance": "0x6abd7a808913ed2",
"nonce": 64
}
}
}

View file

@ -83,7 +83,7 @@
}, },
"post": { "post": {
"0x808b4da0be6c9512e948521452227efc619bea52": { "0x808b4da0be6c9512e948521452227efc619bea52": {
"balance": "0x2cd72a36dd031f089", "balance": "0x2cd987071ba2346b6",
"nonce": 1223933 "nonce": 1223933
}, },
"0x8f03f1a3f10c05e7cccf75c1fd10168e06659be7": { "0x8f03f1a3f10c05e7cccf75c1fd10168e06659be7": {

View file

@ -0,0 +1,82 @@
{
"genesis": {
"baseFeePerGas": "7",
"difficulty": "2",
"extraData": "0xd983010d0e846765746888676f312e32312e318664617277696e0000000000001713699f05f79a59abec177c7a87b90ceda79b72ff5edc9197dd7627a447cde45b079bbc3765a236cdf680e2d4d2247135d0e6bb6fd92b50638b92504ddb274f00",
"gasLimit": "30000000",
"hash": "0x6ad5258175c66f4e883d238a92a08428d8ebcbeac631ab7b972634cc05effab3",
"miner": "0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0x0000000000000000",
"number": "39137",
"stateRoot": "0x715f00df764dbadd4863247a215ac44b5420beafde3ec458b15db7aafa89be0c",
"timestamp": "1709022192",
"totalDifficulty": "78275",
"alloc": {
"0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0": {
"balance": "0x10f06447a8d44dba190",
"nonce": "2"
},
"0x82211934c340b29561381392348d48413e15adc8": {
"balance": "0x6abd7a808913ed2",
"nonce": "64"
}
},
"config": {
"chainId": 12345,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"muirGlacierBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"arrowGlacierBlock": 0,
"grayGlacierBlock": 0,
"clique": {
"period": 5,
"epoch": 30000
}
}
},
"context": {
"number": "39138",
"difficulty": "2",
"timestamp": "1709022197",
"gasLimit": "30000000",
"miner": "0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0"
},
"input": "0x02f902af823039408459682f008459682f088302b3538080b90254608060405234801561001057600080fd5b50610234806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806309ce9ccb1461003b5780633fb5c1cb14610059575b600080fd5b610043610075565b60405161005091906100e2565b60405180910390f35b610073600480360381019061006e919061012e565b61007b565b005b60005481565b80600081905550600a8111156100c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906101de565b60405180910390fd5b50565b6000819050919050565b6100dc816100c9565b82525050565b60006020820190506100f760008301846100d3565b92915050565b600080fd5b61010b816100c9565b811461011657600080fd5b50565b60008135905061012881610102565b92915050565b600060208284031215610144576101436100fd565b5b600061015284828501610119565b91505092915050565b600082825260208201905092915050565b7f4e756d6265722069732067726561746572207468616e2031302c207472616e7360008201527f616374696f6e2072657665727465642e00000000000000000000000000000000602082015250565b60006101c860308361015b565b91506101d38261016c565b604082019050919050565b600060208201905081810360008301526101f7816101bb565b905091905056fea264697066735822122069018995fecf03bda91a88b6eafe41641709dee8b4a706fe301c8a569fe8c1b364736f6c63430008130033c001a0a8cf4729b7e4664687abb3e2559853d7d489eb441519be2a17493061fb4c3a03a04b5a904ba8a6e59c6c40049c4d14a73233aeb8a45b38403199f304630dc0d453",
"tracerConfig": {
"diffMode": true
},
"result": {
"post": {
"0x1bda2f8e4735507930bd6cfe873bf0bf0f4ab1de": {
"code": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806309ce9ccb1461003b5780633fb5c1cb14610059575b600080fd5b610043610075565b60405161005091906100e2565b60405180910390f35b610073600480360381019061006e919061012e565b61007b565b005b60005481565b80600081905550600a8111156100c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906101de565b60405180910390fd5b50565b6000819050919050565b6100dc816100c9565b82525050565b60006020820190506100f760008301846100d3565b92915050565b600080fd5b61010b816100c9565b811461011657600080fd5b50565b60008135905061012881610102565b92915050565b600060208284031215610144576101436100fd565b5b600061015284828501610119565b91505092915050565b600082825260208201905092915050565b7f4e756d6265722069732067726561746572207468616e2031302c207472616e7360008201527f616374696f6e2072657665727465642e00000000000000000000000000000000602082015250565b60006101c860308361015b565b91506101d38261016c565b604082019050919050565b600060208201905081810360008301526101f7816101bb565b905091905056fea264697066735822122069018995fecf03bda91a88b6eafe41641709dee8b4a706fe301c8a569fe8c1b364736f6c63430008130033",
"nonce": 1
},
"0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0": {
"balance": "0x10f0645688331eb5690"
},
"0x82211934c340b29561381392348d48413e15adc8": {
"balance": "0x6aae9b21b6ee855",
"nonce": 65
}
},
"pre": {
"0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0": {
"balance": "0x10f06447a8d44dba190",
"nonce": 2
},
"0x82211934c340b29561381392348d48413e15adc8": {
"balance": "0x6abd7a808913ed2",
"nonce": 64
}
}
}
}

View file

@ -9,59 +9,6 @@ import (
_ "github.com/ethereum/go-ethereum/eth/tracers/native" _ "github.com/ethereum/go-ethereum/eth/tracers/native"
) )
// To generate a new callTracer test, copy paste the makeTest method below into
// a Geth console and call it with a transaction hash you which to export.
/*
// makeTest generates a callTracer test by running a prestate reassembled and a
// call trace run, assembling all the gathered information into a test case.
var makeTest = function(tx, rewind) {
// Generate the genesis block from the block, transaction and prestate data
var block = eth.getBlock(eth.getTransaction(tx).blockHash);
var genesis = eth.getBlock(block.parentHash);
delete genesis.gasUsed;
delete genesis.logsBloom;
delete genesis.parentHash;
delete genesis.receiptsRoot;
delete genesis.sha3Uncles;
delete genesis.size;
delete genesis.transactions;
delete genesis.transactionsRoot;
delete genesis.uncles;
genesis.gasLimit = genesis.gasLimit.toString();
genesis.number = genesis.number.toString();
genesis.timestamp = genesis.timestamp.toString();
genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer", rewind: rewind});
for (var key in genesis.alloc) {
var nonce = genesis.alloc[key].nonce;
if (nonce) {
genesis.alloc[key].nonce = nonce.toString();
}
}
genesis.config = admin.nodeInfo.protocols.eth.config;
// Generate the call trace and produce the test input
var result = debug.traceTransaction(tx, {tracer: "callTracer", rewind: rewind});
delete result.time;
console.log(JSON.stringify({
genesis: genesis,
context: {
number: block.number.toString(),
difficulty: block.difficulty,
timestamp: block.timestamp.toString(),
gasLimit: block.gasLimit.toString(),
miner: block.miner,
},
input: eth.getRawTransaction(tx),
result: result,
}, null, 2));
}
*/
// camel converts a snake cased input string into a camel cased output. // camel converts a snake cased input string into a camel cased output.
func camel(str string) string { func camel(str string) string {
pieces := strings.Split(str, "_") pieces := strings.Split(str, "_")

View file

@ -23,12 +23,15 @@ import (
"math/big" "math/big"
"github.com/dop251/goja" "github.com/dop251/goja"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/tracers/directory"
"github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers"
jsassets "github.com/ethereum/go-ethereum/eth/tracers/js/internal/tracers" jsassets "github.com/ethereum/go-ethereum/eth/tracers/js/internal/tracers"
) )
@ -41,16 +44,16 @@ func init() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error) type ctorFn = func(*directory.Context, json.RawMessage) (*directory.Tracer, error)
lookup := func(code string) ctorFn { lookup := func(code string) ctorFn {
return func(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { return func(ctx *directory.Context, cfg json.RawMessage) (*directory.Tracer, error) {
return newJsTracer(code, ctx, cfg) return newJsTracer(code, ctx, cfg)
} }
} }
for name, code := range assetTracers { for name, code := range assetTracers {
tracers.DefaultDirectory.Register(name, lookup(code), true) directory.DefaultDirectory.Register(name, lookup(code), true)
} }
tracers.DefaultDirectory.RegisterJSEval(newJsTracer) directory.DefaultDirectory.RegisterJSEval(newJsTracer)
} }
// bigIntProgram is compiled once and the exported function mostly invoked to convert // bigIntProgram is compiled once and the exported function mostly invoked to convert
@ -96,7 +99,7 @@ func fromBuf(vm *goja.Runtime, bufType goja.Value, buf goja.Value, allowString b
// JS functions on the relevant EVM hooks. It uses Goja as its JS engine. // JS functions on the relevant EVM hooks. It uses Goja as its JS engine.
type jsTracer struct { type jsTracer struct {
vm *goja.Runtime vm *goja.Runtime
env *vm.EVM env *tracing.VMContext
toBig toBigFn // Converts a hex string into a JS bigint toBig toBigFn // Converts a hex string into a JS bigint
toBuf toBufFn // Converts a []byte into a JS buffer toBuf toBufFn // Converts a []byte into a JS buffer
fromBuf fromBufFn // Converts an array, hex string or Uint8Array to a []byte fromBuf fromBufFn // Converts an array, hex string or Uint8Array to a []byte
@ -104,7 +107,6 @@ type jsTracer struct {
activePrecompiles []common.Address // List of active precompiles at current block activePrecompiles []common.Address // List of active precompiles at current block
traceStep bool // True if tracer object exposes a `step()` method traceStep bool // True if tracer object exposes a `step()` method
traceFrame bool // True if tracer object exposes the `enter()` and `exit()` methods traceFrame bool // True if tracer object exposes the `enter()` and `exit()` methods
gasLimit uint64 // Amount of gas bought for the whole tx
err error // Any error that should stop tracing err error // Any error that should stop tracing
obj *goja.Object // Trace object obj *goja.Object // Trace object
@ -134,7 +136,7 @@ type jsTracer struct {
// The methods `result` and `fault` are required to be present. // The methods `result` and `fault` are required to be present.
// The methods `step`, `enter`, and `exit` are optional, but note that // The methods `step`, `enter`, and `exit` are optional, but note that
// `enter` and `exit` always go together. // `enter` and `exit` always go together.
func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { func newJsTracer(code string, ctx *directory.Context, cfg json.RawMessage) (*directory.Tracer, error) {
vm := goja.New() vm := goja.New()
// By default field names are exported to JS as is, i.e. capitalized. // By default field names are exported to JS as is, i.e. capitalized.
vm.SetFieldNameMapper(goja.UncapFieldNameMapper()) vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
@ -147,7 +149,7 @@ func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracer
t.setBuiltinFunctions() t.setBuiltinFunctions()
if ctx == nil { if ctx == nil {
ctx = new(tracers.Context) ctx = new(directory.Context)
} }
if ctx.BlockHash != (common.Hash{}) { if ctx.BlockHash != (common.Hash{}) {
blockHash, err := t.toBuf(vm, ctx.BlockHash.Bytes()) blockHash, err := t.toBuf(vm, ctx.BlockHash.Bytes())
@ -217,30 +219,62 @@ func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracer
t.frameValue = t.frame.setupObject() t.frameValue = t.frame.setupObject()
t.frameResultValue = t.frameResult.setupObject() t.frameResultValue = t.frameResult.setupObject()
t.logValue = t.log.setupObject() t.logValue = t.log.setupObject()
return t, nil
return &directory.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil
} }
// CaptureTxStart implements the Tracer interface and is invoked at the beginning of // OnTxStart implements the Tracer interface and is invoked at the beginning of
// transaction processing. // transaction processing.
func (t *jsTracer) CaptureTxStart(gasLimit uint64) { func (t *jsTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
t.gasLimit = gasLimit
}
// CaptureTxEnd implements the Tracer interface and is invoked at the end of
// transaction processing.
func (t *jsTracer) CaptureTxEnd(restGas uint64) {
t.ctx["gasUsed"] = t.vm.ToValue(t.gasLimit - restGas)
}
// CaptureStart implements the Tracer interface to initialize the tracing operation.
func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
cancel := func(err error) {
t.err = err
t.env.Cancel()
}
t.env = env t.env = env
// Need statedb access for db object
db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf} db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf}
t.dbValue = db.setupObject() t.dbValue = db.setupObject()
// Update list of precompiles based on current block
rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time)
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())
if err != nil {
t.err = err
return
}
t.ctx["gasPrice"] = gasPriceBig
}
// OnTxEnd implements the Tracer interface and is invoked at the end of
// transaction processing.
func (t *jsTracer) OnTxEnd(receipt *types.Receipt, err error) {
if t.err != nil {
return
}
if err != nil {
// Don't override vm error
if _, ok := t.ctx["error"]; !ok {
t.ctx["error"] = t.vm.ToValue(err.Error())
}
return
}
t.ctx["gasUsed"] = t.vm.ToValue(receipt.GasUsed)
}
// onStart implements the Tracer interface to initialize the tracing operation.
func (t *jsTracer) onStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
if t.err != nil {
return
}
if create { if create {
t.ctx["type"] = t.vm.ToValue("CREATE") t.ctx["type"] = t.vm.ToValue("CREATE")
} else { } else {
@ -248,43 +282,32 @@ func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr
} }
fromVal, err := t.toBuf(t.vm, from.Bytes()) fromVal, err := t.toBuf(t.vm, from.Bytes())
if err != nil { if err != nil {
cancel(err) t.err = err
return return
} }
t.ctx["from"] = fromVal t.ctx["from"] = fromVal
toVal, err := t.toBuf(t.vm, to.Bytes()) toVal, err := t.toBuf(t.vm, to.Bytes())
if err != nil { if err != nil {
cancel(err) t.err = err
return return
} }
t.ctx["to"] = toVal t.ctx["to"] = toVal
inputVal, err := t.toBuf(t.vm, input) inputVal, err := t.toBuf(t.vm, input)
if err != nil { if err != nil {
cancel(err) t.err = err
return return
} }
t.ctx["input"] = inputVal t.ctx["input"] = inputVal
t.ctx["gas"] = t.vm.ToValue(t.gasLimit)
gasPriceBig, err := t.toBig(t.vm, env.TxContext.GasPrice.String())
if err != nil {
cancel(err)
return
}
t.ctx["gasPrice"] = gasPriceBig
valueBig, err := t.toBig(t.vm, value.String()) valueBig, err := t.toBig(t.vm, value.String())
if err != nil { if err != nil {
cancel(err) t.err = err
return return
} }
t.ctx["value"] = valueBig t.ctx["value"] = valueBig
t.ctx["block"] = t.vm.ToValue(env.Context.BlockNumber.Uint64())
// Update list of precompiles based on current block
rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time)
t.activePrecompiles = vm.ActivePrecompiles(rules)
} }
// CaptureState implements the Tracer interface to trace a single step of VM execution. // OnOpcode implements the Tracer interface to trace a single step of VM execution.
func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (t *jsTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
if !t.traceStep { if !t.traceStep {
return return
} }
@ -293,10 +316,10 @@ func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope
} }
log := t.log log := t.log
log.op.op = op log.op.op = vm.OpCode(op)
log.memory.memory = scope.Memory log.memory.memory = scope.MemoryData()
log.stack.stack = scope.Stack log.stack.stack = scope.StackData()
log.contract.contract = scope.Contract log.contract.scope = scope
log.pc = pc log.pc = pc
log.gas = gas log.gas = gas
log.cost = cost log.cost = cost
@ -308,20 +331,23 @@ func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope
} }
} }
// CaptureFault implements the Tracer interface to trace an execution fault // OnFault implements the Tracer interface to trace an execution fault
func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { func (t *jsTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) {
if t.err != nil { if t.err != nil {
return return
} }
// Other log fields have been already set as part of the last CaptureState. // Other log fields have been already set as part of the last OnOpcode.
t.log.err = err t.log.err = err
if _, err := t.fault(t.obj, t.logValue, t.dbValue); err != nil { if _, err := t.fault(t.obj, t.logValue, t.dbValue); err != nil {
t.onError("fault", err) t.onError("fault", err)
} }
} }
// CaptureEnd is called after the call finishes to finalize the tracing. // onEnd is called after the call finishes to finalize the tracing.
func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { func (t *jsTracer) onEnd(output []byte, gasUsed uint64, err error, reverted bool) {
if t.err != nil {
return
}
if err != nil { if err != nil {
t.ctx["error"] = t.vm.ToValue(err.Error()) t.ctx["error"] = t.vm.ToValue(err.Error())
} }
@ -333,16 +359,20 @@ func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
t.ctx["output"] = outputVal t.ctx["output"] = outputVal
} }
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *jsTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if !t.traceFrame {
return
}
if t.err != nil { if t.err != nil {
return return
} }
if depth == 0 {
t.onStart(from, to, vm.OpCode(typ) == vm.CREATE, input, gas, value)
return
}
if !t.traceFrame {
return
}
t.frame.typ = typ.String() t.frame.typ = vm.OpCode(typ).String()
t.frame.from = from t.frame.from = from
t.frame.to = to t.frame.to = to
t.frame.input = common.CopyBytes(input) t.frame.input = common.CopyBytes(input)
@ -357,9 +387,16 @@ func (t *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Ad
} }
} }
// CaptureExit is called when EVM exits a scope, even if the scope didn't // OnExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // execute any code.
func (t *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) { func (t *jsTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
if t.err != nil {
return
}
if depth == 0 {
t.onEnd(output, gasUsed, err, reverted)
return
}
if !t.traceFrame { if !t.traceFrame {
return return
} }
@ -375,6 +412,9 @@ func (t *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error // GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
func (t *jsTracer) GetResult() (json.RawMessage, error) { func (t *jsTracer) GetResult() (json.RawMessage, error) {
if t.err != nil {
return nil, t.err
}
ctx := t.vm.ToValue(t.ctx) ctx := t.vm.ToValue(t.ctx)
res, err := t.result(t.obj, ctx, t.dbValue) res, err := t.result(t.obj, ctx, t.dbValue)
if err != nil { if err != nil {
@ -384,7 +424,7 @@ func (t *jsTracer) GetResult() (json.RawMessage, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return json.RawMessage(encoded), t.err return encoded, t.err
} }
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
@ -397,9 +437,6 @@ func (t *jsTracer) Stop(err error) {
// execution. // execution.
func (t *jsTracer) onError(context string, err error) { func (t *jsTracer) onError(context string, err error) {
t.err = wrapError(context, err) t.err = wrapError(context, err)
// `env` is set on CaptureStart which comes before any JS execution.
// So it should be non-nil.
t.env.Cancel()
} }
func wrapError(context string, err error) error { func wrapError(context string, err error) error {
@ -578,7 +615,7 @@ func (o *opObj) setupObject() *goja.Object {
} }
type memoryObj struct { type memoryObj struct {
memory *vm.Memory memory []byte
vm *goja.Runtime vm *goja.Runtime
toBig toBigFn toBig toBigFn
toBuf toBufFn toBuf toBufFn
@ -606,7 +643,7 @@ func (mo *memoryObj) slice(begin, end int64) ([]byte, error) {
if end < begin || begin < 0 { if end < begin || begin < 0 {
return nil, fmt.Errorf("tracer accessed out of bound memory: offset %d, end %d", begin, end) return nil, fmt.Errorf("tracer accessed out of bound memory: offset %d, end %d", begin, end)
} }
slice, err := tracers.GetMemoryCopyPadded(mo.memory, begin, end-begin) slice, err := directory.GetMemoryCopyPadded(mo.memory, begin, end-begin)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -629,14 +666,14 @@ func (mo *memoryObj) GetUint(addr int64) goja.Value {
// getUint returns the 32 bytes at the specified address interpreted as a uint. // getUint returns the 32 bytes at the specified address interpreted as a uint.
func (mo *memoryObj) getUint(addr int64) (*big.Int, error) { func (mo *memoryObj) getUint(addr int64) (*big.Int, error) {
if mo.memory.Len() < int(addr)+32 || addr < 0 { if len(mo.memory) < int(addr)+32 || addr < 0 {
return nil, fmt.Errorf("tracer accessed out of bound memory: available %d, offset %d, size %d", mo.memory.Len(), addr, 32) return nil, fmt.Errorf("tracer accessed out of bound memory: available %d, offset %d, size %d", len(mo.memory), addr, 32)
} }
return new(big.Int).SetBytes(mo.memory.GetPtr(addr, 32)), nil return new(big.Int).SetBytes(directory.MemoryPtr(mo.memory, addr, 32)), nil
} }
func (mo *memoryObj) Length() int { func (mo *memoryObj) Length() int {
return mo.memory.Len() return len(mo.memory)
} }
func (m *memoryObj) setupObject() *goja.Object { func (m *memoryObj) setupObject() *goja.Object {
@ -648,7 +685,7 @@ func (m *memoryObj) setupObject() *goja.Object {
} }
type stackObj struct { type stackObj struct {
stack *vm.Stack stack []uint256.Int
vm *goja.Runtime vm *goja.Runtime
toBig toBigFn toBig toBigFn
} }
@ -669,14 +706,14 @@ func (s *stackObj) Peek(idx int) goja.Value {
// peek returns the nth-from-the-top element of the stack. // peek returns the nth-from-the-top element of the stack.
func (s *stackObj) peek(idx int) (*big.Int, error) { func (s *stackObj) peek(idx int) (*big.Int, error) {
if len(s.stack.Data()) <= idx || idx < 0 { if len(s.stack) <= idx || idx < 0 {
return nil, fmt.Errorf("tracer accessed out of bound stack: size %d, index %d", len(s.stack.Data()), idx) return nil, fmt.Errorf("tracer accessed out of bound stack: size %d, index %d", len(s.stack), idx)
} }
return s.stack.Back(idx).ToBig(), nil return directory.StackBack(s.stack, idx).ToBig(), nil
} }
func (s *stackObj) Length() int { func (s *stackObj) Length() int {
return len(s.stack.Data()) return len(s.stack)
} }
func (s *stackObj) setupObject() *goja.Object { func (s *stackObj) setupObject() *goja.Object {
@ -687,7 +724,7 @@ func (s *stackObj) setupObject() *goja.Object {
} }
type dbObj struct { type dbObj struct {
db vm.StateDB db tracing.StateDB
vm *goja.Runtime vm *goja.Runtime
toBig toBigFn toBig toBigFn
toBuf toBufFn toBuf toBufFn
@ -779,14 +816,14 @@ func (do *dbObj) setupObject() *goja.Object {
} }
type contractObj struct { type contractObj struct {
contract *vm.Contract scope tracing.OpContext
vm *goja.Runtime vm *goja.Runtime
toBig toBigFn toBig toBigFn
toBuf toBufFn toBuf toBufFn
} }
func (co *contractObj) GetCaller() goja.Value { func (co *contractObj) GetCaller() goja.Value {
caller := co.contract.Caller().Bytes() caller := co.scope.Caller().Bytes()
res, err := co.toBuf(co.vm, caller) res, err := co.toBuf(co.vm, caller)
if err != nil { if err != nil {
co.vm.Interrupt(err) co.vm.Interrupt(err)
@ -796,7 +833,7 @@ func (co *contractObj) GetCaller() goja.Value {
} }
func (co *contractObj) GetAddress() goja.Value { func (co *contractObj) GetAddress() goja.Value {
addr := co.contract.Address().Bytes() addr := co.scope.Address().Bytes()
res, err := co.toBuf(co.vm, addr) res, err := co.toBuf(co.vm, addr)
if err != nil { if err != nil {
co.vm.Interrupt(err) co.vm.Interrupt(err)
@ -806,7 +843,7 @@ func (co *contractObj) GetAddress() goja.Value {
} }
func (co *contractObj) GetValue() goja.Value { func (co *contractObj) GetValue() goja.Value {
value := co.contract.Value() value := co.scope.CallValue()
res, err := co.toBig(co.vm, value.String()) res, err := co.toBig(co.vm, value.String())
if err != nil { if err != nil {
co.vm.Interrupt(err) co.vm.Interrupt(err)
@ -816,7 +853,7 @@ func (co *contractObj) GetValue() goja.Value {
} }
func (co *contractObj) GetInput() goja.Value { func (co *contractObj) GetInput() goja.Value {
input := common.CopyBytes(co.contract.Input) input := common.CopyBytes(co.scope.CallInput())
res, err := co.toBuf(co.vm, input) res, err := co.toBuf(co.vm, input)
if err != nil { if err != nil {
co.vm.Interrupt(err) co.vm.Interrupt(err)

View file

@ -219,7 +219,7 @@
return this.finalize(result); return this.finalize(result);
}, },
// finalize recreates a call object using the final desired field oder for json // finalize recreates a call object using the final desired field order for json
// serialization. This is a nicety feature to pass meaningfully ordered results // serialization. This is a nicety feature to pass meaningfully ordered results
// to users who don't interpret it, just display it. // to users who don't interpret it, just display it.
finalize: function(call) { finalize: function(call) {

View file

@ -26,8 +26,9 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers/directory"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -60,9 +61,9 @@ 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)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
} }
func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte) (json.RawMessage, error) { func runTrace(tracer *directory.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte) (json.RawMessage, error) {
var ( var (
env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Tracer: tracer}) env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Tracer: tracer.Hooks})
gasLimit uint64 = 31000 gasLimit uint64 = 31000
startGas uint64 = 10000 startGas uint64 = 10000
value = big.NewInt(0) value = big.NewInt(0)
@ -73,12 +74,12 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon
contract.Code = contractCode contract.Code = contractCode
} }
tracer.CaptureTxStart(gasLimit) tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit}), contract.Caller())
tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value) tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value)
ret, err := env.Interpreter().Run(contract, []byte{}, false) ret, err := env.Interpreter().Run(contract, []byte{}, false)
tracer.CaptureEnd(ret, startGas-contract.Gas, err) tracer.OnExit(0, ret, startGas-contract.Gas, err, true)
// Rest gas assumes no refund // Rest gas assumes no refund
tracer.CaptureTxEnd(contract.Gas) tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -180,15 +181,16 @@ func TestHaltBetweenSteps(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: tracer})
scope := &vm.ScopeContext{ scope := &vm.ScopeContext{
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0), Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
} }
tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 0, big.NewInt(0)) env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: tracer.Hooks})
tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil) tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{}), common.Address{})
tracer.OnEnter(0, byte(vm.CALL), common.Address{}, common.Address{}, []byte{}, 0, big.NewInt(0))
tracer.OnOpcode(0, 0, 0, 0, scope, nil, 0, nil)
timeout := errors.New("stahp") timeout := errors.New("stahp")
tracer.Stop(timeout) tracer.Stop(timeout)
tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil) tracer.OnOpcode(0, 0, 0, 0, scope, nil, 0, nil)
if _, err := tracer.GetResult(); !strings.Contains(err.Error(), timeout.Error()) { if _, err := tracer.GetResult(); !strings.Contains(err.Error(), timeout.Error()) {
t.Errorf("Expected timeout error, got %v", err) t.Errorf("Expected timeout error, got %v", err)
@ -204,9 +206,10 @@ func TestNoStepExec(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(100)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: tracer}) env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(100)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: tracer.Hooks})
tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 1000, big.NewInt(0)) tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{}), common.Address{})
tracer.CaptureEnd(nil, 0, nil) tracer.OnEnter(0, byte(vm.CALL), common.Address{}, common.Address{}, []byte{}, 1000, big.NewInt(0))
tracer.OnExit(0, nil, 0, nil, false)
ret, err := tracer.GetResult() ret, err := tracer.GetResult()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -261,22 +264,22 @@ func TestIsPrecompile(t *testing.T) {
func TestEnterExit(t *testing.T) { func TestEnterExit(t *testing.T) {
// test that either both or none of enter() and exit() are defined // test that either both or none of enter() and exit() are defined
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(tracers.Context), nil); err == nil { if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(directory.Context), nil); err == nil {
t.Fatal("tracer creation should've failed without exit() definition") t.Fatal("tracer creation should've failed without exit() definition")
} }
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context), nil); err != nil { if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(directory.Context), nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// test that the enter and exit method are correctly invoked and the values passed // test that the enter and exit method are correctly invoked and the values passed
tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context), nil) tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(directory.Context), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
scope := &vm.ScopeContext{ scope := &vm.ScopeContext{
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0), Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
} }
tracer.CaptureEnter(vm.CALL, scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int)) tracer.OnEnter(1, byte(vm.CALL), scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int))
tracer.CaptureExit([]byte{}, 400, nil) tracer.OnExit(1, []byte{}, 400, nil, false)
have, err := tracer.GetResult() have, err := tracer.GetResult()
if err != nil { if err != nil {
@ -290,7 +293,7 @@ func TestEnterExit(t *testing.T) {
func TestSetup(t *testing.T) { func TestSetup(t *testing.T) {
// Test empty config // Test empty config
_, err := newJsTracer(`{setup: function(cfg) { if (cfg !== "{}") { throw("invalid empty config") } }, fault: function() {}, result: function() {}}`, new(tracers.Context), nil) _, err := newJsTracer(`{setup: function(cfg) { if (cfg !== "{}") { throw("invalid empty config") } }, fault: function() {}, result: function() {}}`, new(directory.Context), nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -300,12 +303,12 @@ func TestSetup(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// Test no setup func // Test no setup func
_, err = newJsTracer(`{fault: function() {}, result: function() {}}`, new(tracers.Context), cfg) _, err = newJsTracer(`{fault: function() {}, result: function() {}}`, new(directory.Context), cfg)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Test config value // Test config value
tracer, err := newJsTracer("{config: null, setup: function(cfg) { this.config = JSON.parse(cfg) }, step: function() {}, fault: function() {}, result: function() { return this.config.foo }}", new(tracers.Context), cfg) tracer, err := newJsTracer("{config: null, setup: function(cfg) { this.config = JSON.parse(cfg) }, step: function() {}, fault: function() {}, result: function() { return this.config.foo }}", new(directory.Context), cfg)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

96
eth/tracers/live/noop.go Normal file
View file

@ -0,0 +1,96 @@
package live
import (
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/tracers/directory/live"
"github.com/ethereum/go-ethereum/params"
)
func init() {
live.Directory.Register("noop", newNoopTracer)
}
// noop is a no-op live tracer. It's there to
// catch changes in the tracing interface, as well as
// for testing live tracing performance. Can be removed
// as soon as we have a real live tracer.
type noop struct{}
func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) {
t := &noop{}
return &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
OnGasChange: t.OnGasChange,
OnBlockchainInit: t.OnBlockchainInit,
OnBlockStart: t.OnBlockStart,
OnBlockEnd: t.OnBlockEnd,
OnSkippedBlock: t.OnSkippedBlock,
OnGenesisBlock: t.OnGenesisBlock,
OnBalanceChange: t.OnBalanceChange,
OnNonceChange: t.OnNonceChange,
OnCodeChange: t.OnCodeChange,
OnStorageChange: t.OnStorageChange,
OnLog: t.OnLog,
}, nil
}
func (t *noop) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
}
func (t *noop) OnFault(pc uint64, op byte, gas, cost uint64, _ tracing.OpContext, depth int, err error) {
}
func (t *noop) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}
func (t *noop) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
}
func (t *noop) OnTxStart(vm *tracing.VMContext, tx *types.Transaction, from common.Address) {
}
func (t *noop) OnTxEnd(receipt *types.Receipt, err error) {
}
func (t *noop) OnBlockStart(ev tracing.BlockEvent) {
}
func (t *noop) OnBlockEnd(err error) {
}
func (t *noop) OnSkippedBlock(ev tracing.BlockEvent) {}
func (t *noop) OnBlockchainInit(chainConfig *params.ChainConfig) {
}
func (t *noop) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) {
}
func (t *noop) OnBalanceChange(a common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) {
}
func (t *noop) OnNonceChange(a common.Address, prev, new uint64) {
}
func (t *noop) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
}
func (t *noop) OnStorageChange(a common.Address, k, prev, new common.Hash) {
}
func (t *noop) OnLog(l *types.Log) {
}
func (t *noop) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {
}

View file

@ -17,9 +17,8 @@
package logger package logger
import ( import (
"math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
) )
@ -132,17 +131,20 @@ func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompi
} }
} }
func (a *AccessListTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (a *AccessListTracer) Hooks() *tracing.Hooks {
return &tracing.Hooks{
OnOpcode: a.OnOpcode,
}
} }
// CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist. // OnOpcode captures all opcodes that touch storage or addresses and adds them to the accesslist.
func (a *AccessListTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (a *AccessListTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
stack := scope.Stack stackData := scope.StackData()
stackData := stack.Data()
stackLen := len(stackData) stackLen := len(stackData)
op := vm.OpCode(opcode)
if (op == vm.SLOAD || op == vm.SSTORE) && stackLen >= 1 { if (op == vm.SLOAD || op == vm.SSTORE) && stackLen >= 1 {
slot := common.Hash(stackData[stackLen-1].Bytes32()) slot := common.Hash(stackData[stackLen-1].Bytes32())
a.list.addSlot(scope.Contract.Address(), slot) a.list.addSlot(scope.Address(), slot)
} }
if (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT) && stackLen >= 1 { if (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT) && stackLen >= 1 {
addr := common.Address(stackData[stackLen-1].Bytes20()) addr := common.Address(stackData[stackLen-1].Bytes20())
@ -158,20 +160,6 @@ func (a *AccessListTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint6
} }
} }
func (*AccessListTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
}
func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {}
func (*AccessListTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}
func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {}
func (*AccessListTracer) CaptureTxStart(gasLimit uint64) {}
func (*AccessListTracer) CaptureTxEnd(restGas uint64) {}
// AccessList returns the current accesslist maintained by the tracer. // AccessList returns the current accesslist maintained by the tracer.
func (a *AccessListTracer) AccessList() types.AccessList { func (a *AccessListTracer) AccessList() types.AccessList {
return a.list.accessList() return a.list.accessList()

View file

@ -28,8 +28,10 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/directory"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -108,14 +110,13 @@ func (s *StructLog) ErrorString() string {
// contract their storage. // contract their storage.
type StructLogger struct { type StructLogger struct {
cfg Config cfg Config
env *vm.EVM env *tracing.VMContext
storage map[common.Address]Storage storage map[common.Address]Storage
logs []StructLog logs []StructLog
output []byte output []byte
err error err error
gasLimit uint64 usedGas uint64
usedGas uint64
interrupt atomic.Bool // Atomic flag to signal execution interruption interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
@ -132,6 +133,23 @@ func NewStructLogger(cfg *Config) *StructLogger {
return logger return logger
} }
func (l *StructLogger) Hooks() *tracing.Hooks {
return &tracing.Hooks{
OnTxStart: l.OnTxStart,
OnTxEnd: l.OnTxEnd,
OnExit: l.OnExit,
OnOpcode: l.OnOpcode,
}
}
func (l *StructLogger) Tracer() *directory.Tracer {
return &directory.Tracer{
Hooks: l.Hooks(),
GetResult: l.GetResult,
Stop: l.Stop,
}
}
// Reset clears the data held by the logger. // Reset clears the data held by the logger.
func (l *StructLogger) Reset() { func (l *StructLogger) Reset() {
l.storage = make(map[common.Address]Storage) l.storage = make(map[common.Address]Storage)
@ -140,15 +158,10 @@ func (l *StructLogger) Reset() {
l.err = nil l.err = nil
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. // OnOpcode logs a new structured log message and pushes it out to the environment
func (l *StructLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
l.env = env
}
// CaptureState logs a new structured log message and pushes it out to the environment
// //
// CaptureState also tracks SLOAD/SSTORE ops to track storage change. // OnOpcode also tracks SLOAD/SSTORE ops to track storage change.
func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { 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, set the error and stop
if l.interrupt.Load() { if l.interrupt.Load() {
return return
@ -158,49 +171,47 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
return return
} }
memory := scope.Memory op := vm.OpCode(opcode)
stack := scope.Stack memory := scope.MemoryData()
contract := scope.Contract stack := scope.StackData()
// Copy a snapshot of the current memory state to a new buffer // Copy a snapshot of the current memory state to a new buffer
var mem []byte var mem []byte
if l.cfg.EnableMemory { if l.cfg.EnableMemory {
mem = make([]byte, len(memory.Data())) mem = make([]byte, len(memory))
copy(mem, memory.Data()) copy(mem, memory)
} }
// Copy a snapshot of the current stack state to a new buffer // Copy a snapshot of the current stack state to a new buffer
var stck []uint256.Int var stck []uint256.Int
if !l.cfg.DisableStack { if !l.cfg.DisableStack {
stck = make([]uint256.Int, len(stack.Data())) stck = make([]uint256.Int, len(stack))
for i, item := range stack.Data() { copy(stck, stack)
stck[i] = item
}
} }
stackData := stack.Data() contractAddr := scope.Address()
stackLen := len(stackData) stackLen := len(stack)
// Copy a snapshot of the current storage to a new container // Copy a snapshot of the current storage to a new container
var storage Storage var storage Storage
if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) { if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) {
// initialise new changed values storage container for this contract // initialise new changed values storage container for this contract
// if not present. // if not present.
if l.storage[contract.Address()] == nil { if l.storage[contractAddr] == nil {
l.storage[contract.Address()] = make(Storage) l.storage[contractAddr] = make(Storage)
} }
// capture SLOAD opcodes and record the read entry in the local storage // capture SLOAD opcodes and record the read entry in the local storage
if op == vm.SLOAD && stackLen >= 1 { if op == vm.SLOAD && stackLen >= 1 {
var ( var (
address = common.Hash(stackData[stackLen-1].Bytes32()) address = common.Hash(stack[stackLen-1].Bytes32())
value = l.env.StateDB.GetState(contract.Address(), address) value = l.env.StateDB.GetState(contractAddr, address)
) )
l.storage[contract.Address()][address] = value l.storage[contractAddr][address] = value
storage = l.storage[contract.Address()].Copy() storage = l.storage[contractAddr].Copy()
} else if op == vm.SSTORE && stackLen >= 2 { } else if op == vm.SSTORE && stackLen >= 2 {
// capture SSTORE opcodes and record the written entry in the local storage. // capture SSTORE opcodes and record the written entry in the local storage.
var ( var (
value = common.Hash(stackData[stackLen-2].Bytes32()) value = common.Hash(stack[stackLen-2].Bytes32())
address = common.Hash(stackData[stackLen-1].Bytes32()) address = common.Hash(stack[stackLen-1].Bytes32())
) )
l.storage[contract.Address()][address] = value l.storage[contractAddr][address] = value
storage = l.storage[contract.Address()].Copy() storage = l.storage[contractAddr].Copy()
} }
} }
var rdata []byte var rdata []byte
@ -209,17 +220,15 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
copy(rdata, rData) copy(rdata, rData)
} }
// create a new snapshot of the EVM. // create a new snapshot of the EVM.
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err} log := StructLog{pc, op, gas, cost, mem, len(memory), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err}
l.logs = append(l.logs, log) l.logs = append(l.logs, log)
} }
// CaptureFault implements the EVMLogger interface to trace an execution fault // OnExit is called a call frame finishes processing.
// while running an opcode. func (l *StructLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { if depth != 0 {
} return
}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
l.output = output l.output = output
l.err = err l.err = err
if l.cfg.Debug { if l.cfg.Debug {
@ -230,12 +239,6 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
} }
} }
func (l *StructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}
func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {
}
func (l *StructLogger) GetResult() (json.RawMessage, error) { func (l *StructLogger) GetResult() (json.RawMessage, error) {
// Tracing aborted // Tracing aborted
if l.reason != nil { if l.reason != nil {
@ -262,12 +265,19 @@ func (l *StructLogger) Stop(err error) {
l.interrupt.Store(true) l.interrupt.Store(true)
} }
func (l *StructLogger) CaptureTxStart(gasLimit uint64) { func (l *StructLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
l.gasLimit = gasLimit l.env = env
} }
func (l *StructLogger) CaptureTxEnd(restGas uint64) { func (l *StructLogger) OnTxEnd(receipt *types.Receipt, err error) {
l.usedGas = l.gasLimit - restGas if err != nil {
// Don't override vm error
if l.err == nil {
l.err = err
}
return
}
l.usedGas = receipt.GasUsed
} }
// StructLogs returns the captured log entries. // StructLogs returns the captured log entries.
@ -329,7 +339,7 @@ func WriteLogs(writer io.Writer, logs []*types.Log) {
type mdLogger struct { type mdLogger struct {
out io.Writer out io.Writer
cfg *Config cfg *Config
env *vm.EVM env *tracing.VMContext
} }
// NewMarkdownLogger creates a logger which outputs information in a format adapted // NewMarkdownLogger creates a logger which outputs information in a format adapted
@ -342,8 +352,25 @@ func NewMarkdownLogger(cfg *Config, writer io.Writer) *mdLogger {
return l return l
} }
func (t *mdLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (t *mdLogger) Hooks() *tracing.Hooks {
return &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
}
}
func (t *mdLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
t.env = env t.env = env
}
func (t *mdLogger) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if depth != 0 {
return
}
create := vm.OpCode(typ) == vm.CREATE
if !create { if !create {
fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n", fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n",
from.String(), to.String(), from.String(), to.String(),
@ -360,15 +387,22 @@ func (t *mdLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Addr
`) `)
} }
// CaptureState also tracks SLOAD/SSTORE ops to track storage change. func (t *mdLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
func (t *mdLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { if depth == 0 {
stack := scope.Stack fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n",
output, gasUsed, err)
}
}
// OnOpcode also tracks SLOAD/SSTORE ops to track storage change.
func (t *mdLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
stack := scope.StackData()
fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost)
if !t.cfg.DisableStack { if !t.cfg.DisableStack {
// format stack // format stack
var a []string var a []string
for _, elem := range stack.Data() { for _, elem := range stack {
a = append(a, elem.Hex()) a = append(a, elem.Hex())
} }
b := fmt.Sprintf("[%v]", strings.Join(a, ",")) b := fmt.Sprintf("[%v]", strings.Join(a, ","))
@ -381,24 +415,10 @@ func (t *mdLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope
} }
} }
func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { func (t *mdLogger) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) {
fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
} }
func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n",
output, gasUsed, err)
}
func (t *mdLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}
func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
func (*mdLogger) CaptureTxStart(gasLimit uint64) {}
func (*mdLogger) CaptureTxEnd(restGas uint64) {}
// ExecutionResult groups all structured logs emitted by the EVM // ExecutionResult groups all structured logs emitted by the EVM
// while replaying a transaction in debug mode as well as transaction // while replaying a transaction in debug mode as well as transaction
// execution status, the amount of gas used and the return value // execution status, the amount of gas used and the return value

View file

@ -19,58 +19,59 @@ package logger
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"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"
) )
type JSONLogger struct { type jsonLogger struct {
encoder *json.Encoder encoder *json.Encoder
cfg *Config cfg *Config
env *vm.EVM env *tracing.VMContext
} }
// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects // NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
// into the provided stream. // into the provided stream.
func NewJSONLogger(cfg *Config, writer io.Writer) *JSONLogger { func NewJSONLogger(cfg *Config, writer io.Writer) *tracing.Hooks {
l := &JSONLogger{encoder: json.NewEncoder(writer), cfg: cfg} l := &jsonLogger{encoder: json.NewEncoder(writer), cfg: cfg}
if l.cfg == nil { if l.cfg == nil {
l.cfg = &Config{} l.cfg = &Config{}
} }
return l return &tracing.Hooks{
OnTxStart: l.OnTxStart,
OnExit: l.OnExit,
OnOpcode: l.OnOpcode,
OnFault: l.OnFault,
}
} }
func (l *JSONLogger) CaptureStart(env *vm.EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (l *jsonLogger) OnFault(pc uint64, op byte, gas uint64, cost uint64, scope tracing.OpContext, depth int, err error) {
l.env = env
}
func (l *JSONLogger) CaptureFault(pc uint64, op vm.OpCode, gas uint64, cost uint64, scope *vm.ScopeContext, depth int, err error) {
// TODO: Add rData to this interface as well // TODO: Add rData to this interface as well
l.CaptureState(pc, op, gas, cost, scope, nil, depth, err) l.OnOpcode(pc, op, gas, cost, scope, nil, depth, err)
} }
// CaptureState outputs state information on the logger. func (l *jsonLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { memory := scope.MemoryData()
memory := scope.Memory stack := scope.StackData()
stack := scope.Stack
log := StructLog{ log := StructLog{
Pc: pc, Pc: pc,
Op: op, Op: vm.OpCode(op),
Gas: gas, Gas: gas,
GasCost: cost, GasCost: cost,
MemorySize: memory.Len(), MemorySize: len(memory),
Depth: depth, Depth: depth,
RefundCounter: l.env.StateDB.GetRefund(), RefundCounter: l.env.StateDB.GetRefund(),
Err: err, Err: err,
} }
if l.cfg.EnableMemory { if l.cfg.EnableMemory {
log.Memory = memory.Data() log.Memory = memory
} }
if !l.cfg.DisableStack { if !l.cfg.DisableStack {
log.Stack = stack.Data() log.Stack = stack
} }
if l.cfg.EnableReturnData { if l.cfg.EnableReturnData {
log.ReturnData = rData log.ReturnData = rData
@ -78,8 +79,10 @@ func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco
l.encoder.Encode(log) l.encoder.Encode(log)
} }
// CaptureEnd is triggered at end of execution. func (l *jsonLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { if depth > 0 {
return
}
type endLog struct { type endLog struct {
Output string `json:"output"` Output string `json:"output"`
GasUsed math.HexOrDecimal64 `json:"gasUsed"` GasUsed math.HexOrDecimal64 `json:"gasUsed"`
@ -92,11 +95,6 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg}) l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg})
} }
func (l *JSONLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (l *jsonLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
l.env = env
} }
func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
func (l *JSONLogger) CaptureTxStart(gasLimit uint64) {}
func (l *JSONLogger) CaptureTxEnd(restGas uint64) {}

View file

@ -55,12 +55,12 @@ func (*dummyStatedb) SetState(_ common.Address, _ common.Hash, _ common.Hash) {}
func TestStoreCapture(t *testing.T) { func TestStoreCapture(t *testing.T) {
var ( var (
logger = NewStructLogger(nil) logger = NewStructLogger(nil)
env = vm.NewEVM(vm.BlockContext{}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger}) env = vm.NewEVM(vm.BlockContext{}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger.Hooks()})
contract = vm.NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 100000) contract = vm.NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 100000)
) )
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
var index common.Hash var index common.Hash
logger.CaptureStart(env, common.Address{}, contract.Address(), false, nil, 0, nil) logger.OnTxStart(env.GetVMContext(), nil, common.Address{})
_, err := env.Interpreter().Run(contract, []byte{}, false) _, err := env.Interpreter().Run(contract, []byte{}, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View file

@ -23,12 +23,14 @@ import (
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"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"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers/directory"
) )
func init() { func init() {
tracers.DefaultDirectory.Register("4byteTracer", newFourByteTracer, false) directory.DefaultDirectory.Register("4byteTracer", newFourByteTracer, false)
} }
// fourByteTracer searches for 4byte-identifiers, and collects them for post-processing. // fourByteTracer searches for 4byte-identifiers, and collects them for post-processing.
@ -46,20 +48,26 @@ func init() {
// 0xc281d19e-0: 1 // 0xc281d19e-0: 1
// } // }
type fourByteTracer struct { type fourByteTracer struct {
noopTracer
ids map[string]int // ids aggregates the 4byte ids found ids map[string]int // ids aggregates the 4byte ids found
interrupt atomic.Bool // Atomic flag to signal execution interruption interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
activePrecompiles []common.Address // Updated on CaptureStart based on given rules activePrecompiles []common.Address // Updated on tx start based on given rules
} }
// newFourByteTracer returns a native go tracer which collects // newFourByteTracer returns a native go tracer which collects
// 4 byte-identifiers of a tx, and implements vm.EVMLogger. // 4 byte-identifiers of a tx, and implements vm.EVMLogger.
func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { func newFourByteTracer(ctx *directory.Context, _ json.RawMessage) (*directory.Tracer, error) {
t := &fourByteTracer{ t := &fourByteTracer{
ids: make(map[string]int), ids: make(map[string]int),
} }
return t, nil return &directory.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnEnter: t.OnEnter,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil
} }
// isPrecompiled returns whether the addr is a precompile. Logic borrowed from newJsTracer in eth/tracers/js/tracer.go // isPrecompiled returns whether the addr is a precompile. Logic borrowed from newJsTracer in eth/tracers/js/tracer.go
@ -78,20 +86,14 @@ func (t *fourByteTracer) store(id []byte, size int) {
t.ids[key] += 1 t.ids[key] += 1
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. func (t *fourByteTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
// Update list of precompiles based on current block // Update list of precompiles based on current block
rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time) rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time)
t.activePrecompiles = vm.ActivePrecompiles(rules) t.activePrecompiles = vm.ActivePrecompiles(rules)
// Save the outer calldata also
if len(input) >= 4 {
t.store(input[0:4], len(input)-4)
}
} }
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *fourByteTracer) OnEnter(depth int, opcode byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
// Skip if tracing was interrupted // Skip if tracing was interrupted
if t.interrupt.Load() { if t.interrupt.Load() {
return return
@ -99,6 +101,7 @@ func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to comm
if len(input) < 4 { if len(input) < 4 {
return return
} }
op := vm.OpCode(opcode)
// primarily we want to avoid CREATE/CREATE2/SELFDESTRUCT // primarily we want to avoid CREATE/CREATE2/SELFDESTRUCT
if op != vm.DELEGATECALL && op != vm.STATICCALL && if op != vm.DELEGATECALL && op != vm.STATICCALL &&
op != vm.CALL && op != vm.CALLCODE { op != vm.CALL && op != vm.CALLCODE {

View file

@ -25,15 +25,16 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"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"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers/directory"
"github.com/ethereum/go-ethereum/log"
) )
//go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go //go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go
func init() { func init() {
tracers.DefaultDirectory.Register("callTracer", newCallTracer, false) directory.DefaultDirectory.Register("callTracer", newCallTracer, false)
} }
type callLog struct { type callLog struct {
@ -59,7 +60,8 @@ type callFrame struct {
Logs []callLog `json:"logs,omitempty" rlp:"optional"` Logs []callLog `json:"logs,omitempty" rlp:"optional"`
// Placed at end on purpose. The RLP will be decoded to 0 instead of // Placed at end on purpose. The RLP will be decoded to 0 instead of
// nil if there are non-empty elements after in the struct. // nil if there are non-empty elements after in the struct.
Value *big.Int `json:"value,omitempty" rlp:"optional"` Value *big.Int `json:"value,omitempty" rlp:"optional"`
revertedSnapshot bool
} }
func (f callFrame) TypeString() string { func (f callFrame) TypeString() string {
@ -67,16 +69,17 @@ func (f callFrame) TypeString() string {
} }
func (f callFrame) failed() bool { func (f callFrame) failed() bool {
return len(f.Error) > 0 return len(f.Error) > 0 && f.revertedSnapshot
} }
func (f *callFrame) processOutput(output []byte, err error) { func (f *callFrame) processOutput(output []byte, err error, reverted bool) {
output = common.CopyBytes(output) output = common.CopyBytes(output)
if err == nil { if err == nil {
f.Output = output f.Output = output
return return
} }
f.Error = err.Error() f.Error = err.Error()
f.revertedSnapshot = reverted
if f.Type == vm.CREATE || f.Type == vm.CREATE2 { if f.Type == vm.CREATE || f.Type == vm.CREATE2 {
f.To = nil f.To = nil
} }
@ -102,10 +105,10 @@ type callFrameMarshaling struct {
} }
type callTracer struct { type callTracer struct {
noopTracer
callstack []callFrame callstack []callFrame
config callTracerConfig config callTracerConfig
gasLimit uint64 gasLimit uint64
depth int
interrupt atomic.Bool // Atomic flag to signal execution interruption interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
} }
@ -117,7 +120,25 @@ type callTracerConfig struct {
// newCallTracer returns a native go tracer which tracks // newCallTracer returns a native go tracer which tracks
// call frames of a tx, and implements vm.EVMLogger. // call frames of a tx, and implements vm.EVMLogger.
func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { func newCallTracer(ctx *directory.Context, cfg json.RawMessage) (*directory.Tracer, error) {
t, err := newCallTracerObject(ctx, cfg)
if err != nil {
return nil, err
}
return &directory.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnLog: t.OnLog,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil
}
func newCallTracerObject(ctx *directory.Context, cfg json.RawMessage) (*callTracer, error) {
var config callTracerConfig var config callTracerConfig
if cfg != nil { if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil { if err := json.Unmarshal(cfg, &config); err != nil {
@ -126,41 +147,12 @@ func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, e
} }
// First callframe contains tx context info // First callframe contains tx context info
// and is populated on start and end. // and is populated on start and end.
return &callTracer{callstack: make([]callFrame, 1), config: config}, nil return &callTracer{callstack: make([]callFrame, 0, 1), config: config}, nil
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (t *callTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
toCopy := to t.depth = depth
t.callstack[0] = callFrame{
Type: vm.CALL,
From: from,
To: &toCopy,
Input: common.CopyBytes(input),
Gas: t.gasLimit,
Value: value,
}
if create {
t.callstack[0].Type = vm.CREATE
}
}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
t.callstack[0].processOutput(output, err)
}
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
// skip if the previous op caused an error
if err != nil {
return
}
// Only logs need to be captured via opcode processing
if !t.config.WithLog {
return
}
// Avoid processing nested calls when only caring about top call
if t.config.OnlyTopCall && depth > 0 { if t.config.OnlyTopCall && depth > 0 {
return return
} }
@ -168,93 +160,95 @@ func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco
if t.interrupt.Load() { if t.interrupt.Load() {
return return
} }
switch op {
case vm.LOG0, vm.LOG1, vm.LOG2, vm.LOG3, vm.LOG4:
size := int(op - vm.LOG0)
stack := scope.Stack
stackData := stack.Data()
// Don't modify the stack
mStart := stackData[len(stackData)-1]
mSize := stackData[len(stackData)-2]
topics := make([]common.Hash, size)
for i := 0; i < size; i++ {
topic := stackData[len(stackData)-2-(i+1)]
topics[i] = common.Hash(topic.Bytes32())
}
data, err := tracers.GetMemoryCopyPadded(scope.Memory, int64(mStart.Uint64()), int64(mSize.Uint64()))
if err != nil {
// mSize was unrealistically large
log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "callTracer", "offset", mStart, "size", mSize)
return
}
log := callLog{
Address: scope.Contract.Address(),
Topics: topics,
Data: hexutil.Bytes(data),
Position: hexutil.Uint(len(t.callstack[len(t.callstack)-1].Calls)),
}
t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, log)
}
}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if t.config.OnlyTopCall {
return
}
// Skip if tracing was interrupted
if t.interrupt.Load() {
return
}
toCopy := to toCopy := to
call := callFrame{ call := callFrame{
Type: typ, Type: vm.OpCode(typ),
From: from, From: from,
To: &toCopy, To: &toCopy,
Input: common.CopyBytes(input), Input: common.CopyBytes(input),
Gas: gas, Gas: gas,
Value: value, Value: value,
} }
if depth == 0 {
call.Gas = t.gasLimit
}
t.callstack = append(t.callstack, call) t.callstack = append(t.callstack, call)
} }
// CaptureExit is called when EVM exits a scope, even if the scope didn't // OnExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // execute any code.
func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { func (t *callTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
if depth == 0 {
t.captureEnd(output, gasUsed, err, reverted)
return
}
t.depth = depth - 1
if t.config.OnlyTopCall { if t.config.OnlyTopCall {
return return
} }
size := len(t.callstack) size := len(t.callstack)
if size <= 1 { if size <= 1 {
return return
} }
// pop call // Pop call.
call := t.callstack[size-1] call := t.callstack[size-1]
t.callstack = t.callstack[:size-1] t.callstack = t.callstack[:size-1]
size -= 1 size -= 1
call.GasUsed = gasUsed call.GasUsed = gasUsed
call.processOutput(output, err) call.processOutput(output, err, reverted)
// Nest call into parent.
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
} }
func (t *callTracer) CaptureTxStart(gasLimit uint64) { func (t *callTracer) captureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
t.gasLimit = gasLimit if len(t.callstack) != 1 {
return
}
t.callstack[0].processOutput(output, err, reverted)
} }
func (t *callTracer) CaptureTxEnd(restGas uint64) { func (t *callTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
t.callstack[0].GasUsed = t.gasLimit - restGas t.gasLimit = tx.Gas()
}
func (t *callTracer) OnTxEnd(receipt *types.Receipt, err error) {
// Error happened during tx validation.
if err != nil {
return
}
t.callstack[0].GasUsed = receipt.GasUsed
if t.config.WithLog { if t.config.WithLog {
// Logs are not emitted when the call fails // Logs are not emitted when the call fails
clearFailedLogs(&t.callstack[0], false) clearFailedLogs(&t.callstack[0], false)
} }
} }
func (t *callTracer) OnLog(log *types.Log) {
// Only logs need to be captured via opcode processing
if !t.config.WithLog {
return
}
// Avoid processing nested calls when only caring about top call
if t.config.OnlyTopCall && t.depth > 0 {
return
}
// Skip if tracing was interrupted
if t.interrupt.Load() {
return
}
l := callLog{
Address: log.Address,
Topics: log.Topics,
Data: log.Data,
Position: hexutil.Uint(len(t.callstack[len(t.callstack)-1].Calls)),
}
t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, l)
}
// GetResult returns the json-encoded nested list of call traces, and any // GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`). // error arising from the encoding or forceful termination (via `Stop`).
func (t *callTracer) GetResult() (json.RawMessage, error) { func (t *callTracer) GetResult() (json.RawMessage, error) {
@ -266,7 +260,7 @@ func (t *callTracer) GetResult() (json.RawMessage, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return json.RawMessage(res), t.reason return res, t.reason
} }
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.

View file

@ -25,15 +25,17 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"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"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers/directory"
) )
//go:generate go run github.com/fjl/gencodec -type flatCallAction -field-override flatCallActionMarshaling -out gen_flatcallaction_json.go //go:generate go run github.com/fjl/gencodec -type flatCallAction -field-override flatCallActionMarshaling -out gen_flatcallaction_json.go
//go:generate go run github.com/fjl/gencodec -type flatCallResult -field-override flatCallResultMarshaling -out gen_flatcallresult_json.go //go:generate go run github.com/fjl/gencodec -type flatCallResult -field-override flatCallResultMarshaling -out gen_flatcallresult_json.go
func init() { func init() {
tracers.DefaultDirectory.Register("flatCallTracer", newFlatCallTracer, false) directory.DefaultDirectory.Register("flatCallTracer", newFlatCallTracer, false)
} }
var parityErrorMapping = map[string]string{ var parityErrorMapping = map[string]string{
@ -110,9 +112,9 @@ type flatCallResultMarshaling struct {
type flatCallTracer struct { type flatCallTracer struct {
tracer *callTracer tracer *callTracer
config flatCallTracerConfig config flatCallTracerConfig
ctx *tracers.Context // Holds tracer context data ctx *directory.Context // Holds tracer context data
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
activePrecompiles []common.Address // Updated on CaptureStart based on given rules activePrecompiles []common.Address // Updated on tx start based on given rules
} }
type flatCallTracerConfig struct { type flatCallTracerConfig struct {
@ -121,7 +123,7 @@ type flatCallTracerConfig struct {
} }
// newFlatCallTracer returns a new flatCallTracer. // newFlatCallTracer returns a new flatCallTracer.
func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { func newFlatCallTracer(ctx *directory.Context, cfg json.RawMessage) (*directory.Tracer, error) {
var config flatCallTracerConfig var config flatCallTracerConfig
if cfg != nil { if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil { if err := json.Unmarshal(cfg, &config); err != nil {
@ -131,45 +133,31 @@ func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Trace
// Create inner call tracer with default configuration, don't forward // Create inner call tracer with default configuration, don't forward
// the OnlyTopCall or WithLog to inner for now // the OnlyTopCall or WithLog to inner for now
tracer, err := tracers.DefaultDirectory.New("callTracer", ctx, nil) t, err := newCallTracerObject(ctx, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
t, ok := tracer.(*callTracer)
if !ok { ft := &flatCallTracer{tracer: t, ctx: ctx, config: config}
return nil, errors.New("internal error: embedded tracer has wrong type") return &directory.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: ft.OnTxStart,
OnTxEnd: ft.OnTxEnd,
OnEnter: ft.OnEnter,
OnExit: ft.OnExit,
},
Stop: ft.Stop,
GetResult: ft.GetResult,
}, nil
}
// OnEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *flatCallTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
t.tracer.OnEnter(depth, typ, from, to, input, gas, value)
if depth == 0 {
return
} }
return &flatCallTracer{tracer: t, ctx: ctx, config: config}, nil
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (t *flatCallTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
t.tracer.CaptureStart(env, from, to, create, input, gas, value)
// Update list of precompiles based on current block
rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time)
t.activePrecompiles = vm.ActivePrecompiles(rules)
}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *flatCallTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
t.tracer.CaptureEnd(output, gasUsed, err)
}
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
func (t *flatCallTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
t.tracer.CaptureState(pc, op, gas, cost, scope, rData, depth, err)
}
// CaptureFault implements the EVMLogger interface to trace an execution fault.
func (t *flatCallTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
t.tracer.CaptureFault(pc, op, gas, cost, scope, depth, err)
}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *flatCallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
t.tracer.CaptureEnter(typ, from, to, input, gas, value)
// Child calls must have a value, even if it's zero. // Child calls must have a value, even if it's zero.
// Practically speaking, only STATICCALL has nil value. Set it to zero. // Practically speaking, only STATICCALL has nil value. Set it to zero.
if t.tracer.callstack[len(t.tracer.callstack)-1].Value == nil && value == nil { if t.tracer.callstack[len(t.tracer.callstack)-1].Value == nil && value == nil {
@ -177,11 +165,14 @@ func (t *flatCallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to com
} }
} }
// CaptureExit is called when EVM exits a scope, even if the scope didn't // OnExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // execute any code.
func (t *flatCallTracer) CaptureExit(output []byte, gasUsed uint64, err error) { func (t *flatCallTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
t.tracer.CaptureExit(output, gasUsed, err) t.tracer.OnExit(depth, output, gasUsed, err, reverted)
if depth == 0 {
return
}
// Parity traces don't include CALL/STATICCALLs to precompiles. // Parity traces don't include CALL/STATICCALLs to precompiles.
// By default we remove them from the callstack. // By default we remove them from the callstack.
if t.config.IncludePrecompiles { if t.config.IncludePrecompiles {
@ -201,12 +192,15 @@ func (t *flatCallTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
} }
} }
func (t *flatCallTracer) CaptureTxStart(gasLimit uint64) { func (t *flatCallTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
t.tracer.CaptureTxStart(gasLimit) t.tracer.OnTxStart(env, tx, from)
// Update list of precompiles based on current block
rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time)
t.activePrecompiles = vm.ActivePrecompiles(rules)
} }
func (t *flatCallTracer) CaptureTxEnd(restGas uint64) { func (t *flatCallTracer) OnTxEnd(receipt *types.Receipt, err error) {
t.tracer.CaptureTxEnd(restGas) t.tracer.OnTxEnd(receipt, err)
} }
// GetResult returns an empty json object. // GetResult returns an empty json object.
@ -242,7 +236,7 @@ func (t *flatCallTracer) isPrecompiled(addr common.Address) bool {
return false return false
} }
func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx *tracers.Context) (output []flatCallFrame, err error) { func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx *directory.Context) (output []flatCallFrame, err error) {
var frame *flatCallFrame var frame *flatCallFrame
switch input.Type { switch input.Type {
case vm.CREATE, vm.CREATE2: case vm.CREATE, vm.CREATE2:
@ -341,7 +335,7 @@ func newFlatSelfdestruct(input *callFrame) *flatCallFrame {
} }
} }
func fillCallFrameFromContext(callFrame *flatCallFrame, ctx *tracers.Context) { func fillCallFrameFromContext(callFrame *flatCallFrame, ctx *directory.Context) {
if ctx == nil { if ctx == nil {
return return
} }

View file

@ -21,33 +21,34 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/tracers/directory"
) )
func init() { func init() {
tracers.DefaultDirectory.Register("muxTracer", newMuxTracer, false) directory.DefaultDirectory.Register("muxTracer", newMuxTracer, false)
} }
// muxTracer is a go implementation of the Tracer interface which // muxTracer is a go implementation of the Tracer interface which
// runs multiple tracers in one go. // runs multiple tracers in one go.
type muxTracer struct { type muxTracer struct {
names []string names []string
tracers []tracers.Tracer tracers []*directory.Tracer
} }
// newMuxTracer returns a new mux tracer. // newMuxTracer returns a new mux tracer.
func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { func newMuxTracer(ctx *directory.Context, cfg json.RawMessage) (*directory.Tracer, error) {
var config map[string]json.RawMessage var config map[string]json.RawMessage
if cfg != nil { if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil { if err := json.Unmarshal(cfg, &config); err != nil {
return nil, err return nil, err
} }
} }
objects := make([]tracers.Tracer, 0, len(config)) objects := make([]*directory.Tracer, 0, len(config))
names := make([]string, 0, len(config)) names := make([]string, 0, len(config))
for k, v := range config { for k, v := range config {
t, err := tracers.DefaultDirectory.New(k, ctx, v) t, err := directory.DefaultDirectory.New(k, ctx, v)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -55,61 +56,120 @@ func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, er
names = append(names, k) names = append(names, k)
} }
return &muxTracer{names: names, tracers: objects}, nil t := &muxTracer{names: names, tracers: objects}
return &directory.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
OnGasChange: t.OnGasChange,
OnBalanceChange: t.OnBalanceChange,
OnNonceChange: t.OnNonceChange,
OnCodeChange: t.OnCodeChange,
OnStorageChange: t.OnStorageChange,
OnLog: t.OnLog,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. func (t *muxTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
func (t *muxTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureStart(env, from, to, create, input, gas, value) if t.OnOpcode != nil {
t.OnOpcode(pc, op, gas, cost, scope, rData, depth, err)
}
} }
} }
// CaptureEnd is called after the call finishes to finalize the tracing. func (t *muxTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) {
func (t *muxTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureEnd(output, gasUsed, err) if t.OnFault != nil {
t.OnFault(pc, op, gas, cost, scope, depth, err)
}
} }
} }
// CaptureState implements the EVMLogger interface to trace a single step of VM execution. func (t *muxTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {
func (t *muxTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureState(pc, op, gas, cost, scope, rData, depth, err) if t.OnGasChange != nil {
t.OnGasChange(old, new, reason)
}
} }
} }
// CaptureFault implements the EVMLogger interface to trace an execution fault. func (t *muxTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
func (t *muxTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureFault(pc, op, gas, cost, scope, depth, err) if t.OnEnter != nil {
t.OnEnter(depth, typ, from, to, input, gas, value)
}
} }
} }
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). func (t *muxTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
func (t *muxTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureEnter(typ, from, to, input, gas, value) if t.OnExit != nil {
t.OnExit(depth, output, gasUsed, err, reverted)
}
} }
} }
// CaptureExit is called when EVM exits a scope, even if the scope didn't func (t *muxTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
// execute any code.
func (t *muxTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureExit(output, gasUsed, err) if t.OnTxStart != nil {
t.OnTxStart(env, tx, from)
}
} }
} }
func (t *muxTracer) CaptureTxStart(gasLimit uint64) { func (t *muxTracer) OnTxEnd(receipt *types.Receipt, err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureTxStart(gasLimit) if t.OnTxEnd != nil {
t.OnTxEnd(receipt, err)
}
} }
} }
func (t *muxTracer) CaptureTxEnd(restGas uint64) { func (t *muxTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureTxEnd(restGas) if t.OnBalanceChange != nil {
t.OnBalanceChange(a, prev, new, reason)
}
}
}
func (t *muxTracer) OnNonceChange(a common.Address, prev, new uint64) {
for _, t := range t.tracers {
if t.OnNonceChange != nil {
t.OnNonceChange(a, prev, new)
}
}
}
func (t *muxTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
for _, t := range t.tracers {
if t.OnCodeChange != nil {
t.OnCodeChange(a, prevCodeHash, prev, codeHash, code)
}
}
}
func (t *muxTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {
for _, t := range t.tracers {
if t.OnStorageChange != nil {
t.OnStorageChange(a, k, prev, new)
}
}
}
func (t *muxTracer) OnLog(log *types.Log) {
for _, t := range t.tracers {
if t.OnLog != nil {
t.OnLog(log)
}
} }
} }

View file

@ -1,77 +0,0 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package native
import (
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
)
func init() {
tracers.DefaultDirectory.Register("noopTracer", newNoopTracer, false)
}
// noopTracer is a go implementation of the Tracer interface which
// performs no action. It's mostly useful for testing purposes.
type noopTracer struct{}
// newNoopTracer returns a new noop tracer.
func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
return &noopTracer{}, nil
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (t *noopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *noopTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
}
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
func (t *noopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
// CaptureFault implements the EVMLogger interface to trace an execution fault.
func (t *noopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}
// CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code.
func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
}
func (*noopTracer) CaptureTxStart(gasLimit uint64) {}
func (*noopTracer) CaptureTxEnd(restGas uint64) {}
// GetResult returns an empty json object.
func (t *noopTracer) GetResult() (json.RawMessage, error) {
return json.RawMessage(`{}`), nil
}
// Stop terminates execution of the tracer at the first opportune moment.
func (t *noopTracer) Stop(err error) {
}

Some files were not shown because too many files have changed in this diff Show more