diff --git a/eth/tracers/api.go b/eth/tracers/api.go index d9171f4efd85..53570fcddb15 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -194,6 +194,7 @@ type StdTraceConfig struct { // txTraceResult is the result of a single transaction trace. type txTraceResult struct { + TxHash common.Hash `json:"txHash"` // Hash of the transaction being traced Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer Error string `json:"error,omitempty"` // Trace failure produced by the tracer } @@ -280,9 +281,10 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config for i, tx := range task.block.Transactions() { msg, _ := tx.AsMessage(signer, task.block.BaseFee()) txctx := &Context{ - BlockHash: task.block.Hash(), - TxIndex: i, - TxHash: tx.Hash(), + BlockNumber: task.block.NumberU64(), + BlockHash: task.block.Hash(), + TxIndex: i, + TxHash: tx.Hash(), } l1DataFee, err := fees.CalculateL1DataFee(tx, task.statedb, api.backend.ChainConfig(), task.block.Number()) @@ -613,6 +615,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac } blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), api.backend.ChainConfig(), nil) blockHash := block.Hash() + blockNumber := block.NumberU64() for th := 0; th < threads; th++ { pend.Add(1) go func() { @@ -621,23 +624,33 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac for task := range jobs { msg, _ := txs[task.index].AsMessage(signer, block.BaseFee()) txctx := &Context{ - BlockHash: blockHash, - TxIndex: task.index, - TxHash: txs[task.index].Hash(), + BlockNumber: blockNumber, + BlockHash: blockHash, + TxIndex: task.index, + TxHash: txs[task.index].Hash(), } l1DataFee, err := fees.CalculateL1DataFee(txs[task.index], task.statedb, api.backend.ChainConfig(), block.Number()) if err != nil { // though it's not a "tracing error", we still need to put it here - results[task.index] = &txTraceResult{Error: err.Error()} + results[task.index] = &txTraceResult{ + TxHash: txs[task.index].Hash(), + Error: err.Error(), + } continue } res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config, l1DataFee) if err != nil { - results[task.index] = &txTraceResult{Error: err.Error()} + results[task.index] = &txTraceResult{ + TxHash: txs[task.index].Hash(), + Error: err.Error(), + } continue } - results[task.index] = &txTraceResult{Result: res} + results[task.index] = &txTraceResult{ + TxHash: txs[task.index].Hash(), + Result: res, + } } }() } diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index 0a20d2100205..355c2ded9d9b 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -55,7 +55,7 @@ type fourByteTracer struct { // newFourByteTracer returns a native go tracer which collects // 4 byte-identifiers of a tx, and implements vm.EVMLogger. -func newFourByteTracer() tracers.Tracer { +func newFourByteTracer(ctx *tracers.Context) tracers.Tracer { t := &fourByteTracer{ ids: make(map[string]int), } diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 44be8c5690db..9fad7e1e0b16 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -56,7 +56,7 @@ type callTracer struct { // newCallTracer returns a native go tracer which tracks // call frames of a tx, and implements vm.EVMLogger. -func newCallTracer() tracers.Tracer { +func newCallTracer(ctx *tracers.Context) tracers.Tracer { // First callframe contains tx context info // and is populated on start and end. t := &callTracer{callstack: make([]callFrame, 1)} diff --git a/eth/tracers/native/call_flat.go b/eth/tracers/native/call_flat.go new file mode 100644 index 000000000000..b82398c78a1d --- /dev/null +++ b/eth/tracers/native/call_flat.go @@ -0,0 +1,318 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package native + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "strings" + + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/common/hexutil" + "github.com/scroll-tech/go-ethereum/core/vm" + "github.com/scroll-tech/go-ethereum/eth/tracers" +) + +//go:generate go run github.com/fjl/gencodec -type flatCallAction -field-override flatCallActionMarshaling -out gen_flatcallaction_json.go +//go:generate go run github.com/fjl/gencodec -type flatCallResult -field-override flatCallResultMarshaling -out gen_flatcallresult_json.go + +func init() { + // tracers.DefaultDirectory.Register("flatCallTracer", newFlatCallTracer, false) + register("flatCallTracer", newFlatCallTracer) +} + +var parityErrorMapping = map[string]string{ + "contract creation code storage out of gas": "Out of gas", + "out of gas": "Out of gas", + "gas uint64 overflow": "Out of gas", + "max code size exceeded": "Out of gas", + "invalid jump destination": "Bad jump destination", + "execution reverted": "Reverted", + "return data out of bounds": "Out of bounds", + "stack limit reached 1024 (1023)": "Out of stack", + "precompiled failed": "Built-in failed", + "invalid input length": "Built-in failed", +} + +var parityErrorMappingStartingWith = map[string]string{ + "invalid opcode:": "Bad instruction", + "stack underflow": "Stack underflow", +} + +// flatCallFrame is a standalone callframe. +type flatCallFrame struct { + Action flatCallAction `json:"action"` + BlockHash *common.Hash `json:"blockHash"` + BlockNumber uint64 `json:"blockNumber"` + Error string `json:"error,omitempty"` + Result *flatCallResult `json:"result,omitempty"` + Subtraces int `json:"subtraces"` + TraceAddress []int `json:"traceAddress"` + TransactionHash *common.Hash `json:"transactionHash"` + TransactionPosition uint64 `json:"transactionPosition"` + Type string `json:"type"` +} + +type flatCallAction struct { + Author *common.Address `json:"author,omitempty"` + RewardType string `json:"rewardType,omitempty"` + SelfDestructed *common.Address `json:"address,omitempty"` + Balance *big.Int `json:"balance,omitempty"` + CallType string `json:"callType,omitempty"` + CreationMethod string `json:"creationMethod,omitempty"` + From *common.Address `json:"from,omitempty"` + Gas *uint64 `json:"gas,omitempty"` + Init *[]byte `json:"init,omitempty"` + Input *[]byte `json:"input,omitempty"` + RefundAddress *common.Address `json:"refundAddress,omitempty"` + To *common.Address `json:"to,omitempty"` + Value *big.Int `json:"value,omitempty"` +} + +type flatCallActionMarshaling struct { + Balance *hexutil.Big + Gas *hexutil.Uint64 + Init *hexutil.Bytes + Input *hexutil.Bytes + Value *hexutil.Big +} + +type flatCallResult struct { + Address *common.Address `json:"address,omitempty"` + Code *[]byte `json:"code,omitempty"` + GasUsed *uint64 `json:"gasUsed,omitempty"` + Output *[]byte `json:"output,omitempty"` +} + +type flatCallResultMarshaling struct { + Code *hexutil.Bytes + GasUsed *hexutil.Uint64 + Output *hexutil.Bytes +} + +// flatCallTracer reports call frame information of a tx in a flat format, i.e. +// as opposed to the nested format of `callTracer`. +type flatCallTracer struct { + ctx *tracers.Context + *callTracer +} + +// newFlatCallTracer returns a new flatCallTracer. +func newFlatCallTracer(ctx *tracers.Context) tracers.Tracer { + t := &callTracer{callstack: make([]callFrame, 1)} + + return &flatCallTracer{callTracer: t, ctx: ctx} + +} + +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. +func (t *flatCallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + t.callTracer.CaptureEnter(typ, from, to, input, gas, value) + if len(t.callTracer.callstack) > 0 && t.callTracer.callstack[len(t.callTracer.callstack)-1].Value == "" { + t.callTracer.callstack[len(t.callTracer.callstack)-1].Value = "0x0" + } +} + +// GetResult returns the json-encoded nested list of call traces, and any +// error arising from the encoding or forceful termination (via `Stop`). +func (t *flatCallTracer) GetResult() (json.RawMessage, error) { + if len(t.callTracer.callstack) < 1 { + return nil, errors.New("invalid number of calls") + } + + flat, err := flatFromNested(&t.callTracer.callstack[0], []int{}, true, t.ctx) + if err != nil { + return nil, err + } + + res, err := json.Marshal(flat) + if err != nil { + return nil, err + } + return res, t.reason + +} + +func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx *tracers.Context) (output []flatCallFrame, err error) { + var frame *flatCallFrame + switch vm.StringToOp(input.Type) { + case vm.CREATE, vm.CREATE2: + frame = newFlatCreate(input) + case vm.SELFDESTRUCT: + frame = newFlatSuicide(input) + case vm.CALL, vm.STATICCALL, vm.CALLCODE, vm.DELEGATECALL: + frame = newFlatCall(input) + default: + return nil, fmt.Errorf("unrecognized call frame type: %s", input.Type) + } + + frame.TraceAddress = traceAddress + frame.Error = input.Error + frame.Subtraces = len(input.Calls) + fillCallFrameFromContext(frame, ctx) + if convertErrs { + convertErrorToParity(frame) + } + + // Revert output contains useful information (revert reason). + // Otherwise discard result. + if input.Error != "" && input.Error != vm.ErrExecutionReverted.Error() { + frame.Result = nil + } + + output = append(output, *frame) + if len(input.Calls) > 0 { + for i, childCall := range input.Calls { + childAddr := childTraceAddress(traceAddress, i) + childCallCopy := childCall + flat, err := flatFromNested(&childCallCopy, childAddr, convertErrs, ctx) + if err != nil { + return nil, err + } + output = append(output, flat...) + } + } + + return output, nil +} + +func addressPointer(a string) *common.Address { + if a == "" { + return nil + } + addr := common.HexToAddress(a) + return &addr +} + +func uint64Pointer(v string) *uint64 { + if v == "" { + return nil + } + val, _ := hexutil.DecodeUint64(v) + return &val +} + +func bigInt(v string) *big.Int { + if v == "" { + return nil + } + val, _ := hexutil.DecodeBig(v) + return val +} + +func bytesPointer(v string) *[]byte { + if v == "" { + return nil + } + val := hexutil.MustDecode(v) + return &val + +} + +func newFlatCreate(input *callFrame) *flatCallFrame { + var ( + actionInit = bytesPointer(input.Input) + resultCode = bytesPointer(input.Output) + ) + + return &flatCallFrame{ + Type: strings.ToLower(vm.CREATE.String()), + Action: flatCallAction{ + From: addressPointer(input.From), + Gas: uint64Pointer(input.Gas), + Value: bigInt(input.Value), + Init: actionInit, + }, + Result: &flatCallResult{ + GasUsed: uint64Pointer(input.GasUsed), + Address: addressPointer(input.To), + Code: resultCode, + }, + } +} + +func newFlatCall(input *callFrame) *flatCallFrame { + var ( + actionInput = bytesPointer(input.Input) + resultOutput = bytesPointer(input.Output) + ) + + return &flatCallFrame{ + Type: strings.ToLower(vm.CALL.String()), + Action: flatCallAction{ + From: addressPointer(input.From), + To: addressPointer(input.To), + Gas: uint64Pointer(input.Gas), + Value: bigInt(input.Value), + CallType: strings.ToLower(input.Type), + Input: actionInput, + }, + Result: &flatCallResult{ + GasUsed: uint64Pointer(input.GasUsed), + Output: resultOutput, + }, + } +} + +func newFlatSuicide(input *callFrame) *flatCallFrame { + return &flatCallFrame{ + Type: "suicide", + Action: flatCallAction{ + SelfDestructed: addressPointer(input.From), + Balance: bigInt(input.Value), + RefundAddress: addressPointer(input.To), + }, + } +} + +func fillCallFrameFromContext(callFrame *flatCallFrame, ctx *tracers.Context) { + if ctx.BlockHash != (common.Hash{}) { + callFrame.BlockHash = &ctx.BlockHash + } + if ctx.BlockNumber != 0 { + callFrame.BlockNumber = ctx.BlockNumber + } + if ctx.TxHash != (common.Hash{}) { + callFrame.TransactionHash = &ctx.TxHash + } + callFrame.TransactionPosition = uint64(ctx.TxIndex) +} + +func convertErrorToParity(call *flatCallFrame) { + if call.Error == "" { + return + } + + if parityError, ok := parityErrorMapping[call.Error]; ok { + call.Error = parityError + } else { + for gethError, parityError := range parityErrorMappingStartingWith { + if strings.HasPrefix(call.Error, gethError) { + call.Error = parityError + } + } + } +} + +func childTraceAddress(a []int, i int) []int { + child := make([]int, 0, len(a)+1) + child = append(child, a...) + child = append(child, i) + return child +} diff --git a/eth/tracers/native/gen_flatcallaction_json.go b/eth/tracers/native/gen_flatcallaction_json.go new file mode 100644 index 000000000000..149d8cdc45f0 --- /dev/null +++ b/eth/tracers/native/gen_flatcallaction_json.go @@ -0,0 +1,110 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package native + +import ( + "encoding/json" + "math/big" + + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/common/hexutil" +) + +var _ = (*flatCallActionMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (f flatCallAction) MarshalJSON() ([]byte, error) { + type flatCallAction struct { + Author *common.Address `json:"author,omitempty"` + RewardType string `json:"rewardType,omitempty"` + SelfDestructed *common.Address `json:"address,omitempty"` + Balance *hexutil.Big `json:"balance,omitempty"` + CallType string `json:"callType,omitempty"` + CreationMethod string `json:"creationMethod,omitempty"` + From *common.Address `json:"from,omitempty"` + Gas *hexutil.Uint64 `json:"gas,omitempty"` + Init *hexutil.Bytes `json:"init,omitempty"` + Input *hexutil.Bytes `json:"input,omitempty"` + RefundAddress *common.Address `json:"refundAddress,omitempty"` + To *common.Address `json:"to,omitempty"` + Value *hexutil.Big `json:"value,omitempty"` + } + var enc flatCallAction + enc.Author = f.Author + enc.RewardType = f.RewardType + enc.SelfDestructed = f.SelfDestructed + enc.Balance = (*hexutil.Big)(f.Balance) + enc.CallType = f.CallType + enc.CreationMethod = f.CreationMethod + enc.From = f.From + enc.Gas = (*hexutil.Uint64)(f.Gas) + enc.Init = (*hexutil.Bytes)(f.Init) + enc.Input = (*hexutil.Bytes)(f.Input) + enc.RefundAddress = f.RefundAddress + enc.To = f.To + enc.Value = (*hexutil.Big)(f.Value) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (f *flatCallAction) UnmarshalJSON(input []byte) error { + type flatCallAction struct { + Author *common.Address `json:"author,omitempty"` + RewardType *string `json:"rewardType,omitempty"` + SelfDestructed *common.Address `json:"address,omitempty"` + Balance *hexutil.Big `json:"balance,omitempty"` + CallType *string `json:"callType,omitempty"` + CreationMethod *string `json:"creationMethod,omitempty"` + From *common.Address `json:"from,omitempty"` + Gas *hexutil.Uint64 `json:"gas,omitempty"` + Init *hexutil.Bytes `json:"init,omitempty"` + Input *hexutil.Bytes `json:"input,omitempty"` + RefundAddress *common.Address `json:"refundAddress,omitempty"` + To *common.Address `json:"to,omitempty"` + Value *hexutil.Big `json:"value,omitempty"` + } + var dec flatCallAction + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Author != nil { + f.Author = dec.Author + } + if dec.RewardType != nil { + f.RewardType = *dec.RewardType + } + if dec.SelfDestructed != nil { + f.SelfDestructed = dec.SelfDestructed + } + if dec.Balance != nil { + f.Balance = (*big.Int)(dec.Balance) + } + if dec.CallType != nil { + f.CallType = *dec.CallType + } + if dec.CreationMethod != nil { + f.CreationMethod = *dec.CreationMethod + } + if dec.From != nil { + f.From = dec.From + } + if dec.Gas != nil { + f.Gas = (*uint64)(dec.Gas) + } + if dec.Init != nil { + f.Init = (*[]byte)(dec.Init) + } + if dec.Input != nil { + f.Input = (*[]byte)(dec.Input) + } + if dec.RefundAddress != nil { + f.RefundAddress = dec.RefundAddress + } + if dec.To != nil { + f.To = dec.To + } + if dec.Value != nil { + f.Value = (*big.Int)(dec.Value) + } + return nil +} diff --git a/eth/tracers/native/gen_flatcallresult_json.go b/eth/tracers/native/gen_flatcallresult_json.go new file mode 100644 index 000000000000..faf46c40a6ed --- /dev/null +++ b/eth/tracers/native/gen_flatcallresult_json.go @@ -0,0 +1,55 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package native + +import ( + "encoding/json" + + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/common/hexutil" +) + +var _ = (*flatCallResultMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (f flatCallResult) MarshalJSON() ([]byte, error) { + type flatCallResult struct { + Address *common.Address `json:"address,omitempty"` + Code *hexutil.Bytes `json:"code,omitempty"` + GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"` + Output *hexutil.Bytes `json:"output,omitempty"` + } + var enc flatCallResult + enc.Address = f.Address + enc.Code = (*hexutil.Bytes)(f.Code) + enc.GasUsed = (*hexutil.Uint64)(f.GasUsed) + enc.Output = (*hexutil.Bytes)(f.Output) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (f *flatCallResult) UnmarshalJSON(input []byte) error { + type flatCallResult struct { + Address *common.Address `json:"address,omitempty"` + Code *hexutil.Bytes `json:"code,omitempty"` + GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"` + Output *hexutil.Bytes `json:"output,omitempty"` + } + var dec flatCallResult + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Address != nil { + f.Address = dec.Address + } + if dec.Code != nil { + f.Code = (*[]byte)(dec.Code) + } + if dec.GasUsed != nil { + f.GasUsed = (*uint64)(dec.GasUsed) + } + if dec.Output != nil { + f.Output = (*[]byte)(dec.Output) + } + return nil +} diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go index d08944fa6d78..61f0f715098c 100644 --- a/eth/tracers/native/noop.go +++ b/eth/tracers/native/noop.go @@ -35,7 +35,7 @@ func init() { type noopTracer struct{} // newNoopTracer returns a new noop tracer. -func newNoopTracer() tracers.Tracer { +func newNoopTracer(ctx *tracers.Context) tracers.Tracer { return &noopTracer{} } diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index 45d93160b31c..5b59b6af856b 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -61,7 +61,7 @@ type prestateTracer struct { deleted map[common.Address]bool } -func newPrestateTracer() tracers.Tracer { +func newPrestateTracer(ctx *tracers.Context) tracers.Tracer { return &prestateTracer{ pre: state{}, post: state{}, diff --git a/eth/tracers/native/tracer.go b/eth/tracers/native/tracer.go index b1061d243a48..0c14130fc27d 100644 --- a/eth/tracers/native/tracer.go +++ b/eth/tracers/native/tracer.go @@ -27,9 +27,11 @@ Aside from implementing the tracer, it also needs to register itself, using the Example: ```golang -func init() { - register("noopTracerNative", newNoopTracer) -} + + func init() { + register("noopTracerNative", newNoopTracer) + } + ``` */ package native @@ -57,12 +59,12 @@ The go spec (https://golang.org/ref/spec#Package_initialization) says Hence, we cannot make the map in init, but must make it upon first use. */ -var ctors map[string]func() tracers.Tracer +var ctors map[string]func(ctx *tracers.Context) tracers.Tracer // register is used by native tracers to register their presence. -func register(name string, ctor func() tracers.Tracer) { +func register(name string, ctor func(ctx *tracers.Context) tracers.Tracer) { if ctors == nil { - ctors = make(map[string]func() tracers.Tracer) + ctors = make(map[string]func(ctx *tracers.Context) tracers.Tracer) } ctors[name] = ctor } @@ -70,10 +72,10 @@ func register(name string, ctor func() tracers.Tracer) { // lookup returns a tracer, if one can be matched to the given name. func lookup(name string, ctx *tracers.Context) (tracers.Tracer, error) { if ctors == nil { - ctors = make(map[string]func() tracers.Tracer) + ctors = make(map[string]func(ctx *tracers.Context) tracers.Tracer) } if ctor, ok := ctors[name]; ok { - return ctor(), nil + return ctor(ctx), nil } return nil, errors.New("no tracer found") } diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go index 69d3d2cdc2a4..97c94f3c5d1e 100644 --- a/eth/tracers/tracers.go +++ b/eth/tracers/tracers.go @@ -29,9 +29,10 @@ import ( // Context contains some contextual infos for a transaction execution that is not // available from within the EVM object. type Context struct { - BlockHash common.Hash // Hash of the block the tx is contained within (zero if dangling tx or call) - TxIndex int // Index of the transaction within a block (zero if dangling tx or call) - TxHash common.Hash // Hash of the transaction being traced (zero if dangling call) + BlockNumber uint64 // Block number of the block the tx is contained within (zero if dangling tx or call) + BlockHash common.Hash // Hash of the block the tx is contained within (zero if dangling tx or call) + TxIndex int // Index of the transaction within a block (zero if dangling tx or call) + TxHash common.Hash // Hash of the transaction being traced (zero if dangling call) } // Tracer interface extends vm.EVMLogger and additionally diff --git a/rollup/tracing/tracing.go b/rollup/tracing/tracing.go index 3feea62ba388..1e667a4a9c29 100644 --- a/rollup/tracing/tracing.go +++ b/rollup/tracing/tracing.go @@ -314,9 +314,10 @@ func (env *TraceEnv) getTxResult(state *state.StateDB, index int, block *types.B txContext := core.NewEVMTxContext(msg) tracerContext := tracers.Context{ - BlockHash: block.Hash(), - TxIndex: index, - TxHash: tx.Hash(), + BlockNumber: block.NumberU64(), + BlockHash: block.Hash(), + TxIndex: index, + TxHash: tx.Hash(), } callTracer, err := tracers.New("callTracer", &tracerContext) if err != nil {