Skip to content

Commit

Permalink
Merge pull request #122 from blocknative/TS_rename
Browse files Browse the repository at this point in the history
refactor: Rename blocknative.txnOpCodeTracer -> blocknative.tracer.
  • Loading branch information
tyler-smith authored Sep 23, 2023
2 parents 1d592fe + 856d724 commit 71d6e21
Show file tree
Hide file tree
Showing 30 changed files with 304 additions and 293 deletions.
2 changes: 1 addition & 1 deletion core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ func ApplyTransactionWithResult(config *params.ChainConfig, bc ChainContext, aut

func ApplyUnsignedTransactionWithResult(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, msg *Message, usedGas *uint64, cfg vm.Config) (*types.Receipt, *ExecutionResult, interface{}, error) {
// Create a blocknative tracer to get execution traces.
tracer, err := blocknative.NewTxnOpCodeTracer(nil)
tracer, err := blocknative.NewTracer(nil)
if err != nil {
return nil, nil, nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion eth/filters/trace_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ func traceTx(message *core.Message, txCtx *tracers.Context, vmctx vm.BlockContex
txTraceLocksMu.Unlock()

// No trace in cache or in-progress so create a new one.
tracer, err := blocknative.NewTxnOpCodeTracerWithOpts(tracerOpts)
tracer, err := blocknative.NewTracerWithOpts(tracerOpts)
if err != nil {
return nil, err
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# txnOpCodeTracer
# Blocknative Tracer

## Options

Expand Down
4 changes: 4 additions & 0 deletions eth/tracers/blocknative/blocknative.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package blocknative

import (
"encoding/json"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/blocknative/decoder"
)

// Tracer is the interface for the Blocknative tracer.
// It implements the standard EVMLogger tracer interface, but also exposes the
// resulting Trace object directly.
type Tracer interface {
vm.EVMLogger
GetTrace() (*Trace, error)
Expand Down
7 changes: 4 additions & 3 deletions eth/tracers/blocknative/decoder/calldata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import (
"path/filepath"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"

"github.com/ethereum/go-ethereum/common"
)

type decodeCallDataTest struct {
Expand Down Expand Up @@ -92,8 +93,8 @@ type testCall struct {

func loadTestVectors() ([]*testVector, error) {
testVectorDirs := []string{
"../../internal/tracetest/testdata/txnOpCode_tracer",
"../../internal/tracetest/testdata/txnOpCode_tracer_with_netbalchanges",
"../../internal/tracetest/testdata/blocknative",
"../../internal/tracetest/testdata/blocknative/with_decoding",
}

var testVectors []*testVector
Expand Down
261 changes: 261 additions & 0 deletions eth/tracers/blocknative/tracer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
package blocknative

import (
"encoding/json"
"math/big"
"sync/atomic"
"time"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/blocknative/decoder"
)

var (
// decoderCache is a global cache for the decoder, shared across traces.
decoderCache = decoder.NewCaches()
)

// tracer is Blocknative's transaction tracer. It decodes messages into
// call-frames and decodes them into higher-level abstractions. It returns all
// the information required to reconstruct a transaction's execution while
// decoding inputs, outputs, logs, and environmental effects.
type tracer struct {
opts TracerOpts
evm *vm.EVM
decoder *decoder.Decoder

trace Trace
startTime time.Time
callStack []CallFrame

interrupt *atomic.Bool
interruptReason error
}

// NewTracer returns a new tracer with the given json decoded as TracerOpts.
// This allows us to easily construct a tracer from the standard tracer API
// which receives options as json.
func NewTracer(cfg json.RawMessage) (Tracer, error) {
var opts TracerOpts
if cfg != nil {
if err := json.Unmarshal(cfg, &opts); err != nil {
return nil, err
}
}
return NewTracerWithOpts(opts)
}

// NewTracerWithOpts is the primary constructor for the tracer.
func NewTracerWithOpts(opts TracerOpts) (Tracer, error) {
opts.Decode = opts.Decode || opts.BalanceChanges

var t = tracer{
opts: opts,
callStack: make([]CallFrame, 1, 4),
interrupt: new(atomic.Bool),
}

if !opts.DisableBlockContext {
t.trace.BlockContext = &BlockContext{}
}

return &t, nil

}

// SetStateRoot implements core.stateRootSetter and stores the given root in the
// trace's BlockContext. It's called between the constructor and the first
// call-frame.
func (t *tracer) SetStateRoot(root common.Hash) {
if t.trace.BlockContext != nil {
t.trace.BlockContext.StateRoot = bytesToHex(root.Bytes())
}
}

// CaptureStart is called before the top-level call starts.
// This is also where we get the EVM instance, so we initialize the things that
// need it here instead of the constructor.
func (t *tracer) CaptureStart(evm *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
t.startTime = time.Now()
t.evm = evm

if t.opts.Decode {
t.decoder = decoder.New(decoderCache, decoderEVM{evm})
}

if !t.opts.DisableBlockContext {
t.trace.BlockContext.Number = evm.Context.BlockNumber.Uint64()
t.trace.BlockContext.BaseFee = evm.Context.BaseFee.Uint64()
t.trace.BlockContext.Time = evm.Context.Time
t.trace.BlockContext.Coinbase = addrToHex(evm.Context.Coinbase)
t.trace.BlockContext.GasLimit = evm.Context.GasLimit
if evm.Context.Random != nil {
t.trace.BlockContext.Random = bytesToHex(evm.Context.Random.Bytes())
}
}

// Create a call-frame for the top-level call.
t.callStack[0] = CallFrame{
Type: "CALL",
From: addrToHex(from),
To: addrToHex(to),
Input: bytesToHex(input),
Gas: uintToHex(gas),
Value: bigToHex(value),
}
if create {
t.callStack[0].Type = "CREATE"
}

// Try adding decode information, but don't fail if we can't.
if t.opts.Decode {
if decoded, err := t.decoder.DecodeCallFrame(from, to, value, input); err == nil {
t.callStack[0].Decoded = decoded
}
}
}

// CaptureEnd is called after the top-level call finishes to finalize tracing.
func (t *tracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
finalizeCallFrame(&t.callStack[0], output, gasUsed, err)

// If the user wants the logs, grab them from the state
if t.opts.Logs {
for _, stateLog := range t.evm.StateDB.Logs() {
t.trace.Logs = append(t.trace.Logs, CallLog{
Address: stateLog.Address,
Data: bytesToHex(stateLog.Data),
Topics: stateLog.Topics,
})
}
}

// Add gas payments to balance changes
if t.opts.Decode {
t.decoder.CaptureGas(t.evm.TxContext.Origin, t.evm.Context.Coinbase, gasUsed, t.evm.TxContext.GasPrice, t.evm.Context.BaseFee)
}

// Add total time duration for this trace request
t.trace.Time = time.Now().Sub(t.startTime).Nanoseconds()
}

// CaptureEnter is called before any new sub-call starts.
// (via call, create or selfdestruct).
func (t *tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if t.interrupt.Load() {
return
}

// Create CallFrame, decode it, and all it to the end of the callstack.
call := CallFrame{
Type: typ.String(),
From: addrToHex(from),
To: addrToHex(to),
Input: bytesToHex(input),
Gas: uintToHex(gas),
Value: bigToHex(value),
}
if t.opts.Decode {
if decoded, err := t.decoder.DecodeCallFrame(from, to, value, input); err == nil {
call.Decoded = decoded
}
}

t.callStack = append(t.callStack, call)
}

// CaptureExit is called after any sub call ends.
func (t *tracer) CaptureExit(output []byte, gasUsed uint64, err error) {
// Skip if we have no call-frames.
size := len(t.callStack)
if size == 0 {
return
}

// We have a call-frame, so finalize it.
finalizeCallFrame(&t.callStack[size-1], output, gasUsed, err)

// We have a parent call-frame, so nest this one under it.
if size <= 1 {
return
}
end := size - 1
call := t.callStack[end]
t.callStack = t.callStack[:end]
end -= 1
t.callStack[end].Calls = append(t.callStack[end].Calls, call)
}

// Stop terminates execution of the tracer at the first opportune moment.
func (t *tracer) Stop(err error) {
t.interrupt.Store(true)
t.interruptReason = err
t.evm.Cancel()
}

// GetTrace returns a Trace from the current state.
func (t *tracer) GetTrace() (*Trace, error) {
if t.interrupt.Load() {
return nil, t.interruptReason
}

t.trace.CallFrame = t.callStack[0]

if t.opts.Decode {
t.trace.BalanceChanges = t.decoder.GetBalanceChanges()
}

return &t.trace, nil
}

// GetResult returns a JSON encoded Trace from the current state.
func (t *tracer) GetResult() (json.RawMessage, error) {
trace, err := t.GetTrace()
if err != nil {
return nil, err
}

return json.Marshal(trace)
}

func finalizeCallFrame(call *CallFrame, output []byte, gasUsed uint64, err error) {
call.GasUsed = uintToHex(gasUsed)

// If there was an error then try decoding it and stop.
if err != nil {
call.Error = err.Error()
if err.Error() == "execution reverted" && len(output) > 0 {
call.Output = bytesToHex(output)
revertReason, _ := abi.UnpackRevert(output)
call.ErrorReason = revertReason
}

if call.Type == "CREATE" || call.Type == "CREATE2" {
call.To = ""
}
return
}

// The call was successful so decode the output.
call.Output = bytesToHex(output)
}

//
// Unused interface methods.
//

// CaptureState implements the tracer interface, but is unused.
func (t *tracer) CaptureState(_ uint64, _ vm.OpCode, _, _ uint64, _ *vm.ScopeContext, _ []byte, _ int, _ error) {
}

// CaptureFault implements the tracer interface, but is unused.
func (t *tracer) CaptureFault(_ uint64, _ vm.OpCode, _, _ uint64, _ *vm.ScopeContext, _ int, _ error) {
}

// CaptureTxStart implements the tracer interface, but is unused.
func (t *tracer) CaptureTxStart(_ uint64) {}

// CaptureTxEnd implements the tracer interface, but is unused.
func (t *tracer) CaptureTxEnd(_ uint64) {}
Loading

0 comments on commit 71d6e21

Please sign in to comment.