eth/tracers: live chain tracing with hooks (#29189)

Here we add a Go API for running tracing plugins within the main block import process. 

As an advanced user of geth, you can now create a Go file in eth/tracers/live/, and within
that file register your custom tracer implementation. Then recompile geth and select your tracer
on the command line. Hooks defined in the tracer will run whenever a block is processed.

The hook system is defined in package core/tracing. It uses a struct with callbacks, instead of 
requiring an interface, for several reasons:

- We plan to keep this API stable long-term. The core/tracing hook API does not depend on
  on deep geth internals.
- There are a lot of hooks, and tracers will only need some of them. Using a struct allows you
   to implement only the hooks you want to actually use.

All existing tracers in eth/tracers/native have been rewritten to use the new hook system.

This change breaks compatibility with the vm.EVMLogger interface that we used to have.
If you are a user of vm.EVMLogger, please migrate to core/tracing, and sorry for breaking
your stuff. But we just couldn't have both the old and new tracing APIs coexist in the EVM.

---------

Co-authored-by: Matthieu Vachon <matthieu.o.vachon@gmail.com>
Co-authored-by: Delweng <delweng@gmail.com>
Co-authored-by: Martin HS <martin@swende.se>
pull/29318/head
Sina M 6 months ago committed by GitHub
parent 38eb8b3e20
commit 064f37d6f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      cmd/evm/blockrunner.go
  2. 50
      cmd/evm/internal/t8ntool/execution.go
  3. 81
      cmd/evm/internal/t8ntool/tracewriter.go
  4. 24
      cmd/evm/internal/t8ntool/transition.go
  5. 5
      cmd/evm/runner.go
  6. 2
      cmd/evm/staterunner.go
  7. 104
      cmd/evm/t8n_test.go
  8. 1
      cmd/evm/testdata/31/README.md
  9. 16
      cmd/evm/testdata/31/alloc.json
  10. 20
      cmd/evm/testdata/31/env.json
  11. 1
      cmd/evm/testdata/31/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json
  12. 6
      cmd/evm/testdata/31/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl
  13. 14
      cmd/evm/testdata/31/txs.json
  14. 2
      cmd/geth/chaincmd.go
  15. 1
      cmd/geth/config.go
  16. 3
      cmd/geth/main.go
  17. 39
      cmd/utils/flags.go
  18. 3
      consensus/beacon/consensus.go
  19. 7
      consensus/ethash/consensus.go
  20. 5
      consensus/misc/dao.go
  21. 174
      core/blockchain.go
  22. 2
      core/blockchain_test.go
  23. 5
      core/evm.go
  24. 41
      core/genesis.go
  25. 1
      core/state/dump.go
  26. 23
      core/state/state_object.go
  27. 15
      core/state/state_test.go
  28. 38
      core/state/statedb.go
  29. 3
      core/state/statedb_fuzz_test.go
  30. 43
      core/state/statedb_test.go
  31. 3
      core/state/sync_test.go
  32. 3
      core/state/trie_prefetcher_test.go
  33. 21
      core/state_processor.go
  34. 30
      core/state_transition.go
  35. 275
      core/tracing/hooks.go
  36. 41
      core/txpool/blobpool/blobpool_test.go
  37. 13
      core/txpool/legacypool/legacypool2_test.go
  38. 13
      core/txpool/legacypool/legacypool_test.go
  39. 17
      core/vm/contract.go
  40. 6
      core/vm/contracts.go
  41. 2
      core/vm/contracts_fuzz_test.go
  42. 8
      core/vm/contracts_test.go
  43. 120
      core/vm/errors.go
  44. 185
      core/vm/evm.go
  45. 46
      core/vm/instructions.go
  46. 5
      core/vm/interface.go
  47. 82
      core/vm/interpreter.go
  48. 43
      core/vm/logger.go
  49. 3
      core/vm/operations_acl.go
  50. 9
      core/vm/runtime/runtime.go
  51. 14
      core/vm/runtime/runtime_test.go
  52. 2
      eth/api_backend.go
  53. 3
      eth/api_debug_test.go
  54. 13
      eth/backend.go
  55. 4
      eth/ethconfig/config.go
  56. 4
      eth/state_accessor.go
  57. 61
      eth/tracers/api.go
  58. 5
      eth/tracers/api_test.go
  59. 49
      eth/tracers/dir.go
  60. 10
      eth/tracers/internal/tracetest/README.md
  61. 68
      eth/tracers/internal/tracetest/calltrace_test.go
  62. 17
      eth/tracers/internal/tracetest/flat_calltrace_test.go
  63. 48
      eth/tracers/internal/tracetest/makeTest.js
  64. 10
      eth/tracers/internal/tracetest/prestate_test.go
  65. 12
      eth/tracers/internal/tracetest/testdata/call_tracer/inner_instafail.json
  66. 17
      eth/tracers/internal/tracetest/testdata/call_tracer_flat/callcode_precompiled_fail_hide.json
  67. 16
      eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_instafail.json
  68. 15
      eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create_inerror.json
  69. 17
      eth/tracers/internal/tracetest/testdata/call_tracer_flat/selfdestruct.json
  70. 15
      eth/tracers/internal/tracetest/testdata/call_tracer_flat/skip_no_balance_error.json
  71. 189
      eth/tracers/internal/tracetest/testdata/call_tracer_withLog/frontier_create_outofstorage.json
  72. 62
      eth/tracers/internal/tracetest/testdata/prestate_tracer/create_create.json
  73. 53
      eth/tracers/internal/tracetest/util.go
  74. 81
      eth/tracers/internal/util.go
  75. 60
      eth/tracers/internal/util_test.go
  76. 190
      eth/tracers/js/goja.go
  77. 33
      eth/tracers/js/tracer_test.go
  78. 31
      eth/tracers/live.go
  79. 96
      eth/tracers/live/noop.go
  80. 32
      eth/tracers/logger/access_list_tracer.go
  81. 151
      eth/tracers/logger/logger.go
  82. 56
      eth/tracers/logger/logger_json.go
  83. 4
      eth/tracers/logger/logger_test.go
  84. 31
      eth/tracers/native/4byte.go
  85. 176
      eth/tracers/native/call.go
  86. 76
      eth/tracers/native/call_flat.go
  87. 116
      eth/tracers/native/mux.go
  88. 57
      eth/tracers/native/noop.go
  89. 190
      eth/tracers/native/prestate.go
  90. 40
      eth/tracers/tracers_test.go
  91. 46
      internal/ethapi/api.go
  92. 101
      internal/ethapi/transaction_args.go
  93. 2
      miner/worker.go
  94. 3
      tests/block_test_util.go
  95. 29
      tests/state_test_util.go

@ -26,7 +26,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/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"
@ -51,7 +51,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{

@ -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"
"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"
@ -119,7 +123,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) (vm.EVMLogger, error)) (*state.StateDB, *ExecutionResult, []byte, error) { getTracerFn func(txIndex int, txHash common.Hash) (*tracers.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
@ -222,11 +226,13 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
continue continue
} }
} }
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 (
@ -236,6 +242,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 {
@ -243,6 +252,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)
@ -285,6 +302,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++
@ -310,15 +333,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, uint256.MustFromBig(reward)) statedb.AddBalance(ommer.Address, uint256.MustFromBig(reward), tracing.BalanceIncreaseRewardMineUncle)
} }
statedb.AddBalance(pre.Env.Coinbase, uint256.MustFromBig(minerReward)) statedb.AddBalance(pre.Env.Coinbase, uint256.MustFromBig(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, uint256.MustFromBig(amount)) statedb.AddBalance(w.Address, uint256.MustFromBig(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))
@ -361,7 +384,7 @@ func MakePreState(db ethdb.Database, accounts types.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, uint256.MustFromBig(a.Balance)) statedb.SetBalance(addr, uint256.MustFromBig(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)
} }
@ -398,3 +421,16 @@ func calcDifficulty(config *params.ChainConfig, number, currentTime, parentTime
} }
return ethash.CalcDifficulty(config, currentTime, parent) return ethash.CalcDifficulty(config, currentTime, parent)
} }
func writeTraceResult(tracer *tracers.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
}

@ -1,81 +0,0 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package t8ntool
import (
"encoding/json"
"io"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/log"
)
// traceWriter is an vm.EVMLogger which also holds an inner logger/tracer.
// When the TxEnd event happens, the inner tracer result is written to the file, and
// the file is closed.
type traceWriter struct {
inner vm.EVMLogger
f io.WriteCloser
}
// Compile-time interface check
var _ = vm.EVMLogger((*traceWriter)(nil))
func (t *traceWriter) CaptureTxEnd(restGas uint64) {
t.inner.CaptureTxEnd(restGas)
defer t.f.Close()
if tracer, ok := t.inner.(tracers.Tracer); ok {
result, err := tracer.GetResult()
if err != nil {
log.Warn("Error in tracer", "err", err)
return
}
err = json.NewEncoder(t.f).Encode(result)
if err != nil {
log.Warn("Error writing tracer output", "err", err)
return
}
}
}
func (t *traceWriter) CaptureTxStart(gasLimit uint64) { t.inner.CaptureTxStart(gasLimit) }
func (t *traceWriter) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
t.inner.CaptureStart(env, from, to, create, input, gas, value)
}
func (t *traceWriter) CaptureEnd(output []byte, gasUsed uint64, err error) {
t.inner.CaptureEnd(output, gasUsed, err)
}
func (t *traceWriter) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
t.inner.CaptureEnter(typ, from, to, input, gas, value)
}
func (t *traceWriter) CaptureExit(output []byte, gasUsed uint64, err error) {
t.inner.CaptureExit(output, gasUsed, err)
}
func (t *traceWriter) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
t.inner.CaptureState(pc, op, gas, cost, scope, rData, depth, err)
}
func (t *traceWriter) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
t.inner.CaptureFault(pc, op, gas, cost, scope, depth, err)
}

@ -20,6 +20,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"math/big" "math/big"
"os" "os"
"path/filepath" "path/filepath"
@ -80,7 +81,7 @@ type input struct {
} }
func Transition(ctx *cli.Context) error { func Transition(ctx *cli.Context) error {
var getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { return nil, nil } var getTracer = func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error) { return nil, nil, nil }
baseDir, err := createBasedir(ctx) baseDir, err := createBasedir(ctx)
if err != nil { if err != nil {
@ -95,28 +96,35 @@ func Transition(ctx *cli.Context) error {
EnableReturnData: ctx.Bool(TraceEnableReturnDataFlag.Name), EnableReturnData: ctx.Bool(TraceEnableReturnDataFlag.Name),
Debug: true, Debug: true,
} }
getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { getTracer = func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error) {
traceFile, err := os.Create(filepath.Join(baseDir, fmt.Sprintf("trace-%d-%v.jsonl", txIndex, txHash.String()))) traceFile, err := os.Create(filepath.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))
} }
return &traceWriter{logger.NewJSONLogger(logConfig, traceFile), traceFile}, nil logger := logger.NewJSONLogger(logConfig, traceFile)
tracer := &tracers.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 if ctx.IsSet(TraceTracerFlag.Name) { } else if ctx.IsSet(TraceTracerFlag.Name) {
var config json.RawMessage var config json.RawMessage
if ctx.IsSet(TraceTracerConfigFlag.Name) { if ctx.IsSet(TraceTracerConfigFlag.Name) {
config = []byte(ctx.String(TraceTracerConfigFlag.Name)) config = []byte(ctx.String(TraceTracerConfigFlag.Name))
} }
getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { getTracer = func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error) {
traceFile, err := os.Create(filepath.Join(baseDir, fmt.Sprintf("trace-%d-%v.json", txIndex, txHash.String()))) traceFile, err := os.Create(filepath.Join(baseDir, fmt.Sprintf("trace-%d-%v.json", 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))
} }
tracer, err := tracers.DefaultDirectory.New(ctx.String(TraceTracerFlag.Name), nil, config) tracer, err := tracers.DefaultDirectory.New(ctx.String(TraceTracerFlag.Name), nil, config)
if err != nil { if err != nil {
return nil, NewError(ErrorConfig, fmt.Errorf("failed instantiating tracer: %w", err)) return nil, nil, NewError(ErrorConfig, fmt.Errorf("failed instantiating tracer: %w", err))
} }
return &traceWriter{tracer, traceFile}, nil 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

@ -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)
} }

@ -63,7 +63,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 {

@ -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

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

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

@ -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"
}

@ -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"}

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

@ -99,6 +99,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),

@ -179,6 +179,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

@ -42,6 +42,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,

@ -21,6 +21,7 @@ import (
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"encoding/hex" "encoding/hex"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"math" "math"
@ -538,7 +539,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",
@ -1889,6 +1899,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
@ -2167,12 +2189,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 := tracers.LiveDirectory.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
} }

@ -26,6 +26,7 @@ import (
"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/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/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
@ -358,7 +359,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
// Convert amount from gwei to wei. // Convert amount from gwei to wei.
amount := new(uint256.Int).SetUint64(w.Amount) amount := new(uint256.Int).SetUint64(w.Amount)
amount = amount.Mul(amount, uint256.NewInt(params.GWei)) amount = amount.Mul(amount, uint256.NewInt(params.GWei))
state.AddBalance(w.Address, amount) state.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.
} }

@ -29,6 +29,7 @@ import (
"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/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/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
@ -570,7 +571,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 *state.StateDB, header *types.Header, uncles []*types.Header) { func accumulateRewards(config *params.ChainConfig, stateDB *state.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) {
@ -589,10 +590,10 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header
r.Sub(r, hNum) r.Sub(r, hNum)
r.Mul(r, blockReward) r.Mul(r, blockReward)
r.Div(r, u256_8) r.Div(r, u256_8)
state.AddBalance(uncle.Coinbase, r) stateDB.AddBalance(uncle.Coinbase, r, tracing.BalanceIncreaseRewardMineUncle)
r.Div(blockReward, u256_32) r.Div(blockReward, u256_32)
reward.Add(reward, r) reward.Add(reward, r)
} }
state.AddBalance(header.Coinbase, reward) stateDB.AddBalance(header.Coinbase, reward, tracing.BalanceIncreaseRewardMineBlock)
} }

@ -22,6 +22,7 @@ import (
"math/big" "math/big"
"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/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -81,7 +82,7 @@ func ApplyDAOHardFork(statedb *state.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(uint256.Int)) statedb.SetBalance(addr, new(uint256.Int), tracing.BalanceDecreaseDaoAccount)
} }
} }

@ -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"
@ -253,6 +254,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
@ -295,6 +297,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit), txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit),
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)
@ -421,6 +424,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
@ -452,6 +474,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
} }
rawdb.WriteChainConfig(db, genesisHash, chainConfig) rawdb.WriteChainConfig(db, genesisHash, chainConfig)
} }
// Start tx indexer if it's enabled. // Start tx indexer if it's enabled.
if txLookupLimit != nil { if txLookupLimit != nil {
bc.txIndexer = newTxIndexer(*txLookupLimit, bc) bc.txIndexer = newTxIndexer(*txLookupLimit, bc)
@ -1783,6 +1806,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.
@ -1800,6 +1831,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")
@ -1813,7 +1845,10 @@ 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) // Disable tracing for prefetcher executions.
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() {
@ -1823,21 +1858,99 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
} }
} }
// The traced section of block import.
res, err := bc.processBlock(block, statedb, start, setHead)
followupInterrupt.Store(true)
if err != nil {
return it.index, err
}
// Report the import stats before returning the various results
stats.processed++
stats.usedGas += res.usedGas
var snapDiffItems, snapBufItems common.StorageSize
if bc.snaps != nil {
snapDiffItems, snapBufItems = bc.snaps.Size()
}
trieDiffNodes, trieBufNodes, _ := bc.triedb.Size()
stats.report(chain, it.index, snapDiffItems, snapBufItems, trieDiffNodes, trieBufNodes, setHead)
if !setHead {
// After merge we expect few side chains. Simply count
// all blocks the CL gives us for GC processing time
bc.gcproc += res.procTime
return it.index, nil // Direct block insertion of a single block
}
switch res.status {
case CanonStatTy:
log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(),
"uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(),
"elapsed", common.PrettyDuration(time.Since(start)),
"root", block.Root())
lastCanon = block
// Only count canonical blocks for GC processing time
bc.gcproc += res.procTime
case SideStatTy:
log.Debug("Inserted forked block", "number", block.Number(), "hash", block.Hash(),
"diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)),
"txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()),
"root", block.Root())
default:
// This in theory is impossible, but lets be nice to our future selves and leave
// a log, instead of trying to track down blocks imports that don't emit logs.
log.Warn("Inserted block with unknown status", "number", block.Number(), "hash", block.Hash(),
"diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)),
"txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()),
"root", block.Root())
}
}
stats.ignored += it.remaining()
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 // Process block using the parent state as reference point
pstart := time.Now() pstart := time.Now()
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
if err != nil { if err != nil {
bc.reportBlock(block, receipts, err) bc.reportBlock(block, receipts, err)
followupInterrupt.Store(true) return nil, err
return it.index, err
} }
ptime := time.Since(pstart) ptime := time.Since(pstart)
vstart := time.Now() vstart := time.Now()
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil { if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil {
bc.reportBlock(block, receipts, err) bc.reportBlock(block, receipts, err)
followupInterrupt.Store(true) return nil, err
return it.index, err
} }
vtime := time.Since(vstart) vtime := time.Since(vstart)
proctime := time.Since(start) // processing + validation proctime := time.Since(start) // processing + validation
@ -1869,9 +1982,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
} else { } else {
status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false) status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false)
} }
followupInterrupt.Store(true)
if err != nil { if err != nil {
return it.index, err return nil, err
} }
// Update the metrics touched during block commit // Update the metrics touched during block commit
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
@ -1882,53 +1994,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits) blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits)
blockInsertTimer.UpdateSince(start) blockInsertTimer.UpdateSince(start)
// Report the import stats before returning the various results return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil
stats.processed++
stats.usedGas += usedGas
var snapDiffItems, snapBufItems common.StorageSize
if bc.snaps != nil {
snapDiffItems, snapBufItems = bc.snaps.Size()
}
trieDiffNodes, trieBufNodes, _ := bc.triedb.Size()
stats.report(chain, it.index, snapDiffItems, snapBufItems, trieDiffNodes, trieBufNodes, setHead)
if !setHead {
// After merge we expect few side chains. Simply count
// all blocks the CL gives us for GC processing time
bc.gcproc += proctime
return it.index, nil // Direct block insertion of a single block
}
switch status {
case CanonStatTy:
log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(),
"uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(),
"elapsed", common.PrettyDuration(time.Since(start)),
"root", block.Root())
lastCanon = block
// Only count canonical blocks for GC processing time
bc.gcproc += proctime
case SideStatTy:
log.Debug("Inserted forked block", "number", block.Number(), "hash", block.Hash(),
"diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)),
"txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()),
"root", block.Root())
default:
// This in theory is impossible, but lets be nice to our future selves and leave
// a log, instead of trying to track down blocks imports that don't emit logs.
log.Warn("Inserted block with unknown status", "number", block.Number(), "hash", block.Hash(),
"diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)),
"txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()),
"root", block.Root())
}
}
stats.ignored += it.remaining()
return it.index, err
} }
// insertSideChain is called when an import batch hits upon a pruned ancestor // insertSideChain is called when an import batch hits upon a pruned ancestor

@ -4287,7 +4287,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)
} }

@ -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"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -136,6 +137,6 @@ func CanTransfer(db vm.StateDB, addr common.Address, amount *uint256.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 *uint256.Int) { func Transfer(db vm.StateDB, sender, recipient common.Address, amount *uint256.Int) {
db.SubBalance(sender, amount) db.SubBalance(sender, amount, tracing.BalanceChangeTransfer)
db.AddBalance(recipient, amount) db.AddBalance(recipient, amount, tracing.BalanceChangeTransfer)
} }

@ -29,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"
@ -133,7 +134,7 @@ func hashAlloc(ga *types.GenesisAlloc, 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, uint256.MustFromBig(account.Balance)) statedb.AddBalance(addr, uint256.MustFromBig(account.Balance), tracing.BalanceIncreaseGenesisBalance)
} }
statedb.SetCode(addr, account.Code) statedb.SetCode(addr, account.Code)
statedb.SetNonce(addr, account.Nonce) statedb.SetNonce(addr, account.Nonce)
@ -154,7 +155,9 @@ func flushAlloc(ga *types.GenesisAlloc, db ethdb.Database, triedb *triedb.Databa
} }
for addr, account := range *ga { for addr, account := range *ga {
if account.Balance != nil { if account.Balance != nil {
statedb.AddBalance(addr, uint256.MustFromBig(account.Balance)) // This is not actually logged via tracer because OnGenesisBlock
// already captures the allocations.
statedb.AddBalance(addr, uint256.MustFromBig(account.Balance), tracing.BalanceIncreaseGenesisBalance)
} }
statedb.SetCode(addr, account.Code) statedb.SetCode(addr, account.Code)
statedb.SetNonce(addr, account.Nonce) statedb.SetNonce(addr, account.Nonce)
@ -181,6 +184,39 @@ func flushAlloc(ga *types.GenesisAlloc, db ethdb.Database, triedb *triedb.Databa
return nil return nil
} }
func getGenesisState(db ethdb.Database, blockhash common.Hash) (alloc types.GenesisAlloc, err error) {
blob := rawdb.ReadGenesisStateSpec(db, blockhash)
if len(blob) != 0 {
if err := alloc.UnmarshalJSON(blob); err != nil {
return nil, err
}
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
type genesisSpecMarshaling struct { type genesisSpecMarshaling struct {
Nonce math.HexOrDecimal64 Nonce math.HexOrDecimal64
@ -252,6 +288,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g
} 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 {

@ -57,7 +57,6 @@ type DumpAccount struct {
Storage map[common.Hash]string `json:"storage,omitempty"` Storage map[common.Hash]string `json:"storage,omitempty"`
Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode
AddressHash hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key AddressHash hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key
} }
// Dump represents the full dump in a collected format, as one large map. // Dump represents the full dump in a collected format, as one large map.

@ -23,6 +23,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/rlp" "github.com/ethereum/go-ethereum/rlp"
@ -240,6 +241,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)
} }
@ -399,7 +403,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 *uint256.Int) { func (s *stateObject) AddBalance(amount *uint256.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.IsZero() { if amount.IsZero() {
@ -408,23 +412,26 @@ func (s *stateObject) AddBalance(amount *uint256.Int) {
} }
return return
} }
s.SetBalance(new(uint256.Int).Add(s.Balance(), amount)) s.SetBalance(new(uint256.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 *uint256.Int) { func (s *stateObject) SubBalance(amount *uint256.Int, reason tracing.BalanceChangeReason) {
if amount.IsZero() { if amount.IsZero() {
return return
} }
s.SetBalance(new(uint256.Int).Sub(s.Balance(), amount)) s.SetBalance(new(uint256.Int).Sub(s.Balance(), amount), reason)
} }
func (s *stateObject) SetBalance(amount *uint256.Int) { func (s *stateObject) SetBalance(amount *uint256.Int, reason tracing.BalanceChangeReason) {
s.db.journal.append(balanceChange{ s.db.journal.append(balanceChange{
account: &s.address, account: &s.address,
prev: new(uint256.Int).Set(s.data.Balance), prev: new(uint256.Int).Set(s.data.Balance),
}) })
if s.db.logger != nil && s.db.logger.OnBalanceChange != nil {
s.db.logger.OnBalanceChange(s.address, s.Balance().ToBig(), amount.ToBig(), reason)
}
s.setBalance(amount) s.setBalance(amount)
} }
@ -502,6 +509,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)
} }
@ -516,6 +526,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)
} }

@ -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"
@ -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(uint256.NewInt(22)) obj1.AddBalance(uint256.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(uint256.NewInt(44)) obj3.SetBalance(uint256.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(uint256.NewInt(22)) obj1.AddBalance(uint256.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(uint256.NewInt(44)) obj3.SetBalance(uint256.NewInt(44), tracing.BalanceChangeUnspecified)
obj4 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x00})) obj4 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x00}))
obj4.AddBalance(uint256.NewInt(1337)) obj4.AddBalance(uint256.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(uint256.NewInt(42)) so0.SetBalance(uint256.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(uint256.NewInt(52)) so1.SetBalance(uint256.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

@ -19,12 +19,14 @@ package state
import ( import (
"fmt" "fmt"
"math/big"
"sort" "sort"
"time" "time"
"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/log" "github.com/ethereum/go-ethereum/log"
@ -56,6 +58,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
@ -165,6 +168,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.
@ -205,6 +213,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++
} }
@ -366,25 +377,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 *uint256.Int) { func (s *StateDB) AddBalance(addr common.Address, amount *uint256.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 *uint256.Int) { func (s *StateDB) SubBalance(addr common.Address, amount *uint256.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 *uint256.Int) { func (s *StateDB) SetBalance(addr common.Address, amount *uint256.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)
} }
} }
@ -440,13 +451,20 @@ func (s *StateDB) SelfDestruct(addr common.Address) {
if stateObject == nil { if stateObject == nil {
return return
} }
var (
prev = new(uint256.Int).Set(stateObject.Balance())
n = new(uint256.Int)
)
s.journal.append(selfDestructChange{ s.journal.append(selfDestructChange{
account: &addr, account: &addr,
prev: stateObject.selfDestructed, prev: stateObject.selfDestructed,
prevbalance: new(uint256.Int).Set(stateObject.Balance()), prevbalance: prev,
}) })
if s.logger != nil && s.logger.OnBalanceChange != nil && prev.Sign() > 0 {
s.logger.OnBalanceChange(addr, prev.ToBig(), n.ToBig(), tracing.BalanceDecreaseSelfdestruct)
}
stateObject.markSelfdestructed() stateObject.markSelfdestructed()
stateObject.data.Balance = new(uint256.Int) stateObject.data.Balance = n
} }
func (s *StateDB) Selfdestruct6780(addr common.Address) { func (s *StateDB) Selfdestruct6780(addr common.Address) {
@ -823,6 +841,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.ToBig(), 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.

@ -31,6 +31,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"
@ -61,7 +62,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, uint256.NewInt(uint64(a.args[0]))) s.SetBalance(addr, uint256.NewInt(uint64(a.args[0])), tracing.BalanceChangeUnspecified)
}, },
args: make([]int64, 1), args: make([]int64, 1),
}, },

@ -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"
@ -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, uint256.NewInt(uint64(11*i))) state.AddBalance(addr, uint256.NewInt(uint64(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, uint256.NewInt(uint64(11*i)+uint64(tweak))) state.SetBalance(addr, uint256.NewInt(uint64(11*i)+uint64(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(uint256.NewInt(uint64(i))) obj.AddBalance(uint256.NewInt(uint64(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.getOrNewStateObject(common.BytesToAddress([]byte{i})) copyObj := copy.getOrNewStateObject(common.BytesToAddress([]byte{i}))
ccopyObj := ccopy.getOrNewStateObject(common.BytesToAddress([]byte{i})) ccopyObj := ccopy.getOrNewStateObject(common.BytesToAddress([]byte{i}))
origObj.AddBalance(uint256.NewInt(2 * uint64(i))) origObj.AddBalance(uint256.NewInt(2*uint64(i)), tracing.BalanceChangeUnspecified)
copyObj.AddBalance(uint256.NewInt(3 * uint64(i))) copyObj.AddBalance(uint256.NewInt(3*uint64(i)), tracing.BalanceChangeUnspecified)
ccopyObj.AddBalance(uint256.NewInt(4 * uint64(i))) ccopyObj.AddBalance(uint256.NewInt(4*uint64(i)), tracing.BalanceChangeUnspecified)
orig.updateStateObject(origObj) orig.updateStateObject(origObj)
copy.updateStateObject(copyObj) copy.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, uint256.NewInt(uint64(a.args[0]))) s.SetBalance(addr, uint256.NewInt(uint64(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, uint256.NewInt(uint64(a.args[0]))) s.AddBalance(addr, uint256.NewInt(uint64(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(uint256.Int)) s.state.AddBalance(common.Address{}, new(uint256.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,7 +553,7 @@ 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, uint256.NewInt(42)) state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified)
if got := state.Copy().GetBalance(addr).Uint64(); got != 42 { if got := state.Copy().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)
@ -575,7 +576,7 @@ func TestCopyCommitCopy(t *testing.T) {
skey := common.HexToHash("aaa") skey := common.HexToHash("aaa")
sval := common.HexToHash("bbb") sval := common.HexToHash("bbb")
state.SetBalance(addr, uint256.NewInt(42)) // Change the account trie state.SetBalance(addr, uint256.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
@ -648,7 +649,7 @@ func TestCopyCopyCommitCopy(t *testing.T) {
skey := common.HexToHash("aaa") skey := common.HexToHash("aaa")
sval := common.HexToHash("bbb") sval := common.HexToHash("bbb")
state.SetBalance(addr, uint256.NewInt(42)) // Change the account trie state.SetBalance(addr, uint256.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
@ -717,7 +718,7 @@ func TestCommitCopy(t *testing.T) {
skey := common.HexToHash("aaa") skey := common.HexToHash("aaa")
sval := common.HexToHash("bbb") sval := common.HexToHash("bbb")
state.SetBalance(addr, uint256.NewInt(42)) // Change the account trie state.SetBalance(addr, uint256.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
@ -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, uint256.NewInt(1)) state.SetBalance(addr, uint256.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, uint256.NewInt(2)) state.SetBalance(addr, uint256.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, uint256.NewInt(1)) state.SetBalance(addr, uint256.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, uint256.NewInt(100)) state.SetBalance(a2, uint256.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, uint256.NewInt(2)) state.SetBalance(addr, uint256.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)
@ -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, uint256.NewInt(1)) state.SetBalance(addr, uint256.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, uint256.NewInt(2)) state.SetBalance(addr, uint256.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, uint256.NewInt(1)) state.SetBalance(addr, uint256.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())

@ -22,6 +22,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"
@ -61,7 +62,7 @@ func makeTestState(scheme string) (ethdb.Database, Database, *triedb.Database, c
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(uint256.NewInt(uint64(11 * i))) obj.AddBalance(uint256.NewInt(uint64(11*i)), tracing.BalanceChangeUnspecified)
acc.balance = uint256.NewInt(uint64(11 * i)) acc.balance = uint256.NewInt(uint64(11 * i))
obj.SetNonce(uint64(42 * i)) obj.SetNonce(uint64(42 * i))

@ -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/holiman/uint256" "github.com/holiman/uint256"
) )
@ -35,7 +36,7 @@ func filledStateDB() *StateDB {
skey := common.HexToHash("aaa") skey := common.HexToHash("aaa")
sval := common.HexToHash("bbb") sval := common.HexToHash("bbb")
state.SetBalance(addr, uint256.NewInt(42)) // Change the account trie state.SetBalance(addr, uint256.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++ {

@ -67,6 +67,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
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)
@ -86,7 +87,8 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
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)
} }
@ -104,7 +106,18 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
return receipts, allLogs, *usedGas, nil return receipts, allLogs, *usedGas, nil
} }
func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, statedb *state.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 *state.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(evm.GetVMContext(), 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)
@ -126,7 +139,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 {
@ -167,7 +180,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

@ -23,6 +23,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/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
@ -263,11 +264,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
mgvalU256, _ := uint256.FromBig(mgval) mgvalU256, _ := uint256.FromBig(mgval)
st.state.SubBalance(st.msg.From, mgvalU256) st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
return nil return nil
} }
@ -380,13 +385,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)
@ -402,6 +400,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
@ -456,7 +457,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
} else { } else {
fee := new(uint256.Int).SetUint64(st.gasUsed()) fee := new(uint256.Int).SetUint64(st.gasUsed())
fee.Mul(fee, effectiveTipU256) fee.Mul(fee, effectiveTipU256)
st.state.AddBalance(st.evm.Context.Coinbase, fee) st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
} }
return &ExecutionResult{ return &ExecutionResult{
@ -473,12 +474,21 @@ 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 := uint256.NewInt(st.gasRemaining) remaining := uint256.NewInt(st.gasRemaining)
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice)) remaining.Mul(remaining, uint256.MustFromBig(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.

@ -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() *uint256.Int
CallInput() []byte
}
// StateDB gives tracers access to the whole state.
type StateDB interface {
GetBalance(common.Address) *uint256.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
)

@ -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/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -545,19 +546,19 @@ 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), uint256.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(gapper.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(dangler.PublicKey), uint256.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(dangler.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(filler.PublicKey), uint256.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(filler.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.SetNonce(crypto.PubkeyToAddress(filler.PublicKey), 3) statedb.SetNonce(crypto.PubkeyToAddress(filler.PublicKey), 3)
statedb.AddBalance(crypto.PubkeyToAddress(overlapper.PublicKey), uint256.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(overlapper.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.SetNonce(crypto.PubkeyToAddress(overlapper.PublicKey), 2) statedb.SetNonce(crypto.PubkeyToAddress(overlapper.PublicKey), 2)
statedb.AddBalance(crypto.PubkeyToAddress(underpayer.PublicKey), uint256.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(underpayer.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(outpricer.PublicKey), uint256.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(outpricer.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(exceeder.PublicKey), uint256.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(exceeder.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(overdrafter.PublicKey), uint256.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(overdrafter.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(overcapper.PublicKey), uint256.NewInt(10000000)) statedb.AddBalance(crypto.PubkeyToAddress(overcapper.PublicKey), uint256.NewInt(10000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(duplicater.PublicKey), uint256.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(duplicater.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(crypto.PubkeyToAddress(repeater.PublicKey), uint256.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(repeater.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified)
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
@ -676,7 +677,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, uint256.NewInt(1_000_000_000)) statedb.AddBalance(addr, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
@ -776,9 +777,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, uint256.NewInt(1_000_000_000)) statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000)) statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000)) statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
@ -856,9 +857,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, uint256.NewInt(1_000_000_000)) statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000)) statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000)) statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.Commit(0, true) statedb.Commit(0, true)
chain := &testBlockChain{ chain := &testBlockChain{
@ -1272,7 +1273,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 account // Seed the state database with this account
statedb.AddBalance(addrs[acc], new(uint256.Int).SetUint64(seed.balance)) statedb.AddBalance(addrs[acc], new(uint256.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
@ -1352,7 +1353,7 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
statedb.AddBalance(addr, uint256.NewInt(1_000_000_000)) statedb.AddBalance(addr, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
pool.add(tx) pool.add(tx)
} }
statedb.Commit(0, true) statedb.Commit(0, true)

@ -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"
@ -50,7 +51,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), uint256.NewInt(10000000000)) pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.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))
@ -92,7 +93,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), uint256.NewInt(100000000000)) pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.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))
@ -129,7 +130,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), uint256.NewInt(100000000000)) pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.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))
@ -183,7 +184,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), uint256.NewInt(100000000000)) pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.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)
} }
@ -191,7 +192,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), uint256.NewInt(100000000000)) pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.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))
} }
@ -228,7 +229,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), uint256.NewInt(100000000000)) pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified)
futureTxs := types.Transactions{} futureTxs := types.Transactions{}
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {

@ -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/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -253,7 +254,7 @@ func (c *testChain) State() (*state.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(uint256.Int).SetUint64(params.Ether)) c.statedb.SetBalance(c.address, new(uint256.Int).SetUint64(params.Ether), tracing.BalanceChangeUnspecified)
*c.trigger = false *c.trigger = false
} }
return stdb, nil return stdb, nil
@ -273,7 +274,7 @@ func TestStateChangeDuringReset(t *testing.T) {
) )
// setup pool with 2 transaction in it // setup pool with 2 transaction in it
statedb.SetBalance(address, new(uint256.Int).SetUint64(params.Ether)) statedb.SetBalance(address, new(uint256.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)
@ -307,7 +308,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, uint256.MustFromBig(amount)) pool.currentState.AddBalance(addr, uint256.MustFromBig(amount), tracing.BalanceChangeUnspecified)
pool.mu.Unlock() pool.mu.Unlock()
} }
@ -468,7 +469,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, uint256.NewInt(100000000000000)) statedb.AddBalance(addr, uint256.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)
@ -497,7 +498,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, uint256.NewInt(100000000000000)) statedb.AddBalance(addr, uint256.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)
@ -2660,7 +2661,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, uint256.NewInt(1000000)) pool.currentState.AddBalance(account, uint256.NewInt(1000000), tracing.BalanceChangeUnspecified)
tx := transaction(uint64(0), 100000, key) tx := transaction(uint64(0), 100000, key)
batches[i] = tx batches[i] = tx
} }

@ -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/holiman/uint256" "github.com/holiman/uint256"
) )
@ -157,14 +158,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()

@ -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"
"github.com/ethereum/go-ethereum/crypto/bls12381" "github.com/ethereum/go-ethereum/crypto/bls12381"
@ -168,11 +169,14 @@ 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, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, logger *tracing.Hooks) (ret []byte, remainingGas uint64, err error) {
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(input) output, err := p.Run(input)
return output, suppliedGas, err return output, suppliedGas, err

@ -36,7 +36,7 @@ func FuzzPrecompiledContracts(f *testing.F) {
return return
} }
inWant := string(input) inWant := string(input)
RunPrecompiledContract(p, input, gas) RunPrecompiledContract(p, input, gas, 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)
} }

@ -98,7 +98,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 := RunPrecompiledContract(p, in, gas); err != nil { if res, _, err := RunPrecompiledContract(p, in, gas, 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))
@ -120,7 +120,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 := RunPrecompiledContract(p, in, gas) _, _, err := RunPrecompiledContract(p, in, gas, 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)
} }
@ -137,7 +137,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 := RunPrecompiledContract(p, in, gas) _, _, err := RunPrecompiledContract(p, in, gas, 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)
} }
@ -169,7 +169,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 = RunPrecompiledContract(p, data, reqGas) res, _, err = RunPrecompiledContract(p, data, reqGas, nil)
} }
bench.StopTimer() bench.StopTimer()
elapsed := uint64(time.Since(start)) elapsed := uint64(time.Since(start))

@ -19,6 +19,7 @@ package vm
import ( import (
"errors" "errors"
"fmt" "fmt"
"math"
) )
// List evm execution errors // List evm execution errors
@ -70,3 +71,122 @@ 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
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, 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
}
}

@ -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"
@ -177,6 +179,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 *uint256.Int) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.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.ToBig())
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
@ -187,44 +196,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.IsZero() { if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
// 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.ToBig())
evm.Config.Tracer.CaptureEnd(ret, 0, nil)
} else {
evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value.ToBig())
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.ToBig())
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.ToBig())
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas)
}
}
if isPrecompile { if isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas) ret, gas, err = RunPrecompiledContract(p, input, gas, 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.
@ -242,11 +225,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:
@ -264,6 +251,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 *uint256.Int) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.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.ToBig())
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
@ -277,17 +271,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.ToBig())
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, input, gas) ret, gas, err = RunPrecompiledContract(p, input, gas, 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.
@ -300,6 +286,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
} }
} }
@ -312,27 +302,26 @@ 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.ToBig()) evm.captureBegin(evm.depth, DELEGATECALL, caller.Address(), addr, input, gas, parent.value.ToBig())
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 {
ret, gas, err = RunPrecompiledContract(p, input, gas) ret, gas, err = RunPrecompiledContract(p, input, gas, 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
@ -344,6 +333,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
} }
} }
@ -355,6 +347,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
@ -370,18 +369,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, new(uint256.Int)) evm.StateDB.AddBalance(addr, new(uint256.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, input, gas) ret, gas, err = RunPrecompiledContract(p, input, gas, 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'
@ -400,6 +391,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
} }
} }
@ -419,7 +414,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 *uint256.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) { func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.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.ToBig())
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) {
@ -441,6 +442,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
@ -456,15 +461,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.ToBig())
} else {
evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value.ToBig())
}
}
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 {
@ -482,7 +479,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
@ -490,22 +487,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
} }
@ -527,3 +517,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,
}
}

@ -20,6 +20,7 @@ import (
"math" "math"
"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"
@ -249,7 +250,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
} }
@ -590,7 +590,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)
res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract, input, gas, &value) res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract, input, gas, &value)
// Push item on the stack based on the returned error. If the ruleset is // Push item on the stack based on the returned error. If the ruleset is
@ -605,7 +605,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
@ -628,7 +629,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
res, addr, returnGas, suberr := interpreter.evm.Create2(scope.Contract, input, gas, res, addr, returnGas, suberr := interpreter.evm.Create2(scope.Contract, input, gas,
@ -640,7 +641,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
@ -679,7 +681,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
@ -711,7 +714,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
@ -739,7 +743,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
@ -767,7 +772,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
@ -802,11 +808,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.ToBig()) if tracer.OnEnter != nil {
tracer.CaptureExit([]byte{}, 0, nil) tracer.OnEnter(interpreter.evm.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig())
}
if tracer.OnExit != nil {
tracer.OnExit(interpreter.evm.depth, []byte{}, 0, nil, false)
}
} }
return nil, errStopToken return nil, errStopToken
} }
@ -817,12 +827,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.ToBig()) if tracer.OnEnter != nil {
tracer.CaptureExit([]byte{}, 0, nil) tracer.OnEnter(interpreter.evm.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig())
}
if tracer.OnExit != nil {
tracer.OnExit(interpreter.evm.depth, []byte{}, 0, nil, false)
}
} }
return nil, errStopToken return nil, errStopToken
} }

@ -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"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -29,8 +30,8 @@ import (
type StateDB interface { type StateDB interface {
CreateAccount(common.Address) CreateAccount(common.Address)
SubBalance(common.Address, *uint256.Int) SubBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason)
AddBalance(common.Address, *uint256.Int) AddBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason)
GetBalance(common.Address) *uint256.Int GetBalance(common.Address) *uint256.Int
GetNonce(common.Address) uint64 GetNonce(common.Address) uint64

@ -19,13 +19,15 @@ package vm
import ( 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/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
@ -39,6 +41,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() *uint256.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 +187,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 gets 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 +196,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 +228,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 +255,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 {
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 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 {
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 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 {

@ -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)
}

@ -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
} }
} }

@ -123,6 +123,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 != 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)
@ -156,6 +159,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 != 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)
@ -184,6 +190,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 != 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)

@ -336,7 +336,7 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode
b.Fatal(err) b.Fatal(err)
} }
cfg.EVMConfig = vm.Config{ cfg.EVMConfig = vm.Config{
Tracer: tracer, Tracer: tracer.Hooks,
} }
} }
var ( var (
@ -511,7 +511,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},
}, },
}) })
@ -664,7 +664,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
@ -812,7 +812,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),
} }
@ -825,7 +825,7 @@ 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 := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil)
if err != nil { if err != nil {
@ -835,7 +835,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)
@ -869,7 +869,7 @@ func TestJSTracerCreateTx(t *testing.T) {
_, _, _, 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)

@ -420,6 +420,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, *state.StateDB, tracers.StateReleaseFunc, error) { func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec) return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
} }

@ -27,6 +27,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/triedb" "github.com/ethereum/go-ethereum/triedb"
@ -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], uint256.NewInt(1)) sdb.SetBalance(addrs[i], uint256.NewInt(1), tracing.BalanceChangeUnspecified)
if _, ok := m[addr]; ok { if _, ok := m[addr]; ok {
t.Fatalf("bad") t.Fatalf("bad")
} else { } else {

@ -18,6 +18,7 @@
package eth package eth
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
@ -42,6 +43,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"
"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/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
@ -199,6 +201,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 := tracers.LiveDirectory.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 {

@ -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:"-"`

@ -217,7 +217,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, *state.StateDB, tracers.StateReleaseFunc, error) { func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.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")
@ -244,7 +244,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{})

@ -22,6 +22,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"math/big"
"os" "os"
"runtime" "runtime"
"sync" "sync"
@ -86,7 +87,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 *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, StateReleaseFunc, error) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, StateReleaseFunc, error)
StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, StateReleaseFunc, error) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.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.
@ -277,14 +278,12 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
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
@ -598,7 +597,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))
@ -612,14 +610,11 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
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
} }
@ -659,7 +654,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
// concurrent use. // concurrent use.
// See: https://github.com/ethereum/go-ethereum/issues/29114 // See: https://github.com/ethereum/go-ethereum/issues/29114
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) 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
@ -794,7 +789,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()
} }
@ -851,11 +848,15 @@ 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 := &Context{
BlockHash: blockHash, BlockHash: blockHash,
@ -863,7 +864,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
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
@ -924,40 +925,49 @@ 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(), vmctx.BaseFee) if err := args.CallDefaults(api.backend.RPCGasCap(), vmctx.BaseFee, api.backend.ChainConfig().ChainID); err != nil {
if err != nil {
return nil, err return nil, err
} }
var (
var traceConfig *TraceConfig msg = args.ToMessage(vmctx.BaseFee)
tx = args.ToTransaction()
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(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 *state.StateDB, config *TraceConfig) (interface{}, error) { func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
var ( var (
tracer Tracer tracer *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) if config.Tracer == nil {
if config.Tracer != nil { logger := logger.NewStructLogger(config.Config)
tracer = &Tracer{
Hooks: logger.Hooks(),
GetResult: logger.GetResult,
Stop: logger.Stop,
}
} else {
tracer, err = DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig) tracer, err = 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 {
@ -978,7 +988,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()

@ -155,7 +155,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, *state.StateDB, StateReleaseFunc, error) { func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.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
@ -174,7 +174,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 {
@ -666,7 +666,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))},

@ -14,17 +14,14 @@
// 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 tracers package tracers
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 +33,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 +78,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 +96,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
}

@ -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' })
```

@ -18,6 +18,7 @@ package tracetest
import ( import (
"encoding/json" "encoding/json"
"fmt"
"math/big" "math/big"
"os" "os"
"path/filepath" "path/filepath"
@ -31,6 +32,7 @@ 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/crypto"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
@ -141,15 +143,19 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }
state.StateDB.SetLogger(tracer.Hooks)
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee) 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), state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer}) evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state.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 {
@ -245,7 +251,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
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, state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer}) evm := vm.NewEVM(context, txContext, state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
snap := state.StateDB.Snapshot() snap := state.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 {
@ -260,12 +266,12 @@ 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,
@ -274,9 +280,10 @@ 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) *tracers.Tracer {
tr, err := tracers.DefaultDirectory.New(name, nil, cfg) tr, err := tracers.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)
@ -287,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 *tracers.Tracer
want string want string
}{ }{
{ {
@ -301,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",
@ -320,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
@ -339,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
@ -358,7 +365,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":"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) {
@ -372,22 +379,31 @@ func TestInternals(t *testing.T) {
}, },
}, false, rawdb.HashScheme) }, false, rawdb.HashScheme)
defer state.Close() defer state.Close()
state.StateDB.SetLogger(tc.tracer.Hooks)
evm := vm.NewEVM(context, txContext, state.StateDB, params.MainnetChainConfig, vm.Config{Tracer: tc.tracer}) tx, err := types.SignNewTx(key, signer, &types.LegacyTx{
msg := &core.Message{
To: &to, To: &to,
From: origin,
Value: big.NewInt(0), Value: big.NewInt(0),
GasLimit: 80000, Gas: 80000,
GasPrice: big.NewInt(0), GasPrice: big.NewInt(1),
GasFeeCap: big.NewInt(0), })
GasTipCap: big.NewInt(0), if err != nil {
SkipAccountChecks: false, t.Fatalf("test %v: failed to sign transaction: %v", tc.name, err)
} }
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, state.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 {

@ -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"
"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.
@ -103,16 +101,19 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string
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)
} }
state.StateDB.SetLogger(tracer.Hooks)
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee) 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)
} }
evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer}) evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if _, err = st.TransitionDb(); err != nil { 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()
@ -124,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, "", " ")

@ -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));
}

@ -117,15 +117,19 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }
state.StateDB.SetLogger(tracer.Hooks)
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee) 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), state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer}) evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) 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 {
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 {

@ -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"
}]
} }
} }

@ -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"
} }
] ]
} }

@ -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"
} }
] ]
} }

@ -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"
} }
] ]
} }

@ -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

@ -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"
}
}
}

@ -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, "_")

@ -0,0 +1,81 @@
// 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 internal
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
}
// MemoryPtr returns a pointer to a slice of memory.
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]
}

@ -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 internal
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)
}
}
}

@ -23,12 +23,16 @@ 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"
"github.com/ethereum/go-ethereum/eth/tracers/internal"
"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,9 +45,9 @@ 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(*tracers.Context, json.RawMessage) (*tracers.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 *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) {
return newJsTracer(code, ctx, cfg) return newJsTracer(code, ctx, cfg)
} }
} }
@ -96,7 +100,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 +108,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 +137,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 *tracers.Context, cfg json.RawMessage) (*tracers.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())
@ -217,30 +220,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
}
// CaptureTxStart implements the Tracer interface and is invoked at the beginning of return &tracers.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
}
// 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 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}
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
} }
// CaptureTxEnd implements the Tracer interface and is invoked at the end of // OnTxEnd implements the Tracer interface and is invoked at the end of
// transaction processing. // transaction processing.
func (t *jsTracer) CaptureTxEnd(restGas uint64) { func (t *jsTracer) OnTxEnd(receipt *types.Receipt, err error) {
t.ctx["gasUsed"] = t.vm.ToValue(t.gasLimit - restGas) 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)
} }
// CaptureStart implements the Tracer interface to initialize the tracing operation. // onStart 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) { func (t *jsTracer) onStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
cancel := func(err error) { if t.err != nil {
t.err = err return
t.env.Cancel()
} }
t.env = env
db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf}
t.dbValue = db.setupObject()
if create { if create {
t.ctx["type"] = t.vm.ToValue("CREATE") t.ctx["type"] = t.vm.ToValue("CREATE")
} else { } else {
@ -248,43 +283,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 +317,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 +332,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 +360,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 { if t.err != nil {
return return
} }
if t.err != nil { if depth == 0 {
t.onStart(from, to, vm.OpCode(typ) == vm.CREATE, input, gas, value)
return
}
if !t.traceFrame {
return 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 +388,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 +413,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 +425,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 +438,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 +616,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 +644,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 := internal.GetMemoryCopyPadded(mo.memory, begin, end-begin)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -629,14 +667,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(internal.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 +686,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 +707,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 internal.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 +725,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 +817,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 +834,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 +844,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 +854,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)

@ -26,6 +26,7 @@ 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"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -61,9 +62,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 *tracers.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 = uint256.NewInt(0) value = uint256.NewInt(0)
@ -74,12 +75,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.ToBig()) tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value.ToBig())
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
} }
@ -181,15 +182,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{}, uint256.NewInt(0), 0), Contract: vm.NewContract(&account{}, &account{}, uint256.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)
@ -205,9 +207,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)
@ -276,8 +279,8 @@ func TestEnterExit(t *testing.T) {
scope := &vm.ScopeContext{ scope := &vm.ScopeContext{
Contract: vm.NewContract(&account{}, &account{}, uint256.NewInt(0), 0), Contract: vm.NewContract(&account{}, &account{}, uint256.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 {

@ -0,0 +1,31 @@
package tracers
import (
"encoding/json"
"errors"
"github.com/ethereum/go-ethereum/core/tracing"
)
type ctorFunc func(config json.RawMessage) (*tracing.Hooks, error)
// LiveDirectory is the collection of tracers which can be used
// during normal block import operations.
var LiveDirectory = liveDirectory{elems: make(map[string]ctorFunc)}
type liveDirectory struct {
elems map[string]ctorFunc
}
// Register registers a tracer constructor by name.
func (d *liveDirectory) Register(name string, f ctorFunc) {
d.elems[name] = f
}
// New instantiates a tracer by name.
func (d *liveDirectory) 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")
}

@ -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"
"github.com/ethereum/go-ethereum/params"
)
func init() {
tracers.LiveDirectory.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) {
}

@ -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()

@ -28,6 +28,7 @@ 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/params" "github.com/ethereum/go-ethereum/params"
@ -108,13 +109,12 @@ 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
@ -132,6 +132,15 @@ 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,
}
}
// 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 +149,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 +162,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
} }
} contractAddr := scope.Address()
stackData := stack.Data() stackLen := len(stack)
stackLen := len(stackData)
// 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 +211,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 +230,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 +256,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 +330,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 +343,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 +378,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 +406,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

@ -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,
func (l *JSONLogger) CaptureStart(env *vm.EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { OnOpcode: l.OnOpcode,
l.env = env OnFault: l.OnFault,
}
} }
func (l *JSONLogger) CaptureFault(pc uint64, op vm.OpCode, gas uint64, cost uint64, scope *vm.ScopeContext, depth int, err error) { func (l *jsonLogger) OnFault(pc uint64, op byte, gas uint64, cost uint64, scope tracing.OpContext, 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) {}

@ -56,12 +56,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(uint256.Int), 100000) contract = vm.NewContract(&dummyContractRef{}, &dummyContractRef{}, new(uint256.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)

@ -23,6 +23,8 @@ 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"
) )
@ -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 *tracers.Context, _ json.RawMessage) (*tracers.Tracer, error) {
t := &fourByteTracer{ t := &fourByteTracer{
ids: make(map[string]int), ids: make(map[string]int),
} }
return t, nil return &tracers.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 {

@ -25,9 +25,10 @@ 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"
"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
@ -60,6 +61,7 @@ type callFrame struct {
// 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 *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) {
t, err := newCallTracerObject(ctx, cfg)
if err != nil {
return nil, err
}
return &tracers.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 *tracers.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,84 +147,13 @@ 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.
func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
toCopy := to
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 > 1 {
return
}
// Skip if tracing was interrupted
if t.interrupt.Load() {
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). // OnEnter 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) { func (t *callTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if t.config.OnlyTopCall { t.depth = depth
if t.config.OnlyTopCall && depth > 0 {
return return
} }
// Skip if tracing was interrupted // Skip if tracing was interrupted
@ -213,48 +163,92 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
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) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
t.gasLimit = tx.Gas()
} }
func (t *callTracer) CaptureTxEnd(restGas uint64) { func (t *callTracer) OnTxEnd(receipt *types.Receipt, err error) {
t.callstack[0].GasUsed = t.gasLimit - restGas // 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.

@ -25,6 +25,8 @@ 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"
) )
@ -112,7 +114,7 @@ type flatCallTracer struct {
config flatCallTracerConfig config flatCallTracerConfig
ctx *tracers.Context // Holds tracer context data ctx *tracers.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 *tracers.Context, cfg json.RawMessage) (*tracers.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 {
return nil, errors.New("internal error: embedded tracer has wrong type")
}
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. ft := &flatCallTracer{tracer: t, ctx: ctx, config: config}
func (t *flatCallTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { return &tracers.Tracer{
t.tracer.CaptureState(pc, op, gas, cost, scope, rData, depth, err) Hooks: &tracing.Hooks{
} OnTxStart: ft.OnTxStart,
OnTxEnd: ft.OnTxEnd,
// CaptureFault implements the EVMLogger interface to trace an execution fault. OnEnter: ft.OnEnter,
func (t *flatCallTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { OnExit: ft.OnExit,
t.tracer.CaptureFault(pc, op, gas, cost, scope, depth, err) },
Stop: ft.Stop,
GetResult: ft.GetResult,
}, nil
} }
// 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 *flatCallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *flatCallTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
t.tracer.CaptureEnter(typ, from, to, input, gas, value) t.tracer.OnEnter(depth, typ, from, to, input, gas, value)
if depth == 0 {
return
}
// 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.

@ -21,7 +21,8 @@ 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/core/types"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
) )
@ -33,18 +34,18 @@ func init() {
// 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 []*tracers.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 *tracers.Context, cfg json.RawMessage) (*tracers.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([]*tracers.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 := tracers.DefaultDirectory.New(k, ctx, v)
@ -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 &tracers.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)
}
}
}
func (t *muxTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) {
for _, t := range t.tracers {
if t.OnFault != nil {
t.OnFault(pc, op, gas, cost, scope, depth, err)
}
}
}
func (t *muxTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {
for _, t := range t.tracers {
if t.OnGasChange != nil {
t.OnGasChange(old, new, reason)
}
}
}
func (t *muxTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
for _, t := range t.tracers {
if t.OnEnter != nil {
t.OnEnter(depth, typ, from, to, input, gas, value)
}
}
}
func (t *muxTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
for _, t := range t.tracers {
if t.OnExit != nil {
t.OnExit(depth, output, gasUsed, err, reverted)
}
} }
} }
// CaptureEnd is called after the call finishes to finalize the tracing. func (t *muxTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
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.OnTxStart != nil {
t.OnTxStart(env, tx, from)
}
} }
} }
// CaptureState implements the EVMLogger interface to trace a single step of VM execution. func (t *muxTracer) OnTxEnd(receipt *types.Receipt, err error) {
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.OnTxEnd != nil {
t.OnTxEnd(receipt, err)
}
} }
} }
// CaptureFault implements the EVMLogger interface to trace an execution fault. func (t *muxTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) {
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.OnBalanceChange != nil {
t.OnBalanceChange(a, prev, new, reason)
}
} }
} }
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). func (t *muxTracer) OnNonceChange(a common.Address, prev, new uint64) {
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.OnNonceChange != nil {
t.OnNonceChange(a, prev, new)
}
} }
} }
// CaptureExit is called when EVM exits a scope, even if the scope didn't func (t *muxTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
// 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.OnCodeChange != nil {
t.OnCodeChange(a, prevCodeHash, prev, codeHash, code)
}
} }
} }
func (t *muxTracer) CaptureTxStart(gasLimit uint64) { func (t *muxTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureTxStart(gasLimit) if t.OnStorageChange != nil {
t.OnStorageChange(a, k, prev, new)
}
} }
} }
func (t *muxTracer) CaptureTxEnd(restGas uint64) { func (t *muxTracer) OnLog(log *types.Log) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureTxEnd(restGas) if t.OnLog != nil {
t.OnLog(log)
}
} }
} }

@ -21,7 +21,8 @@ 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/core/types"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
) )
@ -34,38 +35,58 @@ func init() {
type noopTracer struct{} type noopTracer struct{}
// newNoopTracer returns a new noop tracer. // newNoopTracer returns a new noop tracer.
func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (*tracers.Tracer, error) {
return &noopTracer{}, nil t := &noopTracer{}
return &tracers.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 *noopTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
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) OnFault(pc uint64, op byte, gas, cost uint64, _ tracing.OpContext, depth int, err error) {
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) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {}
func (t *noopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
func (t *noopTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
} }
// CaptureFault implements the EVMLogger interface to trace an execution fault. func (t *noopTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
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 (*noopTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
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 func (*noopTracer) OnTxEnd(receipt *types.Receipt, err error) {}
// execute any code.
func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, 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) CaptureTxStart(gasLimit uint64) {} func (*noopTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {}
func (*noopTracer) CaptureTxEnd(restGas uint64) {} func (*noopTracer) OnLog(log *types.Log) {}
// GetResult returns an empty json object. // GetResult returns an empty json object.
func (t *noopTracer) GetResult() (json.RawMessage, error) { func (t *noopTracer) GetResult() (json.RawMessage, error) {

@ -24,11 +24,13 @@ 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/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/internal"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
) )
//go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go //go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go
@ -37,13 +39,14 @@ func init() {
tracers.DefaultDirectory.Register("prestateTracer", newPrestateTracer, false) tracers.DefaultDirectory.Register("prestateTracer", newPrestateTracer, false)
} }
type state = map[common.Address]*account type stateMap = map[common.Address]*account
type account struct { type account struct {
Balance *big.Int `json:"balance,omitempty"` Balance *big.Int `json:"balance,omitempty"`
Code []byte `json:"code,omitempty"` Code []byte `json:"code,omitempty"`
Nonce uint64 `json:"nonce,omitempty"` Nonce uint64 `json:"nonce,omitempty"`
Storage map[common.Hash]common.Hash `json:"storage,omitempty"` Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
empty bool
} }
func (a *account) exists() bool { func (a *account) exists() bool {
@ -56,13 +59,10 @@ type accountMarshaling struct {
} }
type prestateTracer struct { type prestateTracer struct {
noopTracer env *tracing.VMContext
env *vm.EVM pre stateMap
pre state post stateMap
post state
create bool
to common.Address to common.Address
gasLimit uint64 // Amount of gas bought for the whole tx
config prestateTracerConfig config prestateTracerConfig
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
@ -74,76 +74,33 @@ type prestateTracerConfig struct {
DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications
} }
func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) {
var config prestateTracerConfig var config prestateTracerConfig
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
} }
} }
return &prestateTracer{ t := &prestateTracer{
pre: state{}, pre: stateMap{},
post: state{}, post: stateMap{},
config: config, config: config,
created: make(map[common.Address]bool), created: make(map[common.Address]bool),
deleted: make(map[common.Address]bool), deleted: make(map[common.Address]bool),
}, nil
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
t.env = env
t.create = create
t.to = to
t.lookupAccount(from)
t.lookupAccount(to)
t.lookupAccount(env.Context.Coinbase)
// The recipient balance includes the value transferred.
toBal := new(big.Int).Sub(t.pre[to].Balance, value)
t.pre[to].Balance = toBal
if env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time).IsEIP158 && create {
t.pre[to].Nonce--
}
// The sender balance is after reducing: value and gasLimit.
// We need to re-add them to get the pre-tx balance.
fromBal := new(big.Int).Set(t.pre[from].Balance)
gasPrice := env.TxContext.GasPrice
consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit))
fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
// Add blob fee to the sender's balance.
if env.Context.BlobBaseFee != nil && len(env.TxContext.BlobHashes) > 0 {
blobGas := uint64(params.BlobTxBlobGasPerBlob * len(env.TxContext.BlobHashes))
fromBal.Add(fromBal, new(big.Int).Mul(env.Context.BlobBaseFee, new(big.Int).SetUint64(blobGas)))
}
t.pre[from].Balance = fromBal
t.pre[from].Nonce--
if create && t.config.DiffMode {
t.created[to] = true
}
}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
if t.config.DiffMode {
return
}
if t.create {
// Keep existing account prior to contract creation at that address
if s := t.pre[t.to]; s != nil && !s.exists() {
// Exclude newly created contract.
delete(t.pre, t.to)
}
} }
return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnOpcode: t.OnOpcode,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil
} }
// CaptureState implements the EVMLogger interface to trace a single step of VM execution. // OnOpcode implements the EVMLogger interface to trace a single step of VM execution.
func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (t *prestateTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
if err != nil { if err != nil {
return return
} }
@ -151,10 +108,10 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64,
if t.interrupt.Load() { if t.interrupt.Load() {
return return
} }
stack := scope.Stack op := vm.OpCode(opcode)
stackData := stack.Data() stackData := scope.StackData()
stackLen := len(stackData) stackLen := len(stackData)
caller := scope.Contract.Address() caller := scope.Address()
switch { switch {
case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE): case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE):
slot := common.Hash(stackData[stackLen-1].Bytes32()) slot := common.Hash(stackData[stackLen-1].Bytes32())
@ -176,7 +133,7 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64,
case stackLen >= 4 && op == vm.CREATE2: case stackLen >= 4 && op == vm.CREATE2:
offset := stackData[stackLen-2] offset := stackData[stackLen-2]
size := stackData[stackLen-3] size := stackData[stackLen-3]
init, err := tracers.GetMemoryCopyPadded(scope.Memory, int64(offset.Uint64()), int64(size.Uint64())) init, err := internal.GetMemoryCopyPadded(scope.MemoryData(), int64(offset.Uint64()), int64(size.Uint64()))
if err != nil { if err != nil {
log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "prestateTracer", "offset", offset, "size", size) log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "prestateTracer", "offset", offset, "size", size)
return return
@ -189,15 +146,62 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64,
} }
} }
func (t *prestateTracer) CaptureTxStart(gasLimit uint64) { func (t *prestateTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
t.gasLimit = gasLimit t.env = env
if tx.To() == nil {
t.to = crypto.CreateAddress(from, env.StateDB.GetNonce(from))
t.created[t.to] = true
} else {
t.to = *tx.To()
}
t.lookupAccount(from)
t.lookupAccount(t.to)
t.lookupAccount(env.Coinbase)
} }
func (t *prestateTracer) CaptureTxEnd(restGas uint64) { func (t *prestateTracer) OnTxEnd(receipt *types.Receipt, err error) {
if !t.config.DiffMode { if err != nil {
return return
} }
if t.config.DiffMode {
t.processDiffState()
}
// the new created contracts' prestate were empty, so delete them
for a := range t.created {
// the created contract maybe exists in statedb before the creating tx
if s := t.pre[a]; s != nil && s.empty {
delete(t.pre, a)
}
}
}
// GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`).
func (t *prestateTracer) GetResult() (json.RawMessage, error) {
var res []byte
var err error
if t.config.DiffMode {
res, err = json.Marshal(struct {
Post stateMap `json:"post"`
Pre stateMap `json:"pre"`
}{t.post, t.pre})
} else {
res, err = json.Marshal(t.pre)
}
if err != nil {
return nil, err
}
return json.RawMessage(res), t.reason
}
// Stop terminates execution of the tracer at the first opportune moment.
func (t *prestateTracer) Stop(err error) {
t.reason = err
t.interrupt.Store(true)
}
func (t *prestateTracer) processDiffState() {
for addr, state := range t.pre { for addr, state := range t.pre {
// The deleted account's state is pruned from `post` but kept in `pre` // The deleted account's state is pruned from `post` but kept in `pre`
if _, ok := t.deleted[addr]; ok { if _, ok := t.deleted[addr]; ok {
@ -247,38 +251,6 @@ func (t *prestateTracer) CaptureTxEnd(restGas uint64) {
delete(t.pre, addr) delete(t.pre, addr)
} }
} }
// the new created contracts' prestate were empty, so delete them
for a := range t.created {
// the created contract maybe exists in statedb before the creating tx
if s := t.pre[a]; s != nil && !s.exists() {
delete(t.pre, a)
}
}
}
// GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`).
func (t *prestateTracer) GetResult() (json.RawMessage, error) {
var res []byte
var err error
if t.config.DiffMode {
res, err = json.Marshal(struct {
Post state `json:"post"`
Pre state `json:"pre"`
}{t.post, t.pre})
} else {
res, err = json.Marshal(t.pre)
}
if err != nil {
return nil, err
}
return json.RawMessage(res), t.reason
}
// Stop terminates execution of the tracer at the first opportune moment.
func (t *prestateTracer) Stop(err error) {
t.reason = err
t.interrupt.Store(true)
} }
// lookupAccount fetches details of an account and adds it to the prestate // lookupAccount fetches details of an account and adds it to the prestate
@ -288,12 +260,16 @@ func (t *prestateTracer) lookupAccount(addr common.Address) {
return return
} }
t.pre[addr] = &account{ acc := &account{
Balance: t.env.StateDB.GetBalance(addr).ToBig(), Balance: t.env.StateDB.GetBalance(addr).ToBig(),
Nonce: t.env.StateDB.GetNonce(addr), Nonce: t.env.StateDB.GetNonce(addr),
Code: t.env.StateDB.GetCode(addr), Code: t.env.StateDB.GetCode(addr),
Storage: make(map[common.Hash]common.Hash), Storage: make(map[common.Hash]common.Hash),
} }
if !acc.exists() {
acc.empty = true
}
t.pre[addr] = acc
} }
// lookupStorage fetches the requested storage slot and adds // lookupStorage fetches the requested storage slot and adds

@ -89,7 +89,7 @@ func BenchmarkTransactionTrace(b *testing.B) {
//EnableMemory: false, //EnableMemory: false,
//EnableReturnData: false, //EnableReturnData: false,
}) })
evm := vm.NewEVM(context, txContext, state.StateDB, params.AllEthashProtocolChanges, vm.Config{Tracer: tracer}) evm := vm.NewEVM(context, txContext, state.StateDB, params.AllEthashProtocolChanges, vm.Config{Tracer: tracer.Hooks()})
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee) msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
if err != nil { if err != nil {
b.Fatalf("failed to prepare transaction for tracing: %v", err) b.Fatalf("failed to prepare transaction for tracing: %v", err)
@ -111,41 +111,3 @@ func BenchmarkTransactionTrace(b *testing.B) {
tracer.Reset() tracer.Reset()
} }
} }
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, 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)
}
}
}

@ -36,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"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"
@ -457,7 +458,7 @@ func (s *PersonalAccountAPI) signTransaction(ctx context.Context, args *Transact
return nil, err return nil, err
} }
// Assemble the transaction and sign with the wallet // Assemble the transaction and sign with the wallet
tx := args.toTransaction() tx := args.ToTransaction()
return wallet.SignTxWithPassphrase(account, passwd, tx, s.b.ChainConfig().ChainID) return wallet.SignTxWithPassphrase(account, passwd, tx, s.b.ChainConfig().ChainID)
} }
@ -506,7 +507,7 @@ func (s *PersonalAccountAPI) SignTransaction(ctx context.Context, args Transacti
return nil, errors.New("nonce not specified") return nil, errors.New("nonce not specified")
} }
// Before actually signing the transaction, ensure the transaction fee is reasonable. // Before actually signing the transaction, ensure the transaction fee is reasonable.
tx := args.toTransaction() tx := args.ToTransaction()
if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil { if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil {
return nil, err return nil, err
} }
@ -962,42 +963,42 @@ type OverrideAccount struct {
type StateOverride map[common.Address]OverrideAccount type StateOverride map[common.Address]OverrideAccount
// Apply overrides the fields of specified accounts into the given state. // Apply overrides the fields of specified accounts into the given state.
func (diff *StateOverride) Apply(state *state.StateDB) error { func (diff *StateOverride) Apply(statedb *state.StateDB) error {
if diff == nil { if diff == nil {
return nil return nil
} }
for addr, account := range *diff { for addr, account := range *diff {
// Override account nonce. // Override account nonce.
if account.Nonce != nil { if account.Nonce != nil {
state.SetNonce(addr, uint64(*account.Nonce)) statedb.SetNonce(addr, uint64(*account.Nonce))
} }
// Override account(contract) code. // Override account(contract) code.
if account.Code != nil { if account.Code != nil {
state.SetCode(addr, *account.Code) statedb.SetCode(addr, *account.Code)
} }
// Override account balance. // Override account balance.
if account.Balance != nil { if account.Balance != nil {
u256Balance, _ := uint256.FromBig((*big.Int)(*account.Balance)) u256Balance, _ := uint256.FromBig((*big.Int)(*account.Balance))
state.SetBalance(addr, u256Balance) statedb.SetBalance(addr, u256Balance, tracing.BalanceChangeUnspecified)
} }
if account.State != nil && account.StateDiff != nil { if account.State != nil && account.StateDiff != nil {
return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
} }
// Replace entire state if caller requires. // Replace entire state if caller requires.
if account.State != nil { if account.State != nil {
state.SetStorage(addr, *account.State) statedb.SetStorage(addr, *account.State)
} }
// Apply state diff into specified accounts. // Apply state diff into specified accounts.
if account.StateDiff != nil { if account.StateDiff != nil {
for key, value := range *account.StateDiff { for key, value := range *account.StateDiff {
state.SetState(addr, key, value) statedb.SetState(addr, key, value)
} }
} }
} }
// Now finalize the changes. Finalize is normally performed between transactions. // Now finalize the changes. Finalize is normally performed between transactions.
// By using finalize, the overrides are semantically behaving as // By using finalize, the overrides are semantically behaving as
// if they were created in a transaction just before the tracing occur. // if they were created in a transaction just before the tracing occur.
state.Finalise(false) statedb.Finalise(false)
return nil return nil
} }
@ -1097,10 +1098,10 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
if blockOverrides != nil { if blockOverrides != nil {
blockOverrides.Apply(&blockCtx) blockOverrides.Apply(&blockCtx)
} }
msg, err := args.ToMessage(globalGasCap, blockCtx.BaseFee) if err := args.CallDefaults(globalGasCap, blockCtx.BaseFee, b.ChainConfig().ChainID); err != nil {
if err != nil {
return nil, err return nil, err
} }
msg := args.ToMessage(blockCtx.BaseFee)
evm := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx) evm := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx)
// Wait for the context to be done and cancel the evm. Even if the // Wait for the context to be done and cancel the evm. Even if the
@ -1181,11 +1182,14 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
State: state, State: state,
ErrorRatio: estimateGasErrorRatio, ErrorRatio: estimateGasErrorRatio,
} }
// Run the gas estimation andwrap any revertals into a custom return if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID); err != nil {
call, err := args.ToMessage(gasCap, header.BaseFee) return 0, err
}
call := args.ToMessage(header.BaseFee)
if err != nil { if err != nil {
return 0, err return 0, err
} }
// Run the gas estimation andwrap any revertals into a custom return
estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap) estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap)
if err != nil { if err != nil {
if len(revert) > 0 { if len(revert) > 0 {
@ -1514,18 +1518,18 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
statedb := db.Copy() statedb := db.Copy()
// Set the accesslist to the last al // Set the accesslist to the last al
args.AccessList = &accessList args.AccessList = &accessList
msg, err := args.ToMessage(b.RPCGasCap(), header.BaseFee) msg := args.ToMessage(header.BaseFee)
if err != nil { if err != nil {
return nil, 0, nil, err return nil, 0, nil, err
} }
// Apply the transaction with the access list tracer // Apply the transaction with the access list tracer
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
config := vm.Config{Tracer: tracer, NoBaseFee: true} config := vm.Config{Tracer: tracer.Hooks(), NoBaseFee: true}
vmenv := b.GetEVM(ctx, msg, statedb, header, &config, nil) vmenv := b.GetEVM(ctx, msg, statedb, header, &config, nil)
res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)) res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit))
if err != nil { if err != nil {
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err) return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.ToTransaction().Hash(), err)
} }
if tracer.Equal(prevTracer) { if tracer.Equal(prevTracer) {
return accessList, res.UsedGas, res.Err, nil return accessList, res.UsedGas, res.Err, nil
@ -1794,7 +1798,7 @@ func (s *TransactionAPI) SendTransaction(ctx context.Context, args TransactionAr
return common.Hash{}, err return common.Hash{}, err
} }
// Assemble the transaction and sign with the wallet // Assemble the transaction and sign with the wallet
tx := args.toTransaction() tx := args.ToTransaction()
signed, err := wallet.SignTx(account, tx, s.b.ChainConfig().ChainID) signed, err := wallet.SignTx(account, tx, s.b.ChainConfig().ChainID)
if err != nil { if err != nil {
@ -1814,7 +1818,7 @@ func (s *TransactionAPI) FillTransaction(ctx context.Context, args TransactionAr
return nil, err return nil, err
} }
// Assemble the transaction and obtain rlp // Assemble the transaction and obtain rlp
tx := args.toTransaction() tx := args.ToTransaction()
data, err := tx.MarshalBinary() data, err := tx.MarshalBinary()
if err != nil { if err != nil {
return nil, err return nil, err
@ -1883,7 +1887,7 @@ func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionAr
return nil, err return nil, err
} }
// Before actually sign the transaction, ensure the transaction fee is reasonable. // Before actually sign the transaction, ensure the transaction fee is reasonable.
tx := args.toTransaction() tx := args.ToTransaction()
if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil { if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil {
return nil, err return nil, err
} }
@ -1931,7 +1935,7 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g
if err := sendArgs.setDefaults(ctx, s.b, false); err != nil { if err := sendArgs.setDefaults(ctx, s.b, false); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
matchTx := sendArgs.toTransaction() matchTx := sendArgs.ToTransaction()
// Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.
var price = matchTx.GasPrice() var price = matchTx.GasPrice()
@ -1961,7 +1965,7 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g
if gasLimit != nil && *gasLimit != 0 { if gasLimit != nil && *gasLimit != 0 {
sendArgs.Gas = gasLimit sendArgs.Gas = gasLimit
} }
signedTx, err := s.sign(sendArgs.from(), sendArgs.toTransaction()) signedTx, err := s.sign(sendArgs.from(), sendArgs.ToTransaction())
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }

@ -364,41 +364,71 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, b Backend) er
return nil return nil
} }
// ToMessage converts the transaction arguments to the Message type used by the // CallDefaults sanitizes the transaction arguments, often filling in zero values,
// core evm. This method is used in calls and traces that do not require a real // for the purpose of eth_call class of RPC methods.
// live transaction. func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int, chainID *big.Int) error {
func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (*core.Message, error) {
// Reject invalid combinations of pre- and post-1559 fee styles // Reject invalid combinations of pre- and post-1559 fee styles
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
} }
// Set sender address or use zero address if none specified. if args.ChainID == nil {
addr := args.from() args.ChainID = (*hexutil.Big)(chainID)
} else {
// Set default gas & gas price if none were set if have := (*big.Int)(args.ChainID); have.Cmp(chainID) != 0 {
return fmt.Errorf("chainId does not match node's (have=%v, want=%v)", have, chainID)
}
}
if args.Gas == nil {
gas := globalGasCap gas := globalGasCap
if gas == 0 { if gas == 0 {
gas = uint64(math.MaxUint64 / 2) gas = uint64(math.MaxUint64 / 2)
} }
if args.Gas != nil { args.Gas = (*hexutil.Uint64)(&gas)
gas = uint64(*args.Gas) } else {
if globalGasCap > 0 && globalGasCap < uint64(*args.Gas) {
log.Warn("Caller gas above allowance, capping", "requested", args.Gas, "cap", globalGasCap)
args.Gas = (*hexutil.Uint64)(&globalGasCap)
}
}
if args.Nonce == nil {
args.Nonce = new(hexutil.Uint64)
}
if args.Value == nil {
args.Value = new(hexutil.Big)
}
if baseFee == nil {
// If there's no basefee, then it must be a non-1559 execution
if args.GasPrice == nil {
args.GasPrice = new(hexutil.Big)
}
} else {
// A basefee is provided, necessitating 1559-type execution
if args.MaxFeePerGas == nil {
args.MaxFeePerGas = new(hexutil.Big)
}
if args.MaxPriorityFeePerGas == nil {
args.MaxPriorityFeePerGas = new(hexutil.Big)
}
} }
if globalGasCap != 0 && globalGasCap < gas { if args.BlobFeeCap == nil && args.BlobHashes != nil {
log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap) args.BlobFeeCap = new(hexutil.Big)
gas = globalGasCap
} }
return nil
}
// ToMessage converts the transaction arguments to the Message type used by the
// core evm. This method is used in calls and traces that do not require a real
// live transaction.
// Assumes that fields are not nil, i.e. setDefaults or CallDefaults has been called.
func (args *TransactionArgs) ToMessage(baseFee *big.Int) *core.Message {
var ( var (
gasPrice *big.Int gasPrice *big.Int
gasFeeCap *big.Int gasFeeCap *big.Int
gasTipCap *big.Int gasTipCap *big.Int
blobFeeCap *big.Int
) )
if baseFee == nil { if baseFee == nil {
// If there's no basefee, then it must be a non-1559 execution
gasPrice = new(big.Int)
if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt() gasPrice = args.GasPrice.ToInt()
}
gasFeeCap, gasTipCap = gasPrice, gasPrice gasFeeCap, gasTipCap = gasPrice, gasPrice
} else { } else {
// A basefee is provided, necessitating 1559-type execution // A basefee is provided, necessitating 1559-type execution
@ -408,14 +438,8 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (*
gasFeeCap, gasTipCap = gasPrice, gasPrice gasFeeCap, gasTipCap = gasPrice, gasPrice
} else { } else {
// User specified 1559 gas fields (or none), use those // User specified 1559 gas fields (or none), use those
gasFeeCap = new(big.Int)
if args.MaxFeePerGas != nil {
gasFeeCap = args.MaxFeePerGas.ToInt() gasFeeCap = args.MaxFeePerGas.ToInt()
}
gasTipCap = new(big.Int)
if args.MaxPriorityFeePerGas != nil {
gasTipCap = args.MaxPriorityFeePerGas.ToInt() gasTipCap = args.MaxPriorityFeePerGas.ToInt()
}
// Backfill the legacy gasPrice for EVM execution, unless we're all zeroes // Backfill the legacy gasPrice for EVM execution, unless we're all zeroes
gasPrice = new(big.Int) gasPrice = new(big.Int)
if gasFeeCap.BitLen() > 0 || gasTipCap.BitLen() > 0 { if gasFeeCap.BitLen() > 0 || gasTipCap.BitLen() > 0 {
@ -423,40 +447,29 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (*
} }
} }
} }
if args.BlobFeeCap != nil {
blobFeeCap = args.BlobFeeCap.ToInt()
} else if args.BlobHashes != nil {
blobFeeCap = new(big.Int)
}
value := new(big.Int)
if args.Value != nil {
value = args.Value.ToInt()
}
data := args.data()
var accessList types.AccessList var accessList types.AccessList
if args.AccessList != nil { if args.AccessList != nil {
accessList = *args.AccessList accessList = *args.AccessList
} }
msg := &core.Message{ return &core.Message{
From: addr, From: args.from(),
To: args.To, To: args.To,
Value: value, Value: (*big.Int)(args.Value),
GasLimit: gas, GasLimit: uint64(*args.Gas),
GasPrice: gasPrice, GasPrice: gasPrice,
GasFeeCap: gasFeeCap, GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap, GasTipCap: gasTipCap,
Data: data, Data: args.data(),
AccessList: accessList, AccessList: accessList,
BlobGasFeeCap: blobFeeCap, BlobGasFeeCap: (*big.Int)(args.BlobFeeCap),
BlobHashes: args.BlobHashes, BlobHashes: args.BlobHashes,
SkipAccountChecks: true, SkipAccountChecks: true,
} }
return msg, nil
} }
// toTransaction converts the arguments to a transaction. // ToTransaction converts the arguments to a transaction.
// This assumes that setDefaults has been called. // This assumes that setDefaults has been called.
func (args *TransactionArgs) toTransaction() *types.Transaction { func (args *TransactionArgs) ToTransaction() *types.Transaction {
var data types.TxData var data types.TxData
switch { switch {
case args.BlobHashes != nil: case args.BlobHashes != nil:

@ -265,7 +265,7 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*
snap = env.state.Snapshot() snap = env.state.Snapshot()
gp = env.gasPool.Gas() gp = env.gasPool.Gas()
) )
receipt, err := core.ApplyTransaction(miner.chainConfig, miner.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *miner.chain.GetVMConfig()) receipt, err := core.ApplyTransaction(miner.chainConfig, miner.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vm.Config{})
if err != nil { if err != nil {
env.state.RevertToSnapshot(snap) env.state.RevertToSnapshot(snap)
env.gasPool.SetGas(gp) env.gasPool.SetGas(gp)

@ -34,6 +34,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/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/log" "github.com/ethereum/go-ethereum/log"
@ -109,7 +110,7 @@ type btHeaderMarshaling struct {
ExcessBlobGas *math.HexOrDecimal64 ExcessBlobGas *math.HexOrDecimal64
} }
func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, postCheck func(error, *core.BlockChain)) (result error) { func (t *BlockTest) Run(snapshotter bool, scheme string, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) {
config, ok := Forks[t.json.Network] config, ok := Forks[t.json.Network]
if !ok { if !ok {
return UnsupportedForkError{t.json.Network} return UnsupportedForkError{t.json.Network}

@ -33,6 +33,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/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -227,15 +228,15 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bo
// RunNoVerify runs a specific subtest and returns the statedb and post-state root. // RunNoVerify runs a specific subtest and returns the statedb and post-state root.
// Remember to call state.Close after verifying the test result! // Remember to call state.Close after verifying the test result!
func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool, scheme string) (state StateTestState, root common.Hash, err error) { func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool, scheme string) (st StateTestState, root common.Hash, err error) {
config, eips, err := GetChainConfig(subtest.Fork) config, eips, err := GetChainConfig(subtest.Fork)
if err != nil { if err != nil {
return state, common.Hash{}, UnsupportedForkError{subtest.Fork} return st, common.Hash{}, UnsupportedForkError{subtest.Fork}
} }
vmconfig.ExtraEips = eips vmconfig.ExtraEips = eips
block := t.genesis(config).ToBlock() block := t.genesis(config).ToBlock()
state = MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter, scheme) st = MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter, scheme)
var baseFee *big.Int var baseFee *big.Int
if config.IsLondon(new(big.Int)) { if config.IsLondon(new(big.Int)) {
@ -249,7 +250,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
post := t.json.Post[subtest.Fork][subtest.Index] post := t.json.Post[subtest.Fork][subtest.Index]
msg, err := t.json.Tx.toMessage(post, baseFee) msg, err := t.json.Tx.toMessage(post, baseFee)
if err != nil { if err != nil {
return state, common.Hash{}, err return st, common.Hash{}, err
} }
{ // Blob transactions may be present after the Cancun fork. { // Blob transactions may be present after the Cancun fork.
@ -259,7 +260,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
// Here, we just do this shortcut smaller fix, since state tests do not // Here, we just do this shortcut smaller fix, since state tests do not
// utilize those codepaths // utilize those codepaths
if len(msg.BlobHashes)*params.BlobTxBlobGasPerBlob > params.MaxBlobGasPerBlock { if len(msg.BlobHashes)*params.BlobTxBlobGasPerBlob > params.MaxBlobGasPerBlock {
return state, common.Hash{}, errors.New("blob gas exceeds maximum") return st, common.Hash{}, errors.New("blob gas exceeds maximum")
} }
} }
@ -268,10 +269,10 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
var ttx types.Transaction var ttx types.Transaction
err := ttx.UnmarshalBinary(post.TxBytes) err := ttx.UnmarshalBinary(post.TxBytes)
if err != nil { if err != nil {
return state, common.Hash{}, err return st, common.Hash{}, err
} }
if _, err := types.Sender(types.LatestSigner(config), &ttx); err != nil { if _, err := types.Sender(types.LatestSigner(config), &ttx); err != nil {
return state, common.Hash{}, err return st, common.Hash{}, err
} }
} }
@ -292,26 +293,26 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
if config.IsCancun(new(big.Int), block.Time()) && t.json.Env.ExcessBlobGas != nil { if config.IsCancun(new(big.Int), block.Time()) && t.json.Env.ExcessBlobGas != nil {
context.BlobBaseFee = eip4844.CalcBlobFee(*t.json.Env.ExcessBlobGas) context.BlobBaseFee = eip4844.CalcBlobFee(*t.json.Env.ExcessBlobGas)
} }
evm := vm.NewEVM(context, txContext, state.StateDB, config, vmconfig) evm := vm.NewEVM(context, txContext, st.StateDB, config, vmconfig)
// Execute the message. // Execute the message.
snapshot := state.StateDB.Snapshot() snapshot := st.StateDB.Snapshot()
gaspool := new(core.GasPool) gaspool := new(core.GasPool)
gaspool.AddGas(block.GasLimit()) gaspool.AddGas(block.GasLimit())
_, err = core.ApplyMessage(evm, msg, gaspool) _, err = core.ApplyMessage(evm, msg, gaspool)
if err != nil { if err != nil {
state.StateDB.RevertToSnapshot(snapshot) st.StateDB.RevertToSnapshot(snapshot)
} }
// Add 0-value mining reward. This only makes a difference in the cases // Add 0-value mining reward. This only makes a difference in the cases
// where // where
// - the coinbase self-destructed, or // - the coinbase self-destructed, or
// - there are only 'bad' transactions, which aren't executed. In those cases, // - there are only 'bad' transactions, which aren't executed. In those cases,
// the coinbase gets no txfee, so isn't created, and thus needs to be touched // the coinbase gets no txfee, so isn't created, and thus needs to be touched
state.StateDB.AddBalance(block.Coinbase(), new(uint256.Int)) st.StateDB.AddBalance(block.Coinbase(), new(uint256.Int), tracing.BalanceChangeUnspecified)
// Commit state mutations into database. // Commit state mutations into database.
root, _ = state.StateDB.Commit(block.NumberU64(), config.IsEIP158(block.Number())) root, _ = st.StateDB.Commit(block.NumberU64(), config.IsEIP158(block.Number()))
return state, root, err return st, root, err
} }
func (t *StateTest) gasLimit(subtest StateSubtest) uint64 { func (t *StateTest) gasLimit(subtest StateSubtest) uint64 {
@ -456,7 +457,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, snapshotter bo
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, uint256.MustFromBig(a.Balance)) statedb.SetBalance(addr, uint256.MustFromBig(a.Balance), tracing.BalanceChangeUnspecified)
for k, v := range a.Storage { for k, v := range a.Storage {
statedb.SetState(addr, k, v) statedb.SetState(addr, k, v)
} }

Loading…
Cancel
Save