Skip to content

Commit

Permalink
Merge pull request #30 from testinprod-io/pcw109550/relay-call-trace-api
Browse files Browse the repository at this point in the history
Relay Call Trace API
  • Loading branch information
pcw109550 committed Apr 14, 2023
2 parents efb3205 + 66139b6 commit 0bcc2bc
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 16 deletions.
4 changes: 4 additions & 0 deletions cmd/rpcdaemon/commands/debug_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ func NewPrivateDebugAPI(base *BaseAPI, db kv.RoDB, gascap uint64) *PrivateDebugA
}
}

func (api *PrivateDebugAPIImpl) relayToHistoricalBackend(ctx context.Context, result interface{}, method string, args ...interface{}) error {
return api.historicalRPCService.CallContext(ctx, result, method, args...)
}

// storageRangeAt implements debug_storageRangeAt. Returns information about a range of storage locations (if any) for the given address.
func (api *PrivateDebugAPIImpl) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex uint64, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) {
tx, err := api.db.BeginRo(ctx)
Expand Down
200 changes: 200 additions & 0 deletions cmd/rpcdaemon/commands/otterscan_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"github.com/ledgerwatch/erigon-lib/kv/order"
"github.com/ledgerwatch/erigon-lib/kv/rawdbv3"
"github.com/ledgerwatch/erigon/core/state/temporal"
"github.com/ledgerwatch/erigon/core/vm/evmtypes"
"github.com/ledgerwatch/erigon/eth/tracers"
"github.com/ledgerwatch/log/v3"

"github.com/ledgerwatch/erigon/common/hexutil"
Expand Down Expand Up @@ -112,6 +114,167 @@ func (api *OtterscanAPIImpl) getTransactionByHash(ctx context.Context, tx kv.Tx,
return txn, block, blockHash, blockNum, txnIndex, nil
}

func (api *OtterscanAPIImpl) relayToHistoricalBackend(ctx context.Context, result interface{}, method string, args ...interface{}) error {
return api.historicalRPCService.CallContext(ctx, result, method, args...)
}

func (api *OtterscanAPIImpl) translateCaptureStart(gethTrace *GethTrace, tracer vm.EVMLogger, vmenv *vm.EVM) error {
from := common.HexToAddress(gethTrace.From)
to := common.HexToAddress(gethTrace.To)
input, err := hexutil.Decode(gethTrace.Input)
if err != nil {
if err != hexutil.ErrEmptyString {
return err
}
input = []byte{}
}
valueBig, err := hexutil.DecodeBig(gethTrace.Value)
if err != nil {
if err != hexutil.ErrEmptyString {
return err
}
valueBig = big.NewInt(0)
}
value, _ := uint256.FromBig(valueBig)
gas, err := hexutil.DecodeUint64(gethTrace.Gas)
if err != nil {
return err
}
_, isPrecompile := vmenv.Precompile(to)
// dummy code
code := []byte{}
tracer.CaptureStart(vmenv, from, to, isPrecompile, false, input, gas, value, code)
return nil
}

func (api *OtterscanAPIImpl) translateOpcode(typStr string) (vm.OpCode, error) {
switch typStr {
default:
case "CALL":
return vm.CALL, nil
case "STATICCALL":
return vm.STATICCALL, nil
case "DELEGATECALL":
return vm.DELEGATECALL, nil
case "CALLCODE":
return vm.CALLCODE, nil
case "CREATE":
return vm.CREATE, nil
case "CREATE2":
return vm.CREATE2, nil
case "SELFDESTRUCT":
return vm.SELFDESTRUCT, nil
}
return vm.INVALID, fmt.Errorf("unable to translate %s", typStr)
}

func (api *OtterscanAPIImpl) translateCaptureEnter(gethTrace *GethTrace, tracer vm.EVMLogger, vmenv *vm.EVM) error {
from := common.HexToAddress(gethTrace.From)
to := common.HexToAddress(gethTrace.To)
input, err := hexutil.Decode(gethTrace.Input)
if err != nil {
if err != hexutil.ErrEmptyString {
return err
}
input = []byte{}
}
valueBig, err := hexutil.DecodeBig(gethTrace.Value)
if err != nil {
if err != hexutil.ErrEmptyString {
return err
}
valueBig = big.NewInt(0)
}
value, _ := uint256.FromBig(valueBig)
gas, err := hexutil.DecodeUint64(gethTrace.Gas)
if err != nil {
return err
}
typStr := gethTrace.Type
typ, err := api.translateOpcode(typStr)
if err != nil {
return err
}
_, isPrecompile := vmenv.Precompile(to)
tracer.CaptureEnter(typ, from, to, isPrecompile, false, input, gas, value, nil)
return nil
}

func (api *OtterscanAPIImpl) translateCaptureExit(gethTrace *GethTrace, tracer vm.EVMLogger) error {
usedGas, err := hexutil.DecodeUint64(gethTrace.GasUsed)
if err != nil {
return err
}
output, err := hexutil.Decode(gethTrace.Output)
if err != nil {
if err != hexutil.ErrEmptyString {
return err
}
output = []byte{}
}
err = errors.New(gethTrace.Error)
tracer.CaptureExit(output, usedGas, err)
return nil
}

func (api *OtterscanAPIImpl) translateRelayTraceResult(gethTrace *GethTrace, tracer vm.EVMLogger, chainConfig *chain.Config) error {
vmenv := vm.NewEVM(evmtypes.BlockContext{}, evmtypes.TxContext{}, nil, chainConfig, vm.Config{})
type traceWithIndex struct {
gethTrace *GethTrace
idx int // children index
}
callStacks := make([]*traceWithIndex, 0)
started := false
// Each call stack can call and trigger sub call stack.
// rootIndex indicates the index of child for current inspected parent node trace.
rootIndex := 0
var trace *GethTrace = gethTrace
// iterative postorder traversal
for trace != nil || len(callStacks) > 0 {
if trace != nil {
// push back
callStacks = append(callStacks, &traceWithIndex{trace, rootIndex})
if !started {
started = true
if err := api.translateCaptureStart(trace, tracer, vmenv); err != nil {
return err
}
} else {
if err := api.translateCaptureEnter(trace, tracer, vmenv); err != nil {
return err
}
}
rootIndex = 0
if len(trace.Calls) > 0 {
trace = trace.Calls[0]
} else {
trace = nil
}
continue
}
// pop back
top := callStacks[len(callStacks)-1]
callStacks = callStacks[:len(callStacks)-1]
if err := api.translateCaptureExit(top.gethTrace, tracer); err != nil {
return err
}
// pop back callstack repeatly until popped element is last children of top of the callstack
for len(callStacks) > 0 && top.idx == len(callStacks[len(callStacks)-1].gethTrace.Calls)-1 {
// pop back
top = callStacks[len(callStacks)-1]
callStacks = callStacks[:len(callStacks)-1]
if err := api.translateCaptureExit(top.gethTrace, tracer); err != nil {
return err
}
}
if len(callStacks) > 0 {
trace = callStacks[len(callStacks)-1].gethTrace.Calls[top.idx+1]
rootIndex = top.idx + 1
}
}
return nil
}

func (api *OtterscanAPIImpl) runTracer(ctx context.Context, tx kv.Tx, hash common.Hash, tracer vm.EVMLogger) (*core.ExecutionResult, error) {
txn, block, _, _, txIndex, err := api.getTransactionByHash(ctx, tx, hash)
if err != nil {
Expand All @@ -125,6 +288,43 @@ func (api *OtterscanAPIImpl) runTracer(ctx context.Context, tx kv.Tx, hash commo
if err != nil {
return nil, err
}

blockNum := block.NumberU64()
if chainConfig.IsOptimismPreBedrock(blockNum) {
if api.historicalRPCService == nil {
return nil, rpc.ErrNoHistoricalFallback
}
// geth returns nested json so we have to flatten
treeResult := &GethTrace{}
callTracer := "callTracer"
if err := api.relayToHistoricalBackend(ctx, treeResult, "debug_traceTransaction", hash, &tracers.TraceConfig{Tracer: &callTracer}); err != nil {
return nil, fmt.Errorf("historical backend error: %w", err)
}
if tracer != nil {
err := api.translateRelayTraceResult(treeResult, tracer, chainConfig)
if err != nil {
return nil, err
}
}
usedGas, err := hexutil.DecodeUint64(treeResult.GasUsed)
if err != nil {
return nil, err
}
returnData, err := hexutil.Decode(treeResult.Output)
if err != nil {
if err != hexutil.ErrEmptyString {
return nil, err
}
returnData = []byte{}
}
result := &core.ExecutionResult{
UsedGas: usedGas,
Err: errors.New(treeResult.Error),
ReturnData: returnData,
}
return result, nil
}

engine := api.engine()

msg, blockCtx, txCtx, ibs, _, err := transactions.ComputeTxEnv(ctx, engine, block, chainConfig, api._blockReader, tx, int(txIndex), api.historyV3(tx))
Expand Down
22 changes: 11 additions & 11 deletions cmd/rpcdaemon/commands/trace_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ import (

// GethTrace The trace as received from the existing Geth javascript tracer 'callTracer'
type GethTrace struct {
Type string `json:"type"`
Error string `json:"error"`
From string `json:"from"`
To string `json:"to"`
Value string `json:"value"`
Gas string `json:"gas"`
GasUsed string `json:"gasUsed"`
Input string `json:"input"`
Output string `json:"output"`
Time string `json:"time"`
Calls GethTraces `json:"calls"`
Type string `json:"type,omitempty"`
Error string `json:"error,omitempty"`
From string `json:"from,omitempty"`
To string `json:"to,omitempty"`
Value string `json:"value,omitempty"`
Gas string `json:"gas,omitempty"`
GasUsed string `json:"gasUsed,omitempty"`
Input string `json:"input,omitempty"`
Output string `json:"output,omitempty"`
Time string `json:"time,omitempty"`
Calls GethTraces `json:"calls,omitempty"`
}

// GethTraces an array of GethTraces
Expand Down
17 changes: 17 additions & 0 deletions cmd/rpcdaemon/commands/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package commands

import (
"context"
"encoding/json"
"fmt"
"math/big"
"time"
Expand Down Expand Up @@ -161,6 +162,22 @@ func (api *PrivateDebugAPIImpl) TraceTransaction(ctx context.Context, hash commo
stream.WriteNil()
return nil
}
if chainConfig.IsOptimismPreBedrock(blockNum) {
if api.historicalRPCService == nil {
return rpc.ErrNoHistoricalFallback
}
treeResult := &GethTrace{}
if err := api.relayToHistoricalBackend(ctx, treeResult, "debug_traceTransaction", hash, config); err != nil {
return fmt.Errorf("historical backend error: %w", err)
}
// stream out relayed response
result, err := json.Marshal(treeResult)
if err != nil {
return err
}
stream.WriteRaw(string(result))
return nil
}
// Private API returns 0 if transaction is not found.
if blockNum == 0 && chainConfig.Bor != nil {
blockNumPtr, err := rawdb.ReadBorTxLookupEntry(tx, hash)
Expand Down
4 changes: 4 additions & 0 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ func (evm *EVM) precompile(addr libcommon.Address) (PrecompiledContract, bool) {
return p, ok
}

func (evm *EVM) Precompile(addr libcommon.Address) (PrecompiledContract, bool) {
return evm.precompile(addr)
}

// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
return evm.interpreter.Run(contract, input, readOnly)
Expand Down
10 changes: 5 additions & 5 deletions eth/tracers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (
// TraceConfig holds extra parameters to trace functions.
type TraceConfig struct {
*logger.LogConfig
Tracer *string
Timeout *string
Reexec *uint64
NoRefunds *bool // Turns off gas refunds when tracing
StateOverrides *ethapi.StateOverrides
Tracer *string `json:"tracer"`
Timeout *string `json:"timeout,omitempty"`
Reexec *uint64 `json:"reexec,omitempty"`
NoRefunds *bool `json:"-"` // Turns off gas refunds when tracing
StateOverrides *ethapi.StateOverrides `json:"-"`
}

0 comments on commit 0bcc2bc

Please sign in to comment.