erigon-pulse/cmd/rpcdaemon/commands/trace_adhoc.go
2021-10-19 16:26:35 +01:00

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")
}