Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relay Call Trace API #30

Merged
merged 7 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
197 changes: 197 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,164 @@ 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
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
}
for len(callStacks) > 0 && top.idx == len(callStacks[len(callStacks)-1].gethTrace.Calls)-1 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little bit confusing to understand the meaning and purpose of idx. Please add some comments to explain the defenition of idx, managing rootindex, and the condition on L259.

// 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 +285,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:"-"`
}