mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 15:16:43 +00:00
Merge branch 'extended-tracer-backport-1.13.5' into release/sei-1.x-fh3.0
# Conflicts: # accounts/abi/bind/backends/simulated.go # cmd/geth/config.go # consensus/beacon/consensus.go # consensus/ethash/consensus.go # core/state_processor.go # core/vm/contracts.go # core/vm/contracts_test.go # core/vm/evm.go # eth/tracers/api.go # eth/tracers/js/goja.go # lib/ethapi/api.go # light/odr_test.go
This commit is contained in:
commit
301839fbfe
86 changed files with 2145 additions and 879 deletions
|
|
@ -681,7 +681,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
|
|||
|
||||
// Set infinite balance to the fake caller account.
|
||||
from := stateDB.(*state.StateDB).GetOrNewStateObject(call.From)
|
||||
from.SetBalance(math.MaxBig256)
|
||||
from.SetBalance(math.MaxBig256, state.BalanceChangeUnspecified)
|
||||
|
||||
// Execute the call.
|
||||
msg := &core.Message{
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
}
|
||||
if beaconRoot := pre.Env.ParentBeaconBlockRoot; beaconRoot != nil {
|
||||
evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vmConfig)
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb)
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb, nil)
|
||||
}
|
||||
var blobGasUsed uint64
|
||||
|
||||
|
|
@ -234,6 +234,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
)
|
||||
evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig)
|
||||
|
||||
if tracer != nil {
|
||||
tracer.CaptureTxStart(evm, tx, msg.From)
|
||||
}
|
||||
// (ret []byte, usedGas uint64, failed bool, err error)
|
||||
msgResult, err := core.ApplyMessage(evm, msg, gaspool)
|
||||
if err != nil {
|
||||
|
|
@ -241,6 +244,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err)
|
||||
rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()})
|
||||
gaspool.SetGas(prevGas)
|
||||
if tracer != nil {
|
||||
tracer.CaptureTxEnd(nil, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
includedTxs = append(includedTxs, tx)
|
||||
|
|
@ -282,6 +288,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
//receipt.BlockNumber
|
||||
receipt.TransactionIndex = uint(txIndex)
|
||||
receipts = append(receipts, receipt)
|
||||
if tracer != nil {
|
||||
tracer.CaptureTxEnd(receipt, nil)
|
||||
}
|
||||
}
|
||||
|
||||
txIndex++
|
||||
|
|
@ -307,15 +316,15 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
reward.Sub(reward, new(big.Int).SetUint64(ommer.Delta))
|
||||
reward.Mul(reward, blockReward)
|
||||
reward.Div(reward, big.NewInt(8))
|
||||
statedb.AddBalance(ommer.Address, reward)
|
||||
statedb.AddBalance(ommer.Address, reward, state.BalanceIncreaseRewardMineUncle)
|
||||
}
|
||||
statedb.AddBalance(pre.Env.Coinbase, minerReward)
|
||||
statedb.AddBalance(pre.Env.Coinbase, minerReward, state.BalanceIncreaseRewardMineBlock)
|
||||
}
|
||||
// Apply withdrawals
|
||||
for _, w := range pre.Env.Withdrawals {
|
||||
// Amount is in gwei, turn into wei
|
||||
amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei))
|
||||
statedb.AddBalance(w.Address, amount)
|
||||
statedb.AddBalance(w.Address, amount, state.BalanceIncreaseWithdrawal)
|
||||
}
|
||||
// Commit block
|
||||
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber))
|
||||
|
|
@ -358,7 +367,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB
|
|||
for addr, a := range accounts {
|
||||
statedb.SetCode(addr, a.Code)
|
||||
statedb.SetNonce(addr, a.Nonce)
|
||||
statedb.SetBalance(addr, a.Balance)
|
||||
statedb.SetBalance(addr, a.Balance, state.BalanceIncreaseGenesisBalance)
|
||||
for k, v := range a.Storage {
|
||||
statedb.SetState(addr, k, v)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,14 @@ var (
|
|||
Name: "trace",
|
||||
Usage: "Output full trace logs to files <txhash>.jsonl",
|
||||
}
|
||||
TraceTracerFlag = &cli.StringFlag{
|
||||
Name: "trace.tracer",
|
||||
Usage: "Configures the use of a custom tracer, e.g native or js tracers. Examples are callTracer and 4byteTracer. These tracers emit results into files as trace-<txIndex>-<txHash>.json",
|
||||
}
|
||||
TraceTracerConfigFlag = &cli.StringFlag{
|
||||
Name: "trace.jsonconfig",
|
||||
Usage: "The configurations for the custom tracer specified by --trace.tracer. If provided, must be in JSON format",
|
||||
}
|
||||
TraceDisableMemoryFlag = &cli.BoolFlag{
|
||||
Name: "trace.nomemory",
|
||||
Value: true,
|
||||
|
|
|
|||
92
cmd/evm/internal/t8ntool/tracewriter.go
Normal file
92
cmd/evm/internal/t8ntool/tracewriter.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// 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/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
"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(receipt *types.Receipt, err error) {
|
||||
t.inner.CaptureTxEnd(receipt, err)
|
||||
defer t.f.Close()
|
||||
|
||||
if tracer, ok := t.inner.(directory.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(env *vm.EVM, tx *types.Transaction, from common.Address) {
|
||||
t.inner.CaptureTxStart(env, tx, from)
|
||||
}
|
||||
func (t *traceWriter) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
t.inner.CaptureStart(from, to, create, input, gas, value)
|
||||
}
|
||||
|
||||
func (t *traceWriter) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
t.inner.CaptureEnd(output, gasUsed, err, reverted)
|
||||
}
|
||||
|
||||
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, reverted bool) {
|
||||
t.inner.CaptureExit(output, gasUsed, err, reverted)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (t *traceWriter) CaptureKeccakPreimage(hash common.Hash, data []byte) {
|
||||
t.inner.CaptureKeccakPreimage(hash, data)
|
||||
}
|
||||
|
||||
func (t *traceWriter) OnGasChange(old, new uint64, reason vm.GasChangeReason) {
|
||||
t.inner.OnGasChange(old, new, reason)
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ import (
|
|||
"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/eth/tracers/directory"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -135,9 +136,21 @@ func Transition(ctx *cli.Context) error {
|
|||
prevFile = traceFile
|
||||
return logger.NewJSONLogger(logConfig, traceFile), nil
|
||||
}
|
||||
} else {
|
||||
getTracer = func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error) {
|
||||
return nil, nil
|
||||
} else if ctx.IsSet(TraceTracerFlag.Name) {
|
||||
var config json.RawMessage
|
||||
if ctx.IsSet(TraceTracerConfigFlag.Name) {
|
||||
config = []byte(ctx.String(TraceTracerConfigFlag.Name))
|
||||
}
|
||||
getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) {
|
||||
traceFile, err := os.Create(path.Join(baseDir, fmt.Sprintf("trace-%d-%v.json", txIndex, txHash.String())))
|
||||
if err != nil {
|
||||
return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err))
|
||||
}
|
||||
tracer, err := directory.DefaultDirectory.New(ctx.String(TraceTracerFlag.Name), nil, config)
|
||||
if err != nil {
|
||||
return nil, NewError(ErrorConfig, fmt.Errorf("failed instantiating tracer: %w", err))
|
||||
}
|
||||
return &traceWriter{tracer, traceFile}, nil
|
||||
}
|
||||
}
|
||||
// We need to load three things: alloc, env and transactions. May be either in
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
|
|||
utils.MetricsInfluxDBBucketFlag,
|
||||
utils.MetricsInfluxDBOrganizationFlag,
|
||||
utils.TxLookupLimitFlag,
|
||||
utils.VMTraceFlag,
|
||||
utils.TransactionHistoryFlag,
|
||||
utils.StateHistoryFlag,
|
||||
}, utils.DatabaseFlags),
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/eth/catalyst"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
"github.com/ethereum/go-ethereum/lib/ethapi"
|
||||
"github.com/ethereum/go-ethereum/lib/flags"
|
||||
"github.com/ethereum/go-ethereum/lib/version"
|
||||
|
|
@ -177,6 +178,18 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
|
|||
v := ctx.Uint64(utils.OverrideVerkle.Name)
|
||||
cfg.Eth.OverrideVerkle = &v
|
||||
}
|
||||
|
||||
if ctx.IsSet(utils.VMTraceFlag.Name) {
|
||||
if name := ctx.String(utils.VMTraceFlag.Name); name != "" {
|
||||
t, err := directory.LiveDirectory.New(name)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to create tracer %q: %v", name, err)
|
||||
}
|
||||
|
||||
cfg.Eth.VMTracer = t
|
||||
}
|
||||
}
|
||||
|
||||
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
|
||||
|
||||
// Create gauge with geth system and build information
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import (
|
|||
|
||||
// Force-load the tracer engines to trigger registration
|
||||
_ "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/urfave/cli/v2"
|
||||
|
|
@ -136,6 +137,7 @@ var (
|
|||
utils.DeveloperGasLimitFlag,
|
||||
utils.DeveloperPeriodFlag,
|
||||
utils.VMEnableDebugFlag,
|
||||
utils.VMTraceFlag,
|
||||
utils.NetworkIdFlag,
|
||||
utils.EthStatsURLFlag,
|
||||
utils.NoCompactionFlag,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/eth/filters"
|
||||
"github.com/ethereum/go-ethereum/eth/gasprice"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/remotedb"
|
||||
"github.com/ethereum/go-ethereum/ethstats"
|
||||
|
|
@ -496,6 +497,11 @@ var (
|
|||
Usage: "Record information useful for VM and contract debugging",
|
||||
Category: flags.VMCategory,
|
||||
}
|
||||
VMTraceFlag = &cli.StringFlag{
|
||||
Name: "vmtrace",
|
||||
Usage: "Name of tracer which should record internal VM operations (costly)",
|
||||
Category: flags.VMCategory,
|
||||
}
|
||||
|
||||
// API options.
|
||||
RPCGlobalGasCapFlag = &cli.Uint64Flag{
|
||||
|
|
@ -2116,12 +2122,21 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
|||
cache.TrieDirtyLimit = ctx.Int(CacheFlag.Name) * ctx.Int(CacheGCFlag.Name) / 100
|
||||
}
|
||||
vmcfg := vm.Config{EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name)}
|
||||
|
||||
if ctx.IsSet(VMTraceFlag.Name) {
|
||||
if name := ctx.String(VMTraceFlag.Name); name != "" {
|
||||
t, err := directory.LiveDirectory.New(name)
|
||||
if err != nil {
|
||||
Fatalf("Failed to create tracer %q: %v", name, err)
|
||||
}
|
||||
vmcfg.Tracer = t
|
||||
}
|
||||
}
|
||||
// Disable transaction indexing/unindexing by default.
|
||||
chain, err := core.NewBlockChain(chainDb, cache, gspec, nil, engine, vmcfg, nil, nil)
|
||||
if err != nil {
|
||||
Fatalf("Can't create BlockChain: %v", err)
|
||||
}
|
||||
|
||||
return chain, chainDb
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||
"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/params"
|
||||
|
|
@ -347,9 +348,9 @@ func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.H
|
|||
}
|
||||
|
||||
// Finalize implements consensus.Engine and processes withdrawals on top.
|
||||
func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) {
|
||||
func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, stateDB vm.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) {
|
||||
if !beacon.IsPoSHeader(header) {
|
||||
beacon.ethone.Finalize(chain, header, state, txs, uncles, nil)
|
||||
beacon.ethone.Finalize(chain, header, stateDB, txs, uncles, nil)
|
||||
return
|
||||
}
|
||||
// Withdrawals processing.
|
||||
|
|
@ -357,7 +358,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
|
|||
// Convert amount from gwei to wei.
|
||||
amount := new(big.Int).SetUint64(w.Amount)
|
||||
amount = amount.Mul(amount, big.NewInt(params.GWei))
|
||||
state.AddBalance(w.Address, amount)
|
||||
stateDB.AddBalance(w.Address, amount, state.BalanceIncreaseWithdrawal)
|
||||
}
|
||||
// No block reward which is issued by consensus layer instead.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||
"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/params"
|
||||
|
|
@ -569,7 +570,7 @@ var (
|
|||
// AccumulateRewards credits the coinbase of the given block with the mining
|
||||
// reward. The total reward consists of the static block reward and rewards for
|
||||
// included uncles. The coinbase of each uncle block is also rewarded.
|
||||
func accumulateRewards(config *params.ChainConfig, state vm.StateDB, header *types.Header, uncles []*types.Header) {
|
||||
func accumulateRewards(config *params.ChainConfig, stateDB vm.StateDB, header *types.Header, uncles []*types.Header) {
|
||||
// Select the correct block reward based on chain progression
|
||||
blockReward := FrontierBlockReward
|
||||
if config.IsByzantium(header.Number) {
|
||||
|
|
@ -586,10 +587,10 @@ func accumulateRewards(config *params.ChainConfig, state vm.StateDB, header *typ
|
|||
r.Sub(r, header.Number)
|
||||
r.Mul(r, blockReward)
|
||||
r.Div(r, big8)
|
||||
state.AddBalance(uncle.Coinbase, r)
|
||||
stateDB.AddBalance(uncle.Coinbase, r, state.BalanceIncreaseRewardMineUncle)
|
||||
|
||||
r.Div(blockReward, big32)
|
||||
reward.Add(reward, r)
|
||||
}
|
||||
state.AddBalance(header.Coinbase, reward)
|
||||
stateDB.AddBalance(header.Coinbase, reward, state.BalanceIncreaseRewardMineBlock)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"errors"
|
||||
"math/big"
|
||||
|
||||
"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/params"
|
||||
|
|
@ -80,7 +81,7 @@ func ApplyDAOHardFork(statedb vm.StateDB) {
|
|||
|
||||
// Move every DAO account and extra-balance account funds into the refund contract
|
||||
for _, addr := range params.DAODrainList() {
|
||||
statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr))
|
||||
statedb.SetBalance(addr, new(big.Int))
|
||||
statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr), state.BalanceIncreaseDaoContract)
|
||||
statedb.SetBalance(addr, new(big.Int), state.BalanceDecreaseDaoAccount)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,6 +185,20 @@ func DefaultCacheConfigWithScheme(scheme string) *CacheConfig {
|
|||
return &config
|
||||
}
|
||||
|
||||
// BlockchainLogger is used to collect traces during chain processing.
|
||||
// Please make a copy of the referenced types if you intend to retain them.
|
||||
type BlockchainLogger interface {
|
||||
vm.EVMLogger
|
||||
state.StateLogger
|
||||
// OnBlockStart is called before executing `block`.
|
||||
// `td` is the total difficulty prior to `block`.
|
||||
OnBlockStart(block *types.Block, td *big.Int, finalized *types.Header, safe *types.Header, chainConfig *params.ChainConfig)
|
||||
OnBlockEnd(err error)
|
||||
OnGenesisBlock(genesis *types.Block, alloc GenesisAlloc)
|
||||
OnBeaconBlockRootStart(root common.Hash)
|
||||
OnBeaconBlockRootEnd()
|
||||
}
|
||||
|
||||
// BlockChain represents the canonical chain given a database with a genesis
|
||||
// block. The Blockchain manages chain imports, reverts, chain reorganisations.
|
||||
//
|
||||
|
|
@ -258,6 +272,7 @@ type BlockChain struct {
|
|||
processor Processor // Block transaction processor interface
|
||||
forker *ForkChoice
|
||||
vmConfig vm.Config
|
||||
logger BlockchainLogger
|
||||
}
|
||||
|
||||
// NewBlockChain returns a fully initialised block chain using information
|
||||
|
|
@ -269,7 +284,14 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
|
|||
}
|
||||
// Open trie database with provided config
|
||||
triedb := trie.NewDatabase(db, cacheConfig.triedbConfig())
|
||||
|
||||
var logger BlockchainLogger
|
||||
if vmConfig.Tracer != nil {
|
||||
l, ok := vmConfig.Tracer.(BlockchainLogger)
|
||||
if !ok {
|
||||
return nil, errors.New("only extended tracers are supported for live mode")
|
||||
}
|
||||
logger = l
|
||||
}
|
||||
// Setup the genesis block, commit the provided genesis specification
|
||||
// to database if the genesis block is not present yet, or load the
|
||||
// stored one from database.
|
||||
|
|
@ -301,6 +323,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
|
|||
futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks),
|
||||
engine: engine,
|
||||
vmConfig: vmConfig,
|
||||
logger: logger,
|
||||
}
|
||||
bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit))
|
||||
bc.forker = NewForkChoice(bc, shouldPreserve)
|
||||
|
|
@ -427,6 +450,21 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
|
|||
}
|
||||
}
|
||||
|
||||
if bc.logger != 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
|
||||
if bc.cacheConfig.SnapshotLimit > 0 {
|
||||
// If the chain was rewound past the snapshot persistent layer (causing
|
||||
|
|
@ -1765,6 +1803,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
return it.index, err
|
||||
}
|
||||
stats.processed++
|
||||
if bc.logger != nil {
|
||||
bc.logger.OnBlockStart(block, bc.GetTd(block.ParentHash(), block.NumberU64()-1), bc.CurrentFinalBlock(), bc.CurrentSafeBlock(), bc.chainConfig)
|
||||
bc.logger.OnBlockEnd(nil)
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
|
@ -1782,6 +1824,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
if err != nil {
|
||||
return it.index, err
|
||||
}
|
||||
statedb.SetLogger(bc.logger)
|
||||
|
||||
// Enable prefetching to pull in trie node paths while processing transactions
|
||||
statedb.StartPrefetcher("chain")
|
||||
|
|
@ -1795,7 +1838,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps)
|
||||
|
||||
go func(start time.Time, followup *types.Block, throwaway *state.StateDB) {
|
||||
bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, &followupInterrupt)
|
||||
vmCfg := bc.vmConfig
|
||||
vmCfg.Tracer = nil
|
||||
bc.prefetcher.Prefetch(followup, throwaway, vmCfg, &followupInterrupt)
|
||||
|
||||
blockPrefetchExecuteTimer.Update(time.Since(start))
|
||||
if followupInterrupt.Load() {
|
||||
|
|
@ -1805,68 +1850,15 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
}
|
||||
}
|
||||
|
||||
// Process block using the parent state as reference point
|
||||
pstart := time.Now()
|
||||
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
|
||||
if err != nil {
|
||||
bc.reportBlock(block, receipts, err)
|
||||
followupInterrupt.Store(true)
|
||||
return it.index, err
|
||||
}
|
||||
ptime := time.Since(pstart)
|
||||
|
||||
vstart := time.Now()
|
||||
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil {
|
||||
bc.reportBlock(block, receipts, err)
|
||||
followupInterrupt.Store(true)
|
||||
return it.index, err
|
||||
}
|
||||
vtime := time.Since(vstart)
|
||||
proctime := time.Since(start) // processing + validation
|
||||
|
||||
// Update the metrics touched during block processing and validation
|
||||
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing)
|
||||
storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing)
|
||||
snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete(in processing)
|
||||
snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete(in processing)
|
||||
accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation)
|
||||
storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation)
|
||||
accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation)
|
||||
storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation)
|
||||
triehash := statedb.AccountHashes + statedb.StorageHashes // The time spent on tries hashing
|
||||
trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update
|
||||
trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read
|
||||
trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read
|
||||
blockExecutionTimer.Update(ptime - trieRead) // The time spent on EVM processing
|
||||
blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation
|
||||
|
||||
// Write the block to the chain and get the status.
|
||||
var (
|
||||
wstart = time.Now()
|
||||
status WriteStatus
|
||||
)
|
||||
if !setHead {
|
||||
// Don't set the head, only insert the block
|
||||
err = bc.writeBlockWithState(block, receipts, statedb)
|
||||
} else {
|
||||
status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false)
|
||||
}
|
||||
// The traced section of block import.
|
||||
res, err := bc.processBlock(block, statedb, start, setHead)
|
||||
followupInterrupt.Store(true)
|
||||
if err != nil {
|
||||
return it.index, err
|
||||
}
|
||||
// Update the metrics touched during block commit
|
||||
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
|
||||
storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them
|
||||
snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them
|
||||
triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
|
||||
|
||||
blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits)
|
||||
blockInsertTimer.UpdateSince(start)
|
||||
|
||||
// Report the import stats before returning the various results
|
||||
stats.processed++
|
||||
stats.usedGas += usedGas
|
||||
stats.usedGas += res.usedGas
|
||||
|
||||
var snapDiffItems, snapBufItems common.StorageSize
|
||||
if bc.snaps != nil {
|
||||
|
|
@ -1878,11 +1870,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
if !setHead {
|
||||
// After merge we expect few side chains. Simply count
|
||||
// all blocks the CL gives us for GC processing time
|
||||
bc.gcproc += proctime
|
||||
|
||||
bc.gcproc += res.procTime
|
||||
return it.index, nil // Direct block insertion of a single block
|
||||
}
|
||||
switch status {
|
||||
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(),
|
||||
|
|
@ -1892,7 +1883,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
lastCanon = block
|
||||
|
||||
// Only count canonical blocks for GC processing time
|
||||
bc.gcproc += proctime
|
||||
bc.gcproc += res.procTime
|
||||
|
||||
case SideStatTy:
|
||||
log.Debug("Inserted forked block", "number", block.Number(), "hash", block.Hash(),
|
||||
|
|
@ -1929,6 +1920,84 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
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 {
|
||||
td := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
|
||||
bc.logger.OnBlockStart(block, td, bc.CurrentFinalBlock(), bc.CurrentSafeBlock(), bc.chainConfig)
|
||||
defer func() {
|
||||
bc.logger.OnBlockEnd(blockEndErr)
|
||||
}()
|
||||
}
|
||||
|
||||
// Process block using the parent state as reference point
|
||||
pstart := time.Now()
|
||||
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
|
||||
if err != nil {
|
||||
bc.reportBlock(block, receipts, err)
|
||||
return nil, err
|
||||
}
|
||||
ptime := time.Since(pstart)
|
||||
|
||||
vstart := time.Now()
|
||||
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil {
|
||||
bc.reportBlock(block, receipts, err)
|
||||
return nil, err
|
||||
}
|
||||
vtime := time.Since(vstart)
|
||||
proctime := time.Since(start) // processing + validation
|
||||
|
||||
// Update the metrics touched during block processing and validation
|
||||
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing)
|
||||
storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing)
|
||||
snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete(in processing)
|
||||
snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete(in processing)
|
||||
accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation)
|
||||
storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation)
|
||||
accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation)
|
||||
storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation)
|
||||
triehash := statedb.AccountHashes + statedb.StorageHashes // The time spent on tries hashing
|
||||
trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update
|
||||
trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read
|
||||
trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read
|
||||
blockExecutionTimer.Update(ptime - trieRead) // The time spent on EVM processing
|
||||
blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation
|
||||
|
||||
// Write the block to the chain and get the status.
|
||||
var (
|
||||
wstart = time.Now()
|
||||
status WriteStatus
|
||||
)
|
||||
if !setHead {
|
||||
// Don't set the head, only insert the block
|
||||
err = bc.writeBlockWithState(block, receipts, statedb)
|
||||
} else {
|
||||
status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Update the metrics touched during block commit
|
||||
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
|
||||
storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them
|
||||
snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them
|
||||
triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
|
||||
|
||||
blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits)
|
||||
blockInsertTimer.UpdateSince(start)
|
||||
|
||||
return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil
|
||||
}
|
||||
|
||||
// insertSideChain is called when an import batch hits upon a pruned ancestor
|
||||
// error, which happens when a sidechain with a sufficiently old fork-block is
|
||||
// found.
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
|
|
@ -3248,7 +3247,7 @@ func testDeleteRecreateSlots(t *testing.T, scheme string) {
|
|||
})
|
||||
// Import the canonical chain
|
||||
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{
|
||||
Tracer: logger.NewJSONLogger(nil, os.Stdout),
|
||||
//Tracer: logger.NewJSONLogger(nil, os.Stdout),
|
||||
}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create tester chain: %v", err)
|
||||
|
|
@ -3330,7 +3329,7 @@ func testDeleteRecreateAccount(t *testing.T, scheme string) {
|
|||
})
|
||||
// Import the canonical chain
|
||||
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{
|
||||
Tracer: logger.NewJSONLogger(nil, os.Stdout),
|
||||
//Tracer: logger.NewJSONLogger(nil, os.Stdout),
|
||||
}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create tester chain: %v", err)
|
||||
|
|
@ -4682,7 +4681,7 @@ func TestEIP3651(t *testing.T) {
|
|||
|
||||
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)*/ }, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create tester chain: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
|
|||
blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
|
||||
vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{})
|
||||
)
|
||||
ProcessBeaconBlockRoot(root, vmenv, b.statedb)
|
||||
ProcessBeaconBlockRoot(root, vmenv, b.statedb, nil)
|
||||
}
|
||||
|
||||
// addTx adds a transaction to the generated block. If no coinbase has
|
||||
|
|
@ -406,6 +406,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
|||
// then generate chain on top.
|
||||
func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
|
||||
triedb := trie.NewDatabase(db, trie.HashDefaults)
|
||||
defer triedb.Close()
|
||||
_, err := genesis.Commit(db, triedb)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
)
|
||||
|
|
@ -135,6 +136,6 @@ func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool {
|
|||
|
||||
// Transfer subtracts amount from sender and adds amount to recipient using the given Db
|
||||
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {
|
||||
db.SubBalance(sender, amount)
|
||||
db.AddBalance(recipient, amount)
|
||||
db.SubBalance(sender, amount, state.BalanceChangeTransfer)
|
||||
db.AddBalance(recipient, amount, state.BalanceChangeTransfer)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ func (ga *GenesisAlloc) hash(isVerkle bool) (common.Hash, error) {
|
|||
}
|
||||
for addr, account := range *ga {
|
||||
if account.Balance != nil {
|
||||
statedb.AddBalance(addr, account.Balance)
|
||||
statedb.AddBalance(addr, account.Balance, state.BalanceIncreaseGenesisBalance)
|
||||
}
|
||||
statedb.SetCode(addr, account.Code)
|
||||
statedb.SetNonce(addr, account.Nonce)
|
||||
|
|
@ -163,7 +163,7 @@ func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhas
|
|||
}
|
||||
for addr, account := range *ga {
|
||||
if account.Balance != nil {
|
||||
statedb.AddBalance(addr, account.Balance)
|
||||
statedb.AddBalance(addr, account.Balance, state.BalanceIncreaseGenesisBalance)
|
||||
}
|
||||
statedb.SetCode(addr, account.Code)
|
||||
statedb.SetNonce(addr, account.Nonce)
|
||||
|
|
@ -190,6 +190,39 @@ func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhas
|
|||
return nil
|
||||
}
|
||||
|
||||
func getGenesisState(db ethdb.Database, blockhash common.Hash) (alloc 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
|
||||
}
|
||||
|
||||
// GenesisAccount is an account in the state of the genesis block.
|
||||
type GenesisAccount struct {
|
||||
Code []byte `json:"code,omitempty"`
|
||||
|
|
@ -298,6 +331,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen
|
|||
} else {
|
||||
log.Info("Writing custom genesis block")
|
||||
}
|
||||
|
||||
applyOverrides(genesis.Config)
|
||||
block, err := genesis.Commit(db, triedb)
|
||||
if err != nil {
|
||||
|
|
@ -522,6 +556,8 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database) (*types.Block
|
|||
|
||||
// MustCommit writes the genesis block and state to db, panicking on error.
|
||||
// The block is committed as the canonical head block.
|
||||
// Note the state changes will be committed in hash-based scheme, use Commit
|
||||
// if path-scheme is preferred.
|
||||
func (g *Genesis) MustCommit(db ethdb.Database, triedb *trie.Database) *types.Block {
|
||||
block, err := g.Commit(db, triedb)
|
||||
if err != nil {
|
||||
|
|
|
|||
66
core/state/metadata.go
Normal file
66
core/state/metadata.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// 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 state
|
||||
|
||||
// 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
|
||||
)
|
||||
|
|
@ -243,6 +243,9 @@ func (s *stateObject) SetState(key, value common.Hash) {
|
|||
key: key,
|
||||
prevalue: prev,
|
||||
})
|
||||
if s.db.logger != nil {
|
||||
s.db.logger.OnStorageChange(s.address, key, prev, value)
|
||||
}
|
||||
s.setState(key, value)
|
||||
}
|
||||
|
||||
|
|
@ -405,7 +408,7 @@ func (s *stateObject) commit() (*trienode.NodeSet, error) {
|
|||
|
||||
// AddBalance adds amount to s's balance.
|
||||
// It is used to add funds to the destination account of a transfer.
|
||||
func (s *stateObject) AddBalance(amount *big.Int) {
|
||||
func (s *stateObject) AddBalance(amount *big.Int, reason BalanceChangeReason) {
|
||||
// EIP161: We must check emptiness for the objects such that the account
|
||||
// clearing (0,0,0 objects) can take effect.
|
||||
if amount.Sign() == 0 {
|
||||
|
|
@ -414,23 +417,26 @@ func (s *stateObject) AddBalance(amount *big.Int) {
|
|||
}
|
||||
return
|
||||
}
|
||||
s.SetBalance(new(big.Int).Add(s.Balance(), amount))
|
||||
s.SetBalance(new(big.Int).Add(s.Balance(), amount), reason)
|
||||
}
|
||||
|
||||
// SubBalance removes amount from s's balance.
|
||||
// It is used to remove funds from the origin account of a transfer.
|
||||
func (s *stateObject) SubBalance(amount *big.Int) {
|
||||
func (s *stateObject) SubBalance(amount *big.Int, reason BalanceChangeReason) {
|
||||
if amount.Sign() == 0 {
|
||||
return
|
||||
}
|
||||
s.SetBalance(new(big.Int).Sub(s.Balance(), amount))
|
||||
s.SetBalance(new(big.Int).Sub(s.Balance(), amount), reason)
|
||||
}
|
||||
|
||||
func (s *stateObject) SetBalance(amount *big.Int) {
|
||||
func (s *stateObject) SetBalance(amount *big.Int, reason BalanceChangeReason) {
|
||||
s.db.journal.append(balanceChange{
|
||||
account: &s.address,
|
||||
prev: new(big.Int).Set(s.data.Balance),
|
||||
})
|
||||
if s.db.logger != nil {
|
||||
s.db.logger.OnBalanceChange(s.address, s.Balance(), amount, reason)
|
||||
}
|
||||
s.setBalance(amount)
|
||||
}
|
||||
|
||||
|
|
@ -508,6 +514,9 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
|
|||
prevhash: s.CodeHash(),
|
||||
prevcode: prevcode,
|
||||
})
|
||||
if s.db.logger != nil {
|
||||
s.db.logger.OnCodeChange(s.address, common.BytesToHash(s.CodeHash()), prevcode, codeHash, code)
|
||||
}
|
||||
s.setCode(codeHash, code)
|
||||
}
|
||||
|
||||
|
|
@ -522,6 +531,9 @@ func (s *stateObject) SetNonce(nonce uint64) {
|
|||
account: &s.address,
|
||||
prev: s.data.Nonce,
|
||||
})
|
||||
if s.db.logger != nil {
|
||||
s.db.logger.OnNonceChange(s.address, s.data.Nonce, nonce)
|
||||
}
|
||||
s.setNonce(nonce)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,11 +49,11 @@ func TestDump(t *testing.T) {
|
|||
|
||||
// generate a few entries
|
||||
obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01}))
|
||||
obj1.AddBalance(big.NewInt(22))
|
||||
obj1.AddBalance(big.NewInt(22), BalanceChangeUnspecified)
|
||||
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})
|
||||
obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02}))
|
||||
obj3.SetBalance(big.NewInt(44))
|
||||
obj3.SetBalance(big.NewInt(44), BalanceChangeUnspecified)
|
||||
|
||||
// write some of them to the trie
|
||||
s.state.updateStateObject(obj1)
|
||||
|
|
@ -106,13 +106,13 @@ func TestIterativeDump(t *testing.T) {
|
|||
|
||||
// generate a few entries
|
||||
obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01}))
|
||||
obj1.AddBalance(big.NewInt(22))
|
||||
obj1.AddBalance(big.NewInt(22), BalanceChangeUnspecified)
|
||||
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})
|
||||
obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02}))
|
||||
obj3.SetBalance(big.NewInt(44))
|
||||
obj3.SetBalance(big.NewInt(44), BalanceChangeUnspecified)
|
||||
obj4 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x00}))
|
||||
obj4.AddBalance(big.NewInt(1337))
|
||||
obj4.AddBalance(big.NewInt(1337), BalanceChangeUnspecified)
|
||||
|
||||
// write some of them to the trie
|
||||
s.state.updateStateObject(obj1)
|
||||
|
|
@ -208,7 +208,7 @@ func TestSnapshot2(t *testing.T) {
|
|||
|
||||
// db, trie are already non-empty values
|
||||
so0 := state.getStateObject(stateobjaddr0)
|
||||
so0.SetBalance(big.NewInt(42))
|
||||
so0.SetBalance(big.NewInt(42), BalanceChangeUnspecified)
|
||||
so0.SetNonce(43)
|
||||
so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
|
||||
so0.selfDestructed = false
|
||||
|
|
@ -220,7 +220,7 @@ func TestSnapshot2(t *testing.T) {
|
|||
|
||||
// and one with deleted == true
|
||||
so1 := state.getStateObject(stateobjaddr1)
|
||||
so1.SetBalance(big.NewInt(52))
|
||||
so1.SetBalance(big.NewInt(52), BalanceChangeUnspecified)
|
||||
so1.SetNonce(53)
|
||||
so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'})
|
||||
so1.selfDestructed = true
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
|
|
@ -48,6 +47,24 @@ type revision struct {
|
|||
journalIndex int
|
||||
}
|
||||
|
||||
// StateLogger is used to collect state update traces from EVM transaction
|
||||
// execution.
|
||||
// The following hooks are invoked post execution. I.e. looking up state
|
||||
// after the hook should reflect the new value.
|
||||
// Note that reference types are actual VM data structures; make copies
|
||||
// if you need to retain them beyond the current call.
|
||||
type StateLogger interface {
|
||||
OnBalanceChange(addr common.Address, prev, new *big.Int, reason BalanceChangeReason)
|
||||
OnNonceChange(addr common.Address, prev, new uint64)
|
||||
OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte)
|
||||
OnStorageChange(addr common.Address, slot common.Hash, prev, new common.Hash)
|
||||
OnLog(log *types.Log)
|
||||
// OnNewAccount is called when a new account is created.
|
||||
// Reset indicates an account existed at that address
|
||||
// which will be replaced.
|
||||
OnNewAccount(addr common.Address, reset bool)
|
||||
}
|
||||
|
||||
// StateDB structs within the ethereum protocol are used to store anything
|
||||
// within the merkle trie. StateDBs take care of caching and storing
|
||||
// nested states. It's the general query interface to retrieve:
|
||||
|
|
@ -64,6 +81,7 @@ type StateDB struct {
|
|||
prefetcher *triePrefetcher
|
||||
trie Trie
|
||||
hasher crypto.KeccakState
|
||||
logger StateLogger
|
||||
snaps *snapshot.Tree // Nil if snapshot is not available
|
||||
snap snapshot.Snapshot // Nil if snapshot is not available
|
||||
|
||||
|
|
@ -173,6 +191,11 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
|
|||
return sdb, nil
|
||||
}
|
||||
|
||||
// SetLogger sets the logger for account update hooks.
|
||||
func (s *StateDB) SetLogger(l StateLogger) {
|
||||
s.logger = l
|
||||
}
|
||||
|
||||
// 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
|
||||
// commit phase, most of the needed data is already hot.
|
||||
|
|
@ -213,6 +236,9 @@ func (s *StateDB) AddLog(log *types.Log) {
|
|||
log.TxHash = s.thash
|
||||
log.TxIndex = uint(s.txIndex)
|
||||
log.Index = s.logSize
|
||||
if s.logger != nil {
|
||||
s.logger.OnLog(log)
|
||||
}
|
||||
s.logs[s.thash] = append(s.logs[s.thash], log)
|
||||
s.logSize++
|
||||
}
|
||||
|
|
@ -374,25 +400,25 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool {
|
|||
*/
|
||||
|
||||
// AddBalance adds amount to the account associated with addr.
|
||||
func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) {
|
||||
func (s *StateDB) AddBalance(addr common.Address, amount *big.Int, reason BalanceChangeReason) {
|
||||
stateObject := s.GetOrNewStateObject(addr)
|
||||
if stateObject != nil {
|
||||
stateObject.AddBalance(amount)
|
||||
stateObject.AddBalance(amount, reason)
|
||||
}
|
||||
}
|
||||
|
||||
// SubBalance subtracts amount from the account associated with addr.
|
||||
func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) {
|
||||
func (s *StateDB) SubBalance(addr common.Address, amount *big.Int, reason BalanceChangeReason) {
|
||||
stateObject := s.GetOrNewStateObject(addr)
|
||||
if stateObject != nil {
|
||||
stateObject.SubBalance(amount)
|
||||
stateObject.SubBalance(amount, reason)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) {
|
||||
func (s *StateDB) SetBalance(addr common.Address, amount *big.Int, reason BalanceChangeReason) {
|
||||
stateObject := s.GetOrNewStateObject(addr)
|
||||
if stateObject != nil {
|
||||
stateObject.SetBalance(amount)
|
||||
stateObject.SetBalance(amount, reason)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -448,11 +474,18 @@ func (s *StateDB) SelfDestruct(addr common.Address) {
|
|||
if stateObject == nil {
|
||||
return
|
||||
}
|
||||
var (
|
||||
prev = stateObject.Balance()
|
||||
n = new(big.Int)
|
||||
)
|
||||
s.journal.append(selfDestructChange{
|
||||
account: &addr,
|
||||
prev: stateObject.selfDestructed,
|
||||
prevbalance: new(big.Int).Set(stateObject.Balance()),
|
||||
})
|
||||
if s.logger != nil && prev.Sign() > 0 {
|
||||
s.logger.OnBalanceChange(addr, prev, n, BalanceDecreaseSelfdestruct)
|
||||
}
|
||||
stateObject.markSelfdestructed()
|
||||
stateObject.data.Balance = new(big.Int)
|
||||
}
|
||||
|
|
@ -685,7 +718,7 @@ func (s *StateDB) CreateAccount(addr common.Address) {
|
|||
|
||||
// Copy creates a deep, independent copy of the state.
|
||||
// Snapshots of the copied state cannot be applied to the copy.
|
||||
func (s *StateDB) Copy() vm.StateDB {
|
||||
func (s *StateDB) Copy() interface{} {
|
||||
// Copy all the basic fields, initialize the memory ones
|
||||
state := &StateDB{
|
||||
db: s.db,
|
||||
|
|
@ -835,6 +868,10 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
|||
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
|
||||
obj.deleted = true
|
||||
|
||||
// If ether was sent to account post-selfdestruct it is burnt.
|
||||
if bal := obj.Balance(); bal.Sign() != 0 && s.logger != nil {
|
||||
s.logger.OnBalanceChange(obj.address, bal, new(big.Int), BalanceDecreaseSelfdestructBurn)
|
||||
}
|
||||
// We need to maintain account deletions explicitly (will remain
|
||||
// set indefinitely). Note only the first occurred self-destruct
|
||||
// event is tracked.
|
||||
|
|
@ -1389,8 +1426,6 @@ func (s *StateDB) convertAccountSet(set map[common.Address]*types.StateAccount)
|
|||
return ret
|
||||
}
|
||||
|
||||
func (s *StateDB) SetEVM(evm *vm.EVM) {}
|
||||
|
||||
// copySet returns a deep-copied set.
|
||||
func copySet[k comparable](set map[k][]byte) map[k][]byte {
|
||||
copied := make(map[k][]byte, len(set))
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ func newStateTestAction(addr common.Address, r *rand.Rand, index int) testAction
|
|||
{
|
||||
name: "SetBalance",
|
||||
fn: func(a testAction, s *StateDB) {
|
||||
s.SetBalance(addr, big.NewInt(a.args[0]))
|
||||
s.SetBalance(addr, big.NewInt(a.args[0]), BalanceChangeUnspecified)
|
||||
},
|
||||
args: make([]int64, 1),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ func TestUpdateLeaks(t *testing.T) {
|
|||
// Update it with some accounts
|
||||
for i := byte(0); i < 255; i++ {
|
||||
addr := common.BytesToAddress([]byte{i})
|
||||
state.AddBalance(addr, big.NewInt(int64(11*i)))
|
||||
state.AddBalance(addr, big.NewInt(int64(11*i)), BalanceChangeUnspecified)
|
||||
state.SetNonce(addr, uint64(42*i))
|
||||
if i%2 == 0 {
|
||||
state.SetState(addr, common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i}))
|
||||
|
|
@ -91,7 +91,7 @@ func TestIntermediateLeaks(t *testing.T) {
|
|||
finalState, _ := New(types.EmptyRootHash, NewDatabaseWithNodeDB(finalDb, finalNdb), nil)
|
||||
|
||||
modify := func(state *StateDB, addr common.Address, i, tweak byte) {
|
||||
state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak)))
|
||||
state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak)), BalanceChangeUnspecified)
|
||||
state.SetNonce(addr, uint64(42*i+tweak))
|
||||
if i%2 == 0 {
|
||||
state.SetState(addr, common.Hash{i, i, i, 0}, common.Hash{})
|
||||
|
|
@ -167,30 +167,30 @@ func TestCopy(t *testing.T) {
|
|||
|
||||
for i := byte(0); i < 255; i++ {
|
||||
obj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
|
||||
obj.AddBalance(big.NewInt(int64(i)))
|
||||
obj.AddBalance(big.NewInt(int64(i)), BalanceChangeUnspecified)
|
||||
orig.updateStateObject(obj)
|
||||
}
|
||||
orig.Finalise(false)
|
||||
|
||||
// Copy the state
|
||||
copy := orig.Copy()
|
||||
copy := orig.Copy().(*StateDB)
|
||||
|
||||
// Copy the copy state
|
||||
ccopy := copy.Copy()
|
||||
ccopy := copy.Copy().(*StateDB)
|
||||
|
||||
// modify all in memory
|
||||
for i := byte(0); i < 255; i++ {
|
||||
origObj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
|
||||
copyObj := copy.(*StateDB).GetOrNewStateObject(common.BytesToAddress([]byte{i}))
|
||||
ccopyObj := ccopy.(*StateDB).GetOrNewStateObject(common.BytesToAddress([]byte{i}))
|
||||
copyObj := copy.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
|
||||
ccopyObj := ccopy.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
|
||||
|
||||
origObj.AddBalance(big.NewInt(2 * int64(i)))
|
||||
copyObj.AddBalance(big.NewInt(3 * int64(i)))
|
||||
ccopyObj.AddBalance(big.NewInt(4 * int64(i)))
|
||||
origObj.AddBalance(big.NewInt(2*int64(i)), BalanceChangeUnspecified)
|
||||
copyObj.AddBalance(big.NewInt(3*int64(i)), BalanceChangeUnspecified)
|
||||
ccopyObj.AddBalance(big.NewInt(4*int64(i)), BalanceChangeUnspecified)
|
||||
|
||||
orig.updateStateObject(origObj)
|
||||
copy.(*StateDB).updateStateObject(copyObj)
|
||||
ccopy.(*StateDB).updateStateObject(copyObj)
|
||||
copy.updateStateObject(copyObj)
|
||||
ccopy.updateStateObject(copyObj)
|
||||
}
|
||||
|
||||
// Finalise the changes on all concurrently
|
||||
|
|
@ -202,15 +202,15 @@ func TestCopy(t *testing.T) {
|
|||
var wg sync.WaitGroup
|
||||
wg.Add(3)
|
||||
go finalise(&wg, orig)
|
||||
go finalise(&wg, copy.(*StateDB))
|
||||
go finalise(&wg, ccopy.(*StateDB))
|
||||
go finalise(&wg, copy)
|
||||
go finalise(&wg, ccopy)
|
||||
wg.Wait()
|
||||
|
||||
// Verify that the three states have been updated independently
|
||||
for i := byte(0); i < 255; i++ {
|
||||
origObj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
|
||||
copyObj := copy.(*StateDB).GetOrNewStateObject(common.BytesToAddress([]byte{i}))
|
||||
ccopyObj := ccopy.(*StateDB).GetOrNewStateObject(common.BytesToAddress([]byte{i}))
|
||||
copyObj := copy.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
|
||||
ccopyObj := ccopy.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
|
||||
|
||||
if want := big.NewInt(3 * int64(i)); origObj.Balance().Cmp(want) != 0 {
|
||||
t.Errorf("orig obj %d: balance mismatch: have %v, want %v", i, origObj.Balance(), want)
|
||||
|
|
@ -266,14 +266,14 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
|
|||
{
|
||||
name: "SetBalance",
|
||||
fn: func(a testAction, s *StateDB) {
|
||||
s.SetBalance(addr, big.NewInt(a.args[0]))
|
||||
s.SetBalance(addr, big.NewInt(a.args[0]), BalanceChangeUnspecified)
|
||||
},
|
||||
args: make([]int64, 1),
|
||||
},
|
||||
{
|
||||
name: "AddBalance",
|
||||
fn: func(a testAction, s *StateDB) {
|
||||
s.AddBalance(addr, big.NewInt(a.args[0]))
|
||||
s.AddBalance(addr, big.NewInt(a.args[0]), BalanceChangeUnspecified)
|
||||
},
|
||||
args: make([]int64, 1),
|
||||
},
|
||||
|
|
@ -536,7 +536,7 @@ func TestTouchDelete(t *testing.T) {
|
|||
s.state, _ = New(root, s.state.db, s.state.snaps)
|
||||
|
||||
snapshot := s.state.Snapshot()
|
||||
s.state.AddBalance(common.Address{}, new(big.Int))
|
||||
s.state.AddBalance(common.Address{}, new(big.Int), BalanceChangeUnspecified)
|
||||
|
||||
if len(s.state.journal.dirties) != 1 {
|
||||
t.Fatal("expected one dirty state object")
|
||||
|
|
@ -552,12 +552,12 @@ func TestTouchDelete(t *testing.T) {
|
|||
func TestCopyOfCopy(t *testing.T) {
|
||||
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
addr := common.HexToAddress("aaaa")
|
||||
state.SetBalance(addr, big.NewInt(42))
|
||||
state.SetBalance(addr, big.NewInt(42), BalanceChangeUnspecified)
|
||||
|
||||
if got := state.Copy().GetBalance(addr).Uint64(); got != 42 {
|
||||
if got := state.Copy().(*StateDB).GetBalance(addr).Uint64(); got != 42 {
|
||||
t.Fatalf("1st copy fail, expected 42, got %v", got)
|
||||
}
|
||||
if got := state.Copy().Copy().GetBalance(addr).Uint64(); got != 42 {
|
||||
if got := state.Copy().(*StateDB).Copy().(*StateDB).GetBalance(addr).Uint64(); got != 42 {
|
||||
t.Fatalf("2nd copy fail, expected 42, got %v", got)
|
||||
}
|
||||
}
|
||||
|
|
@ -575,7 +575,7 @@ func TestCopyCommitCopy(t *testing.T) {
|
|||
skey := common.HexToHash("aaa")
|
||||
sval := common.HexToHash("bbb")
|
||||
|
||||
state.SetBalance(addr, big.NewInt(42)) // Change the account trie
|
||||
state.SetBalance(addr, big.NewInt(42), BalanceChangeUnspecified) // Change the account trie
|
||||
state.SetCode(addr, []byte("hello")) // Change an external metadata
|
||||
state.SetState(addr, skey, sval) // Change the storage trie
|
||||
|
||||
|
|
@ -592,7 +592,7 @@ func TestCopyCommitCopy(t *testing.T) {
|
|||
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
// Copy the non-committed state database and check pre/post commit balance
|
||||
copyOne := state.Copy()
|
||||
copyOne := state.Copy().(*StateDB)
|
||||
if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
|
||||
t.Fatalf("first copy pre-commit balance mismatch: have %v, want %v", balance, 42)
|
||||
}
|
||||
|
|
@ -606,7 +606,7 @@ func TestCopyCommitCopy(t *testing.T) {
|
|||
t.Fatalf("first copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
// Copy the copy and check the balance once more
|
||||
copyTwo := copyOne.Copy()
|
||||
copyTwo := copyOne.Copy().(*StateDB)
|
||||
if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
|
||||
t.Fatalf("second copy balance mismatch: have %v, want %v", balance, 42)
|
||||
}
|
||||
|
|
@ -648,7 +648,7 @@ func TestCopyCopyCommitCopy(t *testing.T) {
|
|||
skey := common.HexToHash("aaa")
|
||||
sval := common.HexToHash("bbb")
|
||||
|
||||
state.SetBalance(addr, big.NewInt(42)) // Change the account trie
|
||||
state.SetBalance(addr, big.NewInt(42), BalanceChangeUnspecified) // Change the account trie
|
||||
state.SetCode(addr, []byte("hello")) // Change an external metadata
|
||||
state.SetState(addr, skey, sval) // Change the storage trie
|
||||
|
||||
|
|
@ -665,7 +665,7 @@ func TestCopyCopyCommitCopy(t *testing.T) {
|
|||
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
// Copy the non-committed state database and check pre/post commit balance
|
||||
copyOne := state.Copy()
|
||||
copyOne := state.Copy().(*StateDB)
|
||||
if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
|
||||
t.Fatalf("first copy balance mismatch: have %v, want %v", balance, 42)
|
||||
}
|
||||
|
|
@ -679,7 +679,7 @@ func TestCopyCopyCommitCopy(t *testing.T) {
|
|||
t.Fatalf("first copy committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
// Copy the copy and check the balance once more
|
||||
copyTwo := copyOne.Copy()
|
||||
copyTwo := copyOne.Copy().(*StateDB)
|
||||
if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
|
||||
t.Fatalf("second copy pre-commit balance mismatch: have %v, want %v", balance, 42)
|
||||
}
|
||||
|
|
@ -693,7 +693,7 @@ func TestCopyCopyCommitCopy(t *testing.T) {
|
|||
t.Fatalf("second copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
// Copy the copy-copy and check the balance once more
|
||||
copyThree := copyTwo.Copy()
|
||||
copyThree := copyTwo.Copy().(*StateDB)
|
||||
if balance := copyThree.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
|
||||
t.Fatalf("third copy balance mismatch: have %v, want %v", balance, 42)
|
||||
}
|
||||
|
|
@ -717,7 +717,7 @@ func TestCommitCopy(t *testing.T) {
|
|||
skey := common.HexToHash("aaa")
|
||||
sval := common.HexToHash("bbb")
|
||||
|
||||
state.SetBalance(addr, big.NewInt(42)) // Change the account trie
|
||||
state.SetBalance(addr, big.NewInt(42), BalanceChangeUnspecified) // Change the account trie
|
||||
state.SetCode(addr, []byte("hello")) // Change an external metadata
|
||||
state.SetState(addr, skey, sval) // Change the storage trie
|
||||
|
||||
|
|
@ -735,7 +735,7 @@ func TestCommitCopy(t *testing.T) {
|
|||
}
|
||||
// Copy the committed state database, the copied one is not functional.
|
||||
state.Commit(0, true)
|
||||
copied := state.Copy()
|
||||
copied := state.Copy().(*StateDB)
|
||||
if balance := copied.GetBalance(addr); balance.Cmp(big.NewInt(0)) != 0 {
|
||||
t.Fatalf("unexpected balance: have %v", balance)
|
||||
}
|
||||
|
|
@ -766,7 +766,7 @@ func TestDeleteCreateRevert(t *testing.T) {
|
|||
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
|
||||
addr := common.BytesToAddress([]byte("so"))
|
||||
state.SetBalance(addr, big.NewInt(1))
|
||||
state.SetBalance(addr, big.NewInt(1), BalanceChangeUnspecified)
|
||||
|
||||
root, _ := state.Commit(0, false)
|
||||
state, _ = New(root, state.db, state.snaps)
|
||||
|
|
@ -776,7 +776,7 @@ func TestDeleteCreateRevert(t *testing.T) {
|
|||
state.Finalise(true)
|
||||
|
||||
id := state.Snapshot()
|
||||
state.SetBalance(addr, big.NewInt(2))
|
||||
state.SetBalance(addr, big.NewInt(2), BalanceChangeUnspecified)
|
||||
state.RevertToSnapshot(id)
|
||||
|
||||
// Commit the entire state and make sure we don't crash and have the correct state
|
||||
|
|
@ -818,10 +818,10 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
|
|||
state, _ := New(types.EmptyRootHash, db, nil)
|
||||
addr := common.BytesToAddress([]byte("so"))
|
||||
{
|
||||
state.SetBalance(addr, big.NewInt(1))
|
||||
state.SetBalance(addr, big.NewInt(1), BalanceChangeUnspecified)
|
||||
state.SetCode(addr, []byte{1, 2, 3})
|
||||
a2 := common.BytesToAddress([]byte("another"))
|
||||
state.SetBalance(a2, big.NewInt(100))
|
||||
state.SetBalance(a2, big.NewInt(100), BalanceChangeUnspecified)
|
||||
state.SetCode(a2, []byte{1, 2, 4})
|
||||
root, _ = state.Commit(0, false)
|
||||
t.Logf("root: %x", root)
|
||||
|
|
@ -846,7 +846,7 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
|
|||
t.Errorf("expected %d, got %d", exp, got)
|
||||
}
|
||||
// Modify the state
|
||||
state.SetBalance(addr, big.NewInt(2))
|
||||
state.SetBalance(addr, big.NewInt(2), BalanceChangeUnspecified)
|
||||
root, err := state.Commit(0, false)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got root :%x", root)
|
||||
|
|
@ -1096,7 +1096,7 @@ func TestStateDBTransientStorage(t *testing.T) {
|
|||
// set transient state and then copy the statedb and ensure that
|
||||
// the transient state is copied
|
||||
state.SetTransientState(addr, key, value)
|
||||
cpy := state.Copy()
|
||||
cpy := state.Copy().(*StateDB)
|
||||
if got := cpy.GetTransientState(addr, key); got != value {
|
||||
t.Fatalf("transient storage mismatch: have %x, want %x", got, value)
|
||||
}
|
||||
|
|
@ -1114,13 +1114,13 @@ func TestResetObject(t *testing.T) {
|
|||
slotB = common.HexToHash("0x2")
|
||||
)
|
||||
// Initialize account with balance and storage in first transaction.
|
||||
state.SetBalance(addr, big.NewInt(1))
|
||||
state.SetBalance(addr, big.NewInt(1), BalanceChangeUnspecified)
|
||||
state.SetState(addr, slotA, common.BytesToHash([]byte{0x1}))
|
||||
state.IntermediateRoot(true)
|
||||
|
||||
// Reset account and mutate balance and storages
|
||||
state.CreateAccount(addr)
|
||||
state.SetBalance(addr, big.NewInt(2))
|
||||
state.SetBalance(addr, big.NewInt(2), BalanceChangeUnspecified)
|
||||
state.SetState(addr, slotB, common.BytesToHash([]byte{0x2}))
|
||||
root, _ := state.Commit(0, true)
|
||||
|
||||
|
|
@ -1146,7 +1146,7 @@ func TestDeleteStorage(t *testing.T) {
|
|||
addr = common.HexToAddress("0x1")
|
||||
)
|
||||
// Initialize account and populate storage
|
||||
state.SetBalance(addr, big.NewInt(1))
|
||||
state.SetBalance(addr, big.NewInt(1), BalanceChangeUnspecified)
|
||||
state.CreateAccount(addr)
|
||||
for i := 0; i < 1000; i++ {
|
||||
slot := common.Hash(uint256.NewInt(uint64(i)).Bytes32())
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ func makeTestState(scheme string) (ethdb.Database, Database, *trie.Database, com
|
|||
obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
|
||||
acc := &testAccount{address: common.BytesToAddress([]byte{i})}
|
||||
|
||||
obj.AddBalance(big.NewInt(int64(11 * i)))
|
||||
obj.AddBalance(big.NewInt(int64(11*i)), BalanceChangeUnspecified)
|
||||
acc.balance = big.NewInt(int64(11 * i))
|
||||
|
||||
obj.SetNonce(uint64(42 * i))
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func filledStateDB() *StateDB {
|
|||
skey := common.HexToHash("aaa")
|
||||
sval := common.HexToHash("bbb")
|
||||
|
||||
state.SetBalance(addr, big.NewInt(42)) // Change the account trie
|
||||
state.SetBalance(addr, big.NewInt(42), BalanceChangeUnspecified) // Change the account trie
|
||||
state.SetCode(addr, []byte("hello")) // Change an external metadata
|
||||
state.SetState(addr, skey, sval) // Change the storage trie
|
||||
for i := 0; i < 100; i++ {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb vm.StateDB, cfg vm.
|
|||
allLogs []*types.Log
|
||||
gp = new(GasPool).AddGas(block.GasLimit())
|
||||
)
|
||||
|
||||
// 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 {
|
||||
misc.ApplyDAOHardFork(statedb)
|
||||
|
|
@ -76,7 +77,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb vm.StateDB, cfg vm.
|
|||
signer = types.MakeSigner(p.config, header.Number, header.Time)
|
||||
)
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
||||
ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb, p.bc.logger)
|
||||
}
|
||||
// Iterate over and process the individual transactions
|
||||
for i, tx := range block.Transactions() {
|
||||
|
|
@ -85,7 +86,8 @@ func (p *StateProcessor) Process(block *types.Block, statedb vm.StateDB, cfg vm.
|
|||
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
|
||||
}
|
||||
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 {
|
||||
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
|
||||
}
|
||||
|
|
@ -103,7 +105,16 @@ func (p *StateProcessor) Process(block *types.Block, statedb vm.StateDB, cfg vm.
|
|||
return receipts, allLogs, *usedGas, nil
|
||||
}
|
||||
|
||||
func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, statedb vm.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
|
||||
// ApplyTransactionWithEVM attempts to apply a transaction to the given state database
|
||||
// and uses the input parameters for its environment similar to ApplyTransaction. However,
|
||||
// this method takes an already created EVM instance as input.
|
||||
func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPool, statedb vm.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, err error) {
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.Config.Tracer.CaptureTxStart(evm, tx, msg.From)
|
||||
defer func() {
|
||||
evm.Config.Tracer.CaptureTxEnd(receipt, err)
|
||||
}()
|
||||
}
|
||||
// Create a new context to be used in the EVM environment.
|
||||
txContext := NewEVMTxContext(msg)
|
||||
evm.Reset(txContext, statedb)
|
||||
|
|
@ -125,7 +136,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
|
||||
// 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() {
|
||||
receipt.Status = types.ReceiptStatusFailed
|
||||
} else {
|
||||
|
|
@ -166,12 +177,18 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
|
|||
blockContext := NewEVMBlockContext(header, bc, author)
|
||||
txContext := NewEVMTxContext(msg)
|
||||
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
|
||||
// contract. This method is exported to be used in tests.
|
||||
func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb vm.StateDB) {
|
||||
func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb vm.StateDB, logger BlockchainLogger) {
|
||||
if logger != nil {
|
||||
logger.OnBeaconBlockRootStart(beaconRoot)
|
||||
defer func() {
|
||||
logger.OnBeaconBlockRootEnd()
|
||||
}()
|
||||
}
|
||||
// If EIP-4788 is enabled, we need to invoke the beaconroot storage contract with
|
||||
// the new root
|
||||
msg := &Message{
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
cmath "github.com/ethereum/go-ethereum/common/math"
|
||||
"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/params"
|
||||
|
|
@ -139,6 +140,8 @@ type Message struct {
|
|||
AccessList types.AccessList
|
||||
BlobGasFeeCap *big.Int
|
||||
BlobHashes []common.Hash
|
||||
// Distinguishes type-2 txes
|
||||
DynamicFee bool
|
||||
|
||||
// When SkipAccountChecks is true, the message nonce is not checked against the
|
||||
// account nonce in state. It also disables checking that the sender is an EOA.
|
||||
|
|
@ -161,6 +164,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
|
|||
SkipAccountChecks: false,
|
||||
BlobHashes: tx.BlobHashes(),
|
||||
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
||||
DynamicFee: tx.Type() == 2 || tx.Type() == 3,
|
||||
}
|
||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||
if baseFee != nil {
|
||||
|
|
@ -258,10 +262,15 @@ func (st *StateTransition) buyGas() error {
|
|||
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if st.evm.Config.Tracer != nil {
|
||||
st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, vm.GasChangeTxInitialBalance)
|
||||
}
|
||||
|
||||
st.gasRemaining += st.msg.GasLimit
|
||||
|
||||
st.initialGas = st.msg.GasLimit
|
||||
st.state.SubBalance(st.msg.From, mgval)
|
||||
st.state.SubBalance(st.msg.From, mgval, state.BalanceDecreaseGasBuy)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -369,13 +378,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if tracer := st.evm.Config.Tracer; tracer != nil {
|
||||
tracer.CaptureTxStart(st.initialGas)
|
||||
defer func() {
|
||||
tracer.CaptureTxEnd(st.gasRemaining)
|
||||
}()
|
||||
}
|
||||
|
||||
var (
|
||||
msg = st.msg
|
||||
sender = vm.AccountRef(msg.From)
|
||||
|
|
@ -391,6 +393,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
|||
if 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(st.gasRemaining, st.gasRemaining-gas, vm.GasChangeTxIntrinsicGas)
|
||||
}
|
||||
st.gasRemaining -= gas
|
||||
|
||||
// Check clause 6
|
||||
|
|
@ -440,7 +445,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
|||
} else {
|
||||
fee := new(big.Int).SetUint64(st.gasUsed())
|
||||
fee.Mul(fee, effectiveTip)
|
||||
st.state.AddBalance(st.evm.Context.Coinbase, fee)
|
||||
st.state.AddBalance(st.evm.Context.Coinbase, fee, state.BalanceIncreaseRewardTransactionFee)
|
||||
}
|
||||
|
||||
return &ExecutionResult{
|
||||
|
|
@ -457,11 +462,20 @@ func (st *StateTransition) refundGas(refundQuotient uint64) uint64 {
|
|||
if refund > st.state.GetRefund() {
|
||||
refund = st.state.GetRefund()
|
||||
}
|
||||
|
||||
if st.evm.Config.Tracer != nil && refund > 0 {
|
||||
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, st.gasRemaining+refund, vm.GasChangeTxRefunds)
|
||||
}
|
||||
|
||||
st.gasRemaining += refund
|
||||
|
||||
// Return ETH for remaining gas, exchanged at the original rate.
|
||||
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gasRemaining), st.msg.GasPrice)
|
||||
st.state.AddBalance(st.msg.From, remaining)
|
||||
st.state.AddBalance(st.msg.From, remaining, state.BalanceIncreaseGasReturn)
|
||||
|
||||
if st.evm.Config.Tracer != nil && st.gasRemaining > 0 {
|
||||
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, vm.GasChangeTxLeftOverReturned)
|
||||
}
|
||||
|
||||
// Also return remaining gas to the block gas counter so it is
|
||||
// available for the next transaction.
|
||||
|
|
|
|||
|
|
@ -513,17 +513,17 @@ func TestOpenDrops(t *testing.T) {
|
|||
|
||||
// Create a blob pool out of the pre-seeded data
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(gapper.PublicKey), big.NewInt(1000000))
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(dangler.PublicKey), big.NewInt(1000000))
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(filler.PublicKey), big.NewInt(1000000))
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(gapper.PublicKey), big.NewInt(1000000), state.BalanceChangeUnspecified)
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(dangler.PublicKey), big.NewInt(1000000), state.BalanceChangeUnspecified)
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(filler.PublicKey), big.NewInt(1000000), state.BalanceChangeUnspecified)
|
||||
statedb.SetNonce(crypto.PubkeyToAddress(filler.PublicKey), 3)
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(overlapper.PublicKey), big.NewInt(1000000))
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(overlapper.PublicKey), big.NewInt(1000000), state.BalanceChangeUnspecified)
|
||||
statedb.SetNonce(crypto.PubkeyToAddress(overlapper.PublicKey), 2)
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(underpayer.PublicKey), big.NewInt(1000000))
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(outpricer.PublicKey), big.NewInt(1000000))
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(exceeder.PublicKey), big.NewInt(1000000))
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(overdrafter.PublicKey), big.NewInt(1000000))
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(overcapper.PublicKey), big.NewInt(10000000))
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(underpayer.PublicKey), big.NewInt(1000000), state.BalanceChangeUnspecified)
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(outpricer.PublicKey), big.NewInt(1000000), state.BalanceChangeUnspecified)
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(exceeder.PublicKey), big.NewInt(1000000), state.BalanceChangeUnspecified)
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(overdrafter.PublicKey), big.NewInt(1000000), state.BalanceChangeUnspecified)
|
||||
statedb.AddBalance(crypto.PubkeyToAddress(overcapper.PublicKey), big.NewInt(10000000), state.BalanceChangeUnspecified)
|
||||
statedb.Commit(0, true)
|
||||
|
||||
chain := &testBlockChain{
|
||||
|
|
@ -638,7 +638,7 @@ func TestOpenIndex(t *testing.T) {
|
|||
|
||||
// Create a blob pool out of the pre-seeded data
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
|
||||
statedb.AddBalance(addr, big.NewInt(1_000_000_000))
|
||||
statedb.AddBalance(addr, big.NewInt(1_000_000_000), state.BalanceChangeUnspecified)
|
||||
statedb.Commit(0, true)
|
||||
|
||||
chain := &testBlockChain{
|
||||
|
|
@ -738,9 +738,9 @@ func TestOpenHeap(t *testing.T) {
|
|||
|
||||
// Create a blob pool out of the pre-seeded data
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
|
||||
statedb.AddBalance(addr1, big.NewInt(1_000_000_000))
|
||||
statedb.AddBalance(addr2, big.NewInt(1_000_000_000))
|
||||
statedb.AddBalance(addr3, big.NewInt(1_000_000_000))
|
||||
statedb.AddBalance(addr1, big.NewInt(1_000_000_000), state.BalanceChangeUnspecified)
|
||||
statedb.AddBalance(addr2, big.NewInt(1_000_000_000), state.BalanceChangeUnspecified)
|
||||
statedb.AddBalance(addr3, big.NewInt(1_000_000_000), state.BalanceChangeUnspecified)
|
||||
statedb.Commit(0, true)
|
||||
|
||||
chain := &testBlockChain{
|
||||
|
|
@ -818,9 +818,9 @@ func TestOpenCap(t *testing.T) {
|
|||
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
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
|
||||
statedb.AddBalance(addr1, big.NewInt(1_000_000_000))
|
||||
statedb.AddBalance(addr2, big.NewInt(1_000_000_000))
|
||||
statedb.AddBalance(addr3, big.NewInt(1_000_000_000))
|
||||
statedb.AddBalance(addr1, big.NewInt(1_000_000_000), state.BalanceChangeUnspecified)
|
||||
statedb.AddBalance(addr2, big.NewInt(1_000_000_000), state.BalanceChangeUnspecified)
|
||||
statedb.AddBalance(addr3, big.NewInt(1_000_000_000), state.BalanceChangeUnspecified)
|
||||
statedb.Commit(0, true)
|
||||
|
||||
chain := &testBlockChain{
|
||||
|
|
@ -1211,7 +1211,7 @@ func TestAdd(t *testing.T) {
|
|||
addrs[acc] = crypto.PubkeyToAddress(keys[acc].PublicKey)
|
||||
|
||||
// Seed the state database with this acocunt
|
||||
statedb.AddBalance(addrs[acc], new(big.Int).SetUint64(seed.balance))
|
||||
statedb.AddBalance(addrs[acc], new(big.Int).SetUint64(seed.balance), state.BalanceChangeUnspecified)
|
||||
statedb.SetNonce(addrs[acc], seed.nonce)
|
||||
|
||||
// Sign the seed transactions and store them in the data store
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func fillPool(t testing.TB, pool *LegacyPool) {
|
|||
nonExecutableTxs := types.Transactions{}
|
||||
for i := 0; i < 384; i++ {
|
||||
key, _ := crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(10000000000))
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(10000000000), state.BalanceChangeUnspecified)
|
||||
// Add executable ones
|
||||
for j := 0; j < int(pool.config.AccountSlots); j++ {
|
||||
executableTxs = append(executableTxs, pricedTransaction(uint64(j), 100000, big.NewInt(300), key))
|
||||
|
|
@ -91,7 +91,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
|
||||
{
|
||||
key, _ := crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000))
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000), state.BalanceChangeUnspecified)
|
||||
futureTxs := types.Transactions{}
|
||||
for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ {
|
||||
futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 100000, big.NewInt(500), key))
|
||||
|
|
@ -128,7 +128,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
|
||||
{
|
||||
key, _ := crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000))
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000), state.BalanceChangeUnspecified)
|
||||
futureTxs := types.Transactions{}
|
||||
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))
|
||||
|
|
@ -182,7 +182,7 @@ func TestTransactionZAttack(t *testing.T) {
|
|||
for j := 0; j < int(pool.config.GlobalQueue); j++ {
|
||||
futureTxs := types.Transactions{}
|
||||
key, _ := crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000))
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000), state.BalanceChangeUnspecified)
|
||||
futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 21000, big.NewInt(500), key))
|
||||
pool.addRemotesSync(futureTxs)
|
||||
}
|
||||
|
|
@ -190,7 +190,7 @@ func TestTransactionZAttack(t *testing.T) {
|
|||
overDraftTxs := types.Transactions{}
|
||||
{
|
||||
key, _ := crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000))
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000), state.BalanceChangeUnspecified)
|
||||
for j := 0; j < int(pool.config.GlobalSlots); j++ {
|
||||
overDraftTxs = append(overDraftTxs, pricedValuedTransaction(uint64(j), 600000000000, 21000, big.NewInt(500), key))
|
||||
}
|
||||
|
|
@ -227,7 +227,7 @@ func BenchmarkFutureAttack(b *testing.B) {
|
|||
fillPool(b, pool)
|
||||
|
||||
key, _ := crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000))
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000), state.BalanceChangeUnspecified)
|
||||
futureTxs := types.Transactions{}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ func (c *testChain) State() (vm.StateDB, error) {
|
|||
c.statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
// simulate that the new head block included tx0 and tx1
|
||||
c.statedb.SetNonce(c.address, 2)
|
||||
c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether))
|
||||
c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether), state.BalanceChangeUnspecified)
|
||||
*c.trigger = false
|
||||
}
|
||||
return stdb, nil
|
||||
|
|
@ -276,7 +276,7 @@ func TestStateChangeDuringReset(t *testing.T) {
|
|||
)
|
||||
|
||||
// setup pool with 2 transaction in it
|
||||
statedb.SetBalance(address, new(big.Int).SetUint64(params.Ether))
|
||||
statedb.SetBalance(address, new(big.Int).SetUint64(params.Ether), state.BalanceChangeUnspecified)
|
||||
blockchain := &testChain{newTestBlockChain(params.TestChainConfig, 1000000000, statedb, new(event.Feed)), address, &trigger}
|
||||
|
||||
tx0 := transaction(0, 100000, key)
|
||||
|
|
@ -310,7 +310,7 @@ func TestStateChangeDuringReset(t *testing.T) {
|
|||
|
||||
func testAddBalance(pool *LegacyPool, addr common.Address, amount *big.Int) {
|
||||
pool.mu.Lock()
|
||||
pool.currentState.AddBalance(addr, amount)
|
||||
pool.currentState.AddBalance(addr, amount, state.BalanceChangeUnspecified)
|
||||
pool.mu.Unlock()
|
||||
}
|
||||
|
||||
|
|
@ -471,7 +471,7 @@ func TestChainFork(t *testing.T) {
|
|||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
resetState := func() {
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
statedb.AddBalance(addr, big.NewInt(100000000000000))
|
||||
statedb.AddBalance(addr, big.NewInt(100000000000000), state.BalanceChangeUnspecified)
|
||||
|
||||
pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed))
|
||||
<-pool.requestReset(nil, nil)
|
||||
|
|
@ -500,7 +500,7 @@ func TestDoubleNonce(t *testing.T) {
|
|||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
resetState := func() {
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
statedb.AddBalance(addr, big.NewInt(100000000000000))
|
||||
statedb.AddBalance(addr, big.NewInt(100000000000000), state.BalanceChangeUnspecified)
|
||||
|
||||
pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed))
|
||||
<-pool.requestReset(nil, nil)
|
||||
|
|
@ -2663,7 +2663,7 @@ func BenchmarkMultiAccountBatchInsert(b *testing.B) {
|
|||
for i := 0; i < b.N; i++ {
|
||||
key, _ := crypto.GenerateKey()
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
pool.currentState.AddBalance(account, big.NewInt(1000000))
|
||||
pool.currentState.AddBalance(account, big.NewInt(1000000), state.BalanceChangeUnspecified)
|
||||
tx := transaction(uint64(0), 100000, key)
|
||||
batches[i] = tx
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ type noncer struct {
|
|||
// newNoncer creates a new virtual state database to track the pool nonces.
|
||||
func newNoncer(statedb vm.StateDB) *noncer {
|
||||
return &noncer{
|
||||
fallback: statedb.Copy(),
|
||||
fallback: statedb.Copy().(vm.StateDB),
|
||||
nonces: make(map[common.Address]uint64),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,10 +159,13 @@ func (c *Contract) Caller() common.Address {
|
|||
}
|
||||
|
||||
// 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 EVMLogger, reason GasChangeReason) (ok bool) {
|
||||
if c.Gas < gas {
|
||||
return false
|
||||
}
|
||||
if logger != nil && reason != GasChangeIgnored {
|
||||
logger.OnGasChange(c.Gas, c.Gas-gas, reason)
|
||||
}
|
||||
c.Gas -= gas
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
|
|||
// - the returned bytes,
|
||||
// - the _remaining_ gas,
|
||||
// - any error that occurred
|
||||
func RunPrecompiledContract(p PrecompiledContract, evm *EVM, sender common.Address, callingContract common.Address, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
|
||||
func RunPrecompiledContract(p PrecompiledContract, evm *EVM, sender common.Address, callingContract common.Address, input []byte, suppliedGas uint64, logger EVMLogger) (ret []byte, remainingGas uint64, err error) {
|
||||
if dp, ok := p.(DynamicGasPrecompiledContract); ok {
|
||||
return dp.RunAndCalculateGas(evm, sender, callingContract, input, suppliedGas)
|
||||
}
|
||||
|
|
@ -180,6 +180,9 @@ func RunPrecompiledContract(p PrecompiledContract, evm *EVM, sender common.Addre
|
|||
if suppliedGas < gasCost {
|
||||
return nil, 0, ErrOutOfGas
|
||||
}
|
||||
if logger != nil {
|
||||
logger.OnGasChange(suppliedGas, suppliedGas-gasCost, GasChangeCallPrecompiledContract)
|
||||
}
|
||||
suppliedGas -= gasCost
|
||||
output, err := p.Run(evm, sender, input)
|
||||
return output, suppliedGas, err
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func FuzzPrecompiledContracts(f *testing.F) {
|
|||
return
|
||||
}
|
||||
inWant := string(input)
|
||||
vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, input, gas)
|
||||
vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, input, gas, nil)
|
||||
if inHave := string(input); inWant != inHave {
|
||||
t.Errorf("Precompiled %v modified input data", a)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
|
|||
in := common.Hex2Bytes(test.Input)
|
||||
gas := p.RequiredGas(in)
|
||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
|
||||
if res, _, err := vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, in, gas); err != nil {
|
||||
if res, _, err := vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, in, gas, nil); err != nil {
|
||||
t.Error(err)
|
||||
} else if common.Bytes2Hex(res) != test.Expected {
|
||||
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
|
||||
|
|
@ -121,7 +121,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
|
|||
gas := p.RequiredGas(in) - 1
|
||||
|
||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
|
||||
_, _, err := vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, in, gas)
|
||||
_, _, err := vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, in, gas, nil)
|
||||
if err.Error() != "out of gas" {
|
||||
t.Errorf("Expected error [out of gas], got [%v]", err)
|
||||
}
|
||||
|
|
@ -138,7 +138,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing
|
|||
in := common.Hex2Bytes(test.Input)
|
||||
gas := p.RequiredGas(in)
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
_, _, err := vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, in, gas)
|
||||
_, _, err := vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, in, gas, nil)
|
||||
if err.Error() != test.ExpectedError {
|
||||
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
|
||||
}
|
||||
|
|
@ -170,7 +170,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
|
|||
bench.ResetTimer()
|
||||
for i := 0; i < bench.N; i++ {
|
||||
copy(data, in)
|
||||
res, _, err = vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, data, reqGas)
|
||||
res, _, err = vm.RunPrecompiledContract(p, nil, common.Address{}, common.Address{}, data, reqGas, nil)
|
||||
}
|
||||
bench.StopTimer()
|
||||
elapsed := uint64(time.Since(start))
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package vm
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// List evm execution errors
|
||||
|
|
@ -71,3 +72,125 @@ type ErrInvalidOpCode struct {
|
|||
}
|
||||
|
||||
func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) }
|
||||
|
||||
// rpcError is the same interface as the one defined in rpc/errors.go
|
||||
// but we do not want to depend on rpc package here so we redefine it.
|
||||
//
|
||||
// It's used to ensure that the VMError implements the RPC error interface.
|
||||
type rpcError interface {
|
||||
Error() string // returns the message
|
||||
ErrorCode() int // returns the code
|
||||
}
|
||||
|
||||
var _ rpcError = (*VMError)(nil)
|
||||
|
||||
// VMError wraps a VM error with an additional stable error code. The error
|
||||
// field is the original error that caused the VM error and must be one of the
|
||||
// VM error defined at the top of this file.
|
||||
//
|
||||
// If the error is not one of the known error above, the error code will be
|
||||
// set to VMErrorCodeUnknown.
|
||||
type VMError struct {
|
||||
error
|
||||
code int
|
||||
}
|
||||
|
||||
func VMErrorFromErr(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &VMError{
|
||||
error: err,
|
||||
code: vmErrorCodeFromErr(err),
|
||||
}
|
||||
}
|
||||
|
||||
func (e *VMError) Error() string {
|
||||
return e.error.Error()
|
||||
}
|
||||
|
||||
func (e *VMError) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
func (e *VMError) ErrorCode() int {
|
||||
return e.code
|
||||
}
|
||||
|
||||
const (
|
||||
// We start the error code at 1 so that we can use 0 later for some possible extension. There
|
||||
// is no unspecified value for the code today because it should always be set to a valid value
|
||||
// that could be VMErrorCodeUnknown if the error is not mapped to a known error code.
|
||||
|
||||
VMErrorCodeOutOfGas = 1 + iota
|
||||
VMErrorCodeCodeStoreOutOfGas
|
||||
VMErrorCodeDepth
|
||||
VMErrorCodeInsufficientBalance
|
||||
VMErrorCodeContractAddressCollision
|
||||
VMErrorCodeExecutionReverted
|
||||
VMErrorCodeMaxInitCodeSizeExceeded
|
||||
VMErrorCodeMaxCodeSizeExceeded
|
||||
VMErrorCodeInvalidJump
|
||||
VMErrorCodeWriteProtection
|
||||
VMErrorCodeReturnDataOutOfBounds
|
||||
VMErrorCodeGasUintOverflow
|
||||
VMErrorCodeInvalidCode
|
||||
VMErrorCodeNonceUintOverflow
|
||||
VMErrorCodeStackUnderflow
|
||||
VMErrorCodeStackOverflow
|
||||
VMErrorCodeInvalidOpCode
|
||||
|
||||
// VMErrorCodeUnknown explicitly marks an error as unknown, this is useful when error is converted
|
||||
// from an actual `error` in which case if the mapping is not known, we can use this value to indicate that.
|
||||
VMErrorCodeUnknown = math.MaxInt - 1
|
||||
)
|
||||
|
||||
func vmErrorCodeFromErr(err error) int {
|
||||
switch {
|
||||
case errors.Is(err, ErrOutOfGas):
|
||||
return VMErrorCodeOutOfGas
|
||||
case errors.Is(err, ErrCodeStoreOutOfGas):
|
||||
return VMErrorCodeCodeStoreOutOfGas
|
||||
case errors.Is(err, ErrDepth):
|
||||
return VMErrorCodeDepth
|
||||
case errors.Is(err, ErrInsufficientBalance):
|
||||
return VMErrorCodeInsufficientBalance
|
||||
case errors.Is(err, ErrContractAddressCollision):
|
||||
return VMErrorCodeContractAddressCollision
|
||||
case errors.Is(err, ErrExecutionReverted):
|
||||
return VMErrorCodeExecutionReverted
|
||||
case errors.Is(err, ErrMaxInitCodeSizeExceeded):
|
||||
return VMErrorCodeMaxInitCodeSizeExceeded
|
||||
case errors.Is(err, ErrMaxCodeSizeExceeded):
|
||||
return VMErrorCodeMaxCodeSizeExceeded
|
||||
case errors.Is(err, ErrInvalidJump):
|
||||
return VMErrorCodeInvalidJump
|
||||
case errors.Is(err, ErrWriteProtection):
|
||||
return VMErrorCodeWriteProtection
|
||||
case errors.Is(err, ErrReturnDataOutOfBounds):
|
||||
return VMErrorCodeReturnDataOutOfBounds
|
||||
case errors.Is(err, ErrGasUintOverflow):
|
||||
return VMErrorCodeGasUintOverflow
|
||||
case errors.Is(err, ErrInvalidCode):
|
||||
return VMErrorCodeInvalidCode
|
||||
case errors.Is(err, ErrNonceUintOverflow):
|
||||
return VMErrorCodeNonceUintOverflow
|
||||
|
||||
default:
|
||||
// Dynamic errors
|
||||
if v := (*ErrStackUnderflow)(nil); errors.As(err, &v) {
|
||||
return VMErrorCodeStackUnderflow
|
||||
}
|
||||
|
||||
if v := (*ErrStackOverflow)(nil); errors.As(err, &v) {
|
||||
return VMErrorCodeStackOverflow
|
||||
}
|
||||
|
||||
if v := (*ErrInvalidOpCode)(nil); errors.As(err, &v) {
|
||||
return VMErrorCodeInvalidOpCode
|
||||
}
|
||||
|
||||
return VMErrorCodeUnknown
|
||||
}
|
||||
}
|
||||
|
|
|
|||
176
core/vm/evm.go
176
core/vm/evm.go
|
|
@ -17,10 +17,12 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -185,6 +187,13 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
|
|||
// the necessary steps to create accounts and reverses the state in case of an
|
||||
// execution error or failed value transfer.
|
||||
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
|
||||
// Capture the tracer start/end events in debug mode
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.captureBegin(evm.depth == 0, CALL, caller.Address(), addr, input, gas, value)
|
||||
defer func(startGas uint64) {
|
||||
evm.captureEnd(evm.depth == 0, CALL, startGas, leftOverGas, ret, err)
|
||||
}(gas)
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
|
|
@ -195,44 +204,18 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
|||
}
|
||||
snapshot := evm.StateDB.Snapshot()
|
||||
p, isPrecompile := evm.precompile(addr)
|
||||
debug := evm.Config.Tracer != nil
|
||||
|
||||
if !evm.StateDB.Exist(addr) {
|
||||
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
|
||||
// Calling a non existing account, don't do anything, but ping the tracer
|
||||
if debug {
|
||||
if evm.depth == 0 {
|
||||
evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
|
||||
evm.Config.Tracer.CaptureEnd(ret, 0, nil)
|
||||
} else {
|
||||
evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value)
|
||||
evm.Config.Tracer.CaptureExit(ret, 0, nil)
|
||||
}
|
||||
}
|
||||
// Calling a non-existing account, don't do anything.
|
||||
return nil, gas, nil
|
||||
}
|
||||
evm.StateDB.CreateAccount(addr)
|
||||
}
|
||||
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
|
||||
|
||||
// Capture the tracer start/end events in debug mode
|
||||
if debug {
|
||||
if evm.depth == 0 {
|
||||
evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
|
||||
defer func(startGas uint64) { // Lazy evaluation of the parameters
|
||||
evm.Config.Tracer.CaptureEnd(ret, startGas-gas, err)
|
||||
}(gas)
|
||||
} else {
|
||||
// Handle tracer events for entering and exiting a call frame
|
||||
evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value)
|
||||
defer func(startGas uint64) {
|
||||
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
|
||||
}(gas)
|
||||
}
|
||||
}
|
||||
|
||||
if isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, evm, caller.Address(), caller.Address(), input, gas)
|
||||
ret, gas, err = RunPrecompiledContract(p, evm, caller.Address(), caller.Address(), input, gas, evm.Config.Tracer)
|
||||
} else {
|
||||
// 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.
|
||||
|
|
@ -250,11 +233,15 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
|||
}
|
||||
}
|
||||
// When an error was returned by the EVM or when setting the creation code
|
||||
// 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.
|
||||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution)
|
||||
}
|
||||
|
||||
gas = 0
|
||||
}
|
||||
// TODO: consider clearing up unused snapshots:
|
||||
|
|
@ -272,6 +259,13 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
|||
// CallCode differs from Call in the sense that it executes the given address'
|
||||
// code with the caller as context.
|
||||
func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
|
||||
// Invoke tracer hooks that signal entering/exiting a call frame
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.captureBegin(false, CALLCODE, caller.Address(), addr, input, gas, value)
|
||||
defer func(startGas uint64) {
|
||||
evm.captureEnd(false, CALLCODE, startGas, leftOverGas, ret, err)
|
||||
}(gas)
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
|
|
@ -285,17 +279,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
|||
}
|
||||
var snapshot = evm.StateDB.Snapshot()
|
||||
|
||||
// Invoke tracer hooks that signal entering/exiting a call frame
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.Config.Tracer.CaptureEnter(CALLCODE, caller.Address(), addr, input, gas, value)
|
||||
defer func(startGas uint64) {
|
||||
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
|
||||
}(gas)
|
||||
}
|
||||
|
||||
// It is allowed to call precompiles, even via delegatecall
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, evm, caller.Address(), caller.Address(), input, gas)
|
||||
ret, gas, err = RunPrecompiledContract(p, evm, caller.Address(), caller.Address(), input, gas, evm.Config.Tracer)
|
||||
} else {
|
||||
addrCopy := addr
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
|
|
@ -308,6 +294,10 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
|||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution)
|
||||
}
|
||||
|
||||
gas = 0
|
||||
}
|
||||
}
|
||||
|
|
@ -320,30 +310,29 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
|||
// DelegateCall differs from CallCode in the sense that it executes the given address'
|
||||
// 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) {
|
||||
// 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
|
||||
if evm.Config.Tracer != nil {
|
||||
// NOTE: caller must, at all times be a contract. It should never happen
|
||||
// that caller is something other than a Contract.
|
||||
parent := caller.(*Contract)
|
||||
// DELEGATECALL inherits value from parent call
|
||||
evm.Config.Tracer.CaptureEnter(DELEGATECALL, caller.Address(), addr, input, gas, parent.value)
|
||||
evm.captureBegin(false, DELEGATECALL, caller.Address(), addr, input, gas, parent.value)
|
||||
defer func(startGas uint64) {
|
||||
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
|
||||
evm.captureEnd(false, DELEGATECALL, startGas, leftOverGas, ret, err)
|
||||
}(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
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
// NOTE: caller must, at all times be a contract. It should never happen
|
||||
// that caller is something other than a Contract.
|
||||
parent := caller.(*Contract)
|
||||
ret, gas, err = RunPrecompiledContract(p, evm, parent.CallerAddress, caller.Address(), input, gas)
|
||||
ret, gas, err = RunPrecompiledContract(p, evm, parent.CallerAddress, caller.Address(), input, gas, evm.Config.Tracer)
|
||||
} else {
|
||||
addrCopy := addr
|
||||
// Initialise a new contract and make initialise the delegate values
|
||||
|
|
@ -355,6 +344,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
|||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution)
|
||||
}
|
||||
gas = 0
|
||||
}
|
||||
}
|
||||
|
|
@ -366,6 +358,13 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
|||
// Opcodes that attempt to perform such modifications will result in exceptions
|
||||
// instead of performing the modifications.
|
||||
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(false, STATICCALL, caller.Address(), addr, input, gas, nil)
|
||||
defer func(startGas uint64) {
|
||||
evm.captureEnd(false, STATICCALL, startGas, leftOverGas, ret, err)
|
||||
}(gas)
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
|
|
@ -381,18 +380,10 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
|||
// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,
|
||||
// but is the correct thing to do and matters on other networks, in tests, and potential
|
||||
// future scenarios
|
||||
evm.StateDB.AddBalance(addr, big0)
|
||||
|
||||
// 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)
|
||||
}
|
||||
evm.StateDB.AddBalance(addr, new(big.Int), state.BalanceChangeTouchAccount)
|
||||
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, evm, caller.Address(), caller.Address(), input, gas)
|
||||
ret, gas, err = RunPrecompiledContract(p, evm, caller.Address(), caller.Address(), input, gas, evm.Config.Tracer)
|
||||
} else {
|
||||
// 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'
|
||||
|
|
@ -411,6 +402,10 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
|||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution)
|
||||
}
|
||||
|
||||
gas = 0
|
||||
}
|
||||
}
|
||||
|
|
@ -430,7 +425,13 @@ func (c *codeAndHash) Hash() common.Hash {
|
|||
}
|
||||
|
||||
// create creates a new contract using code as deployment code.
|
||||
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) {
|
||||
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) {
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.captureBegin(evm.depth == 0, typ, caller.Address(), address, codeAndHash.code, gas, value)
|
||||
defer func(startGas uint64) {
|
||||
evm.captureEnd(evm.depth == 0, typ, startGas, leftOverGas, ret, err)
|
||||
}(gas)
|
||||
}
|
||||
// Depth check execution. Fail if we're trying to execute above the
|
||||
// limit.
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
|
|
@ -452,6 +453,10 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
|||
// Ensure there's no existing contract already at the designated address
|
||||
contractHash := evm.StateDB.GetCodeHash(address)
|
||||
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) {
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution)
|
||||
}
|
||||
|
||||
return nil, common.Address{}, 0, ErrContractAddressCollision
|
||||
}
|
||||
// Create a new account on the state
|
||||
|
|
@ -467,15 +472,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
|||
contract := NewContract(caller, AccountRef(address), value, gas)
|
||||
contract.SetCodeOptionalHash(&address, codeAndHash)
|
||||
|
||||
if evm.Config.Tracer != nil {
|
||||
if evm.depth == 0 {
|
||||
evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
|
||||
} else {
|
||||
evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value)
|
||||
}
|
||||
}
|
||||
|
||||
ret, err := evm.interpreter.Run(contract, nil, false)
|
||||
ret, err = evm.interpreter.Run(contract, nil, false)
|
||||
|
||||
// Check whether the max code size has been exceeded, assign err if the case.
|
||||
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
|
||||
|
|
@ -493,7 +490,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
|||
// by the error checking condition below.
|
||||
if err == nil {
|
||||
createDataGas := uint64(len(ret)) * params.CreateDataGas
|
||||
if contract.UseGas(createDataGas) {
|
||||
if contract.UseGas(createDataGas, evm.Config.Tracer, GasChangeCallCodeStorage) {
|
||||
evm.StateDB.SetCode(address, ret)
|
||||
} else {
|
||||
err = ErrCodeStoreOutOfGas
|
||||
|
|
@ -501,22 +498,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
|||
}
|
||||
|
||||
// When an error was returned by the EVM or when setting the creation code
|
||||
// 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.
|
||||
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
contract.UseGas(contract.Gas)
|
||||
contract.UseGas(contract.Gas, evm.Config.Tracer, 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
|
||||
}
|
||||
|
||||
|
|
@ -538,3 +528,35 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *
|
|||
|
||||
// ChainConfig returns the environment's chain configuration
|
||||
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
|
||||
|
||||
func (evm *EVM) captureBegin(isRoot bool, typ OpCode, from common.Address, to common.Address, input []byte, startGas uint64, value *big.Int) {
|
||||
tracer := evm.Config.Tracer
|
||||
|
||||
if isRoot {
|
||||
tracer.CaptureStart(from, to, typ == CREATE || typ == CREATE2, input, startGas, value)
|
||||
} else {
|
||||
tracer.CaptureEnter(typ, from, to, input, startGas, value)
|
||||
}
|
||||
|
||||
tracer.OnGasChange(0, startGas, GasChangeCallInitialBalance)
|
||||
}
|
||||
|
||||
func (evm *EVM) captureEnd(isRoot bool, typ OpCode, startGas uint64, leftOverGas uint64, ret []byte, err error) {
|
||||
tracer := evm.Config.Tracer
|
||||
|
||||
if leftOverGas != 0 {
|
||||
tracer.OnGasChange(leftOverGas, 0, GasChangeCallLeftOverReturned)
|
||||
}
|
||||
var reverted bool
|
||||
if err != nil {
|
||||
reverted = true
|
||||
}
|
||||
if !evm.chainRules.IsHomestead && errors.Is(err, ErrCodeStoreOutOfGas) {
|
||||
reverted = false
|
||||
}
|
||||
if isRoot {
|
||||
tracer.CaptureEnd(ret, startGas-leftOverGas, VMErrorFromErr(err), reverted)
|
||||
} else {
|
||||
tracer.CaptureExit(ret, startGas-leftOverGas, VMErrorFromErr(err), reverted)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package vm
|
|||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -247,7 +248,9 @@ func OpKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
|
|||
if evm.Config.EnablePreimageRecording {
|
||||
evm.StateDB.AddPreimage(interpreter.hasherBuf, data)
|
||||
}
|
||||
|
||||
if interpreter.evm.Config.Tracer != nil {
|
||||
interpreter.evm.Config.Tracer.CaptureKeccakPreimage(common.BytesToHash(interpreter.hasherBuf[:]), data)
|
||||
}
|
||||
size.SetBytes(interpreter.hasherBuf[:])
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -591,7 +594,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
|||
// reuse size int for stackvalue
|
||||
stackvalue := size
|
||||
|
||||
scope.Contract.UseGas(gas)
|
||||
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, GasChangeCallContractCreation)
|
||||
//TODO: use uint256.Int instead of converting with toBig()
|
||||
var bigVal = big0
|
||||
if !value.IsZero() {
|
||||
|
|
@ -611,6 +614,11 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
|||
stackvalue.SetBytes(addr.Bytes())
|
||||
}
|
||||
scope.Stack.push(&stackvalue)
|
||||
|
||||
if interpreter.evm.Config.Tracer != nil && returnGas > 0 {
|
||||
interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded)
|
||||
}
|
||||
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
if suberr == ErrExecutionReverted {
|
||||
|
|
@ -634,7 +642,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
|||
)
|
||||
// Apply EIP150
|
||||
gas -= gas / 64
|
||||
scope.Contract.UseGas(gas)
|
||||
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, GasChangeCallContractCreation2)
|
||||
// reuse size int for stackvalue
|
||||
stackvalue := size
|
||||
//TODO: use uint256.Int instead of converting with toBig()
|
||||
|
|
@ -651,6 +659,11 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
|||
stackvalue.SetBytes(addr.Bytes())
|
||||
}
|
||||
scope.Stack.push(&stackvalue)
|
||||
|
||||
if interpreter.evm.Config.Tracer != nil && returnGas > 0 {
|
||||
interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded)
|
||||
}
|
||||
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
if suberr == ErrExecutionReverted {
|
||||
|
|
@ -696,6 +709,11 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
|
|||
if err == nil || err == ErrExecutionReverted {
|
||||
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||
}
|
||||
|
||||
if interpreter.evm.Config.Tracer != nil && returnGas > 0 {
|
||||
interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded)
|
||||
}
|
||||
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
interpreter.returnData = ret
|
||||
|
|
@ -731,6 +749,11 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
|
|||
if err == nil || err == ErrExecutionReverted {
|
||||
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||
}
|
||||
|
||||
if interpreter.evm.Config.Tracer != nil && returnGas > 0 {
|
||||
interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded)
|
||||
}
|
||||
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
interpreter.returnData = ret
|
||||
|
|
@ -759,6 +782,11 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
|
|||
if err == nil || err == ErrExecutionReverted {
|
||||
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||
}
|
||||
|
||||
if interpreter.evm.Config.Tracer != nil && returnGas > 0 {
|
||||
interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded)
|
||||
}
|
||||
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
interpreter.returnData = ret
|
||||
|
|
@ -787,6 +815,11 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
|||
if err == nil || err == ErrExecutionReverted {
|
||||
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||
}
|
||||
|
||||
if interpreter.evm.Config.Tracer != nil && returnGas > 0 {
|
||||
interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded)
|
||||
}
|
||||
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
interpreter.returnData = ret
|
||||
|
|
@ -822,11 +855,11 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
|
|||
}
|
||||
beneficiary := scope.Stack.pop()
|
||||
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
|
||||
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance)
|
||||
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, state.BalanceIncreaseSelfdestruct)
|
||||
interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address())
|
||||
if tracer := interpreter.evm.Config.Tracer; tracer != nil {
|
||||
tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance)
|
||||
tracer.CaptureExit([]byte{}, 0, nil)
|
||||
tracer.CaptureExit([]byte{}, 0, nil, false)
|
||||
}
|
||||
return nil, errStopToken
|
||||
}
|
||||
|
|
@ -837,12 +870,12 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon
|
|||
}
|
||||
beneficiary := scope.Stack.pop()
|
||||
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
|
||||
interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance)
|
||||
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance)
|
||||
interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance, state.BalanceDecreaseSelfdestruct)
|
||||
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, state.BalanceIncreaseSelfdestruct)
|
||||
interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address())
|
||||
if tracer := interpreter.evm.Config.Tracer; tracer != nil {
|
||||
tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance)
|
||||
tracer.CaptureExit([]byte{}, 0, nil)
|
||||
tracer.CaptureExit([]byte{}, 0, nil, false)
|
||||
}
|
||||
return nil, errStopToken
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
|
@ -28,8 +29,8 @@ import (
|
|||
type StateDB interface {
|
||||
CreateAccount(common.Address)
|
||||
|
||||
SubBalance(common.Address, *big.Int)
|
||||
AddBalance(common.Address, *big.Int)
|
||||
SubBalance(common.Address, *big.Int, state.BalanceChangeReason)
|
||||
AddBalance(common.Address, *big.Int, state.BalanceChangeReason)
|
||||
GetBalance(common.Address) *big.Int
|
||||
|
||||
GetNonce(common.Address) uint64
|
||||
|
|
@ -82,16 +83,20 @@ type StateDB interface {
|
|||
// new methods
|
||||
Error() error
|
||||
GetStorageRoot(common.Address) common.Hash
|
||||
SetBalance(common.Address, *big.Int)
|
||||
SetBalance(common.Address, *big.Int, state.BalanceChangeReason)
|
||||
SetStorage(common.Address, map[common.Hash]common.Hash)
|
||||
Finalise(deleteEmptyObjects bool)
|
||||
Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error)
|
||||
SetTxContext(thash common.Hash, ti int)
|
||||
Copy() StateDB
|
||||
Copy() interface{}
|
||||
IntermediateRoot(deleteEmptyObjects bool) common.Hash
|
||||
GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash) []*types.Log
|
||||
TxIndex() int
|
||||
Preimages() map[common.Hash][]byte
|
||||
SetLogger(logger state.StateLogger)
|
||||
}
|
||||
|
||||
type EVMAssignable interface {
|
||||
SetEVM(evm *EVM)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -158,9 +158,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
defer func() {
|
||||
if err != nil {
|
||||
if !logged {
|
||||
in.evm.Config.Tracer.CaptureState(pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
|
||||
in.evm.Config.Tracer.CaptureState(pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
|
||||
} else {
|
||||
in.evm.Config.Tracer.CaptureFault(pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err)
|
||||
in.evm.Config.Tracer.CaptureFault(pcCopy, op, gasCopy, cost, callContext, in.evm.depth, VMErrorFromErr(err))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
|
@ -185,9 +185,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
} else if sLen > operation.maxStack {
|
||||
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
||||
}
|
||||
if !contract.UseGas(cost) {
|
||||
if !contract.UseGas(cost, in.evm.Config.Tracer, GasChangeIgnored) {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
|
||||
if operation.dynamicGas != nil {
|
||||
// All ops with a dynamic memory usage also has a dynamic gas cost.
|
||||
var memorySize uint64
|
||||
|
|
@ -211,21 +212,25 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
var dynamicCost uint64
|
||||
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
|
||||
cost += dynamicCost // for tracing
|
||||
if err != nil || !contract.UseGas(dynamicCost) {
|
||||
if err != nil || !contract.UseGas(dynamicCost, in.evm.Config.Tracer, GasChangeIgnored) {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
|
||||
// Do tracing before memory expansion
|
||||
if debug {
|
||||
in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
|
||||
in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, GasChangeCallOpCode)
|
||||
in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
|
||||
logged = true
|
||||
}
|
||||
if memorySize > 0 {
|
||||
mem.Resize(memorySize)
|
||||
}
|
||||
} else if debug {
|
||||
in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
|
||||
in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, GasChangeCallOpCode)
|
||||
in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
|
||||
logged = true
|
||||
}
|
||||
|
||||
// execute the operation
|
||||
res, err = operation.execute(&pc, in, callContext)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// EVMLogger is used to collect execution traces from an EVM transaction
|
||||
|
|
@ -29,15 +30,88 @@ import (
|
|||
// if you need to retain them beyond the current call.
|
||||
type EVMLogger interface {
|
||||
// Transaction level
|
||||
CaptureTxStart(gasLimit uint64)
|
||||
CaptureTxEnd(restGas uint64)
|
||||
// Call simulations don't come with a valid signature. `from` field
|
||||
// to be used for address of the caller.
|
||||
CaptureTxStart(evm *EVM, tx *types.Transaction, from common.Address)
|
||||
CaptureTxEnd(receipt *types.Receipt, err error)
|
||||
// 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)
|
||||
CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
|
||||
// CaptureEnd is invoked when the processing of the top call ends.
|
||||
// See docs for `CaptureExit` for info on the `reverted` parameter.
|
||||
CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool)
|
||||
// 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)
|
||||
// CaptureExit 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`.
|
||||
CaptureExit(output []byte, gasUsed uint64, err error, reverted bool)
|
||||
// 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)
|
||||
CaptureKeccakPreimage(hash common.Hash, data []byte)
|
||||
// Misc
|
||||
OnGasChange(old, new uint64, reason GasChangeReason)
|
||||
}
|
||||
|
||||
// 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 = iota
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// GasChangeCallContractCreation is the amount of gas that will be burned for a CREATE.
|
||||
GasChangeCallContractCreation
|
||||
// GasChangeContractCreation is the amount of gas that will be burned for a CREATE2.
|
||||
GasChangeCallContractCreation2
|
||||
// GasChangeCallCodeStorage is the amount of gas that will be charged for code storage.
|
||||
GasChangeCallCodeStorage
|
||||
// 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 `CaptureState` handling.
|
||||
GasChangeCallOpCode
|
||||
// GasChangeCallPrecompiledContract is the amount of gas that will be charged for a precompiled contract execution.
|
||||
GasChangeCallPrecompiledContract
|
||||
// GasChangeCallStorageColdAccess is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules.
|
||||
GasChangeCallStorageColdAccess
|
||||
// GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert.
|
||||
GasChangeCallFailedExecution
|
||||
|
||||
// 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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
|
|||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
// Charge the remaining difference here already, to correctly calculate available
|
||||
// gas for call
|
||||
if !contract.UseGas(coldCost) {
|
||||
if !contract.UseGas(coldCost, evm.Config.Tracer, GasChangeCallStorageColdAccess) {
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,6 +122,9 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
|
|||
sender = vm.AccountRef(cfg.Origin)
|
||||
rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time)
|
||||
)
|
||||
if cfg.EVMConfig.Tracer != nil {
|
||||
cfg.EVMConfig.Tracer.CaptureTxStart(vmenv, 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:
|
||||
// - prepare accessList(post-berlin)
|
||||
// - reset transient storage(eip 1153)
|
||||
|
|
@ -155,6 +158,9 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
|
|||
sender = vm.AccountRef(cfg.Origin)
|
||||
rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time)
|
||||
)
|
||||
if cfg.EVMConfig.Tracer != nil {
|
||||
cfg.EVMConfig.Tracer.CaptureTxStart(vmenv, types.NewTx(&types.LegacyTx{Data: input, Value: cfg.Value, Gas: cfg.GasLimit}), cfg.Origin)
|
||||
}
|
||||
// Execute the preparatory steps for state transition which includes:
|
||||
// - prepare accessList(post-berlin)
|
||||
// - reset transient storage(eip 1153)
|
||||
|
|
@ -183,6 +189,9 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
|
|||
statedb = cfg.State
|
||||
rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time)
|
||||
)
|
||||
if cfg.EVMConfig.Tracer != nil {
|
||||
cfg.EVMConfig.Tracer.CaptureTxStart(vmenv, 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:
|
||||
// - prepare accessList(post-berlin)
|
||||
// - reset transient storage(eip 1153)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import (
|
|||
"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/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
||||
|
|
@ -330,7 +330,7 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode
|
|||
cfg.State, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
cfg.GasLimit = gas
|
||||
if len(tracerCode) > 0 {
|
||||
tracer, err := tracers.DefaultDirectory.New(tracerCode, new(tracers.Context), nil)
|
||||
tracer, err := directory.DefaultDirectory.New(tracerCode, new(directory.Context), nil)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
|
@ -811,7 +811,7 @@ func TestRuntimeJSTracer(t *testing.T) {
|
|||
byte(vm.PUSH1), 0,
|
||||
byte(vm.RETURN),
|
||||
}
|
||||
depressedCode := []byte{
|
||||
suicideCode := []byte{
|
||||
byte(vm.PUSH1), 0xaa,
|
||||
byte(vm.SELFDESTRUCT),
|
||||
}
|
||||
|
|
@ -824,9 +824,9 @@ func TestRuntimeJSTracer(t *testing.T) {
|
|||
statedb.SetCode(common.HexToAddress("0xcc"), calleeCode)
|
||||
statedb.SetCode(common.HexToAddress("0xdd"), calleeCode)
|
||||
statedb.SetCode(common.HexToAddress("0xee"), calleeCode)
|
||||
statedb.SetCode(common.HexToAddress("0xff"), depressedCode)
|
||||
statedb.SetCode(common.HexToAddress("0xff"), suicideCode)
|
||||
|
||||
tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil)
|
||||
tracer, err := directory.DefaultDirectory.New(jsTracer, new(directory.Context), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -861,7 +861,7 @@ func TestJSTracerCreateTx(t *testing.T) {
|
|||
code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)}
|
||||
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil)
|
||||
tracer, err := directory.DefaultDirectory.New(jsTracer, new(directory.Context), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ func TestAccountRange(t *testing.T) {
|
|||
hash := common.HexToHash(fmt.Sprintf("%x", i))
|
||||
addr := common.BytesToAddress(crypto.Keccak256Hash(hash.Bytes()).Bytes())
|
||||
addrs[i] = addr
|
||||
sdb.SetBalance(addrs[i], big.NewInt(1))
|
||||
sdb.SetBalance(addrs[i], big.NewInt(1), state.BalanceChangeUnspecified)
|
||||
if _, ok := m[addr]; ok {
|
||||
t.Fatalf("bad")
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -192,6 +192,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
var (
|
||||
vmConfig = vm.Config{
|
||||
EnablePreimageRecording: config.EnablePreimageRecording,
|
||||
Tracer: config.VMTracer,
|
||||
}
|
||||
cacheConfig = &core.CacheConfig{
|
||||
TrieCleanLimit: config.TrieCleanCache,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
||||
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/eth/gasprice"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
|
|
@ -141,6 +142,9 @@ type Config struct {
|
|||
// Enables tracking of SHA3 preimages in the VM
|
||||
EnablePreimageRecording bool
|
||||
|
||||
// Enables VM tracing
|
||||
VMTracer vm.EVMLogger
|
||||
|
||||
// Miscellaneous options
|
||||
DocRoot string `toml:"-"`
|
||||
|
||||
|
|
|
|||
|
|
@ -209,10 +209,13 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
|
|||
msgContext = core.NewEVMTxContext(call)
|
||||
evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil)
|
||||
|
||||
dirtyState = opts.State.Copy()
|
||||
dirtyState = opts.State.Copy().(vm.StateDB)
|
||||
evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
|
||||
)
|
||||
dirtyState.SetEVM(evm)
|
||||
|
||||
if assignable, ok := dirtyState.(vm.EVMAssignable); ok {
|
||||
assignable.SetEVM(evm)
|
||||
}
|
||||
// Monitor the outer context and interrupt the EVM upon cancellation. To avoid
|
||||
// a dangling goroutine until the outer estimation finishes, create an internal
|
||||
// context for the lifetime of this method call.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
|
@ -35,6 +36,7 @@ import (
|
|||
"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/eth/tracers/directory"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/lib/ethapi"
|
||||
|
|
@ -271,7 +273,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
|||
// Trace all the transactions contained within
|
||||
for i, tx := range task.block.Transactions() {
|
||||
msg, _ := core.TransactionToMessage(tx, signer, task.block.BaseFee())
|
||||
txctx := &Context{
|
||||
txctx := &directory.Context{
|
||||
BlockHash: task.block.Hash(),
|
||||
BlockNumber: task.block.Number(),
|
||||
TxIndex: i,
|
||||
|
|
@ -283,8 +285,6 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
|||
log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err)
|
||||
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}
|
||||
}
|
||||
// Tracing state is used up, queue it for de-referencing. Note the
|
||||
|
|
@ -386,7 +386,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
|||
// Send the block over to the concurrent tracers (if not in the fast-forward phase)
|
||||
txs := next.Transactions()
|
||||
select {
|
||||
case taskCh <- &blockTraceTask{statedb: statedb.Copy(), block: next, release: release, results: make([]*txTraceResult, len(txs))}:
|
||||
case taskCh <- &blockTraceTask{statedb: statedb.Copy().(vm.StateDB), block: next, release: release, results: make([]*txTraceResult, len(txs))}:
|
||||
case <-closed:
|
||||
tracker.releaseState(number, release)
|
||||
return
|
||||
|
|
@ -590,7 +590,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
|||
// process that generates states in one thread and traces txes
|
||||
// in separate worker threads.
|
||||
if config != nil && config.Tracer != nil && *config.Tracer != "" {
|
||||
if isJS := DefaultDirectory.IsJS(*config.Tracer); isJS {
|
||||
if isJS := directory.DefaultDirectory.IsJS(*config.Tracer); isJS {
|
||||
return api.traceBlockParallel(ctx, block, statedb, config)
|
||||
}
|
||||
}
|
||||
|
|
@ -598,7 +598,6 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
|||
var (
|
||||
txs = block.Transactions()
|
||||
blockHash = block.Hash()
|
||||
is158 = api.backend.ChainConfig().IsEIP158(block.Number())
|
||||
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
||||
results = make([]*txTraceResult, len(txs))
|
||||
|
|
@ -606,7 +605,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
|||
for i, tx := range txs {
|
||||
// Generate the next state snapshot fast without tracing
|
||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
txctx := &Context{
|
||||
txctx := &directory.Context{
|
||||
BlockHash: blockHash,
|
||||
BlockNumber: block.Number(),
|
||||
TxIndex: i,
|
||||
|
|
@ -617,9 +616,6 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
|||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
@ -649,7 +645,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
|
|||
// Fetch and execute the next transaction trace tasks
|
||||
for task := range jobs {
|
||||
msg, _ := core.TransactionToMessage(txs[task.index], signer, block.BaseFee())
|
||||
txctx := &Context{
|
||||
txctx := &directory.Context{
|
||||
BlockHash: blockHash,
|
||||
BlockNumber: block.Number(),
|
||||
TxIndex: task.index,
|
||||
|
|
@ -670,7 +666,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
|
|||
txloop:
|
||||
for i, tx := range txs {
|
||||
// Send the trace task over for execution
|
||||
task := &txTraceTask{statedb: statedb.Copy(), index: i}
|
||||
task := &txTraceTask{statedb: statedb.Copy().(vm.StateDB), index: i}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
failed = ctx.Err()
|
||||
|
|
@ -789,7 +785,9 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
|||
// Execute the transaction and flush any traces to disk
|
||||
vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf)
|
||||
statedb.SetTxContext(tx.Hash(), i)
|
||||
_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit))
|
||||
vmConf.Tracer.CaptureTxStart(vmenv, tx, msg.From)
|
||||
vmRet, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit))
|
||||
vmConf.Tracer.CaptureTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, err)
|
||||
if writer != nil {
|
||||
writer.Flush()
|
||||
}
|
||||
|
|
@ -852,7 +850,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
|
|||
}
|
||||
defer release()
|
||||
|
||||
txctx := &Context{
|
||||
txctx := &directory.Context{
|
||||
BlockHash: blockHash,
|
||||
BlockNumber: block.Number(),
|
||||
TxIndex: int(index),
|
||||
|
|
@ -928,18 +926,17 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
|||
if config != nil {
|
||||
traceConfig = &config.TraceConfig
|
||||
}
|
||||
return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig)
|
||||
return api.traceTx(ctx, msg, new(directory.Context), vmctx, statedb, traceConfig)
|
||||
}
|
||||
|
||||
// traceTx configures a new tracer according to the provided configuration, and
|
||||
// executes the given message in the provided environment. The return value will
|
||||
// be tracer dependent.
|
||||
func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb vm.StateDB, config *TraceConfig) (interface{}, error) {
|
||||
func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *directory.Context, vmctx vm.BlockContext, statedb vm.StateDB, config *TraceConfig) (interface{}, error) {
|
||||
var (
|
||||
tracer Tracer
|
||||
tracer directory.Tracer
|
||||
err error
|
||||
timeout = defaultTraceTimeout
|
||||
txContext = core.NewEVMTxContext(message)
|
||||
)
|
||||
if config == nil {
|
||||
config = &TraceConfig{}
|
||||
|
|
@ -947,12 +944,13 @@ func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Conte
|
|||
// Default tracer is the struct logger
|
||||
tracer = logger.NewStructLogger(config.Config)
|
||||
if config.Tracer != nil {
|
||||
tracer, err = DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig)
|
||||
tracer, err = directory.DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig)
|
||||
if err != nil {
|
||||
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, NoBaseFee: true})
|
||||
statedb.SetLogger(tracer)
|
||||
|
||||
// Define a meaningful timeout of a single transaction trace
|
||||
if config.Timeout != nil {
|
||||
|
|
|
|||
|
|
@ -665,7 +665,6 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||
From: &accounts[0].addr,
|
||||
// BLOCKNUMBER PUSH1 MSTORE
|
||||
Input: newRPCBytes(common.Hex2Bytes("4360005260206000f3")),
|
||||
//&hexutil.Bytes{0x43}, // blocknumber
|
||||
},
|
||||
config: &TraceCallConfig{
|
||||
BlockOverrides: ðapi.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))},
|
||||
|
|
|
|||
30
eth/tracers/directory/live.go
Normal file
30
eth/tracers/directory/live.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package directory
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
)
|
||||
|
||||
type ctorFunc func() (core.BlockchainLogger, 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) (core.BlockchainLogger, error) {
|
||||
if f, ok := d.elems[name]; ok {
|
||||
return f()
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
|
@ -14,64 +14,85 @@
|
|||
// 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
|
||||
package directory
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/eth/tracers"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tracers.DefaultDirectory.Register("noopTracer", newNoopTracer, false)
|
||||
DefaultDirectory.Register("noopTracer", newNoopTracer, false)
|
||||
}
|
||||
|
||||
// noopTracer is a go implementation of the Tracer interface which
|
||||
// NoopTracer is a go implementation of the Tracer interface which
|
||||
// performs no action. It's mostly useful for testing purposes.
|
||||
type noopTracer struct{}
|
||||
type NoopTracer struct{}
|
||||
|
||||
// newNoopTracer returns a new noop tracer.
|
||||
func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
|
||||
return &noopTracer{}, nil
|
||||
func newNoopTracer(ctx *Context, _ json.RawMessage) (Tracer, error) {
|
||||
return &NoopTracer{}, nil
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
func (t *noopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
func (t *NoopTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (t *noopTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
func (t *NoopTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
}
|
||||
|
||||
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
|
||||
func (t *noopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
func (t *NoopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
}
|
||||
|
||||
// CaptureFault implements the EVMLogger interface to trace an execution fault.
|
||||
func (t *noopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
|
||||
func (t *NoopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
|
||||
}
|
||||
|
||||
// CaptureKeccakPreimage is called during the KECCAK256 opcode.
|
||||
func (t *NoopTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
|
||||
|
||||
// OnGasChange is called when gas is either consumed or refunded.
|
||||
func (t *NoopTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {}
|
||||
|
||||
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
||||
func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
func (t *NoopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
// CaptureExit is called when EVM exits a scope, even if the scope didn't
|
||||
// execute any code.
|
||||
func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
func (t *NoopTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
}
|
||||
|
||||
func (*noopTracer) CaptureTxStart(gasLimit uint64) {}
|
||||
func (*NoopTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) {}
|
||||
|
||||
func (*noopTracer) CaptureTxEnd(restGas uint64) {}
|
||||
func (*NoopTracer) CaptureTxEnd(receipt *types.Receipt, err error) {}
|
||||
|
||||
func (*NoopTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason state.BalanceChangeReason) {
|
||||
}
|
||||
|
||||
func (*NoopTracer) OnNonceChange(a common.Address, prev, new uint64) {}
|
||||
|
||||
func (*NoopTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
|
||||
}
|
||||
|
||||
func (*NoopTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {}
|
||||
|
||||
func (*NoopTracer) OnLog(log *types.Log) {}
|
||||
|
||||
func (*NoopTracer) OnNewAccount(a common.Address, reset bool) {}
|
||||
|
||||
// GetResult returns an empty json object.
|
||||
func (t *noopTracer) GetResult() (json.RawMessage, error) {
|
||||
func (t *NoopTracer) GetResult() (json.RawMessage, error) {
|
||||
return json.RawMessage(`{}`), nil
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (t *noopTracer) Stop(err error) {
|
||||
func (t *NoopTracer) Stop(err error) {
|
||||
}
|
||||
|
|
@ -14,16 +14,17 @@
|
|||
// 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 tracers is a manager for transaction tracing engines.
|
||||
package tracers
|
||||
// Package directory provides functionality to lookup tracers by name.
|
||||
// It also includes utility functions that are imported by the other
|
||||
// tracing packages.
|
||||
package directory
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
)
|
||||
|
||||
|
|
@ -36,10 +37,13 @@ type Context struct {
|
|||
TxHash common.Hash // Hash of the transaction being traced (zero if dangling call)
|
||||
}
|
||||
|
||||
// Tracer interface extends vm.EVMLogger and additionally
|
||||
// allows collecting the tracing result.
|
||||
// The set of methods that must be exposed by a tracer
|
||||
// for it to be available through the RPC interface.
|
||||
// This involves a method to retrieve results and one to
|
||||
// stop tracing.
|
||||
type Tracer interface {
|
||||
vm.EVMLogger
|
||||
state.StateLogger
|
||||
GetResult() (json.RawMessage, error)
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
Stop(err error)
|
||||
|
|
@ -97,27 +101,3 @@ func (d *directory) IsJS(name string) bool {
|
|||
// JS eval will execute JS code
|
||||
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
|
||||
}
|
||||
47
eth/tracers/directory/util.go
Normal file
47
eth/tracers/directory/util.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
package directory
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
60
eth/tracers/directory/util_test.go
Normal file
60
eth/tracers/directory/util_test.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
package directory
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
)
|
||||
|
||||
func TestMemCopying(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
memsize int64
|
||||
offset int64
|
||||
size int64
|
||||
wantErr string
|
||||
wantSize int
|
||||
}{
|
||||
{0, 0, 100, "", 100}, // Should pad up to 100
|
||||
{0, 100, 0, "", 0}, // No need to pad (0 size)
|
||||
{100, 50, 100, "", 100}, // Should pad 100-150
|
||||
{100, 50, 5, "", 5}, // Wanted range fully within memory
|
||||
{100, -50, 0, "offset or size must not be negative", 0}, // Errror
|
||||
{0, 1, 1024*1024 + 1, "reached limit for padding memory slice: 1048578", 0}, // Errror
|
||||
{10, 0, 1024*1024 + 100, "reached limit for padding memory slice: 1048666", 0}, // Errror
|
||||
|
||||
} {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
10
eth/tracers/internal/tracetest/README.md
Normal file
10
eth/tracers/internal/tracetest/README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Filling test cases
|
||||
|
||||
To fill test cases for the built-in tracers, the `makeTest.js` script can be used. Given a transaction on a dev/test network, `makeTest.js` will fetch its prestate and then traces with the given configuration.
|
||||
In the Geth console do:
|
||||
|
||||
```terminal
|
||||
let tx = '0x...'
|
||||
loadScript('makeTest.js')
|
||||
makeTest(tx, { tracer: 'callTracer' })
|
||||
```
|
||||
|
|
@ -18,6 +18,7 @@ package tracetest
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -31,7 +32,8 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
|
|
@ -142,19 +144,22 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
|
|||
)
|
||||
triedb.Close()
|
||||
|
||||
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
|
||||
tracer, err := directory.DefaultDirectory.New(tracerName, new(directory.Context), test.TracerConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create call tracer: %v", err)
|
||||
}
|
||||
statedb.SetLogger(tracer)
|
||||
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer})
|
||||
msg, err := core.TransactionToMessage(tx, signer, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||
}
|
||||
tracer.CaptureTxStart(evm, tx, msg.From)
|
||||
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
tracer.CaptureTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
|
||||
// Retrieve the trace result and compare against the expected.
|
||||
res, err := tracer.GetResult()
|
||||
if err != nil {
|
||||
|
|
@ -246,7 +251,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
|
|||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), nil)
|
||||
tracer, err := directory.DefaultDirectory.New(tracerName, new(directory.Context), nil)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create call tracer: %v", err)
|
||||
}
|
||||
|
|
@ -265,12 +270,12 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
|
|||
|
||||
func TestInternals(t *testing.T) {
|
||||
var (
|
||||
config = params.MainnetChainConfig
|
||||
to = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
|
||||
origin = common.HexToAddress("0x00000000000000000000000000000000feed")
|
||||
txContext = vm.TxContext{
|
||||
Origin: origin,
|
||||
GasPrice: big.NewInt(1),
|
||||
}
|
||||
originHex = "0x71562b71999873db5b286df957af199ec94617f7"
|
||||
origin = common.HexToAddress(originHex)
|
||||
signer = types.LatestSigner(config)
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
context = vm.BlockContext{
|
||||
CanTransfer: core.CanTransfer,
|
||||
Transfer: core.Transfer,
|
||||
|
|
@ -279,10 +284,11 @@ func TestInternals(t *testing.T) {
|
|||
Time: 5,
|
||||
Difficulty: big.NewInt(0x30000),
|
||||
GasLimit: uint64(6000000),
|
||||
BaseFee: new(big.Int),
|
||||
}
|
||||
)
|
||||
mkTracer := func(name string, cfg json.RawMessage) tracers.Tracer {
|
||||
tr, err := tracers.DefaultDirectory.New(name, nil, cfg)
|
||||
mkTracer := func(name string, cfg json.RawMessage) directory.Tracer {
|
||||
tr, err := directory.DefaultDirectory.New(name, nil, cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create call tracer: %v", err)
|
||||
}
|
||||
|
|
@ -292,7 +298,7 @@ func TestInternals(t *testing.T) {
|
|||
for _, tc := range []struct {
|
||||
name string
|
||||
code []byte
|
||||
tracer tracers.Tracer
|
||||
tracer directory.Tracer
|
||||
want string
|
||||
}{
|
||||
{
|
||||
|
|
@ -306,13 +312,13 @@ func TestInternals(t *testing.T) {
|
|||
byte(vm.CALL),
|
||||
},
|
||||
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",
|
||||
code: []byte{byte(vm.LOG3)},
|
||||
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",
|
||||
|
|
@ -325,7 +331,7 @@ func TestInternals(t *testing.T) {
|
|||
byte(vm.LOG0),
|
||||
},
|
||||
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
|
||||
|
|
@ -344,7 +350,7 @@ func TestInternals(t *testing.T) {
|
|||
byte(vm.LOG0),
|
||||
},
|
||||
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
|
||||
|
|
@ -363,7 +369,7 @@ func TestInternals(t *testing.T) {
|
|||
byte(vm.LOG0),
|
||||
},
|
||||
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) {
|
||||
|
|
@ -377,22 +383,31 @@ func TestInternals(t *testing.T) {
|
|||
},
|
||||
}, false, rawdb.HashScheme)
|
||||
defer triedb.Close()
|
||||
|
||||
evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Tracer: tc.tracer})
|
||||
msg := &core.Message{
|
||||
statedb.SetLogger(tc.tracer)
|
||||
tx, err := types.SignNewTx(key, signer, &types.LegacyTx{
|
||||
To: &to,
|
||||
From: origin,
|
||||
Value: big.NewInt(0),
|
||||
GasLimit: 80000,
|
||||
GasPrice: big.NewInt(0),
|
||||
GasFeeCap: big.NewInt(0),
|
||||
GasTipCap: big.NewInt(0),
|
||||
SkipAccountChecks: false,
|
||||
Gas: 80000,
|
||||
GasPrice: big.NewInt(1),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("test %v: failed to sign transaction: %v", tc.name, err)
|
||||
}
|
||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(msg.GasLimit))
|
||||
if _, err := st.TransitionDb(); err != nil {
|
||||
txContext := vm.TxContext{
|
||||
Origin: origin,
|
||||
GasPrice: tx.GasPrice(),
|
||||
}
|
||||
evm := vm.NewEVM(context, txContext, statedb, config, vm.Config{Tracer: tc.tracer})
|
||||
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.CaptureTxStart(evm, 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)
|
||||
}
|
||||
tc.tracer.CaptureTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
|
||||
// Retrieve the trace result and compare against the expected
|
||||
res, err := tc.tracer.GetResult()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -16,11 +16,9 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
"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"
|
||||
)
|
||||
|
||||
// flatCallTrace is the result of a callTracerParity run.
|
||||
|
|
@ -104,21 +102,23 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string
|
|||
defer triedb.Close()
|
||||
|
||||
// Create the tracer, the EVM environment and run it
|
||||
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
|
||||
tracer, err := directory.DefaultDirectory.New(tracerName, new(directory.Context), test.TracerConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create call tracer: %v", err)
|
||||
}
|
||||
statedb.SetLogger(tracer)
|
||||
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer})
|
||||
|
||||
msg, err := core.TransactionToMessage(tx, signer, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("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 {
|
||||
tracer.CaptureTxStart(evm, tx, msg.From)
|
||||
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute transaction: %v", err)
|
||||
}
|
||||
tracer.CaptureTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
|
||||
|
||||
// Retrieve the trace result and compare against the etalon
|
||||
res, err := tracer.GetResult()
|
||||
|
|
@ -130,7 +130,7 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string
|
|||
return fmt.Errorf("failed to unmarshal trace result: %v", err)
|
||||
}
|
||||
if !jsonEqualFlat(ret, test.Result) {
|
||||
t.Logf("tracer name: %s", tracerName)
|
||||
t.Logf("test %s failed", filename)
|
||||
|
||||
// uncomment this for easier debugging
|
||||
// have, _ := json.MarshalIndent(ret, "", " ")
|
||||
|
|
|
|||
48
eth/tracers/internal/tracetest/makeTest.js
Normal file
48
eth/tracers/internal/tracetest/makeTest.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// makeTest generates a test for the configured tracer by running
|
||||
// a prestate reassembled and a call trace run, assembling all the
|
||||
// gathered information into a test case.
|
||||
var makeTest = function(tx, traceConfig) {
|
||||
// Generate the genesis block from the block, transaction and prestate data
|
||||
var block = eth.getBlock(eth.getTransaction(tx).blockHash);
|
||||
var genesis = eth.getBlock(block.parentHash);
|
||||
|
||||
delete genesis.gasUsed;
|
||||
delete genesis.logsBloom;
|
||||
delete genesis.parentHash;
|
||||
delete genesis.receiptsRoot;
|
||||
delete genesis.sha3Uncles;
|
||||
delete genesis.size;
|
||||
delete genesis.transactions;
|
||||
delete genesis.transactionsRoot;
|
||||
delete genesis.uncles;
|
||||
|
||||
genesis.gasLimit = genesis.gasLimit.toString();
|
||||
genesis.number = genesis.number.toString();
|
||||
genesis.timestamp = genesis.timestamp.toString();
|
||||
|
||||
genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer"});
|
||||
for (var key in genesis.alloc) {
|
||||
var nonce = genesis.alloc[key].nonce;
|
||||
if (nonce) {
|
||||
genesis.alloc[key].nonce = nonce.toString();
|
||||
}
|
||||
}
|
||||
genesis.config = admin.nodeInfo.protocols.eth.config;
|
||||
|
||||
// Generate the call trace and produce the test input
|
||||
var result = debug.traceTransaction(tx, traceConfig);
|
||||
delete result.time;
|
||||
|
||||
console.log(JSON.stringify({
|
||||
genesis: genesis,
|
||||
context: {
|
||||
number: block.number.toString(),
|
||||
difficulty: block.difficulty,
|
||||
timestamp: block.timestamp.toString(),
|
||||
gasLimit: block.gasLimit.toString(),
|
||||
miner: block.miner,
|
||||
},
|
||||
input: eth.getRawTransaction(tx),
|
||||
result: result,
|
||||
}, null, 2));
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
)
|
||||
|
||||
|
|
@ -112,19 +112,22 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) {
|
|||
)
|
||||
defer triedb.Close()
|
||||
|
||||
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
|
||||
tracer, err := directory.DefaultDirectory.New(tracerName, new(directory.Context), test.TracerConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create call tracer: %v", err)
|
||||
}
|
||||
statedb.SetLogger(tracer)
|
||||
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer})
|
||||
msg, err := core.TransactionToMessage(tx, 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 {
|
||||
tracer.CaptureTxStart(evm, tx, msg.From)
|
||||
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
tracer.CaptureTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
|
||||
// Retrieve the trace result and compare against the expected
|
||||
res, err := tracer.GetResult()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,16 @@
|
|||
"value": "0x0",
|
||||
"gas": "0x1f97e",
|
||||
"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"
|
||||
},
|
||||
"traceAddress": [],
|
||||
"subtraces": 0,
|
||||
"subtraces": 1,
|
||||
"transactionPosition": 74,
|
||||
"transactionHash": "0x5ef60b27ac971c22a7d484e546e50093ca62300c8986d165154e47773764b6a4",
|
||||
"blockNumber": 1555279,
|
||||
"blockHash": "0xd6c98d1b87dfa92a210d99bad2873adaf0c9e51fe43addc63fd9cca03a5c6f46",
|
||||
"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",
|
||||
"output": "0x"
|
||||
},
|
||||
"subtraces": 0,
|
||||
"subtraces": 1,
|
||||
"traceAddress": [],
|
||||
"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"
|
||||
},
|
||||
"traceAddress": [],
|
||||
"subtraces": 0,
|
||||
"subtraces": 1,
|
||||
"transactionPosition": 26,
|
||||
"transactionHash": "0xcb1090fa85d2a3da8326b75333e92b3dca89963c895d9c981bfdaa64643135e4",
|
||||
"blockNumber": 839247,
|
||||
"blockHash": "0xce7ff7d84ca97f0f89d6065e2c12409a795c9f607cdb14aef0713cad5d7e311c",
|
||||
"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"
|
||||
},
|
||||
"traceAddress": [],
|
||||
"subtraces": 1,
|
||||
"subtraces": 2,
|
||||
"transactionPosition": 14,
|
||||
"transactionHash": "0xdd76f02407e2f8329303ba688e111cae4f7008ad0d14d6e42c5698424ea36d79",
|
||||
"blockNumber": 1555146,
|
||||
"blockHash": "0xafb4f1dd27b9054c805acb81a88ed04384788cb31d84164c21874935c81e5c7e",
|
||||
"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",
|
||||
"action": {
|
||||
|
|
@ -79,7 +92,7 @@
|
|||
},
|
||||
"result": null,
|
||||
"traceAddress": [
|
||||
0
|
||||
1
|
||||
],
|
||||
"subtraces": 0,
|
||||
"transactionPosition": 14,
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
189
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/frontier_create_outofstorage.json
vendored
Normal file
189
eth/tracers/internal/tracetest/testdata/call_tracer_withLog/frontier_create_outofstorage.json
vendored
Normal file
File diff suppressed because one or more lines are too long
62
eth/tracers/internal/tracetest/testdata/prestate_tracer/create_create.json
vendored
Normal file
62
eth/tracers/internal/tracetest/testdata/prestate_tracer/create_create.json
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"genesis": {
|
||||
"baseFeePerGas": "875000000",
|
||||
"difficulty": "0",
|
||||
"extraData": "0xd983010d05846765746888676f312e32312e318664617277696e",
|
||||
"gasLimit": "11511229",
|
||||
"hash": "0xd462585c6c5a3b3bf14850ebcde71b6615b9aaf6541403f9a0457212dd0502e0",
|
||||
"miner": "0x0000000000000000000000000000000000000000",
|
||||
"mixHash": "0xfa51e868d6a7c0728f18800e4cc8d4cc1c87430cc9975e947eb6c9c03599b4e2",
|
||||
"nonce": "0x0000000000000000",
|
||||
"number": "1",
|
||||
"stateRoot": "0xd2ebe0a7f3572ffe3e5b4c78147376d3fca767f236e4dd23f9151acfec7cb0d1",
|
||||
"timestamp": "1699617692",
|
||||
"totalDifficulty": "0",
|
||||
"withdrawals": [],
|
||||
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||
"alloc": {
|
||||
"0x0000000000000000000000000000000000000000": {
|
||||
"balance": "0x5208"
|
||||
},
|
||||
"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": {
|
||||
"balance": "0x8ac7230489e80000"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"chainId": 1337,
|
||||
"homesteadBlock": 0,
|
||||
"eip150Block": 0,
|
||||
"eip155Block": 0,
|
||||
"eip158Block": 0,
|
||||
"byzantiumBlock": 0,
|
||||
"constantinopleBlock": 0,
|
||||
"petersburgBlock": 0,
|
||||
"istanbulBlock": 0,
|
||||
"muirGlacierBlock": 0,
|
||||
"berlinBlock": 0,
|
||||
"londonBlock": 0,
|
||||
"arrowGlacierBlock": 0,
|
||||
"grayGlacierBlock": 0,
|
||||
"shanghaiTime": 0,
|
||||
"terminalTotalDifficulty": 0,
|
||||
"terminalTotalDifficultyPassed": true,
|
||||
"isDev": true
|
||||
}
|
||||
},
|
||||
"context": {
|
||||
"number": "2",
|
||||
"difficulty": "0",
|
||||
"timestamp": "1699617847",
|
||||
"gasLimit": "11522469",
|
||||
"miner": "0x0000000000000000000000000000000000000000"
|
||||
},
|
||||
"input": "0x02f902b48205398084b2d05e0085011b1f3f8083031ca88080b90258608060405234801561001057600080fd5b5060405161001d906100e3565b604051809103906000f080158015610039573d6000803e3d6000fd5b50600080546001600160a01b0319166001600160a01b039290921691821781556040517fc66247bafd1305823857fb4c3e651e684d918df8554ef560bbbcb025fdd017039190a26000546040516360fe47b160e01b8152600560048201526001600160a01b03909116906360fe47b190602401600060405180830381600087803b1580156100c657600080fd5b505af11580156100da573d6000803e3d6000fd5b505050506100ef565b60ca8061018e83390190565b6091806100fd6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806380de699314602d575b600080fd5b600054603f906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f3fea2646970667358221220dab781465e7f4cf20304cc388130a763508e20edd25b4bc8ea8f57743a0de8da64736f6c634300081700336080604052348015600f57600080fd5b5060ac8061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806360fe47b11460375780636d4ce63c146049575b600080fd5b60476042366004605e565b600055565b005b60005460405190815260200160405180910390f35b600060208284031215606f57600080fd5b503591905056fea264697066735822122049e09da6320793487d58eaa7b97f802618a062cbc35f08ca1ce92c17349141f864736f6c63430008170033c080a01d4fce93ad08bf413052645721f20e6136830cf5a2759fa57e76a134e90899a7a0399a72832d52118991dc04c4f9e1c0fec3d5e441ad7d4b055f0cf03130d8f815",
|
||||
"result": {
|
||||
"0x0000000000000000000000000000000000000000": {
|
||||
"balance": "0x5208"
|
||||
},
|
||||
"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": {
|
||||
"balance": "0x8ac7230489e80000"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,59 +9,6 @@ import (
|
|||
_ "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.
|
||||
func camel(str string) string {
|
||||
pieces := strings.Split(str, "_")
|
||||
|
|
|
|||
|
|
@ -23,12 +23,13 @@ import (
|
|||
"math/big"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
jsassets "github.com/ethereum/go-ethereum/eth/tracers/js/internal/tracers"
|
||||
)
|
||||
|
||||
|
|
@ -41,16 +42,16 @@ func init() {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error)
|
||||
type ctorFn = func(*directory.Context, json.RawMessage) (directory.Tracer, error)
|
||||
lookup := func(code string) ctorFn {
|
||||
return func(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
|
||||
return func(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) {
|
||||
return newJsTracer(code, ctx, cfg)
|
||||
}
|
||||
}
|
||||
for name, code := range assetTracers {
|
||||
tracers.DefaultDirectory.Register(name, lookup(code), true)
|
||||
directory.DefaultDirectory.Register(name, lookup(code), true)
|
||||
}
|
||||
tracers.DefaultDirectory.RegisterJSEval(newJsTracer)
|
||||
directory.DefaultDirectory.RegisterJSEval(newJsTracer)
|
||||
}
|
||||
|
||||
// bigIntProgram is compiled once and the exported function mostly invoked to convert
|
||||
|
|
@ -95,6 +96,8 @@ func fromBuf(vm *goja.Runtime, bufType goja.Value, buf goja.Value, allowString b
|
|||
// jsTracer is an implementation of the Tracer interface which evaluates
|
||||
// JS functions on the relevant EVM hooks. It uses Goja as its JS engine.
|
||||
type jsTracer struct {
|
||||
directory.NoopTracer
|
||||
|
||||
vm *goja.Runtime
|
||||
env *vm.EVM
|
||||
toBig toBigFn // Converts a hex string into a JS bigint
|
||||
|
|
@ -104,7 +107,6 @@ type jsTracer struct {
|
|||
activePrecompiles []common.Address // List of active precompiles at current block
|
||||
traceStep bool // True if tracer object exposes a `step()` method
|
||||
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
|
||||
obj *goja.Object // Trace object
|
||||
|
||||
|
|
@ -134,7 +136,7 @@ type jsTracer struct {
|
|||
// The methods `result` and `fault` are required to be present.
|
||||
// The methods `step`, `enter`, and `exit` are optional, but note that
|
||||
// `enter` and `exit` always go together.
|
||||
func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
|
||||
func newJsTracer(code string, ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) {
|
||||
vm := goja.New()
|
||||
// By default field names are exported to JS as is, i.e. capitalized.
|
||||
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
||||
|
|
@ -147,7 +149,7 @@ func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracer
|
|||
t.setBuiltinFunctions()
|
||||
|
||||
if ctx == nil {
|
||||
ctx = new(tracers.Context)
|
||||
ctx = new(directory.Context)
|
||||
}
|
||||
if ctx.BlockHash != (common.Hash{}) {
|
||||
blockHash, err := t.toBuf(vm, ctx.BlockHash.Bytes())
|
||||
|
|
@ -222,25 +224,44 @@ func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracer
|
|||
|
||||
// CaptureTxStart implements the Tracer interface and is invoked at the beginning of
|
||||
// transaction processing.
|
||||
func (t *jsTracer) CaptureTxStart(gasLimit uint64) {
|
||||
t.gasLimit = gasLimit
|
||||
func (t *jsTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) {
|
||||
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.Context.BlockNumber, env.Context.Random != nil, env.Context.Time)
|
||||
t.activePrecompiles = vm.ActivePrecompiles(rules)
|
||||
t.ctx["block"] = t.vm.ToValue(t.env.Context.BlockNumber.Uint64())
|
||||
t.ctx["gas"] = t.vm.ToValue(tx.Gas())
|
||||
gasPriceBig, err := t.toBig(t.vm, env.TxContext.GasPrice.String())
|
||||
if err != nil {
|
||||
t.err = err
|
||||
t.env.Cancel()
|
||||
return
|
||||
}
|
||||
t.ctx["gasPrice"] = gasPriceBig
|
||||
}
|
||||
|
||||
// CaptureTxEnd implements the Tracer interface and is invoked at the end of
|
||||
// transaction processing.
|
||||
func (t *jsTracer) CaptureTxEnd(restGas uint64) {
|
||||
t.ctx["gasUsed"] = t.vm.ToValue(t.gasLimit - restGas)
|
||||
func (t *jsTracer) CaptureTxEnd(receipt *types.Receipt, err error) {
|
||||
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.
|
||||
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) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
cancel := func(err error) {
|
||||
t.err = err
|
||||
t.env.Cancel()
|
||||
}
|
||||
t.env = env
|
||||
db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf}
|
||||
t.dbValue = db.setupObject()
|
||||
if create {
|
||||
t.ctx["type"] = t.vm.ToValue("CREATE")
|
||||
} else {
|
||||
|
|
@ -264,23 +285,12 @@ func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr
|
|||
return
|
||||
}
|
||||
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())
|
||||
if err != nil {
|
||||
cancel(err)
|
||||
return
|
||||
}
|
||||
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.
|
||||
|
|
@ -321,7 +331,7 @@ func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope
|
|||
}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
if err != nil {
|
||||
t.ctx["error"] = t.vm.ToValue(err.Error())
|
||||
}
|
||||
|
|
@ -359,7 +369,7 @@ 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
|
||||
// execute any code.
|
||||
func (t *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
func (t *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
if !t.traceFrame {
|
||||
return
|
||||
}
|
||||
|
|
@ -606,7 +616,7 @@ func (mo *memoryObj) slice(begin, end int64) ([]byte, error) {
|
|||
if end < begin || begin < 0 {
|
||||
return nil, fmt.Errorf("tracer accessed out of bound memory: offset %d, end %d", begin, end)
|
||||
}
|
||||
slice, err := tracers.GetMemoryCopyPadded(mo.memory, begin, end-begin)
|
||||
slice, err := directory.GetMemoryCopyPadded(mo.memory, begin, end-begin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,9 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
|
|
@ -60,7 +61,7 @@ func testCtx() *vmContext {
|
|||
return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
|
||||
}
|
||||
|
||||
func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte) (json.RawMessage, error) {
|
||||
func runTrace(tracer directory.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte) (json.RawMessage, error) {
|
||||
var (
|
||||
env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Tracer: tracer})
|
||||
gasLimit uint64 = 31000
|
||||
|
|
@ -73,12 +74,12 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon
|
|||
contract.Code = contractCode
|
||||
}
|
||||
|
||||
tracer.CaptureTxStart(gasLimit)
|
||||
tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value)
|
||||
tracer.CaptureTxStart(env, types.NewTx(&types.LegacyTx{Gas: gasLimit}), contract.Caller())
|
||||
tracer.CaptureStart(contract.Caller(), contract.Address(), false, []byte{}, startGas, value)
|
||||
ret, err := env.Interpreter().Run(contract, []byte{}, false)
|
||||
tracer.CaptureEnd(ret, startGas-contract.Gas, err)
|
||||
tracer.CaptureEnd(ret, startGas-contract.Gas, err, true)
|
||||
// Rest gas assumes no refund
|
||||
tracer.CaptureTxEnd(contract.Gas)
|
||||
tracer.CaptureTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -180,11 +181,12 @@ func TestHaltBetweenSteps(t *testing.T) {
|
|||
if err != nil {
|
||||
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{
|
||||
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
|
||||
}
|
||||
tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 0, big.NewInt(0))
|
||||
env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: tracer})
|
||||
tracer.CaptureTxStart(env, types.NewTx(&types.LegacyTx{}), common.Address{})
|
||||
tracer.CaptureStart(common.Address{}, common.Address{}, false, []byte{}, 0, big.NewInt(0))
|
||||
tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil)
|
||||
timeout := errors.New("stahp")
|
||||
tracer.Stop(timeout)
|
||||
|
|
@ -205,8 +207,9 @@ func TestNoStepExec(t *testing.T) {
|
|||
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})
|
||||
tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 1000, big.NewInt(0))
|
||||
tracer.CaptureEnd(nil, 0, nil)
|
||||
tracer.CaptureTxStart(env, types.NewTx(&types.LegacyTx{}), common.Address{})
|
||||
tracer.CaptureStart(common.Address{}, common.Address{}, false, []byte{}, 1000, big.NewInt(0))
|
||||
tracer.CaptureEnd(nil, 0, nil, false)
|
||||
ret, err := tracer.GetResult()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -261,14 +264,14 @@ func TestIsPrecompile(t *testing.T) {
|
|||
|
||||
func TestEnterExit(t *testing.T) {
|
||||
// test that either both or none of enter() and exit() are defined
|
||||
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(tracers.Context), nil); err == nil {
|
||||
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(directory.Context), nil); err == nil {
|
||||
t.Fatal("tracer creation should've failed without exit() definition")
|
||||
}
|
||||
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context), nil); err != nil {
|
||||
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(directory.Context), nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// test that the enter and exit method are correctly invoked and the values passed
|
||||
tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context), nil)
|
||||
tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(directory.Context), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -276,7 +279,7 @@ func TestEnterExit(t *testing.T) {
|
|||
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
|
||||
}
|
||||
tracer.CaptureEnter(vm.CALL, scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int))
|
||||
tracer.CaptureExit([]byte{}, 400, nil)
|
||||
tracer.CaptureExit([]byte{}, 400, nil, false)
|
||||
|
||||
have, err := tracer.GetResult()
|
||||
if err != nil {
|
||||
|
|
@ -290,7 +293,7 @@ func TestEnterExit(t *testing.T) {
|
|||
|
||||
func TestSetup(t *testing.T) {
|
||||
// Test empty config
|
||||
_, err := newJsTracer(`{setup: function(cfg) { if (cfg !== "{}") { throw("invalid empty config") } }, fault: function() {}, result: function() {}}`, new(tracers.Context), nil)
|
||||
_, err := newJsTracer(`{setup: function(cfg) { if (cfg !== "{}") { throw("invalid empty config") } }, fault: function() {}, result: function() {}}`, new(directory.Context), nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
|
@ -300,12 +303,12 @@ func TestSetup(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
// Test no setup func
|
||||
_, err = newJsTracer(`{fault: function() {}, result: function() {}}`, new(tracers.Context), cfg)
|
||||
_, err = newJsTracer(`{fault: function() {}, result: function() {}}`, new(directory.Context), cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Test config value
|
||||
tracer, err := newJsTracer("{config: null, setup: function(cfg) { this.config = JSON.parse(cfg) }, step: function() {}, fault: function() {}, result: function() { return this.config.foo }}", new(tracers.Context), cfg)
|
||||
tracer, err := newJsTracer("{config: null, setup: function(cfg) { this.config = JSON.parse(cfg) }, step: function() {}, fault: function() {}, result: function() { return this.config.foo }}", new(directory.Context), cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
134
eth/tracers/live/printer.go
Normal file
134
eth/tracers/live/printer.go
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
package live
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"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/eth/tracers/directory"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
func init() {
|
||||
directory.LiveDirectory.Register("printer", newPrinter)
|
||||
}
|
||||
|
||||
type Printer struct{}
|
||||
|
||||
func newPrinter() (core.BlockchainLogger, error) {
|
||||
return &Printer{}, nil
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
func (p *Printer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
fmt.Printf("CaptureStart: from=%v, to=%v, create=%v, input=%s, gas=%v, value=%v\n", from, to, create, hexutil.Bytes(input), gas, value)
|
||||
}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (p *Printer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
fmt.Printf("CaptureEnd: output=%s, gasUsed=%v, err=%v\n", hexutil.Bytes(output), gasUsed, err)
|
||||
}
|
||||
|
||||
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
|
||||
func (p *Printer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
//fmt.Printf("CaptureState: pc=%v, op=%v, gas=%v, cost=%v, scope=%v, rData=%v, depth=%v, err=%v\n", pc, op, gas, cost, scope, rData, depth, err)
|
||||
}
|
||||
|
||||
// CaptureFault implements the EVMLogger interface to trace an execution fault.
|
||||
func (p *Printer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
|
||||
fmt.Printf("CaptureFault: pc=%v, op=%v, gas=%v, cost=%v, depth=%v, err=%v\n", pc, op, gas, cost, depth, err)
|
||||
}
|
||||
|
||||
// CaptureKeccakPreimage is called during the KECCAK256 opcode.
|
||||
func (p *Printer) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
|
||||
|
||||
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
||||
func (p *Printer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
fmt.Printf("CaptureEnter: typ=%v, from=%v, to=%v, input=%s, gas=%v, value=%v\n", typ, from, to, hexutil.Bytes(input), gas, value)
|
||||
}
|
||||
|
||||
// CaptureExit is called when EVM exits a scope, even if the scope didn't
|
||||
// execute any code.
|
||||
func (p *Printer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
fmt.Printf("CaptureExit: output=%s, gasUsed=%v, err=%v\n", hexutil.Bytes(output), gasUsed, err)
|
||||
}
|
||||
|
||||
func (p *Printer) OnBeaconBlockRootStart(root common.Hash) {}
|
||||
func (p *Printer) OnBeaconBlockRootEnd() {}
|
||||
|
||||
func (p *Printer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) {
|
||||
buf, err := json.Marshal(tx)
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("CaptureTxStart: tx=%s\n", buf)
|
||||
}
|
||||
|
||||
func (p *Printer) CaptureTxEnd(receipt *types.Receipt, err error) {
|
||||
if err != nil {
|
||||
fmt.Printf("CaptureTxEnd err: %v\n", err)
|
||||
return
|
||||
}
|
||||
buf, err := json.Marshal(receipt)
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("CaptureTxEnd: receipt=%s\n", buf)
|
||||
}
|
||||
|
||||
func (p *Printer) OnBlockStart(b *types.Block, td *big.Int, finalized, safe *types.Header, _ *params.ChainConfig) {
|
||||
if finalized != nil && safe != nil {
|
||||
fmt.Printf("OnBlockStart: b=%v, td=%v, finalized=%v, safe=%v\n", b.NumberU64(), td, finalized.Number.Uint64(), safe.Number.Uint64())
|
||||
} else {
|
||||
fmt.Printf("OnBlockStart: b=%v, td=%v\n", b.NumberU64(), td)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Printer) OnBlockEnd(err error) {
|
||||
fmt.Printf("OnBlockEnd: err=%v\n", err)
|
||||
}
|
||||
|
||||
func (p *Printer) OnGenesisBlock(b *types.Block, alloc core.GenesisAlloc) {
|
||||
fmt.Printf("OnGenesisBlock: b=%v, allocLength=%d\n", b.NumberU64(), len(alloc))
|
||||
}
|
||||
|
||||
func (p *Printer) OnBalanceChange(a common.Address, prev, new *big.Int, reason state.BalanceChangeReason) {
|
||||
fmt.Printf("OnBalanceChange: a=%v, prev=%v, new=%v\n", a, prev, new)
|
||||
}
|
||||
|
||||
func (p *Printer) OnNonceChange(a common.Address, prev, new uint64) {
|
||||
fmt.Printf("OnNonceChange: a=%v, prev=%v, new=%v\n", a, prev, new)
|
||||
}
|
||||
|
||||
func (p *Printer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
|
||||
fmt.Printf("OnCodeChange: a=%v, prevCodeHash=%v, prev=%s, codeHash=%v, code=%s\n", a, prevCodeHash, hexutil.Bytes(prev), codeHash, hexutil.Bytes(code))
|
||||
}
|
||||
|
||||
func (p *Printer) OnStorageChange(a common.Address, k, prev, new common.Hash) {
|
||||
fmt.Printf("OnStorageChange: a=%v, k=%v, prev=%v, new=%v\n", a, k, prev, new)
|
||||
}
|
||||
|
||||
func (p *Printer) OnLog(l *types.Log) {
|
||||
buf, err := json.Marshal(l)
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("OnLog: l=%s\n", buf)
|
||||
}
|
||||
|
||||
func (p *Printer) OnNewAccount(a common.Address, reset bool) {
|
||||
fmt.Printf("OnNewAccount: a=%v\n", a)
|
||||
}
|
||||
|
||||
func (p *Printer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {
|
||||
fmt.Printf("OnGasChange: old=%v, new=%v, diff=%v\n", old, new, new-old)
|
||||
}
|
||||
|
|
@ -17,11 +17,10 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
)
|
||||
|
||||
// accessList is an accumulator for the set of accounts and storage slots an EVM
|
||||
|
|
@ -103,6 +102,7 @@ func (al accessList) accessList() types.AccessList {
|
|||
// AccessListTracer is a tracer that accumulates touched accounts and storage
|
||||
// slots into an internal set.
|
||||
type AccessListTracer struct {
|
||||
directory.NoopTracer
|
||||
excl map[common.Address]struct{} // Set of account to exclude from the list
|
||||
list accessList // Set of accounts and storage slots touched
|
||||
}
|
||||
|
|
@ -132,9 +132,6 @@ 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) {
|
||||
}
|
||||
|
||||
// CaptureState 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) {
|
||||
stack := scope.Stack
|
||||
|
|
@ -158,20 +155,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.
|
||||
func (a *AccessListTracer) AccessList() types.AccessList {
|
||||
return a.list.accessList()
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
|
@ -107,6 +108,7 @@ func (s *StructLog) ErrorString() string {
|
|||
// a track record of modified storage which is used in reporting snapshots of the
|
||||
// contract their storage.
|
||||
type StructLogger struct {
|
||||
directory.NoopTracer
|
||||
cfg Config
|
||||
env *vm.EVM
|
||||
|
||||
|
|
@ -114,7 +116,6 @@ type StructLogger struct {
|
|||
logs []StructLog
|
||||
output []byte
|
||||
err error
|
||||
gasLimit uint64
|
||||
usedGas uint64
|
||||
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
|
|
@ -140,11 +141,6 @@ func (l *StructLogger) Reset() {
|
|||
l.err = nil
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
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.
|
||||
|
|
@ -213,13 +209,8 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
|
|||
l.logs = append(l.logs, log)
|
||||
}
|
||||
|
||||
// CaptureFault implements the EVMLogger interface to trace an execution fault
|
||||
// while running an opcode.
|
||||
func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
l.output = output
|
||||
l.err = err
|
||||
if l.cfg.Debug {
|
||||
|
|
@ -230,12 +221,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) {
|
||||
// Tracing aborted
|
||||
if l.reason != nil {
|
||||
|
|
@ -262,12 +247,19 @@ func (l *StructLogger) Stop(err error) {
|
|||
l.interrupt.Store(true)
|
||||
}
|
||||
|
||||
func (l *StructLogger) CaptureTxStart(gasLimit uint64) {
|
||||
l.gasLimit = gasLimit
|
||||
func (l *StructLogger) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) {
|
||||
l.env = env
|
||||
}
|
||||
|
||||
func (l *StructLogger) CaptureTxEnd(restGas uint64) {
|
||||
l.usedGas = l.gasLimit - restGas
|
||||
func (l *StructLogger) CaptureTxEnd(receipt *types.Receipt, err error) {
|
||||
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.
|
||||
|
|
@ -327,6 +319,7 @@ func WriteLogs(writer io.Writer, logs []*types.Log) {
|
|||
}
|
||||
|
||||
type mdLogger struct {
|
||||
directory.NoopTracer
|
||||
out io.Writer
|
||||
cfg *Config
|
||||
env *vm.EVM
|
||||
|
|
@ -342,8 +335,7 @@ func NewMarkdownLogger(cfg *Config, writer io.Writer) *mdLogger {
|
|||
return l
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
t.env = env
|
||||
func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
if !create {
|
||||
fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n",
|
||||
from.String(), to.String(),
|
||||
|
|
@ -385,20 +377,11 @@ func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope
|
|||
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) {
|
||||
func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
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
|
||||
// while replaying a transaction in debug mode as well as transaction
|
||||
// execution status, the amount of gas used and the return value
|
||||
|
|
|
|||
|
|
@ -19,14 +19,16 @@ package logger
|
|||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
)
|
||||
|
||||
type JSONLogger struct {
|
||||
directory.NoopTracer
|
||||
encoder *json.Encoder
|
||||
cfg *Config
|
||||
env *vm.EVM
|
||||
|
|
@ -42,10 +44,6 @@ func NewJSONLogger(cfg *Config, writer io.Writer) *JSONLogger {
|
|||
return l
|
||||
}
|
||||
|
||||
func (l *JSONLogger) CaptureStart(env *vm.EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
l.env = env
|
||||
}
|
||||
|
||||
func (l *JSONLogger) CaptureFault(pc uint64, op vm.OpCode, gas uint64, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
// TODO: Add rData to this interface as well
|
||||
l.CaptureState(pc, op, gas, cost, scope, nil, depth, err)
|
||||
|
|
@ -79,7 +77,7 @@ func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco
|
|||
}
|
||||
|
||||
// CaptureEnd is triggered at end of execution.
|
||||
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
type endLog struct {
|
||||
Output string `json:"output"`
|
||||
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
|
||||
|
|
@ -92,11 +90,6 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
|||
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) CaptureTxStart(env *vm.EVM, 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) {}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,8 @@ func TestStoreCapture(t *testing.T) {
|
|||
)
|
||||
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
|
||||
var index common.Hash
|
||||
logger.CaptureStart(env, common.Address{}, contract.Address(), false, nil, 0, nil)
|
||||
logger.CaptureTxStart(env, nil, common.Address{})
|
||||
logger.CaptureStart(common.Address{}, contract.Address(), false, nil, 0, nil)
|
||||
_, err := env.Interpreter().Run(contract, []byte{}, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
|||
|
|
@ -23,12 +23,13 @@ import (
|
|||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/eth/tracers/directory"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tracers.DefaultDirectory.Register("4byteTracer", newFourByteTracer, false)
|
||||
directory.DefaultDirectory.Register("4byteTracer", newFourByteTracer, false)
|
||||
}
|
||||
|
||||
// fourByteTracer searches for 4byte-identifiers, and collects them for post-processing.
|
||||
|
|
@ -46,7 +47,8 @@ func init() {
|
|||
// 0xc281d19e-0: 1
|
||||
// }
|
||||
type fourByteTracer struct {
|
||||
noopTracer
|
||||
directory.NoopTracer
|
||||
env *vm.EVM
|
||||
ids map[string]int // ids aggregates the 4byte ids found
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
|
|
@ -55,7 +57,7 @@ type fourByteTracer struct {
|
|||
|
||||
// newFourByteTracer returns a native go tracer which collects
|
||||
// 4 byte-identifiers of a tx, and implements vm.EVMLogger.
|
||||
func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
|
||||
func newFourByteTracer(ctx *directory.Context, _ json.RawMessage) (directory.Tracer, error) {
|
||||
t := &fourByteTracer{
|
||||
ids: make(map[string]int),
|
||||
}
|
||||
|
|
@ -78,13 +80,16 @@ func (t *fourByteTracer) store(id []byte, size int) {
|
|||
t.ids[key] += 1
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
func (t *fourByteTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) {
|
||||
t.env = env
|
||||
// Update list of precompiles based on current block
|
||||
rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time)
|
||||
rules := t.env.ChainConfig().Rules(t.env.Context.BlockNumber, t.env.Context.Random != nil, t.env.Context.Time)
|
||||
t.activePrecompiles = vm.ActivePrecompiles(rules)
|
||||
}
|
||||
|
||||
// Save the outer calldata also
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
func (t *fourByteTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
// Save the outer calldata
|
||||
if len(input) >= 4 {
|
||||
t.store(input[0:4], len(input)-4)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,15 +25,15 @@ import (
|
|||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go
|
||||
|
||||
func init() {
|
||||
tracers.DefaultDirectory.Register("callTracer", newCallTracer, false)
|
||||
directory.DefaultDirectory.Register("callTracer", newCallTracer, false)
|
||||
}
|
||||
|
||||
type callLog struct {
|
||||
|
|
@ -60,6 +60,7 @@ type callFrame struct {
|
|||
// 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.
|
||||
Value *big.Int `json:"value,omitempty" rlp:"optional"`
|
||||
revertedSnapshot bool
|
||||
}
|
||||
|
||||
func (f callFrame) TypeString() string {
|
||||
|
|
@ -67,16 +68,17 @@ func (f callFrame) TypeString() string {
|
|||
}
|
||||
|
||||
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)
|
||||
if err == nil {
|
||||
f.Output = output
|
||||
return
|
||||
}
|
||||
f.Error = err.Error()
|
||||
f.revertedSnapshot = reverted
|
||||
if f.Type == vm.CREATE || f.Type == vm.CREATE2 {
|
||||
f.To = nil
|
||||
}
|
||||
|
|
@ -102,10 +104,11 @@ type callFrameMarshaling struct {
|
|||
}
|
||||
|
||||
type callTracer struct {
|
||||
noopTracer
|
||||
directory.NoopTracer
|
||||
callstack []callFrame
|
||||
config callTracerConfig
|
||||
gasLimit uint64
|
||||
depth int
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
}
|
||||
|
|
@ -117,7 +120,7 @@ type callTracerConfig struct {
|
|||
|
||||
// newCallTracer returns a native go tracer which tracks
|
||||
// call frames of a tx, and implements vm.EVMLogger.
|
||||
func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
|
||||
func newCallTracer(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) {
|
||||
var config callTracerConfig
|
||||
if cfg != nil {
|
||||
if err := json.Unmarshal(cfg, &config); err != nil {
|
||||
|
|
@ -130,7 +133,7 @@ func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, e
|
|||
}
|
||||
|
||||
// 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) {
|
||||
func (t *callTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
toCopy := to
|
||||
t.callstack[0] = callFrame{
|
||||
Type: vm.CALL,
|
||||
|
|
@ -146,63 +149,17 @@ func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad
|
|||
}
|
||||
|
||||
// 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)
|
||||
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
t.callstack[0].processOutput(output, err, reverted)
|
||||
}
|
||||
|
||||
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
|
||||
func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
// skip if the previous op caused an error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Only logs need to be captured via opcode processing
|
||||
if !t.config.WithLog {
|
||||
return
|
||||
}
|
||||
// Avoid processing nested calls when only caring about top call
|
||||
if t.config.OnlyTopCall && depth > 0 {
|
||||
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).
|
||||
func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
t.depth++
|
||||
if t.config.OnlyTopCall {
|
||||
return
|
||||
}
|
||||
|
|
@ -225,7 +182,8 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
|
|||
|
||||
// CaptureExit is called when EVM exits a scope, even if the scope didn't
|
||||
// execute any code.
|
||||
func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
t.depth--
|
||||
if t.config.OnlyTopCall {
|
||||
return
|
||||
}
|
||||
|
|
@ -239,22 +197,48 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
|||
size -= 1
|
||||
|
||||
call.GasUsed = gasUsed
|
||||
call.processOutput(output, err)
|
||||
call.processOutput(output, err, reverted)
|
||||
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
|
||||
}
|
||||
|
||||
func (t *callTracer) CaptureTxStart(gasLimit uint64) {
|
||||
t.gasLimit = gasLimit
|
||||
func (t *callTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) {
|
||||
t.gasLimit = tx.Gas()
|
||||
}
|
||||
|
||||
func (t *callTracer) CaptureTxEnd(restGas uint64) {
|
||||
t.callstack[0].GasUsed = t.gasLimit - restGas
|
||||
func (t *callTracer) CaptureTxEnd(receipt *types.Receipt, err error) {
|
||||
// Error happened during tx validation.
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.callstack[0].GasUsed = receipt.GasUsed
|
||||
if t.config.WithLog {
|
||||
// Logs are not emitted when the call fails
|
||||
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
|
||||
// error arising from the encoding or forceful termination (via `Stop`).
|
||||
func (t *callTracer) GetResult() (json.RawMessage, error) {
|
||||
|
|
@ -266,7 +250,7 @@ func (t *callTracer) GetResult() (json.RawMessage, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.RawMessage(res), t.reason
|
||||
return res, t.reason
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
|
|
|
|||
|
|
@ -25,15 +25,16 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type flatCallAction -field-override flatCallActionMarshaling -out gen_flatcallaction_json.go
|
||||
//go:generate go run github.com/fjl/gencodec -type flatCallResult -field-override flatCallResultMarshaling -out gen_flatcallresult_json.go
|
||||
|
||||
func init() {
|
||||
tracers.DefaultDirectory.Register("flatCallTracer", newFlatCallTracer, false)
|
||||
directory.DefaultDirectory.Register("flatCallTracer", newFlatCallTracer, false)
|
||||
}
|
||||
|
||||
var parityErrorMapping = map[string]string{
|
||||
|
|
@ -108,9 +109,10 @@ type flatCallResultMarshaling struct {
|
|||
// flatCallTracer reports call frame information of a tx in a flat format, i.e.
|
||||
// as opposed to the nested format of `callTracer`.
|
||||
type flatCallTracer struct {
|
||||
directory.NoopTracer
|
||||
tracer *callTracer
|
||||
config flatCallTracerConfig
|
||||
ctx *tracers.Context // Holds tracer context data
|
||||
ctx *directory.Context // Holds tracer context data
|
||||
reason error // Textual reason for the interruption
|
||||
activePrecompiles []common.Address // Updated on CaptureStart based on given rules
|
||||
}
|
||||
|
|
@ -121,7 +123,7 @@ type flatCallTracerConfig struct {
|
|||
}
|
||||
|
||||
// newFlatCallTracer returns a new flatCallTracer.
|
||||
func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
|
||||
func newFlatCallTracer(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) {
|
||||
var config flatCallTracerConfig
|
||||
if cfg != nil {
|
||||
if err := json.Unmarshal(cfg, &config); err != nil {
|
||||
|
|
@ -131,7 +133,7 @@ func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Trace
|
|||
|
||||
// Create inner call tracer with default configuration, don't forward
|
||||
// the OnlyTopCall or WithLog to inner for now
|
||||
tracer, err := tracers.DefaultDirectory.New("callTracer", ctx, nil)
|
||||
tracer, err := directory.DefaultDirectory.New("callTracer", ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -144,16 +146,13 @@ func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Trace
|
|||
}
|
||||
|
||||
// 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)
|
||||
func (t *flatCallTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
t.tracer.CaptureStart(from, to, create, input, gas, value)
|
||||
}
|
||||
|
||||
// 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)
|
||||
func (t *flatCallTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
t.tracer.CaptureEnd(output, gasUsed, err, reverted)
|
||||
}
|
||||
|
||||
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
|
||||
|
|
@ -179,8 +178,8 @@ 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
|
||||
// execute any code.
|
||||
func (t *flatCallTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
t.tracer.CaptureExit(output, gasUsed, err)
|
||||
func (t *flatCallTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
t.tracer.CaptureExit(output, gasUsed, err, reverted)
|
||||
|
||||
// Parity traces don't include CALL/STATICCALLs to precompiles.
|
||||
// By default we remove them from the callstack.
|
||||
|
|
@ -201,12 +200,15 @@ func (t *flatCallTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *flatCallTracer) CaptureTxStart(gasLimit uint64) {
|
||||
t.tracer.CaptureTxStart(gasLimit)
|
||||
func (t *flatCallTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) {
|
||||
t.tracer.CaptureTxStart(env, tx, from)
|
||||
// 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)
|
||||
}
|
||||
|
||||
func (t *flatCallTracer) CaptureTxEnd(restGas uint64) {
|
||||
t.tracer.CaptureTxEnd(restGas)
|
||||
func (t *flatCallTracer) CaptureTxEnd(receipt *types.Receipt, err error) {
|
||||
t.tracer.CaptureTxEnd(receipt, err)
|
||||
}
|
||||
|
||||
// GetResult returns an empty json object.
|
||||
|
|
@ -242,7 +244,7 @@ func (t *flatCallTracer) isPrecompiled(addr common.Address) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx *tracers.Context) (output []flatCallFrame, err error) {
|
||||
func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx *directory.Context) (output []flatCallFrame, err error) {
|
||||
var frame *flatCallFrame
|
||||
switch input.Type {
|
||||
case vm.CREATE, vm.CREATE2:
|
||||
|
|
@ -341,7 +343,7 @@ func newFlatSelfdestruct(input *callFrame) *flatCallFrame {
|
|||
}
|
||||
}
|
||||
|
||||
func fillCallFrameFromContext(callFrame *flatCallFrame, ctx *tracers.Context) {
|
||||
func fillCallFrameFromContext(callFrame *flatCallFrame, ctx *directory.Context) {
|
||||
if ctx == nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,33 +21,35 @@ import (
|
|||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tracers.DefaultDirectory.Register("muxTracer", newMuxTracer, false)
|
||||
directory.DefaultDirectory.Register("muxTracer", newMuxTracer, false)
|
||||
}
|
||||
|
||||
// muxTracer is a go implementation of the Tracer interface which
|
||||
// runs multiple tracers in one go.
|
||||
type muxTracer struct {
|
||||
names []string
|
||||
tracers []tracers.Tracer
|
||||
tracers []directory.Tracer
|
||||
}
|
||||
|
||||
// newMuxTracer returns a new mux tracer.
|
||||
func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
|
||||
func newMuxTracer(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) {
|
||||
var config map[string]json.RawMessage
|
||||
if cfg != nil {
|
||||
if err := json.Unmarshal(cfg, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
objects := make([]tracers.Tracer, 0, len(config))
|
||||
objects := make([]directory.Tracer, 0, len(config))
|
||||
names := make([]string, 0, len(config))
|
||||
for k, v := range config {
|
||||
t, err := tracers.DefaultDirectory.New(k, ctx, v)
|
||||
t, err := directory.DefaultDirectory.New(k, ctx, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -59,16 +61,16 @@ func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, er
|
|||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
func (t *muxTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
func (t *muxTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
for _, t := range t.tracers {
|
||||
t.CaptureStart(env, from, to, create, input, gas, value)
|
||||
t.CaptureStart(from, to, create, input, gas, value)
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (t *muxTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
func (t *muxTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
for _, t := range t.tracers {
|
||||
t.CaptureEnd(output, gasUsed, err)
|
||||
t.CaptureEnd(output, gasUsed, err, reverted)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +88,20 @@ func (t *muxTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scop
|
|||
}
|
||||
}
|
||||
|
||||
// CaptureKeccakPreimage is called during the KECCAK256 opcode.
|
||||
func (t *muxTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {
|
||||
for _, t := range t.tracers {
|
||||
t.CaptureKeccakPreimage(hash, data)
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureGasConsumed is called when gas is consumed.
|
||||
func (t *muxTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {
|
||||
for _, t := range t.tracers {
|
||||
t.OnGasChange(old, new, reason)
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
||||
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 {
|
||||
|
|
@ -95,21 +111,57 @@ func (t *muxTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.A
|
|||
|
||||
// CaptureExit is called when EVM exits a scope, even if the scope didn't
|
||||
// execute any code.
|
||||
func (t *muxTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
func (t *muxTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) {
|
||||
for _, t := range t.tracers {
|
||||
t.CaptureExit(output, gasUsed, err)
|
||||
t.CaptureExit(output, gasUsed, err, reverted)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) CaptureTxStart(gasLimit uint64) {
|
||||
func (t *muxTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) {
|
||||
for _, t := range t.tracers {
|
||||
t.CaptureTxStart(gasLimit)
|
||||
t.CaptureTxStart(env, tx, from)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) CaptureTxEnd(restGas uint64) {
|
||||
func (t *muxTracer) CaptureTxEnd(receipt *types.Receipt, err error) {
|
||||
for _, t := range t.tracers {
|
||||
t.CaptureTxEnd(restGas)
|
||||
t.CaptureTxEnd(receipt, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason state.BalanceChangeReason) {
|
||||
for _, t := range t.tracers {
|
||||
t.OnBalanceChange(a, prev, new, reason)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnNonceChange(a common.Address, prev, new uint64) {
|
||||
for _, t := range t.tracers {
|
||||
t.OnNonceChange(a, prev, new)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
|
||||
for _, t := range t.tracers {
|
||||
t.OnCodeChange(a, prevCodeHash, prev, codeHash, code)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {
|
||||
for _, t := range t.tracers {
|
||||
t.OnStorageChange(a, k, prev, new)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnLog(log *types.Log) {
|
||||
for _, t := range t.tracers {
|
||||
t.OnLog(log)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnNewAccount(a common.Address, reset bool) {
|
||||
for _, t := range t.tracers {
|
||||
t.OnNewAccount(a, reset)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,25 +24,27 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/directory"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go
|
||||
|
||||
func init() {
|
||||
tracers.DefaultDirectory.Register("prestateTracer", newPrestateTracer, false)
|
||||
directory.DefaultDirectory.Register("prestateTracer", newPrestateTracer, false)
|
||||
}
|
||||
|
||||
type state = map[common.Address]*account
|
||||
type stateMap = map[common.Address]*account
|
||||
|
||||
type account struct {
|
||||
Balance *big.Int `json:"balance,omitempty"`
|
||||
Code []byte `json:"code,omitempty"`
|
||||
Nonce uint64 `json:"nonce,omitempty"`
|
||||
Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
|
||||
empty bool
|
||||
}
|
||||
|
||||
func (a *account) exists() bool {
|
||||
|
|
@ -55,13 +57,11 @@ type accountMarshaling struct {
|
|||
}
|
||||
|
||||
type prestateTracer struct {
|
||||
noopTracer
|
||||
directory.NoopTracer
|
||||
env *vm.EVM
|
||||
pre state
|
||||
post state
|
||||
create bool
|
||||
pre stateMap
|
||||
post stateMap
|
||||
to common.Address
|
||||
gasLimit uint64 // Amount of gas bought for the whole tx
|
||||
config prestateTracerConfig
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
|
|
@ -73,7 +73,7 @@ type prestateTracerConfig struct {
|
|||
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 *directory.Context, cfg json.RawMessage) (directory.Tracer, error) {
|
||||
var config prestateTracerConfig
|
||||
if cfg != nil {
|
||||
if err := json.Unmarshal(cfg, &config); err != nil {
|
||||
|
|
@ -81,57 +81,14 @@ func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Trace
|
|||
}
|
||||
}
|
||||
return &prestateTracer{
|
||||
pre: state{},
|
||||
post: state{},
|
||||
pre: stateMap{},
|
||||
post: stateMap{},
|
||||
config: config,
|
||||
created: 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
|
||||
|
||||
// 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))
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureState 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) {
|
||||
if err != nil {
|
||||
|
|
@ -166,7 +123,7 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64,
|
|||
case stackLen >= 4 && op == vm.CREATE2:
|
||||
offset := stackData[stackLen-2]
|
||||
size := stackData[stackLen-3]
|
||||
init, err := tracers.GetMemoryCopyPadded(scope.Memory, int64(offset.Uint64()), int64(size.Uint64()))
|
||||
init, err := directory.GetMemoryCopyPadded(scope.Memory, int64(offset.Uint64()), int64(size.Uint64()))
|
||||
if err != nil {
|
||||
log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "prestateTracer", "offset", offset, "size", size)
|
||||
return
|
||||
|
|
@ -179,15 +136,62 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64,
|
|||
}
|
||||
}
|
||||
|
||||
func (t *prestateTracer) CaptureTxStart(gasLimit uint64) {
|
||||
t.gasLimit = gasLimit
|
||||
func (t *prestateTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) {
|
||||
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()
|
||||
}
|
||||
|
||||
func (t *prestateTracer) CaptureTxEnd(restGas uint64) {
|
||||
if !t.config.DiffMode {
|
||||
t.lookupAccount(from)
|
||||
t.lookupAccount(t.to)
|
||||
t.lookupAccount(env.Context.Coinbase)
|
||||
}
|
||||
|
||||
func (t *prestateTracer) CaptureTxEnd(receipt *types.Receipt, err error) {
|
||||
if err != nil {
|
||||
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 {
|
||||
// The deleted account's state is pruned from `post` but kept in `pre`
|
||||
if _, ok := t.deleted[addr]; ok {
|
||||
|
|
@ -237,38 +241,6 @@ func (t *prestateTracer) CaptureTxEnd(restGas uint64) {
|
|||
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
|
||||
|
|
@ -278,12 +250,16 @@ func (t *prestateTracer) lookupAccount(addr common.Address) {
|
|||
return
|
||||
}
|
||||
|
||||
t.pre[addr] = &account{
|
||||
acc := &account{
|
||||
Balance: t.env.StateDB.GetBalance(addr),
|
||||
Nonce: t.env.StateDB.GetNonce(addr),
|
||||
Code: t.env.StateDB.GetCode(addr),
|
||||
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
|
||||
|
|
|
|||
|
|
@ -111,41 +111,3 @@ func BenchmarkTransactionTrace(b *testing.B) {
|
|||
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}, // Errror
|
||||
{0, 1, 1024*1024 + 1, "reached limit for padding memory slice: 1048578", 0}, // Errror
|
||||
{10, 0, 1024*1024 + 100, "reached limit for padding memory slice: 1048666", 0}, // Errror
|
||||
|
||||
} {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -950,41 +950,41 @@ type OverrideAccount struct {
|
|||
type StateOverride map[common.Address]OverrideAccount
|
||||
|
||||
// Apply overrides the fields of specified accounts into the given state.
|
||||
func (diff *StateOverride) Apply(state vm.StateDB) error {
|
||||
func (diff *StateOverride) Apply(statedb vm.StateDB) error {
|
||||
if diff == nil {
|
||||
return nil
|
||||
}
|
||||
for addr, account := range *diff {
|
||||
// Override account nonce.
|
||||
if account.Nonce != nil {
|
||||
state.SetNonce(addr, uint64(*account.Nonce))
|
||||
statedb.SetNonce(addr, uint64(*account.Nonce))
|
||||
}
|
||||
// Override account(contract) code.
|
||||
if account.Code != nil {
|
||||
state.SetCode(addr, *account.Code)
|
||||
statedb.SetCode(addr, *account.Code)
|
||||
}
|
||||
// Override account balance.
|
||||
if account.Balance != nil {
|
||||
state.SetBalance(addr, (*big.Int)(*account.Balance))
|
||||
statedb.SetBalance(addr, (*big.Int)(*account.Balance), state.BalanceChangeUnspecified)
|
||||
}
|
||||
if account.State != nil && account.StateDiff != nil {
|
||||
return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
|
||||
}
|
||||
// Replace entire state if caller requires.
|
||||
if account.State != nil {
|
||||
state.SetStorage(addr, *account.State)
|
||||
statedb.SetStorage(addr, *account.State)
|
||||
}
|
||||
// Apply state diff into specified accounts.
|
||||
if account.StateDiff != nil {
|
||||
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.
|
||||
// By using finalize, the overrides are semantically behaving as
|
||||
// if they were created in a transaction just before the tracing occur.
|
||||
state.Finalise(false)
|
||||
statedb.Finalise(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -1533,7 +1533,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
|
|||
log.Trace("Creating access list", "input", accessList)
|
||||
|
||||
// Copy the original db so we don't modify it
|
||||
statedb := db.Copy()
|
||||
statedb := db.Copy().(vm.StateDB)
|
||||
// Set the accesslist to the last al
|
||||
args.AccessList = &accessList
|
||||
msg, err := args.ToMessage(b.RPCGasCap(), header.BaseFee)
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ type environment struct {
|
|||
func (env *environment) copy() *environment {
|
||||
cpy := &environment{
|
||||
signer: env.signer,
|
||||
state: env.state.Copy(),
|
||||
state: env.state.Copy().(vm.StateDB),
|
||||
tcount: env.tcount,
|
||||
coinbase: env.coinbase,
|
||||
header: types.CopyHeader(env.header),
|
||||
|
|
@ -343,7 +343,7 @@ func (w *worker) pending() (*types.Block, vm.StateDB) {
|
|||
if w.snapshotState == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return w.snapshotBlock, w.snapshotState.Copy()
|
||||
return w.snapshotBlock, w.snapshotState.Copy().(vm.StateDB)
|
||||
}
|
||||
|
||||
// pendingBlock returns pending block. The returned block can be nil in case the
|
||||
|
|
@ -738,7 +738,7 @@ func (w *worker) updateSnapshot(env *environment) {
|
|||
trie.NewStackTrie(nil),
|
||||
)
|
||||
w.snapshotReceipts = copyReceipts(env.receipts)
|
||||
w.snapshotState = env.state.Copy()
|
||||
w.snapshotState = env.state.Copy().(vm.StateDB)
|
||||
}
|
||||
|
||||
func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]*types.Log, error) {
|
||||
|
|
@ -784,7 +784,7 @@ func (w *worker) applyTransaction(env *environment, tx *types.Transaction) (*typ
|
|||
snap = env.state.Snapshot()
|
||||
gp = env.gasPool.Gas()
|
||||
)
|
||||
receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig())
|
||||
receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vm.Config{})
|
||||
if err != nil {
|
||||
env.state.RevertToSnapshot(snap)
|
||||
env.gasPool.SetGas(gp)
|
||||
|
|
@ -976,7 +976,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
|
|||
if header.ParentBeaconRoot != nil {
|
||||
context := core.NewEVMBlockContext(header, w.chain, nil)
|
||||
vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, w.chainConfig, vm.Config{})
|
||||
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state)
|
||||
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state, nil)
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const (
|
|||
VersionMajor = 1 // Major version component of the current release
|
||||
VersionMinor = 13 // Minor version component of the current release
|
||||
VersionPatch = 5 // Patch version component of the current release
|
||||
VersionMeta = "unstable" // Version metadata to append to the version string
|
||||
VersionMeta = "stable" // Version metadata to append to the version string
|
||||
)
|
||||
|
||||
// Version holds the textual version string.
|
||||
|
|
|
|||
|
|
@ -298,7 +298,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
|
|||
// - the coinbase self-destructed, or
|
||||
// - 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
|
||||
statedb.AddBalance(block.Coinbase(), new(big.Int))
|
||||
statedb.AddBalance(block.Coinbase(), new(big.Int), state.BalanceChangeUnspecified)
|
||||
|
||||
// Commit state mutations into database.
|
||||
root, _ := statedb.Commit(block.NumberU64(), config.IsEIP158(block.Number()))
|
||||
|
|
@ -322,7 +322,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo
|
|||
for addr, a := range accounts {
|
||||
statedb.SetCode(addr, a.Code)
|
||||
statedb.SetNonce(addr, a.Nonce)
|
||||
statedb.SetBalance(addr, a.Balance)
|
||||
statedb.SetBalance(addr, a.Balance, state.BalanceChangeUnspecified)
|
||||
for k, v := range a.Storage {
|
||||
statedb.SetState(addr, k, v)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue