erigon-pulse/cmd/rpcdaemon/commands/trace_adhoc.go
ledgerwatch 42c71da7dd
Fix for trace_replayTransaction due to gasPrice bug (#2403)
* Print CALL instr

* Print more info

* try to remove gas bailout

* gas bailout control and txHash

* Swap tracer

* Flush stream

* Add more json structure

* Print gasPrice

* Print gas price

* Fix

* Fix

* Clean up

Co-authored-by: Alex Sharp <alexsharp@Alexs-MacBook-Pro.local>
2021-07-20 10:36:32 +01:00

1020 lines
30 KiB
Go

package commands
import (
"bytes"
"context"
"encoding/json"
"fmt"
"math"
"math/big"
"time"
"github.com/holiman/uint256"
"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/common/hexutil"
math2 "github.com/ledgerwatch/erigon/common/math"
"github.com/ledgerwatch/erigon/core"
"github.com/ledgerwatch/erigon/core/rawdb"
"github.com/ledgerwatch/erigon/core/state"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/core/types/accounts"
"github.com/ledgerwatch/erigon/core/vm"
"github.com/ledgerwatch/erigon/core/vm/stack"
"github.com/ledgerwatch/erigon/ethdb"
"github.com/ledgerwatch/erigon/log"
"github.com/ledgerwatch/erigon/rpc"
"github.com/ledgerwatch/erigon/turbo/rpchelper"
"github.com/ledgerwatch/erigon/turbo/shards"
"github.com/ledgerwatch/erigon/turbo/transactions"
)
const callTimeout = 5 * time.Minute
const (
CALL = "call"
CALLCODE = "callcode"
DELEGATECALL = "delegatecall"
STATICCALL = "staticcall"
CREATE = "create"
SUICIDE = "suicide"
REWARD = "reward"
TraceTypeTrace = "trace"
TraceTypeStateDiff = "stateDiff"
TraceTypeVmTrace = "vmTrace"
)
// TraceCallParam (see SendTxArgs -- this allows optional prams plus don't use MixedcaseAddress
type TraceCallParam struct {
From *common.Address `json:"from"`
To *common.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
Value *hexutil.Big `json:"value"`
Data hexutil.Bytes `json:"data"`
AccessList *types.AccessList `json:"accessList"`
txHash *common.Hash
traceTypes []string
}
// TraceCallResult is the response to `trace_call` method
type TraceCallResult struct {
Output hexutil.Bytes `json:"output"`
StateDiff map[common.Address]*StateDiffAccount `json:"stateDiff"`
Trace []*ParityTrace `json:"trace"`
VmTrace *TraceCallVmTrace `json:"vmTrace"`
}
// StateDiffAccount is the part of `trace_call` response that is under "stateDiff" tag
type StateDiffAccount struct {
Balance interface{} `json:"balance"` // Can be either string "=" or mapping "*" => {"from": "hex", "to": "hex"}
Code interface{} `json:"code"`
Nonce interface{} `json:"nonce"`
Storage map[common.Hash]map[string]interface{} `json:"storage"`
}
type StateDiffBalance struct {
From *hexutil.Big `json:"from"`
To *hexutil.Big `json:"to"`
}
type StateDiffCode struct {
From hexutil.Bytes `json:"from"`
To hexutil.Bytes `json:"to"`
}
type StateDiffNonce struct {
From hexutil.Uint64 `json:"from"`
To hexutil.Uint64 `json:"to"`
}
type StateDiffStorage struct {
From common.Hash `json:"from"`
To common.Hash `json:"to"`
}
// TraceCallVmTrace is the part of `trace_call` response that is under "vmTrace" tag
type TraceCallVmTrace struct {
}
// ToMessage converts CallArgs to the Message type used by the core evm
func (args *TraceCallParam) ToMessage(globalGasCap uint64, baseFee *uint256.Int) (types.Message, error) {
// Set sender address or use zero address if none specified.
var addr common.Address
if args.From != nil {
addr = *args.From
}
// Set default gas & gas price if none were set
gas := globalGasCap
if gas == 0 {
gas = uint64(math.MaxUint64 / 2)
}
if args.Gas != nil {
gas = uint64(*args.Gas)
}
if globalGasCap != 0 && globalGasCap < gas {
log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
gas = globalGasCap
}
var (
gasPrice *uint256.Int
gasFeeCap *uint256.Int
gasTipCap *uint256.Int
)
if baseFee == nil {
// If there's no basefee, then it must be a non-1559 execution
gasPrice = new(uint256.Int)
if args.GasPrice != nil {
overflow := gasPrice.SetFromBig(args.GasPrice.ToInt())
if overflow {
return types.Message{}, fmt.Errorf("args.GasPrice higher than 2^256-1")
}
}
gasFeeCap, gasTipCap = gasPrice, gasPrice
} else {
// A basefee is provided, necessitating 1559-type execution
if args.GasPrice != nil {
var overflow bool
// User specified the legacy gas field, convert to 1559 gas typing
gasPrice, overflow = uint256.FromBig(args.GasPrice.ToInt())
if overflow {
return types.Message{}, fmt.Errorf("args.GasPrice higher than 2^256-1")
}
gasFeeCap, gasTipCap = gasPrice, gasPrice
} else {
// User specified 1559 gas feilds (or none), use those
gasFeeCap = new(uint256.Int)
if args.MaxFeePerGas != nil {
overflow := gasFeeCap.SetFromBig(args.MaxFeePerGas.ToInt())
if overflow {
return types.Message{}, fmt.Errorf("args.GasPrice higher than 2^256-1")
}
}
gasTipCap = new(uint256.Int)
if args.MaxPriorityFeePerGas != nil {
overflow := gasTipCap.SetFromBig(args.MaxPriorityFeePerGas.ToInt())
if overflow {
return types.Message{}, fmt.Errorf("args.GasPrice higher than 2^256-1")
}
}
// Backfill the legacy gasPrice for EVM execution, unless we're all zeroes
gasPrice = new(uint256.Int)
if gasFeeCap.BitLen() > 0 || gasTipCap.BitLen() > 0 {
gasPrice = math2.U256Min(new(uint256.Int).Add(gasTipCap, baseFee), gasFeeCap)
}
}
}
value := new(uint256.Int)
if args.Value != nil {
overflow := value.SetFromBig(args.Value.ToInt())
if overflow {
panic(fmt.Errorf("args.Value higher than 2^256-1"))
}
}
var data []byte
if args.Data != nil {
data = args.Data
}
var accessList types.AccessList
if args.AccessList != nil {
accessList = *args.AccessList
}
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, gasFeeCap, gasTipCap, data, accessList, false /* checkNonce */)
return msg, nil
}
// OpenEthereum-style tracer
type OeTracer struct {
r *TraceCallResult
traceAddr []int
traceStack []*ParityTrace
lastTop *ParityTrace
precompile bool // Whether the last CaptureStart was called with `precompile = true`
compat bool // Bug for bug compatibility mode
}
func (ot *OeTracer) CaptureStart(depth int, from common.Address, to common.Address, precompile bool, create bool, calltype vm.CallType, input []byte, gas uint64, value *big.Int, codeHash common.Hash) error {
if precompile {
ot.precompile = true
return nil
}
if gas > 500000000 {
gas = 500000001 - (0x8000000000000000 - gas)
}
//fmt.Printf("CaptureStart depth %d, from %x, to %x, create %t, input %x, gas %d, value %d\n", depth, from, to, create, input, gas, value)
trace := &ParityTrace{}
if create {
trResult := &CreateTraceResult{}
trace.Type = CREATE
trResult.Address = new(common.Address)
copy(trResult.Address[:], to.Bytes())
trace.Result = trResult
} else {
trace.Result = &TraceResult{}
trace.Type = CALL
}
if depth > 0 {
topTrace := ot.traceStack[len(ot.traceStack)-1]
traceIdx := topTrace.Subtraces
ot.traceAddr = append(ot.traceAddr, traceIdx)
topTrace.Subtraces++
if calltype == vm.DELEGATECALLT {
switch action := topTrace.Action.(type) {
case *CreateTraceAction:
value = action.Value.ToInt()
case *CallTraceAction:
value = action.Value.ToInt()
}
}
if calltype == vm.STATICCALLT {
value = big.NewInt(0)
}
}
trace.TraceAddress = make([]int, len(ot.traceAddr))
copy(trace.TraceAddress, ot.traceAddr)
if create {
action := CreateTraceAction{}
action.From = from
action.Gas.ToInt().SetUint64(gas)
action.Init = common.CopyBytes(input)
action.Value.ToInt().Set(value)
trace.Action = &action
} else {
action := CallTraceAction{}
switch calltype {
case vm.CALLT:
action.CallType = CALL
case vm.CALLCODET:
action.CallType = CALLCODE
case vm.DELEGATECALLT:
action.CallType = DELEGATECALL
case vm.STATICCALLT:
action.CallType = STATICCALL
}
action.From = from
action.To = to
action.Gas.ToInt().SetUint64(gas)
action.Input = common.CopyBytes(input)
action.Value.ToInt().Set(value)
trace.Action = &action
}
ot.r.Trace = append(ot.r.Trace, trace)
ot.traceStack = append(ot.traceStack, trace)
return nil
}
func (ot *OeTracer) CaptureEnd(depth int, output []byte, gasUsed uint64, t time.Duration, err error) error {
if ot.precompile {
ot.precompile = false
return nil
}
//fmt.Printf("CaptureEnd depth %d, output %x, gasUsed %d, err %v\n", depth, output, gasUsed, err)
if depth == 0 {
ot.r.Output = common.CopyBytes(output)
}
topTrace := ot.traceStack[len(ot.traceStack)-1]
ot.lastTop = topTrace
ignoreError := false
if ot.compat {
ignoreError = depth == 0 && topTrace.Type == CREATE
}
if err != nil && !ignoreError {
switch err {
case vm.ErrInvalidJump:
topTrace.Error = "Bad jump destination"
case vm.ErrContractAddressCollision, vm.ErrCodeStoreOutOfGas, vm.ErrOutOfGas:
topTrace.Error = "Out of gas"
case vm.ErrExecutionReverted:
topTrace.Error = "Reverted"
case vm.ErrWriteProtection:
topTrace.Error = "Mutable Call In Static Context"
default:
switch err.(type) {
case *vm.ErrStackUnderflow:
topTrace.Error = "Stack underflow"
case *vm.ErrInvalidOpCode:
topTrace.Error = "Bad instruction"
default:
topTrace.Error = err.Error()
}
}
topTrace.Result = nil
} else {
if len(output) > 0 {
switch topTrace.Type {
case CALL:
topTrace.Result.(*TraceResult).Output = common.CopyBytes(output)
case CREATE:
topTrace.Result.(*CreateTraceResult).Code = common.CopyBytes(output)
}
}
switch topTrace.Type {
case CALL:
topTrace.Result.(*TraceResult).GasUsed = new(hexutil.Big)
topTrace.Result.(*TraceResult).GasUsed.ToInt().SetUint64(gasUsed)
case CREATE:
topTrace.Result.(*CreateTraceResult).GasUsed = new(hexutil.Big)
topTrace.Result.(*CreateTraceResult).GasUsed.ToInt().SetUint64(gasUsed)
}
}
ot.traceStack = ot.traceStack[:len(ot.traceStack)-1]
if depth > 0 {
ot.traceAddr = ot.traceAddr[:len(ot.traceAddr)-1]
}
return nil
}
func (ot *OeTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, st *stack.Stack, rData []byte, contract *vm.Contract, opDepth int, err error) error {
return nil
}
func (ot *OeTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *stack.Stack, contract *vm.Contract, opDepth int, err error) error {
return nil
}
func (ot *OeTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *big.Int) {
trace := &ParityTrace{}
trace.Type = SUICIDE
action := &SuicideTraceAction{}
action.Address = from
action.RefundAddress = to
action.Balance.ToInt().Set(value)
trace.Action = action
topTrace := ot.traceStack[len(ot.traceStack)-1]
traceIdx := topTrace.Subtraces
ot.traceAddr = append(ot.traceAddr, traceIdx)
topTrace.Subtraces++
trace.TraceAddress = make([]int, len(ot.traceAddr))
copy(trace.TraceAddress, ot.traceAddr)
ot.traceAddr = ot.traceAddr[:len(ot.traceAddr)-1]
ot.r.Trace = append(ot.r.Trace, trace)
}
func (ot *OeTracer) CaptureAccountRead(account common.Address) error {
return nil
}
func (ot *OeTracer) CaptureAccountWrite(account common.Address) error {
return nil
}
// Implements core/state/StateWriter to provide state diffs
type StateDiff struct {
sdMap map[common.Address]*StateDiffAccount
}
func (sd *StateDiff) UpdateAccountData(address common.Address, original, account *accounts.Account) error {
if _, ok := sd.sdMap[address]; !ok {
sd.sdMap[address] = &StateDiffAccount{Storage: make(map[common.Hash]map[string]interface{})}
}
return nil
}
func (sd *StateDiff) UpdateAccountCode(address common.Address, incarnation uint64, codeHash common.Hash, code []byte) error {
if _, ok := sd.sdMap[address]; !ok {
sd.sdMap[address] = &StateDiffAccount{Storage: make(map[common.Hash]map[string]interface{})}
}
return nil
}
func (sd *StateDiff) DeleteAccount(address common.Address, original *accounts.Account) error {
if _, ok := sd.sdMap[address]; !ok {
sd.sdMap[address] = &StateDiffAccount{Storage: make(map[common.Hash]map[string]interface{})}
}
return nil
}
func (sd *StateDiff) WriteAccountStorage(address common.Address, incarnation uint64, key *common.Hash, original, value *uint256.Int) error {
if *original == *value {
return nil
}
accountDiff := sd.sdMap[address]
if accountDiff == nil {
accountDiff = &StateDiffAccount{Storage: make(map[common.Hash]map[string]interface{})}
sd.sdMap[address] = accountDiff
}
m := make(map[string]interface{})
m["*"] = &StateDiffStorage{From: common.BytesToHash(original.Bytes()), To: common.BytesToHash(value.Bytes())}
accountDiff.Storage[*key] = m
return nil
}
func (sd *StateDiff) CreateContract(address common.Address) error {
if _, ok := sd.sdMap[address]; !ok {
sd.sdMap[address] = &StateDiffAccount{Storage: make(map[common.Hash]map[string]interface{})}
}
return nil
}
// CompareStates uses the addresses accumulated in the sdMap and compares balances, nonces, and codes of the accounts, and fills the rest of the sdMap
func (sd *StateDiff) CompareStates(initialIbs, ibs *state.IntraBlockState) {
var toRemove []common.Address
for addr, accountDiff := range sd.sdMap {
initialExist := initialIbs.Exist(addr)
exist := ibs.Exist(addr)
if initialExist {
if exist {
var allEqual = len(accountDiff.Storage) == 0
fromBalance := initialIbs.GetBalance(addr).ToBig()
toBalance := ibs.GetBalance(addr).ToBig()
if fromBalance.Cmp(toBalance) == 0 {
accountDiff.Balance = "="
} else {
m := make(map[string]*StateDiffBalance)
m["*"] = &StateDiffBalance{From: (*hexutil.Big)(fromBalance), To: (*hexutil.Big)(toBalance)}
accountDiff.Balance = m
allEqual = false
}
fromCode := initialIbs.GetCode(addr)
toCode := ibs.GetCode(addr)
if bytes.Equal(fromCode, toCode) {
accountDiff.Code = "="
} else {
m := make(map[string]*StateDiffCode)
m["*"] = &StateDiffCode{From: fromCode, To: toCode}
accountDiff.Code = m
allEqual = false
}
fromNonce := initialIbs.GetNonce(addr)
toNonce := ibs.GetNonce(addr)
if fromNonce == toNonce {
accountDiff.Nonce = "="
} else {
m := make(map[string]*StateDiffNonce)
m["*"] = &StateDiffNonce{From: hexutil.Uint64(fromNonce), To: hexutil.Uint64(toNonce)}
accountDiff.Nonce = m
allEqual = false
}
if allEqual {
toRemove = append(toRemove, addr)
}
} else {
{
m := make(map[string]*hexutil.Big)
m["-"] = (*hexutil.Big)(initialIbs.GetBalance(addr).ToBig())
accountDiff.Balance = m
}
{
m := make(map[string]hexutil.Bytes)
m["-"] = initialIbs.GetCode(addr)
accountDiff.Code = m
}
{
m := make(map[string]hexutil.Uint64)
m["-"] = hexutil.Uint64(initialIbs.GetNonce(addr))
accountDiff.Nonce = m
}
}
} else if exist {
{
m := make(map[string]*hexutil.Big)
m["+"] = (*hexutil.Big)(ibs.GetBalance(addr).ToBig())
accountDiff.Balance = m
}
{
m := make(map[string]hexutil.Bytes)
m["+"] = ibs.GetCode(addr)
accountDiff.Code = m
}
{
m := make(map[string]hexutil.Uint64)
m["+"] = hexutil.Uint64(ibs.GetNonce(addr))
accountDiff.Nonce = m
}
// Transform storage
for _, sm := range accountDiff.Storage {
str := sm["*"].(*StateDiffStorage)
delete(sm, "*")
sm["+"] = &str.To
}
} else {
toRemove = append(toRemove, addr)
}
}
for _, addr := range toRemove {
delete(sd.sdMap, addr)
}
}
func (api *TraceAPIImpl) ReplayTransaction(ctx context.Context, txHash common.Hash, traceTypes []string) (*TraceCallResult, error) {
tx, err := api.kv.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
blockNumber, err := rawdb.ReadTxLookupEntry(tx, txHash)
if err != nil {
return nil, err
}
if blockNumber == nil {
return nil, nil // not error, see https://github.com/ledgerwatch/erigon/issues/1645
}
// Extract transactions from block
block, _, bErr := rawdb.ReadBlockByNumberWithSenders(tx, *blockNumber)
if bErr != nil {
return nil, bErr
}
if block == nil {
return nil, fmt.Errorf("could not find block %d", *blockNumber)
}
var txIndex uint64
for idx, txn := range block.Transactions() {
if txn.Hash() == txHash {
txIndex = uint64(idx)
break
}
}
bn := hexutil.Uint64(*blockNumber)
parentNr := bn
if parentNr > 0 {
parentNr -= 1
}
// Returns an array of trace arrays, one trace array for each transaction
traces, err := api.callManyTransactions(ctx, tx, block.Transactions(), block.ParentHash(), rpc.BlockNumber(parentNr), block.Header())
if err != nil {
return nil, err
}
var traceTypeTrace, traceTypeStateDiff, traceTypeVmTrace bool
for _, traceType := range traceTypes {
switch traceType {
case TraceTypeTrace:
traceTypeTrace = true
case TraceTypeStateDiff:
traceTypeStateDiff = true
case TraceTypeVmTrace:
traceTypeVmTrace = true
default:
return nil, fmt.Errorf("unrecognized trace type: %s", traceType)
}
}
result := &TraceCallResult{}
for txno, trace := range traces {
txpos := uint64(txno)
// We're only looking for a specific transaction
if txpos == txIndex {
result.Output = trace.Output
if traceTypeTrace {
result.Trace = trace.Trace
}
if traceTypeStateDiff {
result.StateDiff = trace.StateDiff
}
if traceTypeVmTrace {
result.VmTrace = trace.VmTrace
}
return trace, nil
}
}
return result, nil
}
func (api *TraceAPIImpl) ReplayBlockTransactions(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, traceTypes []string) ([]*TraceCallResult, error) {
tx, err := api.kv.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
chainConfig, err := api.chainConfig(tx)
if err != nil {
return nil, err
}
blockNumber, blockHash, err := rpchelper.GetBlockNumber(blockNrOrHash, tx, api.filters)
if err != nil {
return nil, err
}
block := rawdb.ReadBlock(tx, blockHash, blockNumber)
if block == nil {
return nil, fmt.Errorf("block %d(%x) not found", blockNumber, blockHash)
}
getHeader := func(hash common.Hash, number uint64) *types.Header {
return rawdb.ReadHeader(tx, hash, number)
}
var stateReader state.StateReader
if num, ok := blockNrOrHash.Number(); ok && num == rpc.LatestBlockNumber {
stateReader = state.NewPlainStateReader(tx)
} else {
stateReader = state.NewPlainKvState(tx, blockNumber-1)
}
ibs := state.New(stateReader)
// Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout.
var cancel context.CancelFunc
if callTimeout > 0 {
ctx, cancel = context.WithTimeout(ctx, callTimeout)
} else {
ctx, cancel = context.WithCancel(ctx)
}
// Make sure the context is cancelled when the call has completed
// this makes sure resources are cleaned up.
defer cancel()
var traceResults = make([]*TraceCallResult, block.Transactions().Len())
traceResult := &TraceCallResult{Trace: []*ParityTrace{}}
var traceTypeTrace, traceTypeStateDiff, traceTypeVmTrace bool
for _, traceType := range traceTypes {
switch traceType {
case TraceTypeTrace:
traceTypeTrace = true
case TraceTypeStateDiff:
traceTypeStateDiff = true
case TraceTypeVmTrace:
traceTypeVmTrace = true
default:
return nil, fmt.Errorf("unrecognized trace type: %s", traceType)
}
}
var ot OeTracer
ot.compat = api.compatibility
if traceTypeTrace {
ot.r = traceResult
ot.traceAddr = []int{}
}
gp := new(core.GasPool)
gp.AddGas(block.GasLimit())
if traceTypeVmTrace {
return nil, fmt.Errorf("vmTrace not implemented yet")
}
var initialIbs *state.IntraBlockState
usedGas := new(uint64)
var stateWriter state.StateWriter
vmConfig := vm.Config{}
if traceTypeStateDiff || traceTypeTrace {
vmConfig = vm.Config{Debug: traceTypeTrace, Tracer: &ot}
}
stateWriter = state.NewNoopWriter()
var sd *StateDiff
for i, txn := range block.Transactions() {
if err := common.Stopped(ctx.Done()); err != nil {
return nil, err
}
ibs.Prepare(txn.Hash(), block.Hash(), i)
if traceTypeStateDiff {
sdMap := make(map[common.Address]*StateDiffAccount)
sd = &StateDiff{sdMap: sdMap}
traceResult.StateDiff = sdMap
stateWriter = sd
initialIbs = ibs.Copy()
}
_, execResult, err := core.ApplyTransaction(chainConfig, getHeader, nil, &block.Header().Coinbase, gp, ibs, stateWriter, block.Header(), txn, usedGas, vmConfig, nil)
if err != nil {
return nil, fmt.Errorf("could not apply tx %d from block %d [%v]: %w", i, block.NumberU64(), txn.Hash().Hex(), err)
}
traceResult.Output = common.CopyBytes(execResult)
if traceTypeStateDiff {
sd.CompareStates(initialIbs, ibs)
}
traceResults[i] = traceResult
}
if traceTypeVmTrace {
return nil, fmt.Errorf("vmTrace not implemented yet")
}
return traceResults, nil
}
// Call implements trace_call.
func (api *TraceAPIImpl) Call(ctx context.Context, args TraceCallParam, traceTypes []string, blockNrOrHash *rpc.BlockNumberOrHash) (*TraceCallResult, error) {
tx, err := api.kv.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
chainConfig, err := api.chainConfig(tx)
if err != nil {
return nil, err
}
if blockNrOrHash == nil {
var num = rpc.LatestBlockNumber
blockNrOrHash = &rpc.BlockNumberOrHash{BlockNumber: &num}
}
blockNumber, hash, err := rpchelper.GetBlockNumber(*blockNrOrHash, tx, api.filters)
if err != nil {
return nil, err
}
var stateReader state.StateReader
if num, ok := blockNrOrHash.Number(); ok && num == rpc.LatestBlockNumber {
stateReader = state.NewPlainStateReader(tx)
} else {
stateReader = state.NewPlainKvState(tx, blockNumber)
}
ibs := state.New(stateReader)
header := rawdb.ReadHeader(tx, hash, blockNumber)
if header == nil {
return nil, fmt.Errorf("block %d(%x) not found", blockNumber, hash)
}
// Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout.
var cancel context.CancelFunc
if callTimeout > 0 {
ctx, cancel = context.WithTimeout(ctx, callTimeout)
} else {
ctx, cancel = context.WithCancel(ctx)
}
// Make sure the context is cancelled when the call has completed
// this makes sure resources are cleaned up.
defer cancel()
traceResult := &TraceCallResult{Trace: []*ParityTrace{}}
var traceTypeTrace, traceTypeStateDiff, traceTypeVmTrace bool
for _, traceType := range traceTypes {
switch traceType {
case TraceTypeTrace:
traceTypeTrace = true
case TraceTypeStateDiff:
traceTypeStateDiff = true
case TraceTypeVmTrace:
traceTypeVmTrace = true
default:
return nil, fmt.Errorf("unrecognized trace type: %s", traceType)
}
}
var ot OeTracer
ot.compat = api.compatibility
if traceTypeTrace {
ot.r = traceResult
ot.traceAddr = []int{}
}
// Get a new instance of the EVM.
var baseFee *uint256.Int
if header != nil && header.BaseFee != nil {
var overflow bool
baseFee, overflow = uint256.FromBig(header.BaseFee)
if overflow {
return nil, fmt.Errorf("header.BaseFee uint256 overflow")
}
}
msg, err := args.ToMessage(api.gasCap, baseFee)
if err != nil {
return nil, err
}
blockCtx, txCtx := transactions.GetEvmContext(msg, header, blockNrOrHash.RequireCanonical, tx)
blockCtx.GasLimit = math.MaxUint64
blockCtx.MaxGasLimit = true
evm := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vm.Config{Debug: traceTypeTrace, Tracer: &ot})
// Wait for the context to be done and cancel the evm. Even if the
// EVM has finished, cancelling may be done (repeatedly)
go func() {
<-ctx.Done()
evm.Cancel()
}()
gp := new(core.GasPool).AddGas(msg.Gas())
var execResult *core.ExecutionResult
ibs.Prepare(common.Hash{}, common.Hash{}, 0)
execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, true /* gasBailout */)
if err != nil {
return nil, err
}
traceResult.Output = common.CopyBytes(execResult.ReturnData)
if traceTypeStateDiff {
sdMap := make(map[common.Address]*StateDiffAccount)
traceResult.StateDiff = sdMap
sd := &StateDiff{sdMap: sdMap}
if err = ibs.FinalizeTx(evm.ChainRules, sd); err != nil {
return nil, err
}
// Create initial IntraBlockState, we will compare it with ibs (IntraBlockState after the transaction)
initialIbs := state.New(stateReader)
sd.CompareStates(initialIbs, ibs)
}
if traceTypeVmTrace {
return nil, fmt.Errorf("vmTrace not implemented yet")
}
// If the timer caused an abort, return an appropriate error message
if evm.Cancelled() {
return nil, fmt.Errorf("execution aborted (timeout = %v)", callTimeout)
}
return traceResult, nil
}
// CallMany implements trace_callMany.
func (api *TraceAPIImpl) CallMany(ctx context.Context, calls json.RawMessage, blockNrOrHash *rpc.BlockNumberOrHash) ([]*TraceCallResult, error) {
dbtx, err := api.kv.BeginRo(ctx)
if err != nil {
return nil, err
}
defer dbtx.Rollback()
var callParams []TraceCallParam
dec := json.NewDecoder(bytes.NewReader(calls))
tok, err := dec.Token()
if err != nil {
return nil, err
}
if tok != json.Delim('[') {
return nil, fmt.Errorf("expected array of [callparam, tracetypes]")
}
for dec.More() {
tok, err = dec.Token()
if err != nil {
return nil, err
}
if tok != json.Delim('[') {
return nil, fmt.Errorf("expected [callparam, tracetypes]")
}
callParams = append(callParams, TraceCallParam{})
args := &callParams[len(callParams)-1]
if err = dec.Decode(args); err != nil {
return nil, err
}
if err = dec.Decode(&args.traceTypes); err != nil {
return nil, err
}
tok, err = dec.Token()
if err != nil {
return nil, err
}
if tok != json.Delim(']') {
return nil, fmt.Errorf("expected end of [callparam, tracetypes]")
}
}
tok, err = dec.Token()
if err != nil {
return nil, err
}
if tok != json.Delim(']') {
return nil, fmt.Errorf("expected end of array of [callparam, tracetypes]")
}
return api.doCallMany(ctx, dbtx, callParams, blockNrOrHash, nil, true /* gasBailout */)
}
func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx ethdb.Tx, callParams []TraceCallParam, parentNrOrHash *rpc.BlockNumberOrHash, header *types.Header,
gasBailout bool) ([]*TraceCallResult, error) {
chainConfig, err := api.chainConfig(dbtx)
if err != nil {
return nil, err
}
if parentNrOrHash == nil {
var num = rpc.LatestBlockNumber
parentNrOrHash = &rpc.BlockNumberOrHash{BlockNumber: &num}
}
blockNumber, hash, err := rpchelper.GetBlockNumber(*parentNrOrHash, dbtx, api.filters)
if err != nil {
return nil, err
}
var stateReader state.StateReader
if num, ok := parentNrOrHash.Number(); ok && num == rpc.LatestBlockNumber {
stateReader = state.NewPlainStateReader(dbtx)
} else {
stateReader = state.NewPlainKvState(dbtx, blockNumber)
}
stateCache := shards.NewStateCache(32, 0 /* no limit */)
cachedReader := state.NewCachedReader(stateReader, stateCache)
noop := state.NewNoopWriter()
cachedWriter := state.NewCachedWriter(noop, stateCache)
parentHeader := rawdb.ReadHeader(dbtx, hash, blockNumber)
if parentHeader == nil {
return nil, fmt.Errorf("parent header %d(%x) not found", blockNumber, hash)
}
// Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout.
var cancel context.CancelFunc
if callTimeout > 0 {
ctx, cancel = context.WithTimeout(ctx, callTimeout)
} else {
ctx, cancel = context.WithCancel(ctx)
}
// Make sure the context is cancelled when the call has completed
// this makes sure resources are cleaned up.
defer cancel()
results := []*TraceCallResult{}
for txIndex, args := range callParams {
if err := common.Stopped(ctx.Done()); err != nil {
return nil, err
}
traceResult := &TraceCallResult{Trace: []*ParityTrace{}}
var traceTypeTrace, traceTypeStateDiff, traceTypeVmTrace bool
for _, traceType := range args.traceTypes {
switch traceType {
case TraceTypeTrace:
traceTypeTrace = true
case TraceTypeStateDiff:
traceTypeStateDiff = true
case TraceTypeVmTrace:
traceTypeVmTrace = true
default:
return nil, fmt.Errorf("unrecognized trace type: %s", traceType)
}
}
var ot OeTracer
ot.compat = api.compatibility
if traceTypeTrace {
ot.r = traceResult
ot.traceAddr = []int{}
}
// Get a new instance of the EVM.
var baseFee *uint256.Int
if header != nil && header.BaseFee != nil {
var overflow bool
baseFee, overflow = uint256.FromBig(header.BaseFee)
if overflow {
return nil, fmt.Errorf("header.BaseFee uint256 overflow")
}
}
msg, err := args.ToMessage(api.gasCap, baseFee)
if err != nil {
return nil, err
}
useParent := false
if header == nil {
header = parentHeader
useParent = true
}
blockCtx, txCtx := transactions.GetEvmContext(msg, header, parentNrOrHash.RequireCanonical, dbtx)
if useParent {
blockCtx.GasLimit = math.MaxUint64
blockCtx.MaxGasLimit = true
}
ibs := state.New(cachedReader)
// Create initial IntraBlockState, we will compare it with ibs (IntraBlockState after the transaction)
evm := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vm.Config{Debug: traceTypeTrace, Tracer: &ot})
gp := new(core.GasPool).AddGas(msg.Gas())
var execResult *core.ExecutionResult
// Clone the state cache before applying the changes, clone is discarded
var cloneReader state.StateReader
if traceTypeStateDiff {
cloneCache := stateCache.Clone()
cloneReader = state.NewCachedReader(stateReader, cloneCache)
}
if args.txHash != nil {
ibs.Prepare(*args.txHash, header.Hash(), txIndex)
} else {
ibs.Prepare(common.Hash{}, header.Hash(), txIndex)
}
execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, true /* gasBailout */)
if err != nil {
return nil, fmt.Errorf("first run for txIndex %d error: %w", txIndex, err)
}
traceResult.Output = common.CopyBytes(execResult.ReturnData)
if traceTypeStateDiff {
initialIbs := state.New(cloneReader)
sdMap := make(map[common.Address]*StateDiffAccount)
traceResult.StateDiff = sdMap
sd := &StateDiff{sdMap: sdMap}
if err = ibs.FinalizeTx(evm.ChainRules, sd); err != nil {
return nil, err
}
sd.CompareStates(initialIbs, ibs)
if err = ibs.CommitBlock(evm.ChainRules, cachedWriter); err != nil {
return nil, err
}
} else {
if err = ibs.FinalizeTx(evm.ChainRules, noop); err != nil {
return nil, err
}
if err = ibs.CommitBlock(evm.ChainRules, cachedWriter); err != nil {
return nil, err
}
}
if traceTypeVmTrace {
return nil, fmt.Errorf("vmTrace not implemented yet")
}
results = append(results, traceResult)
}
return results, nil
}
// RawTransaction implements trace_rawTransaction.
func (api *TraceAPIImpl) RawTransaction(ctx context.Context, txHash common.Hash, traceTypes []string) ([]interface{}, error) {
var stub []interface{}
return stub, fmt.Errorf(NotImplemented, "trace_rawTransaction")
}