provide StateLogger for short-lived tracers

This commit is contained in:
Sina Mahmoodi 2023-06-28 18:00:44 +02:00
parent 3975c7cfcc
commit 619ac2948c
11 changed files with 132 additions and 44 deletions

View file

@ -952,6 +952,7 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor
}
}
vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true})
statedb.SetLogger(tracer)
// Define a meaningful timeout of a single transaction trace
if config.Timeout != nil {

View file

@ -145,6 +145,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
statedb.SetLogger(tracer)
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer})
msg, err := core.TransactionToMessage(tx, signer, nil)
if err != nil {
@ -360,6 +361,7 @@ func TestInternals(t *testing.T) {
Balance: big.NewInt(500000000000000),
},
}, false)
statedb.SetLogger(tc.tracer)
evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Tracer: tc.tracer})
tx, err := types.SignNewTx(key, signer, &types.LegacyTx{
To: &to,

View file

@ -107,6 +107,7 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string
if err != nil {
return fmt.Errorf("failed to create call tracer: %v", err)
}
statedb.SetLogger(tracer)
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer})
msg, err := core.TransactionToMessage(tx, signer, nil)

View file

@ -114,6 +114,7 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) {
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
statedb.SetLogger(tracer)
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer})
msg, err := core.TransactionToMessage(tx, signer, nil)
if err != nil {

View file

@ -176,6 +176,19 @@ func (*AccessListTracer) CaptureTxStart(tx *types.Transaction) {}
func (*AccessListTracer) CaptureTxEnd(receipt *types.Receipt) {}
func (*AccessListTracer) OnBalanceChange(a common.Address, prev, new *big.Int) {}
func (*AccessListTracer) OnNonceChange(a common.Address, prev, new uint64) {}
func (*AccessListTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
}
func (*AccessListTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {}
func (*AccessListTracer) OnLog(log *types.Log) {}
func (*AccessListTracer) OnNewAccount(a common.Address) {}
// AccessList returns the current accesslist maintained by the tracer.
func (a *AccessListTracer) AccessList() types.AccessList {
return a.list.accessList()

View file

@ -271,6 +271,19 @@ func (l *StructLogger) CaptureTxEnd(receipt *types.Receipt) {
l.usedGas = receipt.GasUsed
}
func (l *StructLogger) OnBalanceChange(a common.Address, prev, new *big.Int) {}
func (l *StructLogger) OnNonceChange(a common.Address, prev, new uint64) {}
func (l *StructLogger) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
}
func (l *StructLogger) OnStorageChange(a common.Address, k, prev, new common.Hash) {}
func (l *StructLogger) OnLog(log *types.Log) {}
func (l *StructLogger) OnNewAccount(a common.Address) {}
// StructLogs returns the captured log entries.
func (l *StructLogger) StructLogs() []StructLog { return l.logs }
@ -404,6 +417,19 @@ func (*mdLogger) CaptureTxStart(tx *types.Transaction) {}
func (*mdLogger) CaptureTxEnd(receipt *types.Receipt) {}
func (*mdLogger) OnBalanceChange(a common.Address, prev, new *big.Int) {}
func (*mdLogger) OnNonceChange(a common.Address, prev, new uint64) {}
func (*mdLogger) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
}
func (*mdLogger) OnStorageChange(a common.Address, k, prev, new common.Hash) {}
func (*mdLogger) OnLog(log *types.Log) {}
func (*mdLogger) OnNewAccount(a common.Address) {}
// ExecutionResult groups all structured logs emitted by the EVM
// while replaying a transaction in debug mode as well as transaction
// execution status, the amount of gas used and the return value

View file

@ -106,3 +106,16 @@ func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
func (l *JSONLogger) CaptureTxStart(tx *types.Transaction) {}
func (l *JSONLogger) CaptureTxEnd(receipt *types.Receipt) {}
func (*JSONLogger) OnBalanceChange(a common.Address, prev, new *big.Int) {}
func (*JSONLogger) OnNonceChange(a common.Address, prev, new uint64) {}
func (*JSONLogger) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
}
func (*JSONLogger) OnStorageChange(a common.Address, k, prev, new common.Hash) {}
func (*JSONLogger) OnLog(log *types.Log) {}
func (*JSONLogger) OnNewAccount(a common.Address) {}

View file

@ -103,6 +103,7 @@ type callTracer struct {
callstack []callFrame
config callTracerConfig
gasLimit uint64
depth int
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
}
@ -149,51 +150,11 @@ func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
// skip if the previous op caused an error
if err != nil {
return
}
// Only logs need to be captured via opcode processing
if !t.config.WithLog {
return
}
// Avoid processing nested calls when only caring about top call
if t.config.OnlyTopCall && depth > 0 {
return
}
// Skip if tracing was interrupted
if t.interrupt.Load() {
return
}
switch op {
case vm.LOG0, vm.LOG1, vm.LOG2, vm.LOG3, vm.LOG4:
size := int(op - vm.LOG0)
stack := scope.Stack
stackData := stack.Data()
// Don't modify the stack
mStart := stackData[len(stackData)-1]
mSize := stackData[len(stackData)-2]
topics := make([]common.Hash, size)
for i := 0; i < size; i++ {
topic := stackData[len(stackData)-2-(i+1)]
topics[i] = common.Hash(topic.Bytes32())
}
data, err := tracers.GetMemoryCopyPadded(scope.Memory, int64(mStart.Uint64()), int64(mSize.Uint64()))
if err != nil {
// mSize was unrealistically large
return
}
log := callLog{Address: scope.Contract.Address(), Topics: topics, Data: hexutil.Bytes(data)}
t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, log)
}
}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
t.depth++
if t.config.OnlyTopCall {
return
}
@ -217,6 +178,7 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
// CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code.
func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
t.depth--
if t.config.OnlyTopCall {
return
}
@ -246,6 +208,22 @@ func (t *callTracer) CaptureTxEnd(receipt *types.Receipt) {
}
}
func (t *callTracer) OnLog(log *types.Log) {
// Only logs need to be captured via opcode processing
if !t.config.WithLog {
return
}
// Avoid processing nested calls when only caring about top call
if t.config.OnlyTopCall && t.depth > 0 {
return
}
// Skip if tracing was interrupted
if t.interrupt.Load() {
return
}
t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, callLog{Address: log.Address, Topics: log.Topics, Data: log.Data})
}
// GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`).
func (t *callTracer) GetResult() (json.RawMessage, error) {
@ -257,7 +235,7 @@ func (t *callTracer) GetResult() (json.RawMessage, error) {
if err != nil {
return nil, err
}
return json.RawMessage(res), t.reason
return res, t.reason
}
// Stop terminates execution of the tracer at the first opportune moment.

View file

@ -128,6 +128,42 @@ func (t *muxTracer) CaptureTxEnd(receipt *types.Receipt) {
}
}
func (t *muxTracer) OnBalanceChange(a common.Address, prev, new *big.Int) {
for _, t := range t.tracers {
t.OnBalanceChange(a, prev, new)
}
}
func (t *muxTracer) OnNonceChange(a common.Address, prev, new uint64) {
for _, t := range t.tracers {
t.OnNonceChange(a, prev, new)
}
}
func (t *muxTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
for _, t := range t.tracers {
t.OnCodeChange(a, prevCodeHash, prev, codeHash, code)
}
}
func (t *muxTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {
for _, t := range t.tracers {
t.OnStorageChange(a, k, prev, new)
}
}
func (t *muxTracer) OnLog(log *types.Log) {
for _, t := range t.tracers {
t.OnLog(log)
}
}
func (t *muxTracer) OnNewAccount(a common.Address) {
for _, t := range t.tracers {
t.OnNewAccount(a)
}
}
// GetResult returns an empty json object.
func (t *muxTracer) GetResult() (json.RawMessage, error) {
resObject := make(map[string]json.RawMessage)

View file

@ -73,6 +73,19 @@ func (*NoopTracer) CaptureTxStart(tx *types.Transaction) {}
func (*NoopTracer) CaptureTxEnd(receipt *types.Receipt) {}
func (*NoopTracer) OnBalanceChange(a common.Address, prev, new *big.Int) {}
func (*NoopTracer) OnNonceChange(a common.Address, prev, new uint64) {}
func (*NoopTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
}
func (*NoopTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {}
func (*NoopTracer) OnLog(log *types.Log) {}
func (*NoopTracer) OnNewAccount(a common.Address) {}
// GetResult returns an empty json object.
func (t *NoopTracer) GetResult() (json.RawMessage, error) {
return json.RawMessage(`{}`), nil

View file

@ -24,6 +24,7 @@ import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
)
@ -36,10 +37,13 @@ type Context struct {
TxHash common.Hash // Hash of the transaction being traced (zero if dangling call)
}
// Tracer interface extends vm.EVMLogger and additionally
// allows collecting the tracing result.
// The set of methods that must be exposed by a tracer
// for it to be available through the RPC interface.
// This involves a method to retrieve results and one to
// stop tracing.
type Tracer interface {
vm.EVMLogger
state.StateLogger
GetResult() (json.RawMessage, error)
// Stop terminates execution of the tracer at the first opportune moment.
Stop(err error)