core/vm, eth/tracers: simplify ethcall response type, encode early

This commit is contained in:
Martin Holst Swende 2024-11-26 10:15:02 +01:00
parent 966b62dbcb
commit 220c250db8
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
2 changed files with 92 additions and 80 deletions

View file

@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/asm" "github.com/ethereum/go-ethereum/core/asm"
"github.com/ethereum/go-ethereum/core/state" "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/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/core/vm/program" "github.com/ethereum/go-ethereum/core/vm/program"
@ -670,17 +671,23 @@ func TestColdAccountAccessCost(t *testing.T) {
want: 7600, want: 7600,
}, },
} { } {
tracer := logger.NewStructLogger(nil) var step = 0
var have = uint64(0)
Execute(tc.code, nil, &Config{ Execute(tc.code, nil, &Config{
EVMConfig: vm.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 { 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) t.Fatalf("testcase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want)
} }
} }

View file

@ -23,7 +23,6 @@ import (
"io" "io"
"maps" "maps"
"math/big" "math/big"
"slices"
"strings" "strings"
"sync/atomic" "sync/atomic"
@ -124,6 +123,69 @@ func (log *StructLog) WriteTo(writer io.Writer) {
fmt.Fprintln(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 is an EVM state logger and implements EVMLogger.
// //
// StructLogger can capture state based on the given Log configuration and also keeps // StructLogger can capture state based on the given Log configuration and also keeps
@ -137,12 +199,13 @@ type StructLogger struct {
env *tracing.VMContext env *tracing.VMContext
storage map[common.Address]Storage storage map[common.Address]Storage
logs []StructLog
output []byte output []byte
err error err error
usedGas uint64 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 interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
@ -191,8 +254,8 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope
if l.interrupt.Load() { if l.interrupt.Load() {
return return
} }
// check if already accumulated the specified number of logs // check if already accumulated the size of the response.
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { if l.cfg.Limit != 0 && l.resultSize > l.cfg.Limit {
return return
} }
var ( var (
@ -242,11 +305,9 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope
log.Storage = storage log.Storage = storage
// create a log // create a log
if l.writer == nil { if l.writer == nil {
// Non-streaming, need to copy slices. entry := log.toLegacyJSON()
log.Memory = slices.Clone(log.Memory) l.resultSize += len(entry)
log.Stack = slices.Clone(log.Stack) l.logs = append(l.logs, entry)
log.ReturnData = slices.Clone(rData)
l.logs = append(l.logs, log)
return return
} }
log.WriteTo(l.writer) log.WriteTo(l.writer)
@ -283,7 +344,7 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
Gas: l.usedGas, Gas: l.usedGas,
Failed: failed, Failed: failed,
ReturnValue: returnVal, ReturnValue: returnVal,
StructLogs: formatLogs(l.StructLogs()), StructLogs: l.jsonLogs(),
}) })
} }
@ -310,8 +371,9 @@ func (l *StructLogger) OnTxEnd(receipt *types.Receipt, err error) {
} }
} }
// StructLogs returns the captured log entries. func (l *StructLogger) jsonLogs() []json.RawMessage {
func (l *StructLogger) StructLogs() []StructLog { return l.logs } return l.logs
}
// Error returns the VM error captured by the trace. // Error returns the VM error captured by the trace.
func (l *StructLogger) Error() error { return l.err } func (l *StructLogger) Error() error { return l.err }
@ -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 // while replaying a transaction in debug mode as well as transaction
// execution status, the amount of gas used and the return value // execution status, the amount of gas used and the return value
type ExecutionResult struct { type ExecutionResult struct {
Gas uint64 `json:"gas"` Gas uint64 `json:"gas"`
Failed bool `json:"failed"` Failed bool `json:"failed"`
ReturnValue string `json:"returnValue"` ReturnValue string `json:"returnValue"`
StructLogs []StructLogRes `json:"structLogs"` StructLogs []json.RawMessage `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
} }