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