Skip to content

Commit

Permalink
Implement the optional output field on ots_traceTransaction (#10014)
Browse files Browse the repository at this point in the history
This is for E2.

It implements the backward compatible output field for traces on
ots_traceTransaction: otterscan/execution-apis#1

It'll be consumed by Otterscan in an upcoming release of this feature:
otterscan/otterscan#1530
  • Loading branch information
wmitsuda authored Apr 22, 2024
1 parent 3f7cf91 commit 34e8d35
Showing 1 changed file with 54 additions and 37 deletions.
91 changes: 54 additions & 37 deletions turbo/jsonrpc/otterscan_trace_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package jsonrpc

import (
"context"
"github.com/ledgerwatch/erigon-lib/common/hexutil"
"math/big"

"github.com/ledgerwatch/erigon-lib/common/hexutil"

"github.com/holiman/uint256"

"github.com/ledgerwatch/erigon-lib/common"
Expand All @@ -29,68 +30,68 @@ func (api *OtterscanAPIImpl) TraceTransaction(ctx context.Context, hash common.H
}

type TraceEntry struct {
Type string `json:"type"`
Depth int `json:"depth"`
From common.Address `json:"from"`
To common.Address `json:"to"`
Value *hexutil.Big `json:"value"`
Input hexutility.Bytes `json:"input"`
Type string `json:"type"`
Depth int `json:"depth"`
From common.Address `json:"from"`
To common.Address `json:"to"`
Value *hexutil.Big `json:"value"`
Input hexutility.Bytes `json:"input"`
Output hexutility.Bytes `json:"output"`
}

type TransactionTracer struct {
DefaultTracer
ctx context.Context
Results []*TraceEntry
depth int // computed from CaptureStart, CaptureEnter, and CaptureExit calls
stack []*TraceEntry
}

func NewTransactionTracer(ctx context.Context) *TransactionTracer {
return &TransactionTracer{
ctx: ctx,
Results: make([]*TraceEntry, 0),
stack: make([]*TraceEntry, 0),
}
}

func (t *TransactionTracer) captureStartOrEnter(typ vm.OpCode, from, to common.Address, precompile bool, input []byte, value *uint256.Int) {
if precompile {
return
}

inputCopy := make([]byte, len(input))
copy(inputCopy, input)
_value := new(big.Int)
if value != nil {
_value.Set(value.ToBig())
}

var entry *TraceEntry
if typ == vm.CALL {
t.Results = append(t.Results, &TraceEntry{"CALL", t.depth, from, to, (*hexutil.Big)(_value), inputCopy})
return
}
if typ == vm.STATICCALL {
t.Results = append(t.Results, &TraceEntry{"STATICCALL", t.depth, from, to, nil, inputCopy})
return
}
if typ == vm.DELEGATECALL {
t.Results = append(t.Results, &TraceEntry{"DELEGATECALL", t.depth, from, to, nil, inputCopy})
return
}
if typ == vm.CALLCODE {
t.Results = append(t.Results, &TraceEntry{"CALLCODE", t.depth, from, to, (*hexutil.Big)(_value), inputCopy})
return
}
if typ == vm.CREATE {
t.Results = append(t.Results, &TraceEntry{"CREATE", t.depth, from, to, (*hexutil.Big)(value.ToBig()), inputCopy})
return
}
if typ == vm.CREATE2 {
t.Results = append(t.Results, &TraceEntry{"CREATE2", t.depth, from, to, (*hexutil.Big)(value.ToBig()), inputCopy})
return
entry = &TraceEntry{"CALL", t.depth, from, to, (*hexutil.Big)(_value), inputCopy, nil}
} else if typ == vm.STATICCALL {
entry = &TraceEntry{"STATICCALL", t.depth, from, to, nil, inputCopy, nil}
} else if typ == vm.DELEGATECALL {
entry = &TraceEntry{"DELEGATECALL", t.depth, from, to, nil, inputCopy, nil}
} else if typ == vm.CALLCODE {
entry = &TraceEntry{"CALLCODE", t.depth, from, to, (*hexutil.Big)(_value), inputCopy, nil}
} else if typ == vm.CREATE {
entry = &TraceEntry{"CREATE", t.depth, from, to, (*hexutil.Big)(value.ToBig()), inputCopy, nil}
} else if typ == vm.CREATE2 {
entry = &TraceEntry{"CREATE2", t.depth, from, to, (*hexutil.Big)(value.ToBig()), inputCopy, nil}
} else if typ == vm.SELFDESTRUCT {
last := t.Results[len(t.Results)-1]
entry = &TraceEntry{"SELFDESTRUCT", last.Depth + 1, from, to, (*hexutil.Big)(value.ToBig()), nil, nil}
} else {
// safeguard in case new CALL-like opcodes are introduced but not handled,
// otherwise CaptureExit/stack will get out of sync
entry = &TraceEntry{"UNKNOWN", t.depth, from, to, (*hexutil.Big)(value.ToBig()), inputCopy, nil}
}

if typ == vm.SELFDESTRUCT {
last := t.Results[len(t.Results)-1]
t.Results = append(t.Results, &TraceEntry{"SELFDESTRUCT", last.Depth + 1, from, to, (*hexutil.Big)(value.ToBig()), nil})
// Ignore precompiles in the returned trace (maybe we shouldn't?)
if !precompile {
t.Results = append(t.Results, entry)
}

// stack precompiles in order to match captureEndOrExit
t.stack = append(t.stack, entry)
}

func (t *TransactionTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) {
Expand All @@ -103,6 +104,22 @@ func (t *TransactionTracer) CaptureEnter(typ vm.OpCode, from common.Address, to
t.captureStartOrEnter(typ, from, to, precompile, input, value)
}

func (t *TransactionTracer) CaptureExit(output []byte, usedGas uint64, err error) {
func (t *TransactionTracer) captureEndOrExit(output []byte, usedGas uint64, err error) {
t.depth--

lastIdx := len(t.stack) - 1
pop := t.stack[lastIdx]
t.stack = t.stack[:lastIdx]

outputCopy := make([]byte, len(output))
copy(outputCopy, output)
pop.Output = outputCopy
}

func (t *TransactionTracer) CaptureExit(output []byte, usedGas uint64, err error) {
t.captureEndOrExit(output, usedGas, err)
}

func (t *TransactionTracer) CaptureEnd(output []byte, usedGas uint64, err error) {
t.captureEndOrExit(output, usedGas, err)
}

0 comments on commit 34e8d35

Please sign in to comment.