mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2025-01-04 01:54:28 +00:00
57ebf94c16
This reverts commit 7234210fe0
.
1213 lines
37 KiB
Go
1213 lines
37 KiB
Go
package commands
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/holiman/uint256"
|
|
libcommon "github.com/ledgerwatch/erigon-lib/common"
|
|
"github.com/ledgerwatch/erigon-lib/kv"
|
|
"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/rpc"
|
|
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
|
"github.com/ledgerwatch/erigon/turbo/shards"
|
|
"github.com/ledgerwatch/erigon/turbo/transactions"
|
|
"github.com/ledgerwatch/log/v3"
|
|
)
|
|
|
|
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 *VmTrace `json:"vmTrace"`
|
|
TransactionHash *common.Hash `json:"transactionHash,omitempty"`
|
|
}
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// VmTrace is the part of `trace_call` response that is under "vmTrace" tag
|
|
type VmTrace struct {
|
|
Code hexutil.Bytes `json:"code"`
|
|
Ops []*VmTraceOp `json:"ops"`
|
|
}
|
|
|
|
// VmTraceOp is one element of the vmTrace ops trace
|
|
type VmTraceOp struct {
|
|
Cost int `json:"cost"`
|
|
Ex *VmTraceEx `json:"ex"`
|
|
Pc int `json:"pc"`
|
|
Sub *VmTrace `json:"sub"`
|
|
Op string `json:"op,omitempty"`
|
|
Idx string `json:"idx,omitempty"`
|
|
}
|
|
|
|
type VmTraceEx struct {
|
|
Mem *VmTraceMem `json:"mem"`
|
|
Push []string `json:"push"`
|
|
Store *VmTraceStore `json:"store"`
|
|
Used int `json:"used"`
|
|
}
|
|
|
|
type VmTraceMem struct {
|
|
Data string `json:"data"`
|
|
Off int `json:"off"`
|
|
}
|
|
|
|
type VmTraceStore struct {
|
|
Key string `json:"key"`
|
|
Val string `json:"val"`
|
|
}
|
|
|
|
// 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)
|
|
} else {
|
|
// This means gasFeeCap == 0, gasTipCap == 0
|
|
gasPrice.Set(baseFee)
|
|
gasFeeCap, gasTipCap = gasPrice, gasPrice
|
|
}
|
|
}
|
|
}
|
|
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
|
|
precompile bool // Whether the last CaptureStart was called with `precompile = true`
|
|
compat bool // Bug for bug compatibility mode
|
|
lastVmOp *VmTraceOp
|
|
lastOp vm.OpCode
|
|
lastMemOff uint64
|
|
lastMemLen uint64
|
|
memOffStack []uint64
|
|
memLenStack []uint64
|
|
lastOffStack *VmTraceOp
|
|
vmOpStack []*VmTraceOp // Stack of vmTrace operations as call depth increases
|
|
idx []string // Prefix for the "idx" inside operations, for easier navigation
|
|
}
|
|
|
|
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, code []byte) error {
|
|
if ot.r.VmTrace != nil {
|
|
var vmTrace *VmTrace
|
|
if depth > 0 {
|
|
var vmT *VmTrace
|
|
if len(ot.vmOpStack) > 0 {
|
|
vmT = ot.vmOpStack[len(ot.vmOpStack)-1].Sub
|
|
} else {
|
|
vmT = ot.r.VmTrace
|
|
}
|
|
if !ot.compat {
|
|
ot.idx = append(ot.idx, fmt.Sprintf("%d-", len(vmT.Ops)-1))
|
|
}
|
|
}
|
|
if ot.lastVmOp != nil {
|
|
vmTrace = &VmTrace{Ops: []*VmTraceOp{}}
|
|
ot.lastVmOp.Sub = vmTrace
|
|
ot.vmOpStack = append(ot.vmOpStack, ot.lastVmOp)
|
|
} else {
|
|
vmTrace = ot.r.VmTrace
|
|
}
|
|
if create {
|
|
vmTrace.Code = common.CopyBytes(input)
|
|
if ot.lastVmOp != nil {
|
|
ot.lastVmOp.Cost += int(gas)
|
|
}
|
|
} else {
|
|
vmTrace.Code = code
|
|
}
|
|
}
|
|
if precompile && depth > 0 {
|
|
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, startGas, endGas uint64, t time.Duration, err error) error {
|
|
if ot.r.VmTrace != nil {
|
|
if len(ot.vmOpStack) > 0 {
|
|
ot.lastOffStack = ot.vmOpStack[len(ot.vmOpStack)-1]
|
|
ot.vmOpStack = ot.vmOpStack[:len(ot.vmOpStack)-1]
|
|
}
|
|
if !ot.compat && depth > 0 {
|
|
ot.idx = ot.idx[:len(ot.idx)-1]
|
|
}
|
|
if depth > 0 {
|
|
ot.lastMemOff = ot.memOffStack[len(ot.memOffStack)-1]
|
|
ot.memOffStack = ot.memOffStack[:len(ot.memOffStack)-1]
|
|
ot.lastMemLen = ot.memLenStack[len(ot.memLenStack)-1]
|
|
ot.memLenStack = ot.memLenStack[:len(ot.memLenStack)-1]
|
|
}
|
|
}
|
|
if ot.precompile {
|
|
ot.precompile = false
|
|
return nil
|
|
}
|
|
if depth == 0 {
|
|
ot.r.Output = common.CopyBytes(output)
|
|
}
|
|
ignoreError := false
|
|
topTrace := ot.traceStack[len(ot.traceStack)-1]
|
|
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, vm.ErrGasUintOverflow:
|
|
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(startGas - endGas)
|
|
case CREATE:
|
|
topTrace.Result.(*CreateTraceResult).GasUsed = new(hexutil.Big)
|
|
topTrace.Result.(*CreateTraceResult).GasUsed.ToInt().SetUint64(startGas - endGas)
|
|
}
|
|
}
|
|
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 {
|
|
if ot.r.VmTrace != nil {
|
|
var vmTrace *VmTrace
|
|
if len(ot.vmOpStack) > 0 {
|
|
vmTrace = ot.vmOpStack[len(ot.vmOpStack)-1].Sub
|
|
} else {
|
|
vmTrace = ot.r.VmTrace
|
|
}
|
|
if ot.lastVmOp != nil && ot.lastVmOp.Ex != nil {
|
|
// Set the "push" of the last operation
|
|
var showStack int
|
|
switch {
|
|
case ot.lastOp >= vm.PUSH1 && ot.lastOp <= vm.PUSH32:
|
|
showStack = 1
|
|
case ot.lastOp >= vm.SWAP1 && ot.lastOp <= vm.SWAP16:
|
|
showStack = int(ot.lastOp-vm.SWAP1) + 2
|
|
case ot.lastOp >= vm.DUP1 && ot.lastOp <= vm.DUP16:
|
|
showStack = int(ot.lastOp-vm.DUP1) + 2
|
|
}
|
|
switch ot.lastOp {
|
|
case vm.CALLDATALOAD, vm.SLOAD, vm.MLOAD, vm.CALLDATASIZE, vm.LT, vm.GT, vm.DIV, vm.SDIV, vm.SAR, vm.AND, vm.EQ, vm.CALLVALUE, vm.ISZERO,
|
|
vm.ADD, vm.EXP, vm.CALLER, vm.SHA3, vm.SUB, vm.ADDRESS, vm.GAS, vm.MUL, vm.RETURNDATASIZE, vm.NOT, vm.SHR, vm.SHL,
|
|
vm.EXTCODESIZE, vm.SLT, vm.OR, vm.NUMBER, vm.PC, vm.TIMESTAMP, vm.BALANCE, vm.SELFBALANCE, vm.MULMOD, vm.ADDMOD, vm.BASEFEE,
|
|
vm.BLOCKHASH, vm.BYTE, vm.XOR, vm.ORIGIN, vm.CODESIZE, vm.MOD, vm.SIGNEXTEND, vm.GASLIMIT, vm.DIFFICULTY, vm.SGT, vm.GASPRICE,
|
|
vm.MSIZE, vm.EXTCODEHASH:
|
|
showStack = 1
|
|
}
|
|
for i := showStack - 1; i >= 0; i-- {
|
|
ot.lastVmOp.Ex.Push = append(ot.lastVmOp.Ex.Push, st.Back(i).String())
|
|
}
|
|
// Set the "mem" of the last operation
|
|
var setMem bool
|
|
switch ot.lastOp {
|
|
case vm.MSTORE, vm.MSTORE8, vm.MLOAD, vm.RETURNDATACOPY, vm.CALLDATACOPY, vm.CODECOPY:
|
|
setMem = true
|
|
}
|
|
if setMem && ot.lastMemLen > 0 {
|
|
cpy := memory.GetCopy(ot.lastMemOff, ot.lastMemLen)
|
|
if len(cpy) == 0 {
|
|
cpy = make([]byte, ot.lastMemLen)
|
|
}
|
|
ot.lastVmOp.Ex.Mem = &VmTraceMem{Data: fmt.Sprintf("0x%0x", cpy), Off: int(ot.lastMemOff)}
|
|
}
|
|
}
|
|
if ot.lastOffStack != nil {
|
|
ot.lastOffStack.Ex.Used = int(gas)
|
|
ot.lastOffStack.Ex.Push = []string{st.Back(0).String()}
|
|
if ot.lastMemLen > 0 && memory != nil {
|
|
cpy := memory.GetCopy(ot.lastMemOff, ot.lastMemLen)
|
|
if len(cpy) == 0 {
|
|
cpy = make([]byte, ot.lastMemLen)
|
|
}
|
|
ot.lastOffStack.Ex.Mem = &VmTraceMem{Data: fmt.Sprintf("0x%0x", cpy), Off: int(ot.lastMemOff)}
|
|
}
|
|
ot.lastOffStack = nil
|
|
}
|
|
if ot.lastOp == vm.STOP && op == vm.STOP && len(ot.vmOpStack) == 0 {
|
|
// Looks like OE is "optimising away" the second STOP
|
|
return nil
|
|
}
|
|
ot.lastVmOp = &VmTraceOp{Ex: &VmTraceEx{}}
|
|
vmTrace.Ops = append(vmTrace.Ops, ot.lastVmOp)
|
|
if !ot.compat {
|
|
var sb strings.Builder
|
|
for _, idx := range ot.idx {
|
|
sb.WriteString(idx)
|
|
}
|
|
ot.lastVmOp.Idx = fmt.Sprintf("%s%d", sb.String(), len(vmTrace.Ops)-1)
|
|
}
|
|
ot.lastOp = op
|
|
ot.lastVmOp.Cost = int(cost)
|
|
ot.lastVmOp.Pc = int(pc)
|
|
ot.lastVmOp.Ex.Push = []string{}
|
|
ot.lastVmOp.Ex.Used = int(gas) - int(cost)
|
|
if !ot.compat {
|
|
ot.lastVmOp.Op = op.String()
|
|
}
|
|
switch op {
|
|
case vm.MSTORE, vm.MLOAD:
|
|
ot.lastMemOff = st.Back(0).Uint64()
|
|
ot.lastMemLen = 32
|
|
case vm.MSTORE8:
|
|
ot.lastMemOff = st.Back(0).Uint64()
|
|
ot.lastMemLen = 1
|
|
case vm.RETURNDATACOPY, vm.CALLDATACOPY, vm.CODECOPY:
|
|
ot.lastMemOff = st.Back(0).Uint64()
|
|
ot.lastMemLen = st.Back(2).Uint64()
|
|
case vm.STATICCALL, vm.DELEGATECALL:
|
|
ot.memOffStack = append(ot.memOffStack, st.Back(4).Uint64())
|
|
ot.memLenStack = append(ot.memLenStack, st.Back(5).Uint64())
|
|
case vm.CALL, vm.CALLCODE:
|
|
ot.memOffStack = append(ot.memOffStack, st.Back(5).Uint64())
|
|
ot.memLenStack = append(ot.memLenStack, st.Back(6).Uint64())
|
|
case vm.CREATE, vm.CREATE2:
|
|
// Effectively disable memory output
|
|
ot.memOffStack = append(ot.memOffStack, 0)
|
|
ot.memLenStack = append(ot.memLenStack, 0)
|
|
case vm.SSTORE:
|
|
ot.lastVmOp.Ex.Store = &VmTraceStore{Key: st.Back(0).String(), Val: st.Back(1).String()}
|
|
}
|
|
if ot.lastVmOp.Ex.Used < 0 {
|
|
ot.lastVmOp.Ex = nil
|
|
}
|
|
}
|
|
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()
|
|
chainConfig, err := api.chainConfig(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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 := api.blockByNumberWithSenders(tx, *blockNumber)
|
|
if bErr != nil {
|
|
return nil, bErr
|
|
}
|
|
if block == nil {
|
|
return nil, fmt.Errorf("could not find block %d", *blockNumber)
|
|
}
|
|
var txIndex int
|
|
for idx, txn := range block.Transactions() {
|
|
if txn.Hash() == txHash {
|
|
txIndex = 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(), traceTypes, block.ParentHash(), rpc.BlockNumber(parentNr), block.Header(), txIndex, types.MakeSigner(chainConfig, *blockNumber))
|
|
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 {
|
|
// We're only looking for a specific transaction
|
|
if txno == 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, _, err := rpchelper.GetBlockNumber(blockNrOrHash, tx, api.filters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
parentNr := blockNumber
|
|
if parentNr > 0 {
|
|
parentNr -= 1
|
|
}
|
|
// Extract transactions from block
|
|
block, bErr := api.blockByNumberWithSenders(tx, blockNumber)
|
|
if bErr != nil {
|
|
return nil, bErr
|
|
}
|
|
if block == nil {
|
|
return nil, fmt.Errorf("could not find block %d", blockNumber)
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
|
|
// Returns an array of trace arrays, one trace array for each transaction
|
|
traces, err := api.callManyTransactions(ctx, tx, block.Transactions(), traceTypes, block.ParentHash(), rpc.BlockNumber(parentNr), block.Header(), -1 /* all tx indices */, types.MakeSigner(chainConfig, blockNumber))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := make([]*TraceCallResult, len(traces))
|
|
for i, trace := range traces {
|
|
tr := &TraceCallResult{}
|
|
tr.Output = trace.Output
|
|
if traceTypeTrace {
|
|
tr.Trace = trace.Trace
|
|
} else {
|
|
tr.Trace = []*ParityTrace{}
|
|
}
|
|
if traceTypeStateDiff {
|
|
tr.StateDiff = trace.StateDiff
|
|
}
|
|
if traceTypeVmTrace {
|
|
tr.VmTrace = trace.VmTrace
|
|
}
|
|
result[i] = tr
|
|
txhash := block.Transactions()[i].Hash()
|
|
tr.TransactionHash = &txhash
|
|
}
|
|
|
|
return result, 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 {
|
|
cacheView, err := api.stateCache.View(ctx, tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stateReader = state.NewCachedReader2(cacheView, tx)
|
|
} else {
|
|
stateReader = state.NewPlainState(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)
|
|
}
|
|
}
|
|
if traceTypeVmTrace {
|
|
traceResult.VmTrace = &VmTrace{Ops: []*VmTraceOp{}}
|
|
}
|
|
var ot OeTracer
|
|
ot.compat = api.compatibility
|
|
if traceTypeTrace || traceTypeVmTrace {
|
|
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
|
|
}
|
|
|
|
contractHasTEVM := func(contractHash common.Hash) (bool, error) { return false, nil }
|
|
if api.TevmEnabled {
|
|
contractHasTEVM = ethdb.GetHasTEVM(tx)
|
|
}
|
|
blockCtx, txCtx := transactions.GetEvmContext(msg, header, blockNrOrHash.RequireCanonical, tx, contractHasTEVM)
|
|
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 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, parentNrOrHash *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]")
|
|
}
|
|
var baseFee *uint256.Int
|
|
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
|
|
}
|
|
parentHeader := rawdb.ReadHeader(dbtx, hash, blockNumber)
|
|
if parentHeader == nil {
|
|
return nil, fmt.Errorf("parent header %d(%x) not found", blockNumber, hash)
|
|
}
|
|
if parentHeader != nil && parentHeader.BaseFee != nil {
|
|
var overflow bool
|
|
baseFee, overflow = uint256.FromBig(parentHeader.BaseFee)
|
|
if overflow {
|
|
return nil, fmt.Errorf("header.BaseFee uint256 overflow")
|
|
}
|
|
}
|
|
msgs := make([]types.Message, len(callParams))
|
|
for i, args := range callParams {
|
|
msgs[i], err = args.ToMessage(api.gasCap, baseFee)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("convert callParam to msg: %w", err)
|
|
}
|
|
}
|
|
return api.doCallMany(ctx, dbtx, msgs, callParams, parentNrOrHash, nil, true /* gasBailout */, -1 /* all tx indices */)
|
|
}
|
|
|
|
func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, msgs []types.Message, callParams []TraceCallParam, parentNrOrHash *rpc.BlockNumberOrHash, header *types.Header,
|
|
gasBailout bool, txIndexNeeded int) ([]*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 {
|
|
cacheView, err := api.stateCache.View(ctx, dbtx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stateReader = state.NewCachedReader2(cacheView, dbtx) // this cache stays between RPC calls
|
|
} else {
|
|
stateReader = state.NewPlainState(dbtx, blockNumber)
|
|
}
|
|
stateCache := shards.NewStateCache(32, 0 /* no limit */) // this cache living only during current RPC call, but required to store state writes
|
|
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{}
|
|
|
|
useParent := false
|
|
if header == nil {
|
|
header = parentHeader
|
|
useParent = true
|
|
}
|
|
|
|
contractHasTEVM := func(contractHash common.Hash) (bool, error) { return false, nil }
|
|
if api.TevmEnabled {
|
|
contractHasTEVM = ethdb.GetHasTEVM(dbtx)
|
|
}
|
|
|
|
for txIndex, msg := range msgs {
|
|
if err := libcommon.Stopped(ctx.Done()); err != nil {
|
|
return nil, err
|
|
}
|
|
traceResult := &TraceCallResult{Trace: []*ParityTrace{}}
|
|
var traceTypeTrace, traceTypeStateDiff, traceTypeVmTrace bool
|
|
args := callParams[txIndex]
|
|
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)
|
|
}
|
|
}
|
|
vmConfig := vm.Config{}
|
|
if (traceTypeTrace && (txIndexNeeded == -1 || txIndex == txIndexNeeded)) || traceTypeVmTrace {
|
|
var ot OeTracer
|
|
ot.compat = api.compatibility
|
|
ot.r = traceResult
|
|
ot.idx = []string{fmt.Sprintf("%d-", txIndex)}
|
|
if traceTypeTrace && (txIndexNeeded == -1 || txIndex == txIndexNeeded) {
|
|
ot.traceAddr = []int{}
|
|
}
|
|
if traceTypeVmTrace {
|
|
traceResult.VmTrace = &VmTrace{Ops: []*VmTraceOp{}}
|
|
}
|
|
vmConfig.Debug = true
|
|
vmConfig.Tracer = &ot
|
|
}
|
|
|
|
// Get a new instance of the EVM.
|
|
blockCtx, txCtx := transactions.GetEvmContext(msg, header, parentNrOrHash.RequireCanonical, dbtx, contractHasTEVM)
|
|
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, vmConfig)
|
|
|
|
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 */, gasBailout /* 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 !traceTypeTrace {
|
|
traceResult.Trace = []*ParityTrace{}
|
|
}
|
|
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")
|
|
}
|