From d74ea42a7d21a263e77b34a7d79d1bb6d4917990 Mon Sep 17 00:00:00 2001 From: linhaiyang <1183679627@qq.com> Date: Wed, 19 May 2021 18:55:14 +0800 Subject: [PATCH] tracetx --- eth/api_pre_exec.go | 127 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 111 insertions(+), 16 deletions(-) diff --git a/eth/api_pre_exec.go b/eth/api_pre_exec.go index 13520a130c..1cf48eb895 100644 --- a/eth/api_pre_exec.go +++ b/eth/api_pre_exec.go @@ -2,15 +2,21 @@ package eth import ( "context" + "errors" + "fmt" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/rpc" "golang.org/x/crypto/sha3" "hash" "math/big" + "time" ) type helpHash struct { @@ -40,6 +46,14 @@ type PreExecTx struct { From, To, Data, Value, Gas, GasPrice, Nonce string } +type preData struct { + block *types.Block + tx *types.Transaction + msg types.Message + stateDb *state.StateDB + header *types.Header +} + // PreExecAPI provides pre exec info for rpc type PreExecAPI struct { e *Ethereum @@ -81,36 +95,117 @@ func (api *PreExecAPI) getBlockAndMsg(origin *PreExecTx, number *big.Int) (*type return block, msg } +func (api *PreExecAPI) prepareData(ctx context.Context, origin *PreExecTx) (*preData, error) { + var ( + d preData + err error + ) + bc := api.e.blockchain + d.header, err = api.e.APIBackend.HeaderByNumber(ctx, rpc.LatestBlockNumber) + if err != nil { + return nil, err + } + latestNumber := d.header.Number + parent := api.e.blockchain.GetBlockByNumber(latestNumber.Uint64()) + d.stateDb, err = state.New(parent.Header().Root, bc.StateCache(), bc.Snapshots()) + if err != nil { + return nil, err + } + d.block, d.msg = api.getBlockAndMsg(origin, latestNumber) + d.tx = d.block.Transactions()[0] + return &d, nil +} + func (api *PreExecAPI) GetLogs(ctx context.Context, origin *PreExecTx) (*types.Receipt, error) { var ( bc = api.e.blockchain ) - header, err := api.e.APIBackend.HeaderByNumber(ctx, rpc.LatestBlockNumber) + d, err := api.prepareData(ctx, origin) if err != nil { return nil, err } - latestNumber := header.Number - - parent := bc.GetBlockByNumber(latestNumber.Uint64()) - stateDb, err := state.New(parent.Header().Root, bc.StateCache(), bc.Snapshots()) - if err != nil { - return nil, err - } - - block, msg := api.getBlockAndMsg(origin, latestNumber) - tx := block.Transactions()[0] - gas := tx.Gas() + gas := d.tx.Gas() gp := new(core.GasPool).AddGas(gas) - stateDb.Prepare(tx.Hash(), block.Hash(), 0) + d.stateDb.Prepare(d.tx.Hash(), d.block.Hash(), 0) receipt, err := core.ApplyTransactionForPreExec( - bc.Config(), bc, nil, gp, stateDb, header, tx, msg, &gas, *bc.GetVMConfig()) + bc.Config(), bc, nil, gp, d.stateDb, d.header, d.tx, d.msg, &gas, *bc.GetVMConfig()) if err != nil { return nil, err } return receipt, nil } -func (api *PreExecAPI) TraceTx(ctx context.Context, tx *PreExecTx) (interface{}, error) { - return tx.From, nil +func (api *PreExecAPI) TraceTx(ctx context.Context, origin *PreExecTx, config *tracers.TraceConfig) (interface{}, error) { + var ( + bc = api.e.blockchain + tracer vm.Tracer + err error + ) + d, err := api.prepareData(ctx, origin) + if err != nil { + return nil, err + } + txContext := core.NewEVMTxContext(d.msg) + + switch { + case config != nil && config.Tracer != nil: + // Define a meaningful timeout of a single transaction trace + timeout := 5 * time.Second + if config.Timeout != nil { + if timeout, err = time.ParseDuration(*config.Timeout); err != nil { + return nil, err + } + } + // Constuct the JavaScript tracer to execute with + if tracer, err = tracers.New(*config.Tracer, txContext); err != nil { + return nil, err + } + // Handle timeouts and RPC cancellations + deadlineCtx, cancel := context.WithTimeout(ctx, timeout) + go func() { + <-deadlineCtx.Done() + if deadlineCtx.Err() == context.DeadlineExceeded { + tracer.(*tracers.Tracer).Stop(errors.New("execution timeout")) + } + }() + defer cancel() + + case config == nil: + tracer = vm.NewStructLogger(nil) + + default: + tracer = vm.NewStructLogger(config.LogConfig) + } + // Run the transaction with tracing enabled. + vmenv := vm.NewEVM(core.NewEVMBlockContext(d.header, bc, nil), txContext, d.stateDb, bc.Config(), vm.Config{Debug: true, Tracer: tracer}) + + // Call Prepare to clear out the statedb access list + d.stateDb.Prepare(d.tx.Hash(), d.block.Hash(), 0) + + result, err := core.ApplyMessage(vmenv, d.msg, new(core.GasPool).AddGas(d.msg.Gas())) + if err != nil { + return nil, fmt.Errorf("tracing failed: %v", err) + } + // Depending on the tracer type, format and return the output. + switch tracer := tracer.(type) { + case *vm.StructLogger: + // If the result contains a revert reason, return it. + returnVal := fmt.Sprintf("%x", result.Return()) + if len(result.Revert()) > 0 { + returnVal = fmt.Sprintf("%x", result.Revert()) + } + return ðapi.ExecutionResult{ + Gas: result.UsedGas, + Failed: result.Failed(), + ReturnValue: returnVal, + StructLogs: ethapi.FormatLogs(tracer.StructLogs()), + }, nil + + case *tracers.Tracer: + return tracer.GetResult() + + default: + panic(fmt.Sprintf("bad tracer type %T", tracer)) + } }