Skip to content

Commit

Permalink
Merge pull request #600 from libotony/update-tracers
Browse files Browse the repository at this point in the history
update tracers
  • Loading branch information
libotony authored Aug 9, 2023
2 parents 68f45ec + 58826bd commit fc8771e
Show file tree
Hide file tree
Showing 26 changed files with 732 additions and 415 deletions.
50 changes: 30 additions & 20 deletions api/debug/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,21 @@ func New(repo *chain.Repository, stater *state.Stater, forkConfig thor.ForkConfi
}
}

func (d *Debug) handleTxEnv(ctx context.Context, blockID thor.Bytes32, txIndex uint64, clauseIndex uint64) (*runtime.Runtime, *runtime.TransactionExecutor, error) {
func (d *Debug) prepareClauseEnv(ctx context.Context, blockID thor.Bytes32, txIndex uint64, clauseIndex uint64) (*runtime.Runtime, *runtime.TransactionExecutor, thor.Bytes32, error) {
block, err := d.repo.GetBlock(blockID)
if err != nil {
if d.repo.IsNotFound(err) {
return nil, nil, utils.Forbidden(errors.New("block not found"))
return nil, nil, thor.Bytes32{}, utils.Forbidden(errors.New("block not found"))
}
return nil, nil, err
return nil, nil, thor.Bytes32{}, err
}
txs := block.Transactions()
if txIndex >= uint64(len(txs)) {
return nil, nil, utils.Forbidden(errors.New("tx index out of range"))
return nil, nil, thor.Bytes32{}, utils.Forbidden(errors.New("tx index out of range"))
}
txID := txs[txIndex].ID()
if clauseIndex >= uint64(len(txs[txIndex].Clauses())) {
return nil, nil, utils.Forbidden(errors.New("clause index out of range"))
return nil, nil, thor.Bytes32{}, utils.Forbidden(errors.New("clause index out of range"))
}
skipPoA := d.repo.GenesisBlock().Header().ID() == devNetGenesisID
rt, err := consensus.New(
Expand All @@ -67,44 +68,53 @@ func (d *Debug) handleTxEnv(ctx context.Context, blockID thor.Bytes32, txIndex u
d.forkConfig,
).NewRuntimeForReplay(block.Header(), skipPoA)
if err != nil {
return nil, nil, err
return nil, nil, thor.Bytes32{}, err
}
for i, tx := range txs {
if uint64(i) > txIndex {
break
}
txExec, err := rt.PrepareTransaction(tx)
if err != nil {
return nil, nil, err
return nil, nil, thor.Bytes32{}, err
}
clauseCounter := uint64(0)
for txExec.HasNextClause() {
if txIndex == uint64(i) && clauseIndex == clauseCounter {
return rt, txExec, nil
return rt, txExec, txID, nil
}
if _, _, err := txExec.NextClause(); err != nil {
return nil, nil, err
return nil, nil, thor.Bytes32{}, err
}
clauseCounter++
}
if _, err := txExec.Finalize(); err != nil {
return nil, nil, err
return nil, nil, thor.Bytes32{}, err
}
select {
case <-ctx.Done():
return nil, nil, ctx.Err()
return nil, nil, thor.Bytes32{}, ctx.Err()
default:
}
}
return nil, nil, utils.Forbidden(errors.New("early reverted"))
return nil, nil, thor.Bytes32{}, utils.Forbidden(errors.New("early reverted"))
}

//trace an existed transaction
func (d *Debug) traceTransaction(ctx context.Context, tracer tracers.Tracer, blockID thor.Bytes32, txIndex uint64, clauseIndex uint64) (interface{}, error) {
rt, txExec, err := d.handleTxEnv(ctx, blockID, txIndex, clauseIndex)
// trace an existed clause
func (d *Debug) traceClause(ctx context.Context, tracer tracers.Tracer, blockID thor.Bytes32, txIndex uint64, clauseIndex uint64) (interface{}, error) {
rt, txExec, txID, err := d.prepareClauseEnv(ctx, blockID, txIndex, clauseIndex)
if err != nil {
return nil, err
}

tracer.SetContext(&tracers.Context{
BlockID: blockID,
BlockTime: rt.Context().Time,
TxID: txID,
TxIndex: int(txIndex),
ClauseIndex: int(clauseIndex),
State: rt.State(),
})
rt.SetVMConfig(vm.Config{Debug: true, Tracer: tracer})
_, _, err = txExec.NextClause()
if err != nil {
Expand All @@ -113,7 +123,7 @@ func (d *Debug) traceTransaction(ctx context.Context, tracer tracers.Tracer, blo
return tracer.GetResult()
}

func (d *Debug) handleTraceTransaction(w http.ResponseWriter, req *http.Request) error {
func (d *Debug) handleTraceClause(w http.ResponseWriter, req *http.Request) error {
var opt *TracerOption
if err := utils.ParseJSON(req.Body, &opt); err != nil {
return utils.BadRequest(errors.WithMessage(err, "body"))
Expand All @@ -133,7 +143,7 @@ func (d *Debug) handleTraceTransaction(w http.ResponseWriter, req *http.Request)
if !strings.HasSuffix(name, "Tracer") {
name += "Tracer"
}
tr, err := tracers.New(name, nil, opt.Config)
tr, err := tracers.DefaultDirectory.New(name, opt.Config)
if err != nil {
return err
}
Expand All @@ -143,15 +153,15 @@ func (d *Debug) handleTraceTransaction(w http.ResponseWriter, req *http.Request)
if err != nil {
return err
}
res, err := d.traceTransaction(req.Context(), tracer, blockID, txIndex, clauseIndex)
res, err := d.traceClause(req.Context(), tracer, blockID, txIndex, clauseIndex)
if err != nil {
return err
}
return utils.WriteJSON(w, res)
}

func (d *Debug) debugStorage(ctx context.Context, contractAddress thor.Address, blockID thor.Bytes32, txIndex uint64, clauseIndex uint64, keyStart []byte, maxResult int) (*StorageRangeResult, error) {
rt, _, err := d.handleTxEnv(ctx, blockID, txIndex, clauseIndex)
rt, _, _, err := d.prepareClauseEnv(ctx, blockID, txIndex, clauseIndex)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -250,7 +260,7 @@ func (d *Debug) parseTarget(target string) (blockID thor.Bytes32, txIndex uint64
func (d *Debug) Mount(root *mux.Router, pathPrefix string) {
sub := root.PathPrefix(pathPrefix).Subrouter()

sub.Path("/tracers").Methods(http.MethodPost).HandlerFunc(utils.WrapHandlerFunc(d.handleTraceTransaction))
sub.Path("/tracers").Methods(http.MethodPost).HandlerFunc(utils.WrapHandlerFunc(d.handleTraceClause))
sub.Path("/storage-range").Methods(http.MethodPost).HandlerFunc(utils.WrapHandlerFunc(d.handleDebugStorage))

}
49 changes: 35 additions & 14 deletions tracers/logger/logger.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2015 The go-ethereum Authors
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
Expand All @@ -24,12 +24,13 @@ import (
"math/big"
"strings"
"sync/atomic"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/holiman/uint256"
"github.com/vechain/thor/tracers"
"github.com/vechain/thor/vm"
)

Expand All @@ -38,7 +39,7 @@ type Storage map[common.Hash]common.Hash

// Copy duplicates the current storage.
func (s Storage) Copy() Storage {
cpy := make(Storage)
cpy := make(Storage, len(s))
for key, value := range s {
cpy[key] = value
}
Expand Down Expand Up @@ -112,8 +113,8 @@ type StructLogger struct {
err error
usedGas uint64

interrupt uint32 // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
interrupt atomic.Value // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
}

// NewStructLogger returns a new logger
Expand Down Expand Up @@ -150,8 +151,7 @@ func (l *StructLogger) CaptureStart(env *vm.EVM, from common.Address, to common.
// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, rData []byte, depth int, err error) {
// If tracing was interrupted, set the error and stop
if atomic.LoadUint32(&l.interrupt) > 0 {
l.env.Cancel()
if stop := l.interrupt.Load(); stop != nil && stop.(bool) {
return
}
// check if already accumulated the specified number of logs
Expand Down Expand Up @@ -217,12 +217,12 @@ func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, m
}

// CaptureEnd is called after the call finishes to finalize the tracing.
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
l.output = output
l.err = err
l.usedGas = gasUsed
if l.cfg.Debug {
fmt.Printf("0x%x\n", output)
fmt.Printf("%#x\n", output)
if err != nil {
fmt.Printf(" error: %v\n", err)
}
Expand All @@ -235,6 +235,9 @@ func (l *StructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to commo
func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {
}

func (l *StructLogger) SetContext(ctx *tracers.Context) {
}

func (l *StructLogger) GetResult() (json.RawMessage, error) {
// Tracing aborted
if l.reason != nil {
Expand All @@ -258,7 +261,7 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
// Stop terminates execution of the tracer at the first opportune moment.
func (l *StructLogger) Stop(err error) {
l.reason = err
atomic.StoreUint32(&l.interrupt, 1)
l.interrupt.Store(true)
}

// StructLogs returns the captured log entries.
Expand Down Expand Up @@ -303,6 +306,20 @@ func WriteTrace(writer io.Writer, logs []StructLog) {
}
}

// WriteLogs writes vm logs in a readable format to the given writer
func WriteLogs(writer io.Writer, logs []*types.Log) {
for _, log := range logs {
fmt.Fprintf(writer, "LOG%d: %x bn=%d txi=%x\n", len(log.Topics), log.Address, log.BlockNumber, log.TxIndex)

for i, topic := range log.Topics {
fmt.Fprintf(writer, "%08d %x\n", i, topic)
}

fmt.Fprint(writer, hex.Dump(log.Data))
fmt.Fprintln(writer)
}
}

type mdLogger struct {
out io.Writer
cfg *Config
Expand All @@ -322,11 +339,11 @@ func NewMarkdownLogger(cfg *Config, writer io.Writer) *mdLogger {
func (t *mdLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
t.env = env
if !create {
fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n",
from.String(), to.String(),
input, gas, value)
} else {
fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n",
from.String(), to.String(),
input, gas, value)
}
Expand Down Expand Up @@ -361,8 +378,8 @@ func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, memor
fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
}

func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) {
fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n",
func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n",
output, gasUsed, err)
}

Expand Down Expand Up @@ -391,6 +408,7 @@ type StructLogRes struct {
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"`
Expand All @@ -416,6 +434,9 @@ func formatLogs(logs []StructLog) []StructLogRes {
}
formatted[index].Stack = &stack
}
if trace.ReturnData != nil && 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 {
Expand Down
8 changes: 3 additions & 5 deletions tracers/logger/logger_json.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2017 The go-ethereum Authors
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
Expand All @@ -20,7 +20,6 @@ import (
"encoding/json"
"io"
"math/big"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
Expand Down Expand Up @@ -77,18 +76,17 @@ func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, mem
}

// CaptureEnd is triggered at end of execution.
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
type endLog struct {
Output string `json:"output"`
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
Time time.Duration `json:"time"`
Err string `json:"error,omitempty"`
}
var errMsg string
if err != nil {
errMsg = err.Error()
}
l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, errMsg})
l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg})
}

func (l *JSONLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
Expand Down
6 changes: 3 additions & 3 deletions tracers/logger/logger_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 The go-ethereum Authors
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
Expand All @@ -18,7 +18,7 @@ package logger

import (
"encoding/json"
"fmt"
"errors"
"math/big"
"testing"

Expand Down Expand Up @@ -85,7 +85,7 @@ func TestStructLogMarshalingOmitEmpty(t *testing.T) {
}{
{"empty err and no fields", &StructLog{},
`{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memSize":0,"stack":null,"depth":0,"refund":0,"opName":"STOP"}`},
{"with err", &StructLog{Err: fmt.Errorf("this failed")},
{"with err", &StructLog{Err: errors.New("this failed")},
`{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memSize":0,"stack":null,"depth":0,"refund":0,"opName":"STOP","error":"this failed"}`},
{"with mem", &StructLog{Memory: make([]byte, 2), MemorySize: 2},
`{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memory":"0x0000","memSize":2,"stack":null,"depth":0,"refund":0,"opName":"STOP"}`},
Expand Down
Loading

0 comments on commit fc8771e

Please sign in to comment.