forked from cerc-io/plugeth
Merge tag 'v1.10.12' into HEAD
This commit is contained in:
+2
-2
@@ -352,8 +352,8 @@ func (b *EthAPIBackend) StartMining(threads int) error {
|
||||
return b.eth.StartMining(threads)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) {
|
||||
return b.eth.stateAtBlock(block, reexec, base, checkLive)
|
||||
func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool) (*state.StateDB, error) {
|
||||
return b.eth.stateAtBlock(block, reexec, base, checkLive, preferDisk)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) {
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideLondon)
|
||||
chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideArrowGlacier)
|
||||
if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok {
|
||||
return nil, genesisErr
|
||||
}
|
||||
|
||||
@@ -202,8 +202,8 @@ type Config struct {
|
||||
// CheckpointOracle is the configuration for checkpoint oracle.
|
||||
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
|
||||
|
||||
// Berlin block override (TODO: remove after the fork)
|
||||
OverrideLondon *big.Int `toml:",omitempty"`
|
||||
// Arrow Glacier block override (TODO: remove after the fork)
|
||||
OverrideArrowGlacier *big.Int `toml:",omitempty"`
|
||||
}
|
||||
|
||||
// CreateConsensusEngine creates a consensus engine for the given chain configuration.
|
||||
|
||||
@@ -59,7 +59,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
RPCTxFeeCap float64
|
||||
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
|
||||
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
|
||||
OverrideLondon *big.Int `toml:",omitempty"`
|
||||
OverrideArrowGlacier *big.Int `toml:",omitempty"`
|
||||
}
|
||||
var enc Config
|
||||
enc.Genesis = c.Genesis
|
||||
@@ -103,7 +103,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
enc.RPCTxFeeCap = c.RPCTxFeeCap
|
||||
enc.Checkpoint = c.Checkpoint
|
||||
enc.CheckpointOracle = c.CheckpointOracle
|
||||
enc.OverrideLondon = c.OverrideLondon
|
||||
enc.OverrideArrowGlacier = c.OverrideArrowGlacier
|
||||
return &enc, nil
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
RPCTxFeeCap *float64
|
||||
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
|
||||
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
|
||||
OverrideLondon *big.Int `toml:",omitempty"`
|
||||
OverrideArrowGlacier *big.Int `toml:",omitempty"`
|
||||
}
|
||||
var dec Config
|
||||
if err := unmarshal(&dec); err != nil {
|
||||
@@ -280,8 +280,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
if dec.CheckpointOracle != nil {
|
||||
c.CheckpointOracle = dec.CheckpointOracle
|
||||
}
|
||||
if dec.OverrideLondon != nil {
|
||||
c.OverrideLondon = dec.OverrideLondon
|
||||
if dec.OverrideArrowGlacier != nil {
|
||||
c.OverrideArrowGlacier = dec.OverrideArrowGlacier
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -391,13 +391,14 @@ func (f *BlockFetcher) loop() {
|
||||
blockAnnounceDOSMeter.Mark(1)
|
||||
break
|
||||
}
|
||||
if notification.number == 0 {
|
||||
break
|
||||
}
|
||||
// If we have a valid block number, check that it's potentially useful
|
||||
if notification.number > 0 {
|
||||
if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
|
||||
log.Debug("Peer discarded announcement", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "distance", dist)
|
||||
blockAnnounceDropMeter.Mark(1)
|
||||
break
|
||||
}
|
||||
if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
|
||||
log.Debug("Peer discarded announcement", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "distance", dist)
|
||||
blockAnnounceDropMeter.Mark(1)
|
||||
break
|
||||
}
|
||||
// All is well, schedule the announce if block's not yet downloading
|
||||
if _, ok := f.fetching[notification.hash]; ok {
|
||||
|
||||
@@ -62,6 +62,7 @@ func BenchmarkBloomBits32k(b *testing.B) {
|
||||
const benchFilterCnt = 2000
|
||||
|
||||
func benchmarkBloomBits(b *testing.B, sectionSize uint64) {
|
||||
b.Skip("test disabled: this tests presume (and modify) an existing datadir.")
|
||||
benchDataDir := node.DefaultDataDir() + "/geth/chaindata"
|
||||
b.Log("Running bloombits benchmark section size:", sectionSize)
|
||||
|
||||
@@ -155,6 +156,7 @@ func clearBloomBits(db ethdb.Database) {
|
||||
}
|
||||
|
||||
func BenchmarkNoBloomBits(b *testing.B) {
|
||||
b.Skip("test disabled: this tests presume (and modify) an existing datadir.")
|
||||
benchDataDir := node.DefaultDataDir() + "/geth/chaindata"
|
||||
b.Log("Running benchmark without bloombits")
|
||||
db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false)
|
||||
|
||||
@@ -65,15 +65,19 @@ func BenchmarkFilters(b *testing.B) {
|
||||
case 2403:
|
||||
receipt := makeReceipt(addr1)
|
||||
gen.AddUncheckedReceipt(receipt)
|
||||
gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil))
|
||||
case 1034:
|
||||
receipt := makeReceipt(addr2)
|
||||
gen.AddUncheckedReceipt(receipt)
|
||||
gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil))
|
||||
case 34:
|
||||
receipt := makeReceipt(addr3)
|
||||
gen.AddUncheckedReceipt(receipt)
|
||||
gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil))
|
||||
case 99999:
|
||||
receipt := makeReceipt(addr4)
|
||||
gen.AddUncheckedReceipt(receipt)
|
||||
gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil))
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
@@ -107,10 +107,13 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke
|
||||
signer = types.LatestSigner(gspec.Config)
|
||||
)
|
||||
config.LondonBlock = londonBlock
|
||||
config.ArrowGlacierBlock = londonBlock
|
||||
engine := ethash.NewFaker()
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
genesis, _ := gspec.Commit(db)
|
||||
|
||||
genesis, err := gspec.Commit(db)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Generate testing blocks
|
||||
blocks, _ := core.GenerateChain(gspec.Config, genesis, engine, db, testHead+1, func(i int, b *core.BlockGen) {
|
||||
b.SetCoinbase(common.Address{1})
|
||||
|
||||
+21
-2
@@ -35,7 +35,17 @@ import (
|
||||
// are attempted to be reexecuted to generate the desired state. The optional
|
||||
// base layer statedb can be passed then it's regarded as the statedb of the
|
||||
// parent block.
|
||||
func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (statedb *state.StateDB, err error) {
|
||||
// Parameters:
|
||||
// - block: The block for which we want the state (== state at the stateRoot of the parent)
|
||||
// - reexec: The maximum number of blocks to reprocess trying to obtain the desired state
|
||||
// - base: If the caller is tracing multiple blocks, the caller can provide the parent state
|
||||
// continuously from the callsite.
|
||||
// - checklive: if true, then the live 'blockchain' state database is used. If the caller want to
|
||||
// perform Commit or other 'save-to-disk' changes, this should be set to false to avoid
|
||||
// storing trash persistently
|
||||
// - preferDisk: this arg can be used by the caller to signal that even though the 'base' is provided,
|
||||
// it would be preferrable to start from a fresh state, if we have it on disk.
|
||||
func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) {
|
||||
var (
|
||||
current *types.Block
|
||||
database state.Database
|
||||
@@ -50,6 +60,15 @@ func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state
|
||||
}
|
||||
}
|
||||
if base != nil {
|
||||
if preferDisk {
|
||||
// Create an ephemeral trie.Database for isolating the live one. Otherwise
|
||||
// the internal junks created by tracing will be persisted into the disk.
|
||||
database = state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16})
|
||||
if statedb, err = state.New(block.Root(), database, nil); err == nil {
|
||||
log.Info("Found disk backend for state trie", "root", block.Root(), "number", block.Number())
|
||||
return statedb, nil
|
||||
}
|
||||
}
|
||||
// The optional base statedb is given, mark the start point as parent block
|
||||
statedb, database, report = base, base.Database(), false
|
||||
current = eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
|
||||
@@ -152,7 +171,7 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec
|
||||
}
|
||||
// Lookup the statedb of parent block from the live database,
|
||||
// otherwise regenerate it on the flight.
|
||||
statedb, err := eth.stateAtBlock(parent, reexec, nil, true)
|
||||
statedb, err := eth.stateAtBlock(parent, reexec, nil, true, false)
|
||||
if err != nil {
|
||||
return nil, vm.BlockContext{}, nil, err
|
||||
}
|
||||
|
||||
+46
-21
@@ -54,6 +54,13 @@ const (
|
||||
// and reexecute to produce missing historical state necessary to run a specific
|
||||
// trace.
|
||||
defaultTraceReexec = uint64(128)
|
||||
|
||||
// defaultTracechainMemLimit is the size of the triedb, at which traceChain
|
||||
// switches over and tries to use a disk-backed database instead of building
|
||||
// on top of memory.
|
||||
// For non-archive nodes, this limit _will_ be overblown, as disk-backed tries
|
||||
// will only be found every ~15K blocks or so.
|
||||
defaultTracechainMemLimit = common.StorageSize(500 * 1024 * 1024)
|
||||
)
|
||||
|
||||
// Backend interface provides the common API services (that are provided by
|
||||
@@ -68,7 +75,10 @@ type Backend interface {
|
||||
ChainConfig() *params.ChainConfig
|
||||
Engine() consensus.Engine
|
||||
ChainDb() ethdb.Database
|
||||
StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error)
|
||||
// StateAtBlock returns the state corresponding to the stateroot of the block.
|
||||
// N.B: For executing transactions on block N, the required stateRoot is block N-1,
|
||||
// so this method should be called with the parent.
|
||||
StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool) (*state.StateDB, error)
|
||||
StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error)
|
||||
}
|
||||
|
||||
@@ -321,6 +331,7 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config
|
||||
}
|
||||
close(results)
|
||||
}()
|
||||
var preferDisk bool
|
||||
// Feed all the blocks both into the tracer, as well as fast process concurrently
|
||||
for number = start.NumberU64(); number < end.NumberU64(); number++ {
|
||||
// Stop tracing if interruption was requested
|
||||
@@ -350,18 +361,24 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config
|
||||
}
|
||||
// Prepare the statedb for tracing. Don't use the live database for
|
||||
// tracing to avoid persisting state junks into the database.
|
||||
statedb, err = api.backend.StateAtBlock(localctx, block, reexec, statedb, false)
|
||||
statedb, err = api.backend.StateAtBlock(localctx, block, reexec, statedb, false, preferDisk)
|
||||
if err != nil {
|
||||
failed = err
|
||||
break
|
||||
}
|
||||
if statedb.Database().TrieDB() != nil {
|
||||
if trieDb := statedb.Database().TrieDB(); trieDb != nil {
|
||||
// Hold the reference for tracer, will be released at the final stage
|
||||
statedb.Database().TrieDB().Reference(block.Root(), common.Hash{})
|
||||
trieDb.Reference(block.Root(), common.Hash{})
|
||||
|
||||
// Release the parent state because it's already held by the tracer
|
||||
if parent != (common.Hash{}) {
|
||||
statedb.Database().TrieDB().Dereference(parent)
|
||||
trieDb.Dereference(parent)
|
||||
}
|
||||
// Prefer disk if the trie db memory grows too much
|
||||
s1, s2 := trieDb.Size()
|
||||
if !preferDisk && (s1+s2) > defaultTracechainMemLimit {
|
||||
log.Info("Switching to prefer-disk mode for tracing", "size", s1+s2)
|
||||
preferDisk = true
|
||||
}
|
||||
}
|
||||
parent = block.Root()
|
||||
@@ -497,7 +514,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
|
||||
if config != nil && config.Reexec != nil {
|
||||
reexec = *config.Reexec
|
||||
}
|
||||
statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true)
|
||||
statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -558,7 +575,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
||||
if config != nil && config.Reexec != nil {
|
||||
reexec = *config.Reexec
|
||||
}
|
||||
statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true)
|
||||
statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -647,7 +664,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
||||
if config != nil && config.Reexec != nil {
|
||||
reexec = *config.Reexec
|
||||
}
|
||||
statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true)
|
||||
statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -811,7 +828,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
||||
if config != nil && config.Reexec != nil {
|
||||
reexec = *config.Reexec
|
||||
}
|
||||
statedb, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true)
|
||||
statedb, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -846,12 +863,14 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
||||
func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
|
||||
// Assemble the structured logger or the JavaScript tracer
|
||||
var (
|
||||
tracer vm.Tracer
|
||||
tracer vm.EVMLogger
|
||||
err error
|
||||
txContext = core.NewEVMTxContext(message)
|
||||
)
|
||||
switch {
|
||||
case config != nil && config.Tracer != nil:
|
||||
case config == nil:
|
||||
tracer = vm.NewStructLogger(nil)
|
||||
case config.Tracer != nil:
|
||||
// Define a meaningful timeout of a single transaction trace
|
||||
timeout := defaultTraceTimeout
|
||||
if config.Timeout != nil {
|
||||
@@ -864,23 +883,29 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
|
||||
tracer = tr(statedb)
|
||||
} else {
|
||||
// Constuct the JavaScript tracer to execute with
|
||||
if tracer, err = New(*config.Tracer, txctx); err != nil {
|
||||
if t, err := New(*config.Tracer, txctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Handle timeouts and RPC cancellations
|
||||
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
go func() {
|
||||
<-deadlineCtx.Done()
|
||||
if deadlineCtx.Err() == context.DeadlineExceeded {
|
||||
tracer.(*Tracer).Stop(errors.New("execution timeout"))
|
||||
if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
|
||||
t.Stop(errors.New("execution timeout"))
|
||||
}
|
||||
}()
|
||||
defer cancel()
|
||||
}()
|
||||
defer cancel()
|
||||
tracer = t
|
||||
}
|
||||
|
||||
case config == nil:
|
||||
tracer = vm.NewStructLogger(nil)
|
||||
|
||||
// Handle timeouts and RPC cancellations
|
||||
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
go func() {
|
||||
<-deadlineCtx.Done()
|
||||
if deadlineCtx.Err() == context.DeadlineExceeded {
|
||||
tracer.(*Tracer).Stop(errors.New("execution timeout"))
|
||||
}
|
||||
}()
|
||||
defer cancel()
|
||||
default:
|
||||
tracer = vm.NewStructLogger(config.LogConfig)
|
||||
}
|
||||
@@ -913,7 +938,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
|
||||
case interfaces.TracerResult:
|
||||
return tracer.GetResult()
|
||||
|
||||
case *Tracer:
|
||||
case Tracer:
|
||||
return tracer.GetResult()
|
||||
|
||||
default:
|
||||
|
||||
@@ -138,7 +138,7 @@ func (b *testBackend) ChainDb() ethdb.Database {
|
||||
return b.chaindb
|
||||
}
|
||||
|
||||
func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) {
|
||||
func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (*state.StateDB, error) {
|
||||
statedb, err := b.chain.StateAt(block.Root())
|
||||
if err != nil {
|
||||
return nil, errStateNotFound
|
||||
@@ -325,7 +325,7 @@ func TestOverriddenTraceCall(t *testing.T) {
|
||||
tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key)
|
||||
b.AddTx(tx)
|
||||
}))
|
||||
randomAccounts, tracer := newAccounts(3), "callTracer"
|
||||
randomAccounts, tracer := newAccounts(3), "callTracerJs"
|
||||
|
||||
var testSuite = []struct {
|
||||
blockNumber rpc.BlockNumber
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,170 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tracers.RegisterNativeTracer("callTracer", NewCallTracer)
|
||||
}
|
||||
|
||||
type callFrame struct {
|
||||
Type string `json:"type"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Gas string `json:"gas"`
|
||||
GasUsed string `json:"gasUsed"`
|
||||
Input string `json:"input"`
|
||||
Output string `json:"output,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Calls []callFrame `json:"calls,omitempty"`
|
||||
}
|
||||
|
||||
type callTracer struct {
|
||||
callstack []callFrame
|
||||
interrupt uint32 // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
}
|
||||
|
||||
// NewCallTracer returns a native go tracer which tracks
|
||||
// call frames of a tx, and implements vm.EVMLogger.
|
||||
func NewCallTracer() tracers.Tracer {
|
||||
// First callframe contains tx context info
|
||||
// and is populated on start and end.
|
||||
t := &callTracer{callstack: make([]callFrame, 1)}
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
t.callstack[0] = callFrame{
|
||||
Type: "CALL",
|
||||
From: addrToHex(from),
|
||||
To: addrToHex(to),
|
||||
Input: bytesToHex(input),
|
||||
Gas: uintToHex(gas),
|
||||
Value: bigToHex(value),
|
||||
}
|
||||
if create {
|
||||
t.callstack[0].Type = "CREATE"
|
||||
}
|
||||
}
|
||||
|
||||
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
|
||||
t.callstack[0].GasUsed = uintToHex(gasUsed)
|
||||
if err != nil {
|
||||
t.callstack[0].Error = err.Error()
|
||||
if err.Error() == "execution reverted" && len(output) > 0 {
|
||||
t.callstack[0].Output = bytesToHex(output)
|
||||
}
|
||||
} else {
|
||||
t.callstack[0].Output = bytesToHex(output)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *callTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
}
|
||||
|
||||
func (t *callTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
|
||||
}
|
||||
|
||||
func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
// Skip if tracing was interrupted
|
||||
if atomic.LoadUint32(&t.interrupt) > 0 {
|
||||
// TODO: env.Cancel()
|
||||
return
|
||||
}
|
||||
|
||||
call := callFrame{
|
||||
Type: typ.String(),
|
||||
From: addrToHex(from),
|
||||
To: addrToHex(to),
|
||||
Input: bytesToHex(input),
|
||||
Gas: uintToHex(gas),
|
||||
Value: bigToHex(value),
|
||||
}
|
||||
t.callstack = append(t.callstack, call)
|
||||
}
|
||||
|
||||
func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
size := len(t.callstack)
|
||||
if size <= 1 {
|
||||
return
|
||||
}
|
||||
// pop call
|
||||
call := t.callstack[size-1]
|
||||
t.callstack = t.callstack[:size-1]
|
||||
size -= 1
|
||||
|
||||
call.GasUsed = uintToHex(gasUsed)
|
||||
if err == nil {
|
||||
call.Output = bytesToHex(output)
|
||||
} else {
|
||||
call.Error = err.Error()
|
||||
if call.Type == "CREATE" || call.Type == "CREATE2" {
|
||||
call.To = ""
|
||||
}
|
||||
}
|
||||
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
|
||||
}
|
||||
|
||||
func (t *callTracer) GetResult() (json.RawMessage, error) {
|
||||
if len(t.callstack) != 1 {
|
||||
return nil, errors.New("incorrect number of top-level calls")
|
||||
}
|
||||
res, err := json.Marshal(t.callstack[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.RawMessage(res), t.reason
|
||||
}
|
||||
|
||||
func (t *callTracer) Stop(err error) {
|
||||
t.reason = err
|
||||
atomic.StoreUint32(&t.interrupt, 1)
|
||||
}
|
||||
|
||||
func bytesToHex(s []byte) string {
|
||||
return "0x" + common.Bytes2Hex(s)
|
||||
}
|
||||
|
||||
func bigToHex(n *big.Int) string {
|
||||
if n == nil {
|
||||
return ""
|
||||
}
|
||||
return "0x" + n.Text(16)
|
||||
}
|
||||
|
||||
func uintToHex(n uint64) string {
|
||||
return "0x" + strconv.FormatUint(n, 16)
|
||||
}
|
||||
|
||||
func addrToHex(a common.Address) string {
|
||||
return strings.ToLower(a.Hex())
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package native
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tracers.RegisterNativeTracer("noopTracerNative", NewNoopTracer)
|
||||
}
|
||||
|
||||
type noopTracer struct{}
|
||||
|
||||
func NewNoopTracer() tracers.Tracer {
|
||||
return &noopTracer{}
|
||||
}
|
||||
|
||||
func (t *noopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (t *noopTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
|
||||
}
|
||||
|
||||
func (t *noopTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
}
|
||||
|
||||
func (t *noopTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
|
||||
}
|
||||
|
||||
func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
}
|
||||
|
||||
func (t *noopTracer) GetResult() (json.RawMessage, error) {
|
||||
return json.RawMessage(`{}`), nil
|
||||
}
|
||||
|
||||
func (t *noopTracer) Stop(err error) {
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
|
||||
// Force-load the native, to trigger registration
|
||||
_ "github.com/ethereum/go-ethereum/eth/tracers/native"
|
||||
)
|
||||
|
||||
type callContext struct {
|
||||
Number math.HexOrDecimal64 `json:"number"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty"`
|
||||
Time math.HexOrDecimal64 `json:"timestamp"`
|
||||
GasLimit math.HexOrDecimal64 `json:"gasLimit"`
|
||||
Miner common.Address `json:"miner"`
|
||||
}
|
||||
|
||||
// callTrace is the result of a callTracer run.
|
||||
type callTrace struct {
|
||||
Type string `json:"type"`
|
||||
From common.Address `json:"from"`
|
||||
To common.Address `json:"to"`
|
||||
Input hexutil.Bytes `json:"input"`
|
||||
Output hexutil.Bytes `json:"output"`
|
||||
Gas *hexutil.Uint64 `json:"gas,omitempty"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"`
|
||||
Value *hexutil.Big `json:"value,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Calls []callTrace `json:"calls,omitempty"`
|
||||
}
|
||||
|
||||
// callTracerTest defines a single test to check the call tracer against.
|
||||
type callTracerTest struct {
|
||||
Genesis *core.Genesis `json:"genesis"`
|
||||
Context *callContext `json:"context"`
|
||||
Input string `json:"input"`
|
||||
Result *callTrace `json:"result"`
|
||||
}
|
||||
|
||||
// Iterates over all the input-output datasets in the tracer test harness and
|
||||
// runs the JavaScript tracers against them.
|
||||
func TestCallTracerLegacy(t *testing.T) {
|
||||
testCallTracer("callTracerLegacy", "call_tracer_legacy", t)
|
||||
}
|
||||
|
||||
func TestCallTracerJs(t *testing.T) {
|
||||
testCallTracer("callTracerJs", "call_tracer", t)
|
||||
}
|
||||
|
||||
func TestCallTracerNative(t *testing.T) {
|
||||
testCallTracer("callTracer", "call_tracer", t)
|
||||
}
|
||||
|
||||
func testCallTracer(tracerName string, dirPath string, t *testing.T) {
|
||||
files, err := ioutil.ReadDir(filepath.Join("..", "testdata", dirPath))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve tracer test suite: %v", err)
|
||||
}
|
||||
for _, file := range files {
|
||||
if !strings.HasSuffix(file.Name(), ".json") {
|
||||
continue
|
||||
}
|
||||
file := file // capture range variable
|
||||
t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
test = new(callTracerTest)
|
||||
tx = new(types.Transaction)
|
||||
)
|
||||
// Call tracer test found, read if from disk
|
||||
if blob, err := ioutil.ReadFile(filepath.Join("..", "testdata", dirPath, file.Name())); err != nil {
|
||||
t.Fatalf("failed to read testcase: %v", err)
|
||||
} else if err := json.Unmarshal(blob, test); err != nil {
|
||||
t.Fatalf("failed to parse testcase: %v", err)
|
||||
}
|
||||
if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
|
||||
t.Fatalf("failed to parse testcase input: %v", err)
|
||||
}
|
||||
// Configure a blockchain with the given prestate
|
||||
var (
|
||||
signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
|
||||
origin, _ = signer.Sender(tx)
|
||||
txContext = vm.TxContext{
|
||||
Origin: origin,
|
||||
GasPrice: tx.GasPrice(),
|
||||
}
|
||||
context = vm.BlockContext{
|
||||
CanTransfer: core.CanTransfer,
|
||||
Transfer: core.Transfer,
|
||||
Coinbase: test.Context.Miner,
|
||||
BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
|
||||
Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
|
||||
Difficulty: (*big.Int)(test.Context.Difficulty),
|
||||
GasLimit: uint64(test.Context.GasLimit),
|
||||
}
|
||||
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
|
||||
)
|
||||
tracer, err := tracers.New(tracerName, new(tracers.Context))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create call tracer: %v", err)
|
||||
}
|
||||
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
|
||||
msg, err := tx.AsMessage(signer, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||
}
|
||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
if _, err = st.TransitionDb(); err != nil {
|
||||
t.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
// Retrieve the trace result and compare against the etalon
|
||||
res, err := tracer.GetResult()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve trace result: %v", err)
|
||||
}
|
||||
ret := new(callTrace)
|
||||
if err := json.Unmarshal(res, ret); err != nil {
|
||||
t.Fatalf("failed to unmarshal trace result: %v", err)
|
||||
}
|
||||
|
||||
if !jsonEqual(ret, test.Result) {
|
||||
// uncomment this for easier debugging
|
||||
//have, _ := json.MarshalIndent(ret, "", " ")
|
||||
//want, _ := json.MarshalIndent(test.Result, "", " ")
|
||||
//t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
|
||||
t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
|
||||
// comparison
|
||||
func jsonEqual(x, y interface{}) bool {
|
||||
xTrace := new(callTrace)
|
||||
yTrace := new(callTrace)
|
||||
if xj, err := json.Marshal(x); err == nil {
|
||||
json.Unmarshal(xj, xTrace)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
if yj, err := json.Marshal(y); err == nil {
|
||||
json.Unmarshal(yj, yTrace)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return reflect.DeepEqual(xTrace, yTrace)
|
||||
}
|
||||
|
||||
// camel converts a snake cased input string into a camel cased output.
|
||||
func camel(str string) string {
|
||||
pieces := strings.Split(str, "_")
|
||||
for i := 1; i < len(pieces); i++ {
|
||||
pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
|
||||
}
|
||||
return strings.Join(pieces, "")
|
||||
}
|
||||
func BenchmarkTracers(b *testing.B) {
|
||||
files, err := ioutil.ReadDir(filepath.Join("..", "testdata", "call_tracer"))
|
||||
if err != nil {
|
||||
b.Fatalf("failed to retrieve tracer test suite: %v", err)
|
||||
}
|
||||
for _, file := range files {
|
||||
if !strings.HasSuffix(file.Name(), ".json") {
|
||||
continue
|
||||
}
|
||||
file := file // capture range variable
|
||||
b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) {
|
||||
blob, err := ioutil.ReadFile(filepath.Join("..", "testdata", "call_tracer", file.Name()))
|
||||
if err != nil {
|
||||
b.Fatalf("failed to read testcase: %v", err)
|
||||
}
|
||||
test := new(callTracerTest)
|
||||
if err := json.Unmarshal(blob, test); err != nil {
|
||||
b.Fatalf("failed to parse testcase: %v", err)
|
||||
}
|
||||
benchTracer("callTracerNative", test, b)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
|
||||
// Configure a blockchain with the given prestate
|
||||
tx := new(types.Transaction)
|
||||
if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
|
||||
b.Fatalf("failed to parse testcase input: %v", err)
|
||||
}
|
||||
signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
|
||||
msg, err := tx.AsMessage(signer, nil)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||
}
|
||||
origin, _ := signer.Sender(tx)
|
||||
txContext := vm.TxContext{
|
||||
Origin: origin,
|
||||
GasPrice: tx.GasPrice(),
|
||||
}
|
||||
context := vm.BlockContext{
|
||||
CanTransfer: core.CanTransfer,
|
||||
Transfer: core.Transfer,
|
||||
Coinbase: test.Context.Miner,
|
||||
BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
|
||||
Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
|
||||
Difficulty: (*big.Int)(test.Context.Difficulty),
|
||||
GasLimit: uint64(test.Context.GasLimit),
|
||||
}
|
||||
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tracer, err := tracers.New(tracerName, new(tracers.Context))
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create call tracer: %v", err)
|
||||
}
|
||||
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
|
||||
snap := statedb.Snapshot()
|
||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
if _, err = st.TransitionDb(); err != nil {
|
||||
b.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
if _, err = tracer.GetResult(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
statedb.RevertToSnapshot(snap)
|
||||
}
|
||||
}
|
||||
+15
-26
@@ -363,9 +363,9 @@ func (r *frameResult) pushObject(vm *duktape.Context) {
|
||||
vm.PutPropString(obj, "getError")
|
||||
}
|
||||
|
||||
// Tracer provides an implementation of Tracer that evaluates a Javascript
|
||||
// jsTracer provides an implementation of Tracer that evaluates a Javascript
|
||||
// function for each VM execution step.
|
||||
type Tracer struct {
|
||||
type jsTracer struct {
|
||||
vm *duktape.Context // Javascript VM instance
|
||||
|
||||
tracerObject int // Stack index of the tracer JavaScript object
|
||||
@@ -409,12 +409,8 @@ type Context struct {
|
||||
// New instantiates a new tracer instance. code specifies a Javascript snippet,
|
||||
// which must evaluate to an expression returning an object with 'step', 'fault'
|
||||
// and 'result' functions.
|
||||
func New(code string, ctx *Context) (*Tracer, error) {
|
||||
// Resolve any tracers by name and assemble the tracer object
|
||||
if tracer, ok := tracer(code); ok {
|
||||
code = tracer
|
||||
}
|
||||
tracer := &Tracer{
|
||||
func newJsTracer(code string, ctx *Context) (*jsTracer, error) {
|
||||
tracer := &jsTracer{
|
||||
vm: duktape.New(),
|
||||
ctx: make(map[string]interface{}),
|
||||
opWrapper: new(opWrapper),
|
||||
@@ -553,17 +549,10 @@ func New(code string, ctx *Context) (*Tracer, error) {
|
||||
tracer.vm.Pop()
|
||||
hasExit := tracer.vm.GetPropString(tracer.tracerObject, "exit")
|
||||
tracer.vm.Pop()
|
||||
|
||||
if hasEnter != hasExit {
|
||||
return nil, fmt.Errorf("trace object must expose either both or none of enter() and exit()")
|
||||
}
|
||||
if !hasStep {
|
||||
// If there's no step function, the enter and exit must be present
|
||||
if !hasEnter {
|
||||
return nil, fmt.Errorf("trace object must expose either step() or both enter() and exit()")
|
||||
}
|
||||
}
|
||||
tracer.traceCallFrames = hasEnter
|
||||
tracer.traceCallFrames = hasEnter && hasExit
|
||||
tracer.traceSteps = hasStep
|
||||
|
||||
// Tracer is valid, inject the big int library to access large numbers
|
||||
@@ -627,14 +616,14 @@ func New(code string, ctx *Context) (*Tracer, error) {
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (jst *Tracer) Stop(err error) {
|
||||
func (jst *jsTracer) Stop(err error) {
|
||||
jst.reason = err
|
||||
atomic.StoreUint32(&jst.interrupt, 1)
|
||||
}
|
||||
|
||||
// call executes a method on a JS object, catching any errors, formatting and
|
||||
// returning them as error objects.
|
||||
func (jst *Tracer) call(noret bool, method string, args ...string) (json.RawMessage, error) {
|
||||
func (jst *jsTracer) call(noret bool, method string, args ...string) (json.RawMessage, error) {
|
||||
// Execute the JavaScript call and return any error
|
||||
jst.vm.PushString(method)
|
||||
for _, arg := range args {
|
||||
@@ -670,7 +659,7 @@ func wrapError(context string, err error) error {
|
||||
}
|
||||
|
||||
// CaptureStart implements the Tracer interface to initialize the tracing operation.
|
||||
func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
jst.ctx["type"] = "CALL"
|
||||
if create {
|
||||
jst.ctx["type"] = "CREATE"
|
||||
@@ -700,7 +689,7 @@ func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr
|
||||
}
|
||||
|
||||
// CaptureState implements the Tracer interface to trace a single step of VM execution.
|
||||
func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
func (jst *jsTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
if !jst.traceSteps {
|
||||
return
|
||||
}
|
||||
@@ -736,7 +725,7 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost
|
||||
}
|
||||
|
||||
// CaptureFault implements the Tracer interface to trace an execution fault
|
||||
func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
func (jst *jsTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
if jst.err != nil {
|
||||
return
|
||||
}
|
||||
@@ -750,7 +739,7 @@ func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost
|
||||
}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
|
||||
func (jst *jsTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
|
||||
jst.ctx["output"] = output
|
||||
jst.ctx["time"] = t.String()
|
||||
jst.ctx["gasUsed"] = gasUsed
|
||||
@@ -761,7 +750,7 @@ func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, er
|
||||
}
|
||||
|
||||
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
||||
func (jst *Tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
func (jst *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
if !jst.traceCallFrames {
|
||||
return
|
||||
}
|
||||
@@ -791,7 +780,7 @@ func (jst *Tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Ad
|
||||
|
||||
// CaptureExit is called when EVM exits a scope, even if the scope didn't
|
||||
// execute any code.
|
||||
func (jst *Tracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
func (jst *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
if !jst.traceCallFrames {
|
||||
return
|
||||
}
|
||||
@@ -815,7 +804,7 @@ func (jst *Tracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
}
|
||||
|
||||
// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
|
||||
func (jst *Tracer) GetResult() (json.RawMessage, error) {
|
||||
func (jst *jsTracer) GetResult() (json.RawMessage, error) {
|
||||
// Transform the context into a JavaScript object and inject into the state
|
||||
obj := jst.vm.PushObject()
|
||||
|
||||
@@ -837,7 +826,7 @@ func (jst *Tracer) GetResult() (json.RawMessage, error) {
|
||||
}
|
||||
|
||||
// addToObj pushes a field to a JS object.
|
||||
func (jst *Tracer) addToObj(obj int, key string, val interface{}) {
|
||||
func (jst *jsTracer) addToObj(obj int, key string, val interface{}) {
|
||||
pushValue(jst.vm, val)
|
||||
jst.vm.PutPropString(obj, key)
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func testCtx() *vmContext {
|
||||
return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
|
||||
}
|
||||
|
||||
func runTrace(tracer *Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) {
|
||||
func runTrace(tracer Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) {
|
||||
env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer})
|
||||
var (
|
||||
startGas uint64 = 10000
|
||||
@@ -168,7 +168,7 @@ func TestHaltBetweenSteps(t *testing.T) {
|
||||
// TestNoStepExec tests a regular value transfer (no exec), and accessing the statedb
|
||||
// in 'result'
|
||||
func TestNoStepExec(t *testing.T) {
|
||||
runEmptyTrace := func(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) {
|
||||
runEmptyTrace := func(tracer Tracer, vmctx *vmContext) (json.RawMessage, error) {
|
||||
env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
|
||||
startGas := uint64(10000)
|
||||
contract := vm.NewContract(account{}, account{}, big.NewInt(0), startGas)
|
||||
|
||||
+42
-11
@@ -18,14 +18,53 @@
|
||||
package tracers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/internal/tracers"
|
||||
)
|
||||
|
||||
// all contains all the built in JavaScript tracers by name.
|
||||
var all = make(map[string]string)
|
||||
// Tracer interface extends vm.EVMLogger and additionally
|
||||
// allows collecting the tracing result.
|
||||
type Tracer interface {
|
||||
vm.EVMLogger
|
||||
GetResult() (json.RawMessage, error)
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
Stop(err error)
|
||||
}
|
||||
|
||||
var (
|
||||
nativeTracers map[string]func() Tracer = make(map[string]func() Tracer)
|
||||
jsTracers = make(map[string]string)
|
||||
)
|
||||
|
||||
// RegisterNativeTracer makes native tracers which adhere
|
||||
// to the `Tracer` interface available to the rest of the codebase.
|
||||
// It is typically invoked in the `init()` function, e.g. see the `native/call.go`.
|
||||
func RegisterNativeTracer(name string, ctor func() Tracer) {
|
||||
nativeTracers[name] = ctor
|
||||
}
|
||||
|
||||
// New returns a new instance of a tracer,
|
||||
// 1. If 'code' is the name of a registered native tracer, then that tracer
|
||||
// is instantiated and returned
|
||||
// 2. If 'code' is the name of a registered js-tracer, then that tracer is
|
||||
// instantiated and returned
|
||||
// 3. Otherwise, the code is interpreted as the js code of a js-tracer, and
|
||||
// is evaluated and returned.
|
||||
func New(code string, ctx *Context) (Tracer, error) {
|
||||
// Resolve native tracer
|
||||
if fn, ok := nativeTracers[code]; ok {
|
||||
return fn(), nil
|
||||
}
|
||||
// Resolve js-tracers by name and assemble the tracer object
|
||||
if tracer, ok := jsTracers[code]; ok {
|
||||
code = tracer
|
||||
}
|
||||
return newJsTracer(code, ctx)
|
||||
}
|
||||
|
||||
// camel converts a snake cased input string into a camel cased output.
|
||||
func camel(str string) string {
|
||||
@@ -40,14 +79,6 @@ func camel(str string) string {
|
||||
func init() {
|
||||
for _, file := range tracers.AssetNames() {
|
||||
name := camel(strings.TrimSuffix(file, ".js"))
|
||||
all[name] = string(tracers.MustAsset(file))
|
||||
jsTracers[name] = string(tracers.MustAsset(file))
|
||||
}
|
||||
}
|
||||
|
||||
// tracer retrieves a specific JavaScript tracer by name.
|
||||
func tracer(name string) (string, bool) {
|
||||
if tracer, ok := all[name]; ok {
|
||||
return tracer, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
+78
-179
@@ -20,23 +20,18 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
)
|
||||
|
||||
@@ -104,20 +99,83 @@ type callTrace struct {
|
||||
Calls []callTrace `json:"calls,omitempty"`
|
||||
}
|
||||
|
||||
type callContext struct {
|
||||
Number math.HexOrDecimal64 `json:"number"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty"`
|
||||
Time math.HexOrDecimal64 `json:"timestamp"`
|
||||
GasLimit math.HexOrDecimal64 `json:"gasLimit"`
|
||||
Miner common.Address `json:"miner"`
|
||||
}
|
||||
|
||||
// callTracerTest defines a single test to check the call tracer against.
|
||||
type callTracerTest struct {
|
||||
Genesis *core.Genesis `json:"genesis"`
|
||||
Context *callContext `json:"context"`
|
||||
Input string `json:"input"`
|
||||
Result *callTrace `json:"result"`
|
||||
// TestZeroValueToNotExitCall tests the calltracer(s) on the following:
|
||||
// Tx to A, A calls B with zero value. B does not already exist.
|
||||
// Expected: that enter/exit is invoked and the inner call is shown in the result
|
||||
func TestZeroValueToNotExitCall(t *testing.T) {
|
||||
var to = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
|
||||
privkey, err := crypto.HexToECDSA("0000000000000000deadbeef00000000000000000000000000000000deadbeef")
|
||||
if err != nil {
|
||||
t.Fatalf("err %v", err)
|
||||
}
|
||||
signer := types.NewEIP155Signer(big.NewInt(1))
|
||||
tx, err := types.SignNewTx(privkey, signer, &types.LegacyTx{
|
||||
GasPrice: big.NewInt(0),
|
||||
Gas: 50000,
|
||||
To: &to,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err %v", err)
|
||||
}
|
||||
origin, _ := signer.Sender(tx)
|
||||
txContext := vm.TxContext{
|
||||
Origin: origin,
|
||||
GasPrice: big.NewInt(1),
|
||||
}
|
||||
context := vm.BlockContext{
|
||||
CanTransfer: core.CanTransfer,
|
||||
Transfer: core.Transfer,
|
||||
Coinbase: common.Address{},
|
||||
BlockNumber: new(big.Int).SetUint64(8000000),
|
||||
Time: new(big.Int).SetUint64(5),
|
||||
Difficulty: big.NewInt(0x30000),
|
||||
GasLimit: uint64(6000000),
|
||||
}
|
||||
var code = []byte{
|
||||
byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero
|
||||
byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS
|
||||
byte(vm.CALL),
|
||||
}
|
||||
var alloc = core.GenesisAlloc{
|
||||
to: core.GenesisAccount{
|
||||
Nonce: 1,
|
||||
Code: code,
|
||||
},
|
||||
origin: core.GenesisAccount{
|
||||
Nonce: 0,
|
||||
Balance: big.NewInt(500000000000000),
|
||||
},
|
||||
}
|
||||
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
|
||||
// Create the tracer, the EVM environment and run it
|
||||
tracer, err := New("callTracerJs", new(Context))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create call tracer: %v", err)
|
||||
}
|
||||
evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer})
|
||||
msg, err := tx.AsMessage(signer, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||
}
|
||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
if _, err = st.TransitionDb(); err != nil {
|
||||
t.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
// Retrieve the trace result and compare against the etalon
|
||||
res, err := tracer.GetResult()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve trace result: %v", err)
|
||||
}
|
||||
have := new(callTrace)
|
||||
if err := json.Unmarshal(res, have); err != nil {
|
||||
t.Fatalf("failed to unmarshal trace result: %v", err)
|
||||
}
|
||||
wantStr := `{"type":"CALL","from":"0x682a80a6f560eec50d54e63cbeda1c324c5f8d1b","to":"0x00000000000000000000000000000000deadbeef","value":"0x0","gas":"0x7148","gasUsed":"0x2d0","input":"0x","output":"0x","calls":[{"type":"CALL","from":"0x00000000000000000000000000000000deadbeef","to":"0x00000000000000000000000000000000000000ff","value":"0x0","gas":"0x6cbf","gasUsed":"0x0","input":"0x","output":"0x"}]}`
|
||||
want := new(callTrace)
|
||||
json.Unmarshal([]byte(wantStr), want)
|
||||
if !jsonEqual(have, want) {
|
||||
t.Error("have != want")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrestateTracerCreate2(t *testing.T) {
|
||||
@@ -201,96 +259,6 @@ func TestPrestateTracerCreate2(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Iterates over all the input-output datasets in the tracer test harness and
|
||||
// runs the JavaScript tracers against them.
|
||||
func TestCallTracerLegacy(t *testing.T) {
|
||||
testCallTracer("callTracerLegacy", "call_tracer_legacy", t)
|
||||
}
|
||||
|
||||
func testCallTracer(tracer string, dirPath string, t *testing.T) {
|
||||
files, err := ioutil.ReadDir(filepath.Join("testdata", dirPath))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve tracer test suite: %v", err)
|
||||
}
|
||||
for _, file := range files {
|
||||
if !strings.HasSuffix(file.Name(), ".json") {
|
||||
continue
|
||||
}
|
||||
file := file // capture range variable
|
||||
t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Call tracer test found, read if from disk
|
||||
blob, err := ioutil.ReadFile(filepath.Join("testdata", dirPath, file.Name()))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read testcase: %v", err)
|
||||
}
|
||||
test := new(callTracerTest)
|
||||
if err := json.Unmarshal(blob, test); err != nil {
|
||||
t.Fatalf("failed to parse testcase: %v", err)
|
||||
}
|
||||
// Configure a blockchain with the given prestate
|
||||
tx := new(types.Transaction)
|
||||
if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
|
||||
t.Fatalf("failed to parse testcase input: %v", err)
|
||||
}
|
||||
signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
|
||||
origin, _ := signer.Sender(tx)
|
||||
txContext := vm.TxContext{
|
||||
Origin: origin,
|
||||
GasPrice: tx.GasPrice(),
|
||||
}
|
||||
context := vm.BlockContext{
|
||||
CanTransfer: core.CanTransfer,
|
||||
Transfer: core.Transfer,
|
||||
Coinbase: test.Context.Miner,
|
||||
BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
|
||||
Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
|
||||
Difficulty: (*big.Int)(test.Context.Difficulty),
|
||||
GasLimit: uint64(test.Context.GasLimit),
|
||||
}
|
||||
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
|
||||
|
||||
// Create the tracer, the EVM environment and run it
|
||||
tracer, err := New(tracer, new(Context))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create call tracer: %v", err)
|
||||
}
|
||||
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
|
||||
|
||||
msg, err := tx.AsMessage(signer, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||
}
|
||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
if _, err = st.TransitionDb(); err != nil {
|
||||
t.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
// Retrieve the trace result and compare against the etalon
|
||||
res, err := tracer.GetResult()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve trace result: %v", err)
|
||||
}
|
||||
ret := new(callTrace)
|
||||
if err := json.Unmarshal(res, ret); err != nil {
|
||||
t.Fatalf("failed to unmarshal trace result: %v", err)
|
||||
}
|
||||
|
||||
if !jsonEqual(ret, test.Result) {
|
||||
// uncomment this for easier debugging
|
||||
//have, _ := json.MarshalIndent(ret, "", " ")
|
||||
//want, _ := json.MarshalIndent(test.Result, "", " ")
|
||||
//t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
|
||||
t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallTracer(t *testing.T) {
|
||||
testCallTracer("callTracer", "call_tracer", t)
|
||||
}
|
||||
|
||||
// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
|
||||
// comparison
|
||||
func jsonEqual(x, y interface{}) bool {
|
||||
@@ -337,6 +305,7 @@ func BenchmarkTransactionTrace(b *testing.B) {
|
||||
Time: new(big.Int).SetUint64(uint64(5)),
|
||||
Difficulty: big.NewInt(0xffffffff),
|
||||
GasLimit: gas,
|
||||
BaseFee: big.NewInt(8),
|
||||
}
|
||||
alloc := core.GenesisAlloc{}
|
||||
// The code pushes 'deadbeef' into memory, then the other params, and calls CREATE2, then returns
|
||||
@@ -386,73 +355,3 @@ func BenchmarkTransactionTrace(b *testing.B) {
|
||||
tracer.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTracers(b *testing.B) {
|
||||
files, err := ioutil.ReadDir(filepath.Join("testdata", "call_tracer"))
|
||||
if err != nil {
|
||||
b.Fatalf("failed to retrieve tracer test suite: %v", err)
|
||||
}
|
||||
for _, file := range files {
|
||||
if !strings.HasSuffix(file.Name(), ".json") {
|
||||
continue
|
||||
}
|
||||
file := file // capture range variable
|
||||
b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) {
|
||||
blob, err := ioutil.ReadFile(filepath.Join("testdata", "call_tracer", file.Name()))
|
||||
if err != nil {
|
||||
b.Fatalf("failed to read testcase: %v", err)
|
||||
}
|
||||
test := new(callTracerTest)
|
||||
if err := json.Unmarshal(blob, test); err != nil {
|
||||
b.Fatalf("failed to parse testcase: %v", err)
|
||||
}
|
||||
benchTracer("callTracer", test, b)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
|
||||
// Configure a blockchain with the given prestate
|
||||
tx := new(types.Transaction)
|
||||
if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
|
||||
b.Fatalf("failed to parse testcase input: %v", err)
|
||||
}
|
||||
signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
|
||||
msg, err := tx.AsMessage(signer, nil)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||
}
|
||||
origin, _ := signer.Sender(tx)
|
||||
txContext := vm.TxContext{
|
||||
Origin: origin,
|
||||
GasPrice: tx.GasPrice(),
|
||||
}
|
||||
context := vm.BlockContext{
|
||||
CanTransfer: core.CanTransfer,
|
||||
Transfer: core.Transfer,
|
||||
Coinbase: test.Context.Miner,
|
||||
BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
|
||||
Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
|
||||
Difficulty: (*big.Int)(test.Context.Difficulty),
|
||||
GasLimit: uint64(test.Context.GasLimit),
|
||||
}
|
||||
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
|
||||
|
||||
// Create the tracer, the EVM environment and run it
|
||||
tracer, err := New(tracerName, new(Context))
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create call tracer: %v", err)
|
||||
}
|
||||
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
snap := statedb.Snapshot()
|
||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
if _, err = st.TransitionDb(); err != nil {
|
||||
b.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
statedb.RevertToSnapshot(snap)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user