erigon-pulse/eth/tracers/logger/json_stream.go
Alex Sharov c293883ec0
evm: no interface (#8376)
after removal of tevm experiment - we left interfaces everywhere 
removing it for performance and for geth compatibility
2023-10-05 12:23:08 +07:00

200 lines
5.9 KiB
Go

package logger
import (
"context"
"encoding/hex"
"sort"
"github.com/holiman/uint256"
jsoniter "github.com/json-iterator/go"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/core/vm"
)
// JsonStreamLogger is an EVM state logger and implements Tracer.
//
// JsonStreamLogger can capture state based on the given Log configuration and also keeps
// a track record of modified storage which is used in reporting snapshots of the
// contract their storage.
type JsonStreamLogger struct {
ctx context.Context
cfg LogConfig
stream *jsoniter.Stream
hexEncodeBuf [128]byte
firstCapture bool
locations common.Hashes // For sorting
storage map[libcommon.Address]Storage
logs []StructLog
output []byte //nolint
err error //nolint
env *vm.EVM
}
// NewStructLogger returns a new logger
func NewJsonStreamLogger(cfg *LogConfig, ctx context.Context, stream *jsoniter.Stream) *JsonStreamLogger {
logger := &JsonStreamLogger{
ctx: ctx,
stream: stream,
storage: make(map[libcommon.Address]Storage),
firstCapture: true,
}
if cfg != nil {
logger.cfg = *cfg
}
return logger
}
func (l *JsonStreamLogger) CaptureTxStart(gasLimit uint64) {}
func (l *JsonStreamLogger) CaptureTxEnd(restGas uint64) {}
// CaptureStart implements the Tracer interface to initialize the tracing operation.
func (l *JsonStreamLogger) CaptureStart(env *vm.EVM, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) {
l.env = env
}
func (l *JsonStreamLogger) CaptureEnter(typ vm.OpCode, from libcommon.Address, to libcommon.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) {
}
// CaptureState logs a new structured log message and pushes it out to the environment
//
// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
func (l *JsonStreamLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
contract := scope.Contract
memory := scope.Memory
stack := scope.Stack
select {
case <-l.ctx.Done():
return
default:
}
// check if already accumulated the specified number of logs
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
return
}
if !l.firstCapture {
l.stream.WriteMore()
} else {
l.firstCapture = false
}
var outputStorage bool
if !l.cfg.DisableStorage {
// initialise new changed values storage container for this contract
// if not present.
if l.storage[contract.Address()] == nil {
l.storage[contract.Address()] = make(Storage)
}
// capture SLOAD opcodes and record the read entry in the local storage
if op == vm.SLOAD && stack.Len() >= 1 {
var (
address = libcommon.Hash(stack.Data[stack.Len()-1].Bytes32())
value uint256.Int
)
l.env.IntraBlockState().GetState(contract.Address(), &address, &value)
l.storage[contract.Address()][address] = value.Bytes32()
outputStorage = true
}
// capture SSTORE opcodes and record the written entry in the local storage.
if op == vm.SSTORE && stack.Len() >= 2 {
var (
value = libcommon.Hash(stack.Data[stack.Len()-2].Bytes32())
address = libcommon.Hash(stack.Data[stack.Len()-1].Bytes32())
)
l.storage[contract.Address()][address] = value
outputStorage = true
}
}
// create a new snapshot of the EVM.
l.stream.WriteObjectStart()
l.stream.WriteObjectField("pc")
l.stream.WriteUint64(pc)
l.stream.WriteMore()
l.stream.WriteObjectField("op")
l.stream.WriteString(op.String())
l.stream.WriteMore()
l.stream.WriteObjectField("gas")
l.stream.WriteUint64(gas)
l.stream.WriteMore()
l.stream.WriteObjectField("gasCost")
l.stream.WriteUint64(cost)
l.stream.WriteMore()
l.stream.WriteObjectField("depth")
l.stream.WriteInt(depth)
if err != nil {
l.stream.WriteMore()
l.stream.WriteObjectField("error")
l.stream.WriteObjectStart()
l.stream.WriteObjectEnd()
//l.stream.WriteString(err.Error())
}
if !l.cfg.DisableStack {
l.stream.WriteMore()
l.stream.WriteObjectField("stack")
l.stream.WriteArrayStart()
for i, stackValue := range stack.Data {
if i > 0 {
l.stream.WriteMore()
}
l.stream.WriteString(stackValue.String())
}
l.stream.WriteArrayEnd()
}
if !l.cfg.DisableMemory {
memData := memory.Data()
l.stream.WriteMore()
l.stream.WriteObjectField("memory")
l.stream.WriteArrayStart()
for i := 0; i+32 <= len(memData); i += 32 {
if i > 0 {
l.stream.WriteMore()
}
l.stream.WriteString(string(l.hexEncodeBuf[0:hex.Encode(l.hexEncodeBuf[:], memData[i:i+32])]))
}
l.stream.WriteArrayEnd()
}
if outputStorage {
l.stream.WriteMore()
l.stream.WriteObjectField("storage")
l.stream.WriteObjectStart()
first := true
// Sort storage by locations for easier comparison with geth
if l.locations != nil {
l.locations = l.locations[:0]
}
s := l.storage[contract.Address()]
for loc := range s {
l.locations = append(l.locations, loc)
}
sort.Sort(l.locations)
for _, loc := range l.locations {
value := s[loc]
if first {
first = false
} else {
l.stream.WriteMore()
}
l.stream.WriteObjectField(string(l.hexEncodeBuf[0:hex.Encode(l.hexEncodeBuf[:], loc[:])]))
l.stream.WriteString(string(l.hexEncodeBuf[0:hex.Encode(l.hexEncodeBuf[:], value[:])]))
}
l.stream.WriteObjectEnd()
}
l.stream.WriteObjectEnd()
_ = l.stream.Flush()
}
// CaptureFault implements the Tracer interface to trace an execution fault
// while running an opcode.
func (l *JsonStreamLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (l *JsonStreamLogger) CaptureEnd(output []byte, usedGas uint64, err error) {
}
func (l *JsonStreamLogger) CaptureExit(output []byte, usedGas uint64, err error) {
}