Skip to content

Commit

Permalink
core/vm, eth/tracers: simplify ethcall response type, encode early
Browse files Browse the repository at this point in the history
  • Loading branch information
holiman committed Nov 26, 2024
1 parent 4855feb commit f1fd5bd
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 80 deletions.
19 changes: 13 additions & 6 deletions core/vm/runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/asm"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/core/vm/program"
Expand Down Expand Up @@ -670,17 +671,23 @@ func TestColdAccountAccessCost(t *testing.T) {
want: 7600,
},
} {
tracer := logger.NewStructLogger(nil)
var step = 0
var have = uint64(0)
Execute(tc.code, nil, &Config{
EVMConfig: vm.Config{
Tracer: tracer.Hooks(),
Tracer: &tracing.Hooks{
OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
// Uncomment to investigate failures:
//t.Logf("%d: %v %d", step, vm.OpCode(op).String(), cost)
if step == tc.step {
have = cost
}
step++
},
},
},
})
have := tracer.StructLogs()[tc.step].GasCost
if want := tc.want; have != want {
for ii, op := range tracer.StructLogs() {
t.Logf("%d: %v %d", ii, op.OpName(), op.GasCost)
}
t.Fatalf("testcase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want)
}
}
Expand Down
153 changes: 79 additions & 74 deletions eth/tracers/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"io"
"maps"
"math/big"
"slices"
"strings"
"sync/atomic"

Expand Down Expand Up @@ -124,6 +123,69 @@ func (log *StructLog) WriteTo(writer io.Writer) {
fmt.Fprintln(writer)
}

// StructLogRes stores a structured log emitted by the EVM while replaying a
// transaction in debug mode
type StructLogRes struct {
Pc uint64 `json:"pc"`
Op string `json:"op"`
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"`
Error string `json:"error,omitempty"`
Stack *[]string `json:"stack,omitempty"`
ReturnData string `json:"returnData,omitempty"`
Memory *[]string `json:"memory,omitempty"`
Storage *map[string]string `json:"storage,omitempty"`
RefundCounter uint64 `json:"refund,omitempty"`
}

// toLegacyJSON converts the structlog to json-encoded legacy form (StructLogRes).
//
// The differences between the structlog json and the 'legacy' json are:
// - Op. Legacy uses string (e.g. "SSTORE"), non-legacy uses a byte.
// - non-legacy has an 'opName' field containing the op name.
// - gas, gasCost: legacy uses integers, non-legacy hex-strings
// - memory: legacy uses a list of 64-char strings, each representing 32-byte chunks
// of evm memory. Non-legacy just uses a string of hexdata, no chunking.
// - storage: legacy has a storage-field.
func (log *StructLog) toLegacyJSON() json.RawMessage {
msg := StructLogRes{
Pc: log.Pc,
Op: log.Op.String(),
Gas: log.Gas,
GasCost: log.GasCost,
Depth: log.Depth,
Error: log.ErrorString(),
RefundCounter: log.RefundCounter,
}
if log.Stack != nil {
stack := make([]string, len(log.Stack))
for i, stackValue := range log.Stack {
stack[i] = stackValue.Hex()
}
msg.Stack = &stack
}
if len(log.ReturnData) > 0 {
msg.ReturnData = hexutil.Bytes(log.ReturnData).String()
}
if log.Memory != nil {
memory := make([]string, 0, (len(log.Memory)+31)/32)
for i := 0; i+32 <= len(log.Memory); i += 32 {
memory = append(memory, fmt.Sprintf("%x", log.Memory[i:i+32]))
}
msg.Memory = &memory
}
if log.Storage != nil {
storage := make(map[string]string)
for i, storageValue := range log.Storage {
storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
}
msg.Storage = &storage
}
element, _ := json.Marshal(msg)
return element
}

// StructLogger is an EVM state logger and implements EVMLogger.
//
// StructLogger can capture state based on the given Log configuration and also keeps
Expand All @@ -137,12 +199,13 @@ type StructLogger struct {
env *tracing.VMContext

storage map[common.Address]Storage
logs []StructLog
output []byte
err error
usedGas uint64

writer io.Writer // If set, the logger will stream instead of store logs
writer io.Writer // If set, the logger will stream instead of store logs
logs []json.RawMessage // buffer of json-encoded logs
resultSize int

interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
Expand Down Expand Up @@ -191,8 +254,8 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope
if l.interrupt.Load() {
return
}
// check if already accumulated the specified number of logs
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
// check if already accumulated the size of the response.
if l.cfg.Limit != 0 && l.resultSize > l.cfg.Limit {
return
}
var (
Expand Down Expand Up @@ -242,11 +305,9 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope
log.Storage = storage
// create a log
if l.writer == nil {
// Non-streaming, need to copy slices.
log.Memory = slices.Clone(log.Memory)
log.Stack = slices.Clone(log.Stack)
log.ReturnData = slices.Clone(rData)
l.logs = append(l.logs, log)
entry := log.toLegacyJSON()
l.resultSize += len(entry)
l.logs = append(l.logs, entry)
return
}
log.WriteTo(l.writer)
Expand Down Expand Up @@ -283,7 +344,7 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
Gas: l.usedGas,
Failed: failed,
ReturnValue: returnVal,
StructLogs: formatLogs(l.StructLogs()),
StructLogs: l.jsonLogs(),
})
}

Expand All @@ -310,8 +371,9 @@ func (l *StructLogger) OnTxEnd(receipt *types.Receipt, err error) {
}
}

// StructLogs returns the captured log entries.
func (l *StructLogger) StructLogs() []StructLog { return l.logs }
func (l *StructLogger) jsonLogs() []json.RawMessage {
return l.logs
}

// Error returns the VM error captured by the trace.
func (l *StructLogger) Error() error { return l.err }
Expand Down Expand Up @@ -423,65 +485,8 @@ func (t *mdLogger) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.O
// while replaying a transaction in debug mode as well as transaction
// execution status, the amount of gas used and the return value
type ExecutionResult struct {
Gas uint64 `json:"gas"`
Failed bool `json:"failed"`
ReturnValue string `json:"returnValue"`
StructLogs []StructLogRes `json:"structLogs"`
}

// StructLogRes stores a structured log emitted by the EVM while replaying a
// transaction in debug mode
type StructLogRes struct {
Pc uint64 `json:"pc"`
Op string `json:"op"`
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"`
Error string `json:"error,omitempty"`
Stack *[]string `json:"stack,omitempty"`
ReturnData string `json:"returnData,omitempty"`
Memory *[]string `json:"memory,omitempty"`
Storage *map[string]string `json:"storage,omitempty"`
RefundCounter uint64 `json:"refund,omitempty"`
}

// formatLogs formats EVM returned structured logs for json output
func formatLogs(logs []StructLog) []StructLogRes {
formatted := make([]StructLogRes, len(logs))
for index, trace := range logs {
formatted[index] = StructLogRes{
Pc: trace.Pc,
Op: trace.Op.String(),
Gas: trace.Gas,
GasCost: trace.GasCost,
Depth: trace.Depth,
Error: trace.ErrorString(),
RefundCounter: trace.RefundCounter,
}
if trace.Stack != nil {
stack := make([]string, len(trace.Stack))
for i, stackValue := range trace.Stack {
stack[i] = stackValue.Hex()
}
formatted[index].Stack = &stack
}
if len(trace.ReturnData) > 0 {
formatted[index].ReturnData = hexutil.Bytes(trace.ReturnData).String()
}
if trace.Memory != nil {
memory := make([]string, 0, (len(trace.Memory)+31)/32)
for i := 0; i+32 <= len(trace.Memory); i += 32 {
memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
}
formatted[index].Memory = &memory
}
if trace.Storage != nil {
storage := make(map[string]string)
for i, storageValue := range trace.Storage {
storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
}
formatted[index].Storage = &storage
}
}
return formatted
Gas uint64 `json:"gas"`
Failed bool `json:"failed"`
ReturnValue string `json:"returnValue"`
StructLogs []json.RawMessage `json:"structLogs"`
}

0 comments on commit f1fd5bd

Please sign in to comment.