mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-22 11:41:19 +00:00
436493350e
1. changes sentinel to use an http-like interface 2. moves hexutil, crypto/blake2b, metrics packages to erigon-lib
1254 lines
38 KiB
Go
1254 lines
38 KiB
Go
package jsonrpc
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/ledgerwatch/erigon-lib/common/hexutil"
|
|
"math"
|
|
"strings"
|
|
|
|
"github.com/holiman/uint256"
|
|
"github.com/ledgerwatch/log/v3"
|
|
|
|
libcommon "github.com/ledgerwatch/erigon-lib/common"
|
|
"github.com/ledgerwatch/erigon-lib/common/hexutility"
|
|
"github.com/ledgerwatch/erigon-lib/kv"
|
|
types2 "github.com/ledgerwatch/erigon-lib/types"
|
|
|
|
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/rpc"
|
|
"github.com/ledgerwatch/erigon/turbo/rpchelper"
|
|
"github.com/ledgerwatch/erigon/turbo/shards"
|
|
"github.com/ledgerwatch/erigon/turbo/transactions"
|
|
)
|
|
|
|
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 *libcommon.Address `json:"from"`
|
|
To *libcommon.Address `json:"to"`
|
|
Gas *hexutil.Uint64 `json:"gas"`
|
|
GasPrice *hexutil.Big `json:"gasPrice"`
|
|
MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
|
|
MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
|
|
MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas"`
|
|
Value *hexutil.Big `json:"value"`
|
|
Data hexutility.Bytes `json:"data"`
|
|
AccessList *types2.AccessList `json:"accessList"`
|
|
txHash *libcommon.Hash
|
|
traceTypes []string
|
|
}
|
|
|
|
// TraceCallResult is the response to `trace_call` method
|
|
type TraceCallResult struct {
|
|
Output hexutility.Bytes `json:"output"`
|
|
StateDiff map[libcommon.Address]*StateDiffAccount `json:"stateDiff"`
|
|
Trace []*ParityTrace `json:"trace"`
|
|
VmTrace *VmTrace `json:"vmTrace"`
|
|
TransactionHash *libcommon.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[libcommon.Hash]map[string]interface{} `json:"storage"`
|
|
}
|
|
|
|
type StateDiffBalance struct {
|
|
From *hexutil.Big `json:"from"`
|
|
To *hexutil.Big `json:"to"`
|
|
}
|
|
|
|
type StateDiffCode struct {
|
|
From hexutility.Bytes `json:"from"`
|
|
To hexutility.Bytes `json:"to"`
|
|
}
|
|
|
|
type StateDiffNonce struct {
|
|
From hexutil.Uint64 `json:"from"`
|
|
To hexutil.Uint64 `json:"to"`
|
|
}
|
|
|
|
type StateDiffStorage struct {
|
|
From libcommon.Hash `json:"from"`
|
|
To libcommon.Hash `json:"to"`
|
|
}
|
|
|
|
// VmTrace is the part of `trace_call` response that is under "vmTrace" tag
|
|
type VmTrace struct {
|
|
Code hexutility.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 libcommon.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
|
|
maxFeePerBlobGas *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.IsZero() || !gasTipCap.IsZero() {
|
|
gasPrice = math2.U256Min(new(uint256.Int).Add(gasTipCap, baseFee), gasFeeCap)
|
|
} else {
|
|
// This means gasFeeCap == 0, gasTipCap == 0
|
|
gasPrice.Set(baseFee)
|
|
gasFeeCap, gasTipCap = gasPrice, gasPrice
|
|
}
|
|
}
|
|
if args.MaxFeePerBlobGas != nil {
|
|
maxFeePerBlobGas.SetFromBig(args.MaxFeePerBlobGas.ToInt())
|
|
}
|
|
}
|
|
value := new(uint256.Int)
|
|
if args.Value != nil {
|
|
overflow := value.SetFromBig(args.Value.ToInt())
|
|
if overflow {
|
|
return types.Message{}, fmt.Errorf("args.Value higher than 2^256-1")
|
|
}
|
|
}
|
|
var data []byte
|
|
if args.Data != nil {
|
|
data = args.Data
|
|
}
|
|
var accessList types2.AccessList
|
|
if args.AccessList != nil {
|
|
accessList = *args.AccessList
|
|
}
|
|
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, gasFeeCap, gasTipCap, data, accessList, false /* checkNonce */, false /* isFree */, maxFeePerBlobGas)
|
|
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) CaptureTxStart(gasLimit uint64) {}
|
|
|
|
func (ot *OeTracer) CaptureTxEnd(restGas uint64) {}
|
|
|
|
func (ot *OeTracer) captureStartOrEnter(deep bool, typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) {
|
|
//fmt.Printf("captureStartOrEnter deep %t, typ %s, from %x, to %x, create %t, input %x, gas %d, value %d, precompile %t\n", deep, typ.String(), from, to, create, input, gas, value, precompile)
|
|
if ot.r.VmTrace != nil {
|
|
var vmTrace *VmTrace
|
|
if deep {
|
|
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 = libcommon.CopyBytes(input)
|
|
if ot.lastVmOp != nil {
|
|
ot.lastVmOp.Cost += int(gas)
|
|
}
|
|
} else {
|
|
vmTrace.Code = code
|
|
}
|
|
}
|
|
if precompile && deep && (value == nil || value.IsZero()) {
|
|
ot.precompile = true
|
|
return
|
|
}
|
|
if gas > 500000000 {
|
|
gas = 500000001 - (0x8000000000000000 - gas)
|
|
}
|
|
trace := &ParityTrace{}
|
|
if create {
|
|
trResult := &CreateTraceResult{}
|
|
trace.Type = CREATE
|
|
trResult.Address = new(libcommon.Address)
|
|
copy(trResult.Address[:], to.Bytes())
|
|
trace.Result = trResult
|
|
} else {
|
|
trace.Result = &TraceResult{}
|
|
trace.Type = CALL
|
|
}
|
|
if deep {
|
|
topTrace := ot.traceStack[len(ot.traceStack)-1]
|
|
traceIdx := topTrace.Subtraces
|
|
ot.traceAddr = append(ot.traceAddr, traceIdx)
|
|
topTrace.Subtraces++
|
|
if typ == vm.DELEGATECALL {
|
|
switch action := topTrace.Action.(type) {
|
|
case *CreateTraceAction:
|
|
value, _ = uint256.FromBig(action.Value.ToInt())
|
|
case *CallTraceAction:
|
|
value, _ = uint256.FromBig(action.Value.ToInt())
|
|
}
|
|
}
|
|
if typ == vm.STATICCALL {
|
|
value = uint256.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 = libcommon.CopyBytes(input)
|
|
action.Value.ToInt().Set(value.ToBig())
|
|
trace.Action = &action
|
|
} else if typ == vm.SELFDESTRUCT {
|
|
trace.Type = SUICIDE
|
|
trace.Result = nil
|
|
action := &SuicideTraceAction{}
|
|
action.Address = from
|
|
action.RefundAddress = to
|
|
action.Balance.ToInt().Set(value.ToBig())
|
|
trace.Action = action
|
|
} else {
|
|
action := CallTraceAction{}
|
|
switch typ {
|
|
case vm.CALL:
|
|
action.CallType = CALL
|
|
case vm.CALLCODE:
|
|
action.CallType = CALLCODE
|
|
case vm.DELEGATECALL:
|
|
action.CallType = DELEGATECALL
|
|
case vm.STATICCALL:
|
|
action.CallType = STATICCALL
|
|
}
|
|
action.From = from
|
|
action.To = to
|
|
action.Gas.ToInt().SetUint64(gas)
|
|
action.Input = libcommon.CopyBytes(input)
|
|
action.Value.ToInt().Set(value.ToBig())
|
|
trace.Action = &action
|
|
}
|
|
ot.r.Trace = append(ot.r.Trace, trace)
|
|
ot.traceStack = append(ot.traceStack, trace)
|
|
}
|
|
|
|
func (ot *OeTracer) CaptureStart(env *vm.EVM, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) {
|
|
ot.captureStartOrEnter(false /* deep */, vm.CALL, from, to, precompile, create, input, gas, value, code)
|
|
}
|
|
|
|
func (ot *OeTracer) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) {
|
|
ot.captureStartOrEnter(true /* deep */, typ, from, to, precompile, create, input, gas, value, code)
|
|
}
|
|
|
|
func (ot *OeTracer) captureEndOrExit(deep bool, output []byte, usedGas uint64, err 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 && deep {
|
|
ot.idx = ot.idx[:len(ot.idx)-1]
|
|
}
|
|
if deep {
|
|
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
|
|
}
|
|
if !deep {
|
|
ot.r.Output = libcommon.CopyBytes(output)
|
|
}
|
|
ignoreError := false
|
|
topTrace := ot.traceStack[len(ot.traceStack)-1]
|
|
if ot.compat {
|
|
ignoreError = !deep && topTrace.Type == CREATE
|
|
}
|
|
if err != nil && !ignoreError {
|
|
if err == vm.ErrExecutionReverted {
|
|
topTrace.Error = "Reverted"
|
|
switch topTrace.Type {
|
|
case CALL:
|
|
topTrace.Result.(*TraceResult).GasUsed = new(hexutil.Big)
|
|
topTrace.Result.(*TraceResult).GasUsed.ToInt().SetUint64(usedGas)
|
|
topTrace.Result.(*TraceResult).Output = libcommon.CopyBytes(output)
|
|
case CREATE:
|
|
topTrace.Result.(*CreateTraceResult).GasUsed = new(hexutil.Big)
|
|
topTrace.Result.(*CreateTraceResult).GasUsed.ToInt().SetUint64(usedGas)
|
|
topTrace.Result.(*CreateTraceResult).Code = libcommon.CopyBytes(output)
|
|
}
|
|
} else {
|
|
topTrace.Result = nil
|
|
topTrace.Error = err.Error()
|
|
}
|
|
} else {
|
|
if len(output) > 0 {
|
|
switch topTrace.Type {
|
|
case CALL:
|
|
topTrace.Result.(*TraceResult).Output = libcommon.CopyBytes(output)
|
|
case CREATE:
|
|
topTrace.Result.(*CreateTraceResult).Code = libcommon.CopyBytes(output)
|
|
}
|
|
}
|
|
switch topTrace.Type {
|
|
case CALL:
|
|
topTrace.Result.(*TraceResult).GasUsed = new(hexutil.Big)
|
|
topTrace.Result.(*TraceResult).GasUsed.ToInt().SetUint64(usedGas)
|
|
case CREATE:
|
|
topTrace.Result.(*CreateTraceResult).GasUsed = new(hexutil.Big)
|
|
topTrace.Result.(*CreateTraceResult).GasUsed.ToInt().SetUint64(usedGas)
|
|
}
|
|
}
|
|
ot.traceStack = ot.traceStack[:len(ot.traceStack)-1]
|
|
if deep {
|
|
ot.traceAddr = ot.traceAddr[:len(ot.traceAddr)-1]
|
|
}
|
|
}
|
|
|
|
func (ot *OeTracer) CaptureEnd(output []byte, usedGas uint64, err error) {
|
|
ot.captureEndOrExit(false /* deep */, output, usedGas, err)
|
|
}
|
|
|
|
func (ot *OeTracer) CaptureExit(output []byte, usedGas uint64, err error) {
|
|
ot.captureEndOrExit(true /* deep */, output, usedGas, err)
|
|
}
|
|
|
|
func (ot *OeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, opDepth int, err error) {
|
|
memory := scope.Memory
|
|
st := scope.Stack
|
|
|
|
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.PUSH0 && 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.KECCAK256, 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, vm.SMOD, vm.CHAINID, vm.COINBASE:
|
|
showStack = 1
|
|
}
|
|
for i := showStack - 1; i >= 0; i-- {
|
|
if st.Len() > 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, vm.EXTCODECOPY:
|
|
setMem = true
|
|
}
|
|
if setMem && ot.lastMemLen > 0 {
|
|
cpy := memory.GetCopy(int64(ot.lastMemOff), int64(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)
|
|
if st.Len() > 0 {
|
|
ot.lastOffStack.Ex.Push = []string{st.Back(0).String()}
|
|
} else {
|
|
ot.lastOffStack.Ex.Push = []string{}
|
|
}
|
|
if ot.lastMemLen > 0 && memory != nil {
|
|
cpy := memory.GetCopy(int64(ot.lastMemOff), int64(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
|
|
}
|
|
ot.lastVmOp = &VmTraceOp{Ex: &VmTraceEx{}}
|
|
vmTrace.Ops = append(vmTrace.Ops, ot.lastVmOp)
|
|
if !ot.compat {
|
|
var sb strings.Builder
|
|
sb.Grow(len(ot.idx))
|
|
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:
|
|
if st.Len() > 0 {
|
|
ot.lastMemOff = st.Back(0).Uint64()
|
|
ot.lastMemLen = 32
|
|
}
|
|
case vm.MSTORE8:
|
|
if st.Len() > 0 {
|
|
ot.lastMemOff = st.Back(0).Uint64()
|
|
ot.lastMemLen = 1
|
|
}
|
|
case vm.RETURNDATACOPY, vm.CALLDATACOPY, vm.CODECOPY:
|
|
if st.Len() > 2 {
|
|
ot.lastMemOff = st.Back(0).Uint64()
|
|
ot.lastMemLen = st.Back(2).Uint64()
|
|
}
|
|
case vm.EXTCODECOPY:
|
|
if st.Len() > 3 {
|
|
ot.lastMemOff = st.Back(1).Uint64()
|
|
ot.lastMemLen = st.Back(3).Uint64()
|
|
}
|
|
case vm.STATICCALL, vm.DELEGATECALL:
|
|
if st.Len() > 5 {
|
|
ot.memOffStack = append(ot.memOffStack, st.Back(4).Uint64())
|
|
ot.memLenStack = append(ot.memLenStack, st.Back(5).Uint64())
|
|
}
|
|
case vm.CALL, vm.CALLCODE:
|
|
if st.Len() > 6 {
|
|
ot.memOffStack = append(ot.memOffStack, st.Back(5).Uint64())
|
|
ot.memLenStack = append(ot.memLenStack, st.Back(6).Uint64())
|
|
}
|
|
case vm.CREATE, vm.CREATE2, vm.SELFDESTRUCT:
|
|
// Effectively disable memory output
|
|
ot.memOffStack = append(ot.memOffStack, 0)
|
|
ot.memLenStack = append(ot.memLenStack, 0)
|
|
case vm.SSTORE:
|
|
if st.Len() > 1 {
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ot *OeTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, opDepth int, err error) {
|
|
}
|
|
|
|
// Implements core/state/StateWriter to provide state diffs
|
|
type StateDiff struct {
|
|
sdMap map[libcommon.Address]*StateDiffAccount
|
|
}
|
|
|
|
func (sd *StateDiff) UpdateAccountData(address libcommon.Address, original, account *accounts.Account) error {
|
|
if _, ok := sd.sdMap[address]; !ok {
|
|
sd.sdMap[address] = &StateDiffAccount{Storage: make(map[libcommon.Hash]map[string]interface{})}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sd *StateDiff) UpdateAccountCode(address libcommon.Address, incarnation uint64, codeHash libcommon.Hash, code []byte) error {
|
|
if _, ok := sd.sdMap[address]; !ok {
|
|
sd.sdMap[address] = &StateDiffAccount{Storage: make(map[libcommon.Hash]map[string]interface{})}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sd *StateDiff) DeleteAccount(address libcommon.Address, original *accounts.Account) error {
|
|
if _, ok := sd.sdMap[address]; !ok {
|
|
sd.sdMap[address] = &StateDiffAccount{Storage: make(map[libcommon.Hash]map[string]interface{})}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sd *StateDiff) WriteAccountStorage(address libcommon.Address, incarnation uint64, key *libcommon.Hash, original, value *uint256.Int) error {
|
|
if *original == *value {
|
|
return nil
|
|
}
|
|
accountDiff := sd.sdMap[address]
|
|
if accountDiff == nil {
|
|
accountDiff = &StateDiffAccount{Storage: make(map[libcommon.Hash]map[string]interface{})}
|
|
sd.sdMap[address] = accountDiff
|
|
}
|
|
m := make(map[string]interface{})
|
|
m["*"] = &StateDiffStorage{From: libcommon.BytesToHash(original.Bytes()), To: libcommon.BytesToHash(value.Bytes())}
|
|
accountDiff.Storage[*key] = m
|
|
return nil
|
|
}
|
|
|
|
func (sd *StateDiff) CreateContract(address libcommon.Address) error {
|
|
if _, ok := sd.sdMap[address]; !ok {
|
|
sd.sdMap[address] = &StateDiffAccount{Storage: make(map[libcommon.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 []libcommon.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]hexutility.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]hexutility.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 libcommon.Hash, traceTypes []string, gasBailOut *bool) (*TraceCallResult, error) {
|
|
if gasBailOut == nil {
|
|
gasBailOut = new(bool) // false by default
|
|
}
|
|
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
|
|
}
|
|
|
|
blockNum, ok, err := api.txnLookup(tx, txHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
// Private API returns 0 if transaction is not found.
|
|
if blockNum == 0 && chainConfig.Bor != nil {
|
|
blockNumPtr, err := rawdb.ReadBorTxLookupEntry(tx, txHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if blockNumPtr == nil {
|
|
return nil, nil
|
|
}
|
|
blockNum = *blockNumPtr
|
|
}
|
|
block, err := api.blockByNumberWithSenders(tx, blockNum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if block == nil {
|
|
return nil, nil
|
|
}
|
|
var txnIndex uint64
|
|
for i, transaction := range block.Transactions() {
|
|
if transaction.Hash() == txHash {
|
|
txnIndex = uint64(i)
|
|
break
|
|
}
|
|
}
|
|
|
|
signer := types.MakeSigner(chainConfig, blockNum, block.Time())
|
|
// Returns an array of trace arrays, one trace array for each transaction
|
|
traces, _, err := api.callManyTransactions(ctx, tx, block, traceTypes, int(txnIndex), *gasBailOut, signer, chainConfig)
|
|
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 == int(txnIndex) {
|
|
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, gasBailOut *bool) ([]*TraceCallResult, error) {
|
|
if gasBailOut == nil {
|
|
gasBailOut = new(bool) // false by default
|
|
}
|
|
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
|
|
}
|
|
|
|
// Extract transactions from block
|
|
block, bErr := api.blockWithSenders(tx, blockHash, 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)
|
|
}
|
|
}
|
|
|
|
signer := types.MakeSigner(chainConfig, blockNumber, block.Time())
|
|
// Returns an array of trace arrays, one trace array for each transaction
|
|
traces, _, err := api.callManyTransactions(ctx, tx, block, traceTypes, -1 /* all tx indices */, *gasBailOut, signer, chainConfig)
|
|
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
|
|
}
|
|
engine := api.engine()
|
|
|
|
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
|
|
}
|
|
|
|
stateReader, err := rpchelper.CreateStateReader(ctx, tx, *blockNrOrHash, 0, api.filters, api.stateCache, api.historyV3(tx), chainConfig.ChainName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ibs := state.New(stateReader)
|
|
|
|
block, err := api.blockWithSenders(tx, hash, blockNumber)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if block == nil {
|
|
return nil, fmt.Errorf("block %d(%x) not found", blockNumber, hash)
|
|
}
|
|
header := block.Header()
|
|
|
|
// 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 api.evmCallTimeout > 0 {
|
|
ctx, cancel = context.WithTimeout(ctx, api.evmCallTimeout)
|
|
} 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
|
|
}
|
|
|
|
blockCtx := transactions.NewEVMBlockContext(engine, header, blockNrOrHash.RequireCanonical, tx, api._blockReader)
|
|
txCtx := core.NewEVMTxContext(msg)
|
|
|
|
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()).AddBlobGas(msg.BlobGas())
|
|
var execResult *core.ExecutionResult
|
|
ibs.SetTxContext(libcommon.Hash{}, libcommon.Hash{}, 0)
|
|
execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, true /* gasBailout */)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
traceResult.Output = libcommon.CopyBytes(execResult.ReturnData)
|
|
if traceTypeStateDiff {
|
|
sdMap := make(map[libcommon.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)", api.evmCallTimeout)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// TODO: can read here only parent header
|
|
parentBlock, err := api.blockWithSenders(dbtx, hash, blockNumber)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if parentBlock == nil {
|
|
return nil, fmt.Errorf("parent block %d(%x) not found", blockNumber, hash)
|
|
}
|
|
parentHeader := parentBlock.Header()
|
|
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)
|
|
}
|
|
}
|
|
results, _, err := api.doCallMany(ctx, dbtx, msgs, callParams, parentNrOrHash, nil, true /* gasBailout */, -1 /* all tx indices */)
|
|
return results, err
|
|
}
|
|
|
|
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, *state.IntraBlockState, error) {
|
|
chainConfig, err := api.chainConfig(dbtx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
engine := api.engine()
|
|
|
|
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, nil, err
|
|
}
|
|
stateReader, err := rpchelper.CreateStateReader(ctx, dbtx, *parentNrOrHash, 0, api.filters, api.stateCache, api.historyV3(dbtx), chainConfig.ChainName)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
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)
|
|
ibs := state.New(cachedReader)
|
|
|
|
// TODO: can read here only parent header
|
|
parentBlock, err := api.blockWithSenders(dbtx, hash, blockNumber)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
parentHeader := parentBlock.Header()
|
|
if parentHeader == nil {
|
|
return nil, 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 api.evmCallTimeout > 0 {
|
|
ctx, cancel = context.WithTimeout(ctx, api.evmCallTimeout)
|
|
} 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
|
|
}
|
|
|
|
for txIndex, msg := range msgs {
|
|
if err := libcommon.Stopped(ctx.Done()); err != nil {
|
|
return nil, 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, 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 := transactions.NewEVMBlockContext(engine, header, parentNrOrHash.RequireCanonical, dbtx, api._blockReader)
|
|
txCtx := core.NewEVMTxContext(msg)
|
|
|
|
if useParent {
|
|
blockCtx.GasLimit = math.MaxUint64
|
|
blockCtx.MaxGasLimit = true
|
|
}
|
|
ibs.Reset()
|
|
// 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()).AddBlobGas(msg.BlobGas())
|
|
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.SetTxContext(*args.txHash, header.Hash(), txIndex)
|
|
} else {
|
|
ibs.SetTxContext(libcommon.Hash{}, header.Hash(), txIndex)
|
|
}
|
|
execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, gasBailout /* gasBailout */)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("first run for txIndex %d error: %w", txIndex, err)
|
|
}
|
|
traceResult.Output = libcommon.CopyBytes(execResult.ReturnData)
|
|
if traceTypeStateDiff {
|
|
initialIbs := state.New(cloneReader)
|
|
sdMap := make(map[libcommon.Address]*StateDiffAccount)
|
|
traceResult.StateDiff = sdMap
|
|
sd := &StateDiff{sdMap: sdMap}
|
|
if err = ibs.FinalizeTx(evm.ChainRules(), sd); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
sd.CompareStates(initialIbs, ibs)
|
|
if err = ibs.CommitBlock(evm.ChainRules(), cachedWriter); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
} else {
|
|
if err = ibs.FinalizeTx(evm.ChainRules(), noop); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if err = ibs.CommitBlock(evm.ChainRules(), cachedWriter); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
if !traceTypeTrace {
|
|
traceResult.Trace = []*ParityTrace{}
|
|
}
|
|
results = append(results, traceResult)
|
|
// When txIndexNeeded is not -1, we are tracing specific transaction in the block and not the entire block, so we stop after we've traced
|
|
// the required transaction
|
|
if txIndexNeeded != -1 && txIndex == txIndexNeeded {
|
|
break
|
|
}
|
|
}
|
|
return results, ibs, nil
|
|
}
|
|
|
|
// RawTransaction implements trace_rawTransaction.
|
|
func (api *TraceAPIImpl) RawTransaction(ctx context.Context, txHash libcommon.Hash, traceTypes []string) ([]interface{}, error) {
|
|
var stub []interface{}
|
|
return stub, fmt.Errorf(NotImplemented, "trace_rawTransaction")
|
|
}
|