From 350d7fe7a9bb0289b62b4b065e32fd7a69887998 Mon Sep 17 00:00:00 2001 From: ledgerwatch Date: Sun, 8 Aug 2021 13:28:03 +0100 Subject: [PATCH] Adding support for vmTrace into trace_ routines (#2497) * VmTrace * Fixes to gasCall, beginning of vmTrace * Fix opcode tracer * Add mem and store, enable vmTrace for all methods * Fix lint * More nuances and debugging| * More fixes * Fix for trace_callMany * Fix for trace_callMany Co-authored-by: Alexey Sharp --- cmd/hack/hack.go | 29 ++- cmd/rpcdaemon/commands/trace_adhoc.go | 233 +++++++++++++++++++---- cmd/rpctest/rpctest/request_generator.go | 2 +- cmd/rpctest/rpctest/utils.go | 14 +- cmd/state/commands/opcode_tracer.go | 4 +- core/evm.go | 1 + core/vm/evm.go | 43 +++-- core/vm/logger.go | 14 +- core/vm/logger_json.go | 6 +- core/vm/memory.go | 2 +- core/vm/operations_acl.go | 22 ++- eth/stagedsync/stage_call_traces.go | 10 +- eth/tracers/tracer.go | 6 +- eth/tracers/tracer_test.go | 4 +- turbo/transactions/tracing.go | 8 +- 15 files changed, 301 insertions(+), 97 deletions(-) diff --git a/cmd/hack/hack.go b/cmd/hack/hack.go index b6f8bcc00d4..77a21acf81c 100644 --- a/cmd/hack/hack.go +++ b/cmd/hack/hack.go @@ -1362,43 +1362,40 @@ func extractCode(chaindata string) error { func iterateOverCode(chaindata string) error { db := mdbx.MustOpen(chaindata) defer db.Close() - var contractCount int - var contractKeyTotalLength int - var contractValTotalLength int - var codeHashTotalLength int - var codeTotalLength int // Total length of all byte code (just to illustrate iterating) + hashes := make(map[common.Hash][]byte) if err1 := db.View(context.Background(), func(tx kv.Tx) error { - c, err := tx.Cursor(kv.PlainContractCode) + c, err := tx.Cursor(kv.Code) if err != nil { return err } - // This is a mapping of contractAddress + incarnation => CodeHash + // This is a mapping of CodeHash => Byte code for k, v, err := c.First(); k != nil; k, v, err = c.Next() { if err != nil { return err } - contractKeyTotalLength += len(k) - contractValTotalLength += len(v) + if len(v) > 0 && v[0] == 0xef { + fmt.Printf("Found code with hash %x: %x\n", k, v) + hashes[common.BytesToHash(k)] = common.CopyBytes(v) + } } - c, err = tx.Cursor(kv.Code) + c, err = tx.Cursor(kv.PlainContractCode) if err != nil { return err } - // This is a mapping of CodeHash => Byte code + // This is a mapping of contractAddress + incarnation => CodeHash for k, v, err := c.First(); k != nil; k, v, err = c.Next() { if err != nil { return err } - codeHashTotalLength += len(k) - codeTotalLength += len(v) - contractCount++ + hash := common.BytesToHash(v) + if code, ok := hashes[hash]; ok { + fmt.Printf("address: %x: %x\n", k[:20], code) + } } return nil }); err1 != nil { return err1 } - fmt.Printf("contractCount: %d,contractKeyTotalLength: %d, contractValTotalLength: %d, codeHashTotalLength: %d, codeTotalLength: %d\n", - contractCount, contractKeyTotalLength, contractValTotalLength, codeHashTotalLength, codeTotalLength) return nil } diff --git a/cmd/rpcdaemon/commands/trace_adhoc.go b/cmd/rpcdaemon/commands/trace_adhoc.go index cb360c14278..b652e150cf1 100644 --- a/cmd/rpcdaemon/commands/trace_adhoc.go +++ b/cmd/rpcdaemon/commands/trace_adhoc.go @@ -7,6 +7,7 @@ import ( "fmt" "math" "math/big" + "strings" "time" "github.com/holiman/uint256" @@ -63,7 +64,7 @@ type TraceCallResult struct { Output hexutil.Bytes `json:"output"` StateDiff map[common.Address]*StateDiffAccount `json:"stateDiff"` Trace []*ParityTrace `json:"trace"` - VmTrace *TraceCallVmTrace `json:"vmTrace"` + VmTrace *VmTrace `json:"vmTrace"` TransactionHash *common.Hash `json:"transactionHash,omitempty"` } @@ -95,8 +96,37 @@ type StateDiffStorage struct { To common.Hash `json:"to"` } -// TraceCallVmTrace is the part of `trace_call` response that is under "vmTrace" tag -type TraceCallVmTrace struct { +// 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 @@ -188,15 +218,45 @@ func (args *TraceCallParam) ToMessage(globalGasCap uint64, baseFee *uint256.Int) // OpenEthereum-style tracer type OeTracer struct { - r *TraceCallResult - traceAddr []int - traceStack []*ParityTrace - lastTop *ParityTrace - precompile bool // Whether the last CaptureStart was called with `precompile = true` - compat bool // Bug for bug compatibility mode + 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, codeHash common.Hash) error { +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 + } + vmTrace.Code = code + } if precompile { ot.precompile = true return nil @@ -266,7 +326,23 @@ func (ot *OeTracer) CaptureStart(depth int, from common.Address, to common.Addre return nil } -func (ot *OeTracer) CaptureEnd(depth int, output []byte, gasUsed uint64, t time.Duration, err error) error { +func (ot *OeTracer) CaptureEnd(depth int, output []byte, startGas, endGas uint64, t time.Duration, err error) error { + topTrace := ot.traceStack[len(ot.traceStack)-1] + 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 && topTrace.Type == CALL { + 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 @@ -274,8 +350,6 @@ func (ot *OeTracer) CaptureEnd(depth int, output []byte, gasUsed uint64, t time. if depth == 0 { ot.r.Output = common.CopyBytes(output) } - topTrace := ot.traceStack[len(ot.traceStack)-1] - ot.lastTop = topTrace ignoreError := false if ot.compat { ignoreError = depth == 0 && topTrace.Type == CREATE @@ -313,10 +387,10 @@ func (ot *OeTracer) CaptureEnd(depth int, output []byte, gasUsed uint64, t time. switch topTrace.Type { case CALL: topTrace.Result.(*TraceResult).GasUsed = new(hexutil.Big) - topTrace.Result.(*TraceResult).GasUsed.ToInt().SetUint64(gasUsed) + topTrace.Result.(*TraceResult).GasUsed.ToInt().SetUint64(startGas - endGas) case CREATE: topTrace.Result.(*CreateTraceResult).GasUsed = new(hexutil.Big) - topTrace.Result.(*CreateTraceResult).GasUsed.ToInt().SetUint64(gasUsed) + topTrace.Result.(*CreateTraceResult).GasUsed.ToInt().SetUint64(startGas - endGas) } } ot.traceStack = ot.traceStack[:len(ot.traceStack)-1] @@ -327,6 +401,98 @@ func (ot *OeTracer) CaptureEnd(depth int, output []byte, gasUsed uint64, t time. } 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 { + // 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: + 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)} + } + } + ot.lastVmOp = &VmTraceOp{} + 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.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.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.SSTORE: + ot.lastVmOp.Ex.Store = &VmTraceStore{Key: st.Back(0).String(), Val: st.Back(1).String()} + } + } return nil } @@ -628,10 +794,6 @@ func (api *TraceAPIImpl) ReplayBlockTransactions(ctx context.Context, blockNrOrH return nil, err } - if traceTypeVmTrace { - return nil, fmt.Errorf("vmTrace not implemented yet") - } - result := make([]*TraceCallResult, len(traces)) for i, trace := range traces { tr := &TraceCallResult{} @@ -716,9 +878,12 @@ func (api *TraceAPIImpl) Call(ctx context.Context, args TraceCallParam, traceTyp 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 { + if traceTypeTrace || traceTypeVmTrace { ot.r = traceResult ot.traceAddr = []int{} } @@ -769,9 +934,6 @@ func (api *TraceAPIImpl) Call(ctx context.Context, args TraceCallParam, traceTyp initialIbs := state.New(stateReader) sd.CompareStates(initialIbs, ibs) } - if traceTypeVmTrace { - return nil, fmt.Errorf("vmTrace not implemented yet") - } // If the timer caused an abort, return an appropriate error message if evm.Cancelled() { @@ -904,6 +1066,11 @@ func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, msgs []type defer cancel() results := []*TraceCallResult{} + useParent := false + if header == nil { + header = parentHeader + useParent = true + } for txIndex, msg := range msgs { if err := common.Stopped(ctx.Done()); err != nil { return nil, err @@ -924,21 +1091,22 @@ func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, msgs []type } } vmConfig := vm.Config{} - if traceTypeTrace && (txIndexNeeded == -1 || txIndex == txIndexNeeded) { + if (traceTypeTrace && (txIndexNeeded == -1 || txIndex == txIndexNeeded)) || traceTypeVmTrace { var ot OeTracer ot.compat = api.compatibility ot.r = traceResult - ot.traceAddr = []int{} + 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. - useParent := false - if header == nil { - header = parentHeader - useParent = true - } blockCtx, txCtx := transactions.GetEvmContext(msg, header, parentNrOrHash.RequireCanonical, dbtx) if useParent { blockCtx.GasLimit = math.MaxUint64 @@ -987,9 +1155,8 @@ func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, msgs []type return nil, err } } - - if traceTypeVmTrace { - return nil, fmt.Errorf("vmTrace not implemented yet") + if !traceTypeTrace { + traceResult.Trace = []*ParityTrace{} } results = append(results, traceResult) } diff --git a/cmd/rpctest/rpctest/request_generator.go b/cmd/rpctest/rpctest/request_generator.go index 6ee9bb45f56..bf9a513fb15 100644 --- a/cmd/rpctest/rpctest/request_generator.go +++ b/cmd/rpctest/rpctest/request_generator.go @@ -137,7 +137,7 @@ func (g *RequestGenerator) traceCallMany(from []common.Address, to []*common.Add if len(data[i]) > 0 { fmt.Fprintf(&sb, `,"data":"%s"`, data[i]) } - fmt.Fprintf(&sb, `},["trace", "stateDiff"]]`) + fmt.Fprintf(&sb, `},["trace", "stateDiff", "vmTrace"]]`) } fmt.Fprintf(&sb, `],"0x%x"], "id":%d}`, bn, g.reqID) return sb.String() diff --git a/cmd/rpctest/rpctest/utils.go b/cmd/rpctest/rpctest/utils.go index 5e9633c64ae..7bade8a5161 100644 --- a/cmd/rpctest/rpctest/utils.go +++ b/cmd/rpctest/rpctest/utils.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "net/http" + "os" "strings" "time" @@ -213,9 +214,16 @@ func requestAndCompare(request string, methodName string, errCtx string, reqGen errs.Flush() // nolint:errcheck // Keep going } else { - fmt.Printf("TG response=================================\n%s\n", res.Response) - fmt.Printf("G response=================================\n%s\n", resg.Response) - return fmt.Errorf("different results for method %s, errCtx %s: %v\n", methodName, errCtx, err) + reqFile, _ := os.Create("request.json") //nolint:errcheck + reqFile.Write([]byte(request)) //nolint:errcheck + reqFile.Close() //nolint:errcheck + erigonRespFile, _ := os.Create("erigon-response.json") //nolint:errcheck + erigonRespFile.Write(res.Response) //nolint:errcheck + erigonRespFile.Close() //nolint:errcheck + oeRespFile, _ := os.Create("oe-response.json") //nolint:errcheck + oeRespFile.Write(resg.Response) //nolint:errcheck + oeRespFile.Close() //nolint:errcheck + return fmt.Errorf("different results for method %s, errCtx %s: %v\nRequest in file request.json, Erigon response in file erigon-response.json, Geth/OE response in file oe-response.json", methodName, errCtx, err) } } } diff --git a/cmd/state/commands/opcode_tracer.go b/cmd/state/commands/opcode_tracer.go index edfd3fa64ba..ed0fec840cc 100644 --- a/cmd/state/commands/opcode_tracer.go +++ b/cmd/state/commands/opcode_tracer.go @@ -154,7 +154,7 @@ type blockTxs struct { Txs slicePtrTx } -func (ot *opcodeTracer) CaptureStart(depth int, from common.Address, to common.Address, precompile bool, create bool, calltype vm.CallType, input []byte, gas uint64, value *big.Int, codeHash common.Hash) error { +func (ot *opcodeTracer) 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 { //fmt.Fprint(ot.summary, ot.lastLine) // When a CaptureStart is called, a Tx is starting. Create its entry in our list and initialize it with the partial data available @@ -184,7 +184,7 @@ func (ot *opcodeTracer) CaptureStart(depth int, from common.Address, to common.A return nil } -func (ot *opcodeTracer) CaptureEnd(depth int, output []byte, gasUsed uint64, t time.Duration, err error) error { +func (ot *opcodeTracer) CaptureEnd(depth int, output []byte, startGas, endGas uint64, t time.Duration, err error) error { // When a CaptureEnd is called, a Tx has finished. Pop our stack ls := len(ot.stack) currentEntry := ot.stack[ls-1] diff --git a/core/evm.go b/core/evm.go index 6418dec206f..b0e847ab094 100644 --- a/core/evm.go +++ b/core/evm.go @@ -43,6 +43,7 @@ func NewEVMBlockContext(header *types.Header, getHeader func(hash common.Hash, n panic(fmt.Errorf("header.BaseFee higher than 2^256-1")) } } + if checkTEVM == nil { checkTEVM = func(_ common.Hash) (bool, error) { return false, nil diff --git a/core/vm/evm.go b/core/vm/evm.go index 19cdbb23944..ffaa046b221 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -212,11 +212,15 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } } p, isPrecompile := evm.precompile(addr) + var code []byte + if !isPrecompile { + code = evm.IntraBlockState.GetCode(addr) + } // Capture the tracer start/end events in debug mode if evm.Config.Debug { - _ = evm.Config.Tracer.CaptureStart(evm.depth, caller.Address(), addr, isPrecompile, false /* create */, CALLT, input, gas, value.ToBig(), common.Hash{}) + _ = evm.Config.Tracer.CaptureStart(evm.depth, caller.Address(), addr, isPrecompile, false /* create */, CALLT, input, gas, value.ToBig(), code) defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters - evm.Config.Tracer.CaptureEnd(evm.depth, ret, startGas-gas, time.Since(startTime), err) //nolint:errcheck + evm.Config.Tracer.CaptureEnd(evm.depth, ret, startGas, gas, time.Since(startTime), err) //nolint:errcheck }(gas, time.Now()) } @@ -237,7 +241,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. - code := evm.IntraBlockState.GetCode(addr) if len(code) == 0 { ret, err = nil, nil // gas is unchanged } else { @@ -295,11 +298,15 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, return nil, gas, ErrInsufficientBalance } p, isPrecompile := evm.precompile(addr) + var code []byte + if !isPrecompile { + code = evm.IntraBlockState.GetCode(addr) + } // Capture the tracer start/end events in debug mode if evm.Config.Debug { - _ = evm.Config.Tracer.CaptureStart(evm.depth, caller.Address(), addr, isPrecompile, false /* create */, CALLCODET, input, gas, value.ToBig(), common.Hash{}) + _ = evm.Config.Tracer.CaptureStart(evm.depth, caller.Address(), addr, isPrecompile, false /* create */, CALLCODET, input, gas, value.ToBig(), code) defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters - evm.Config.Tracer.CaptureEnd(evm.depth, ret, startGas-gas, time.Since(startTime), err) //nolint:errcheck + evm.Config.Tracer.CaptureEnd(evm.depth, ret, startGas, gas, time.Since(startTime), err) //nolint:errcheck }(gas, time.Now()) } var ( @@ -320,7 +327,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, if err == nil { contract := NewContract(caller, AccountRef(caller.Address()), value, gas, evm.Config.SkipAnalysis, isTEVM) - contract.SetCallCode(&addrCopy, codeHash, evm.IntraBlockState.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, codeHash, code) ret, err = run(evm, contract, input, false) gas = contract.Gas } @@ -348,11 +355,15 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by return nil, gas, ErrDepth } p, isPrecompile := evm.precompile(addr) + var code []byte + if !isPrecompile { + code = evm.IntraBlockState.GetCode(addr) + } // Capture the tracer start/end events in debug mode if evm.Config.Debug { - _ = evm.Config.Tracer.CaptureStart(evm.depth, caller.Address(), addr, isPrecompile, false /* create */, DELEGATECALLT, input, gas, big.NewInt(-1), common.Hash{}) + _ = evm.Config.Tracer.CaptureStart(evm.depth, caller.Address(), addr, isPrecompile, false /* create */, DELEGATECALLT, input, gas, big.NewInt(-1), code) defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters - evm.Config.Tracer.CaptureEnd(evm.depth, ret, startGas-gas, time.Since(startTime), err) //nolint:errcheck + evm.Config.Tracer.CaptureEnd(evm.depth, ret, startGas, gas, time.Since(startTime), err) //nolint:errcheck }(gas, time.Now()) } snapshot := evm.IntraBlockState.Snapshot() @@ -369,7 +380,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by if err == nil { contract := NewContract(caller, AccountRef(caller.Address()), nil, gas, evm.Config.SkipAnalysis, isTEVM).AsDelegate() - contract.SetCallCode(&addrCopy, evm.IntraBlockState.GetCodeHash(addrCopy), evm.IntraBlockState.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, codeHash, code) ret, err = run(evm, contract, input, false) gas = contract.Gas } @@ -396,11 +407,15 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte return nil, gas, ErrDepth } p, isPrecompile := evm.precompile(addr) + var code []byte + if !isPrecompile { + code = evm.IntraBlockState.GetCode(addr) + } // Capture the tracer start/end events in debug mode if evm.Config.Debug { - _ = evm.Config.Tracer.CaptureStart(evm.depth, caller.Address(), addr, isPrecompile, false, STATICCALLT, input, gas, big.NewInt(-2), common.Hash{}) + _ = evm.Config.Tracer.CaptureStart(evm.depth, caller.Address(), addr, isPrecompile, false, STATICCALLT, input, gas, big.NewInt(-2), code) defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters - evm.Config.Tracer.CaptureEnd(evm.depth, ret, startGas-gas, time.Since(startTime), err) //nolint:errcheck + evm.Config.Tracer.CaptureEnd(evm.depth, ret, startGas, gas, time.Since(startTime), err) //nolint:errcheck }(gas, time.Now()) } // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped. @@ -431,7 +446,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte if err == nil { contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas, evm.Config.SkipAnalysis, isTEVM) - contract.SetCallCode(&addrCopy, evm.IntraBlockState.GetCodeHash(addrCopy), evm.IntraBlockState.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, codeHash, code) // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in Homestead this also counts for code storage gas errors. @@ -473,9 +488,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, return nil, common.Address{}, gas, ErrInsufficientBalance } if evm.Config.Debug || evm.Config.EnableTEMV { - _ = evm.Config.Tracer.CaptureStart(evm.depth, caller.Address(), address, false /* precompile */, true /* create */, calltype, codeAndHash.code, gas, value.ToBig(), codeAndHash.Hash()) + _ = evm.Config.Tracer.CaptureStart(evm.depth, caller.Address(), address, false /* precompile */, true /* create */, calltype, codeAndHash.code, gas, value.ToBig(), nil) defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters - evm.Config.Tracer.CaptureEnd(evm.depth, ret, startGas-gas, time.Since(startTime), err) //nolint:errcheck + evm.Config.Tracer.CaptureEnd(evm.depth, ret, startGas, gas, time.Since(startTime), err) //nolint:errcheck }(gas, time.Now()) } nonce := evm.IntraBlockState.GetNonce(caller.Address()) diff --git a/core/vm/logger.go b/core/vm/logger.go index fb30cda10f3..fa7448f2d2a 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -120,10 +120,10 @@ const ( // Note that reference types are actual VM data structures; make copies // if you need to retain them beyond the current call. type Tracer interface { - CaptureStart(depth int, from common.Address, to common.Address, precompile bool, create bool, callType CallType, input []byte, gas uint64, value *big.Int, codeHash common.Hash) error + CaptureStart(depth int, from common.Address, to common.Address, precompile bool, create bool, callType CallType, input []byte, gas uint64, value *big.Int, code []byte) error CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *stack.Stack, rData []byte, contract *Contract, depth int, err error) error CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *stack.Stack, contract *Contract, depth int, err error) error - CaptureEnd(depth int, output []byte, gasUsed uint64, t time.Duration, err error) error + CaptureEnd(depth int, output []byte, startGas, endGas uint64, t time.Duration, err error) error CaptureSelfDestruct(from common.Address, to common.Address, value *big.Int) CaptureAccountRead(account common.Address) error CaptureAccountWrite(account common.Address) error @@ -155,7 +155,7 @@ func NewStructLogger(cfg *LogConfig) *StructLogger { } // CaptureStart implements the Tracer interface to initialize the tracing operation. -func (l *StructLogger) CaptureStart(depth int, from common.Address, to common.Address, precompile bool, create bool, calltype CallType, input []byte, gas uint64, value *big.Int, codeHash common.Hash) error { +func (l *StructLogger) CaptureStart(depth int, from common.Address, to common.Address, precompile bool, create bool, calltype CallType, input []byte, gas uint64, value *big.Int, code []byte) error { return nil } @@ -227,7 +227,7 @@ func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost ui } // CaptureEnd is called after the call finishes to finalize the tracing. -func (l *StructLogger) CaptureEnd(depth int, output []byte, gasUsed uint64, t time.Duration, err error) error { +func (l *StructLogger) CaptureEnd(depth int, output []byte, startGas, endGas uint64, t time.Duration, err error) error { if depth != 0 { return nil } @@ -324,7 +324,7 @@ func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger { return l } -func (t *mdLogger) CaptureStart(_ int, from common.Address, to common.Address, preimage bool, create bool, calltype CallType, input []byte, gas uint64, value *big.Int, codeHash common.Hash) error { //nolint:interfacer +func (t *mdLogger) CaptureStart(_ int, from common.Address, to common.Address, preimage bool, create bool, calltype CallType, input []byte, gas uint64, value *big.Int, code []byte) error { //nolint:interfacer if !create { fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n", from.String(), to.String(), @@ -369,9 +369,9 @@ func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64 return nil } -func (t *mdLogger) CaptureEnd(_ int, output []byte, gasUsed uint64, tm time.Duration, err error) error { +func (t *mdLogger) CaptureEnd(_ int, output []byte, startGas, endGas uint64, tm time.Duration, err error) error { fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n", - output, gasUsed, err) + output, startGas-endGas, err) return nil } diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go index ed73bbc4629..e131c4b5518 100644 --- a/core/vm/logger_json.go +++ b/core/vm/logger_json.go @@ -42,7 +42,7 @@ func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger { return l } -func (l *JSONLogger) CaptureStart(depth int, from common.Address, to common.Address, precompile bool, create bool, calltype CallType, input []byte, gas uint64, value *big.Int, codeHash common.Hash) error { +func (l *JSONLogger) CaptureStart(depth int, from common.Address, to common.Address, precompile bool, create bool, calltype CallType, input []byte, gas uint64, value *big.Int, code []byte) error { return nil } @@ -79,7 +79,7 @@ func (l *JSONLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint } // CaptureEnd is triggered at end of execution. -func (l *JSONLogger) CaptureEnd(depth int, output []byte, gasUsed uint64, t time.Duration, err error) error { +func (l *JSONLogger) CaptureEnd(depth int, output []byte, startGas, endGas uint64, t time.Duration, err error) error { type endLog struct { Output string `json:"output"` GasUsed math.HexOrDecimal64 `json:"gasUsed"` @@ -93,7 +93,7 @@ func (l *JSONLogger) CaptureEnd(depth int, output []byte, gasUsed uint64, t time if err != nil { errMsg = err.Error() } - return l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, errMsg}) + return l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(startGas - endGas), t, errMsg}) } func (l *JSONLogger) CaptureSelfDestruct(from common.Address, to common.Address, value *big.Int) { diff --git a/core/vm/memory.go b/core/vm/memory.go index 2f1689b9ff5..3ce2837957b 100644 --- a/core/vm/memory.go +++ b/core/vm/memory.go @@ -76,7 +76,7 @@ func (m *Memory) GetCopy(offset, size uint64) (cpy []byte) { if uint64(len(m.store)) > offset { cpy = make([]byte, size) - copy(cpy, m.store[offset:offset+size]) + copy(cpy, m.store[offset:]) return } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 771727acb2b..599963ee36c 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -167,10 +167,15 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { return func(evm *EVM, contract *Contract, stack *stack.Stack, mem *Memory, memorySize uint64) (uint64, error) { addr := common.Address(stack.Back(1).Bytes20()) // Check slot presence in the access list - if !evm.IntraBlockState.AddressInAccessList(addr) { + warmAccess := evm.IntraBlockState.AddressInAccessList(addr) + // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so + // the cost to charge for cold access, if any, is Cold - Warm + coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 + if !warmAccess { evm.IntraBlockState.AddAddressToAccessList(addr) - // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost - if !contract.UseGas(params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929) { + // Charge the remaining difference here already, to correctly calculate available + // gas for call + if !contract.UseGas(coldCost) { return 0, ErrOutOfGas } } @@ -179,7 +184,16 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { // - transfer value // - memory expansion // - 63/64ths rule - return oldCalculator(evm, contract, stack, mem, memorySize) + gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + if warmAccess || err != nil { + return gas, err + } + // In case of a cold access, we temporarily add the cold charge back, and also + // add it to the returned gas. By adding it to the return, it will be charged + // outside of this function, as part of the dynamic gas, and that will make it + // also become correctly reported to tracers. + contract.Gas += coldCost + return gas + coldCost, nil } } diff --git a/eth/stagedsync/stage_call_traces.go b/eth/stagedsync/stage_call_traces.go index f9c385e0ec3..65c1f458ba2 100644 --- a/eth/stagedsync/stage_call_traces.go +++ b/eth/stagedsync/stage_call_traces.go @@ -16,9 +16,9 @@ import ( "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/common/dbutils" "github.com/ledgerwatch/erigon/common/etl" - "github.com/ledgerwatch/erigon/core/types/accounts" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/core/vm/stack" + "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/ethdb/bitmapdb" "github.com/ledgerwatch/erigon/ethdb/prune" "github.com/ledgerwatch/log/v3" @@ -343,7 +343,7 @@ func NewCallTracer(hasTEVM func(contractHash common.Hash) (bool, error)) *CallTr } } -func (ct *CallTracer) CaptureStart(depth int, from common.Address, to common.Address, precompile bool, create bool, calltype vm.CallType, input []byte, gas uint64, value *big.Int, codeHash common.Hash) error { +func (ct *CallTracer) 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 { ct.froms[from] = struct{}{} created, ok := ct.tos[to] @@ -352,8 +352,8 @@ func (ct *CallTracer) CaptureStart(depth int, from common.Address, to common.Add } if !created && create { - if !accounts.IsEmptyCodeHash(codeHash) && ct.hasTEVM != nil { - has, err := ct.hasTEVM(codeHash) + if len(code) > 0 && ct.hasTEVM != nil { + has, err := ct.hasTEVM(common.BytesToHash(crypto.Keccak256(code))) if !has { ct.tos[to] = true } @@ -371,7 +371,7 @@ func (ct *CallTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, co func (ct *CallTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *stack.Stack, contract *vm.Contract, depth int, err error) error { return nil } -func (ct *CallTracer) CaptureEnd(depth int, output []byte, gasUsed uint64, t time.Duration, err error) error { +func (ct *CallTracer) CaptureEnd(depth int, output []byte, startGas, endGas uint64, t time.Duration, err error) error { return nil } func (ct *CallTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *big.Int) { diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 4e109413b67..4584e801d2f 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -545,7 +545,7 @@ func wrapError(context string, err error) error { } // CaptureStart implements the Tracer interface to initialize the tracing operation. -func (jst *Tracer) CaptureStart(depth int, from common.Address, to common.Address, precompile bool, create bool, calltype vm.CallType, input []byte, gas uint64, value *big.Int, codeHash common.Hash) error { +func (jst *Tracer) 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 depth != 0 { return nil } @@ -633,13 +633,13 @@ func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost } // CaptureEnd is called after the call finishes to finalize the tracing. -func (jst *Tracer) CaptureEnd(depth int, output []byte, gasUsed uint64, t time.Duration, err error) error { +func (jst *Tracer) CaptureEnd(depth int, output []byte, startGas, endGas uint64, t time.Duration, err error) error { if depth != 0 { return nil } jst.ctx["output"] = output jst.ctx["time"] = t.String() - jst.ctx["gasUsed"] = gasUsed + jst.ctx["gasUsed"] = startGas - endGas if err != nil { jst.ctx["error"] = err.Error() diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 44e509f4e29..1be589ed252 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -71,11 +71,11 @@ func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) { contract := vm.NewContract(account{}, account{}, value, startGas, false, false) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} - if err := tracer.CaptureStart(0, contract.Caller(), contract.Address(), false, false, vm.CallType(0), []byte{}, startGas, big.NewInt(int64(value.Uint64())), contract.CodeHash); err != nil { + if err := tracer.CaptureStart(0, contract.Caller(), contract.Address(), false, false, vm.CallType(0), []byte{}, startGas, big.NewInt(int64(value.Uint64())), contract.Code); err != nil { return nil, err } ret, err := env.Interpreter().Run(contract, []byte{}, false) - if err1 := tracer.CaptureEnd(0, ret, startGas-contract.Gas, 1, err); err1 != nil { + if err1 := tracer.CaptureEnd(0, ret, startGas, contract.Gas, 1, err); err1 != nil { return nil, err1 } if err != nil { diff --git a/turbo/transactions/tracing.go b/turbo/transactions/tracing.go index a01a6da6f30..a005581fcff 100644 --- a/turbo/transactions/tracing.go +++ b/turbo/transactions/tracing.go @@ -202,7 +202,7 @@ func NewJsonStreamLogger(cfg *vm.LogConfig, ctx context.Context, stream *jsonite } // CaptureStart implements the Tracer interface to initialize the tracing operation. -func (l *JsonStreamLogger) CaptureStart(depth int, from common.Address, to common.Address, precompile bool, create bool, calltype vm.CallType, input []byte, gas uint64, value *big.Int, codeHash common.Hash) error { +func (l *JsonStreamLogger) 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 { return nil } @@ -270,7 +270,9 @@ func (l *JsonStreamLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, ga if err != nil { l.stream.WriteMore() l.stream.WriteObjectField("error") - l.stream.WriteString(err.Error()) + l.stream.WriteObjectStart() + l.stream.WriteObjectEnd() + //l.stream.WriteString(err.Error()) } if !l.cfg.DisableStack { l.stream.WriteMore() @@ -334,7 +336,7 @@ func (l *JsonStreamLogger) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, ga } // CaptureEnd is called after the call finishes to finalize the tracing. -func (l *JsonStreamLogger) CaptureEnd(depth int, output []byte, gasUsed uint64, t time.Duration, err error) error { +func (l *JsonStreamLogger) CaptureEnd(depth int, output []byte, startGas, endGas uint64, t time.Duration, err error) error { return nil }