Skip to content

Commit

Permalink
Merge pull request #608 from libotony/tracers-dev
Browse files Browse the repository at this point in the history
Tracers dev
  • Loading branch information
libotony authored Sep 1, 2023
2 parents b662610 + 34f8cc7 commit 1750674
Show file tree
Hide file tree
Showing 29 changed files with 1,863 additions and 1,039 deletions.
29 changes: 23 additions & 6 deletions api/debug/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ func (d *Debug) prepareClauseEnv(ctx context.Context, blockID thor.Bytes32, txIn
if txIndex == uint64(i) && clauseIndex == clauseCounter {
return rt, txExec, txID, nil
}
if _, _, err := txExec.NextClause(); err != nil {
exec, _ := txExec.PrepareNext()
if _, _, err := exec(); err != nil {
return nil, nil, thor.Bytes32{}, err
}
clauseCounter++
Expand Down Expand Up @@ -123,10 +124,24 @@ func (d *Debug) traceClause(ctx context.Context, tracer tracers.Tracer, blockID
ClauseIndex: int(clauseIndex),
State: rt.State(),
})
rt.SetVMConfig(vm.Config{Debug: true, Tracer: tracer})
_, _, err = txExec.NextClause()
if err != nil {
rt.SetVMConfig(vm.Config{Tracer: tracer})
errCh := make(chan error, 1)
exec, interrupt := txExec.PrepareNext()
go func() {
_, _, err := exec()
errCh <- err
}()

select {
case <-ctx.Done():
err := ctx.Err()
tracer.Stop(err)
interrupt()
return nil, err
case err := <-errCh:
if err != nil {
return nil, err
}
}
return tracer.GetResult()
}
Expand Down Expand Up @@ -222,7 +237,7 @@ func (d *Debug) traceCall(ctx context.Context, tracer tracers.Tracer, summary *c
BlockTime: summary.Header.Timestamp(),
State: state,
})
rt.SetVMConfig(vm.Config{Debug: true, Tracer: tracer})
rt.SetVMConfig(vm.Config{Tracer: tracer})

errCh := make(chan error, 1)
exec, interrupt := rt.PrepareClause(clause, 0, gas, txCtx)
Expand All @@ -232,8 +247,10 @@ func (d *Debug) traceCall(ctx context.Context, tracer tracers.Tracer, summary *c
}()
select {
case <-ctx.Done():
err := ctx.Err()
tracer.Stop(err)
interrupt()
return nil, ctx.Err()
return nil, err
case err := <-errCh:
if err != nil {
return nil, err
Expand Down
61 changes: 37 additions & 24 deletions runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type Output struct {

type TransactionExecutor struct {
HasNextClause func() bool
NextClause func() (gasUsed uint64, output *Output, err error)
PrepareNext func() (exec func() (gasUsed uint64, output *Output, err error), interrupt func())
Finalize func() (*tx.Receipt, error)
}

Expand Down Expand Up @@ -367,7 +367,8 @@ func (rt *Runtime) ExecuteTransaction(tx *tx.Transaction) (receipt *tx.Receipt,
return nil, err
}
for executor.HasNextClause() {
if _, _, err := executor.NextClause(); err != nil {
exec, _ := executor.PrepareNext()
if _, _, err := exec(); err != nil {
return nil, err
}
}
Expand Down Expand Up @@ -406,34 +407,46 @@ func (rt *Runtime) PrepareTransaction(tx *tx.Transaction) (*TransactionExecutor,

return &TransactionExecutor{
HasNextClause: hasNext,
NextClause: func() (gasUsed uint64, output *Output, err error) {
PrepareNext: func() (exec func() (uint64, *Output, error), interrupt func()) {
nextClauseIndex := uint32(len(txOutputs))
exec, _ := rt.PrepareClause(resolvedTx.Clauses[nextClauseIndex], nextClauseIndex, leftOverGas, txCtx)
output, _, err = exec()
if err != nil {
return 0, nil, err
}
gasUsed = leftOverGas - output.LeftOverGas
leftOverGas = output.LeftOverGas
execFunc, interrupt := rt.PrepareClause(resolvedTx.Clauses[nextClauseIndex], nextClauseIndex, leftOverGas, txCtx)

exec = func() (gasUsed uint64, output *Output, err error) {
if rt.vmConfig.Tracer != nil {
rt.vmConfig.Tracer.CaptureClauseStart(leftOverGas)
defer func() {
rt.vmConfig.Tracer.CaptureClauseEnd(leftOverGas)
}()
}

// Apply refund counter, capped to half of the used gas.
refund := gasUsed / 2
if refund > output.RefundGas {
refund = output.RefundGas
}
output, _, err = execFunc()
if err != nil {
return 0, nil, err
}
gasUsed = leftOverGas - output.LeftOverGas
leftOverGas = output.LeftOverGas

// Apply refund counter, capped to half of the used gas.
refund := gasUsed / 2
if refund > output.RefundGas {
refund = output.RefundGas
}

// won't overflow
leftOverGas += refund
// won't overflow
leftOverGas += refund

if output.VMErr != nil {
// vm exception here
// revert all executed clauses
rt.state.RevertTo(checkpoint)
reverted = true
txOutputs = nil
if output.VMErr != nil {
// vm exception here
// revert all executed clauses
rt.state.RevertTo(checkpoint)
reverted = true
txOutputs = nil
return
}
txOutputs = append(txOutputs, &Tx.Output{Events: output.Events, Transfers: output.Transfers})
return
}
txOutputs = append(txOutputs, &Tx.Output{Events: output.Events, Transfers: output.Transfers})

return
},
Finalize: func() (*Tx.Receipt, error) {
Expand Down
18 changes: 14 additions & 4 deletions tracers/js/goja.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ import (
"github.com/vechain/thor/vm"
)

// var assetTracers = make(map[string]string)
var assetTracers = make(map[string]string)

// init retrieves the JavaScript transaction tracers included in go-ethereum.
func init() {
var err error
assetTracers, err := jsassets.Load()
assetTracers, err = jsassets.Load()
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -202,10 +202,21 @@ func newJsTracer(code string, cfg json.RawMessage) (tracers.Tracer, error) {
return t, nil
}

// CaptureClauseStart implements the Tracer interface and is invoked at the beginning of
// clause processing.
func (t *jsTracer) CaptureClauseStart(gasLimit uint64) {
t.gasLimit = gasLimit
}

// CaptureClauseEnd implements the Tracer interface and is invoked at the end of
// clause processing.
func (t *jsTracer) CaptureClauseEnd(restGas uint64) {
t.ctx["gasUsed"] = t.vm.ToValue(t.gasLimit - restGas)
}

// CaptureStart implements the Tracer interface to initialize the tracing operation.
func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
t.env = env
t.gasLimit = gas
db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf}
t.dbValue = db.setupObject()
if create {
Expand Down Expand Up @@ -269,7 +280,6 @@ func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, memor

// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
t.ctx["gasUsed"] = t.vm.ToValue(gasUsed)
t.ctx["output"] = t.vm.ToValue(output)
if err != nil {
t.ctx["error"] = t.vm.ToValue(err.Error())
Expand Down
7 changes: 5 additions & 2 deletions tracers/js/tracer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func testCtx() vm.Context {

func runTrace(tracer tracers.Tracer, ctx vm.Context, chaincfg *vm.ChainConfig, contractCode []byte) (json.RawMessage, error) {
var (
env = vm.NewEVM(ctx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer})
env = vm.NewEVM(ctx, &dummyStatedb{}, chaincfg, vm.Config{Tracer: tracer})

startGas uint64 = 10000
value = big.NewInt(0)
Expand All @@ -71,9 +71,12 @@ func runTrace(tracer tracers.Tracer, ctx vm.Context, chaincfg *vm.ChainConfig, c
contract.Code = contractCode
}

tracer.CaptureClauseStart(startGas)
tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value)
ret, err := env.Interpreter().Run(contract, []byte{})
tracer.CaptureEnd(ret, startGas-contract.Gas, err)
// Rest gas assumes no refund
tracer.CaptureClauseEnd(contract.Gas)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -178,7 +181,7 @@ func TestHaltBetweenSteps(t *testing.T) {
env := vm.NewEVM(vm.Context{
BlockNumber: big.NewInt(1),
GasPrice: big.NewInt(1),
}, &dummyStatedb{}, &vm.ChainConfig{ChainConfig: *params.TestChainConfig}, vm.Config{Debug: true, Tracer: tracer})
}, &dummyStatedb{}, &vm.ChainConfig{ChainConfig: *params.TestChainConfig}, vm.Config{Tracer: tracer})
tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 0, big.NewInt(0))
contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0)
tracer.CaptureState(0, 0, 0, 0, nil, nil, contract, nil, 0, nil)
Expand Down
24 changes: 18 additions & 6 deletions tracers/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,12 @@ type StructLogger struct {
cfg Config
env *vm.EVM

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

interrupt atomic.Value // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
Expand Down Expand Up @@ -220,7 +221,6 @@ func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, m
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("%#x\n", output)
if err != nil {
Expand Down Expand Up @@ -264,6 +264,14 @@ func (l *StructLogger) Stop(err error) {
l.interrupt.Store(true)
}

func (l *StructLogger) CaptureClauseStart(gasLimit uint64) {
l.gasLimit = gasLimit
}

func (l *StructLogger) CaptureClauseEnd(restGas uint64) {
l.usedGas = l.gasLimit - restGas
}

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

Expand Down Expand Up @@ -388,6 +396,10 @@ func (t *mdLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Ad

func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}

func (*mdLogger) CaptureClauseStart(gasLimit uint64) {}

func (*mdLogger) CaptureClauseEnd(restGas uint64) {}

// 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
Expand Down
2 changes: 1 addition & 1 deletion tracers/logger/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (*dummyStatedb) SetState(_ common.Address, _ common.Hash, _ common.Hash) {}
func TestStoreCapture(t *testing.T) {
var (
logger, _ = NewStructLogger(nil)
env = vm.NewEVM(vm.Context{}, &dummyStatedb{}, &vm.ChainConfig{ChainConfig: *params.TestChainConfig}, vm.Config{Debug: true, Tracer: logger})
env = vm.NewEVM(vm.Context{}, &dummyStatedb{}, &vm.ChainConfig{ChainConfig: *params.TestChainConfig}, vm.Config{Tracer: logger})

contract = vm.NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 100000)
)
Expand Down
20 changes: 14 additions & 6 deletions tracers/native/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ type callTracer struct {
noopTracer
callstack []callFrame
config callTracerConfig
gasLimit uint64
interrupt atomic.Value // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
}
Expand Down Expand Up @@ -124,7 +125,7 @@ func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad
From: from,
To: &toCopy,
Input: common.CopyBytes(input),
Gas: gas,
Gas: t.gasLimit,
Value: value,
}
if create {
Expand All @@ -134,12 +135,7 @@ func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad

// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
t.callstack[0].GasUsed = gasUsed
t.callstack[0].processOutput(output, err)
if t.config.WithLog {
// Logs are not emitted when the call fails
clearFailedLogs(&t.callstack[0], false)
}
}

// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
Expand Down Expand Up @@ -228,6 +224,18 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
}

func (t *callTracer) CaptureClauseStart(gasLimit uint64) {
t.gasLimit = gasLimit
}

func (t *callTracer) CaptureClauseEnd(restGas uint64) {
t.callstack[0].GasUsed = t.gasLimit - restGas
if t.config.WithLog {
// Logs are not emitted when the call fails
clearFailedLogs(&t.callstack[0], false)
}
}

// 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) {
Expand Down
8 changes: 8 additions & 0 deletions tracers/native/noop.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
}

// CaptureClauseStart implements the Tracer interface and is invoked at the beginning of
// clause processing.
func (*noopTracer) CaptureClauseStart(gasLimit uint64) {}

// CaptureClauseEnd implements the Tracer interface and is invoked at the end of
// clause processing.
func (*noopTracer) CaptureClauseEnd(restGas uint64) {}

// SetContext set the tracer context
func (t *noopTracer) SetContext(ctx *tracers.Context) {
}
Expand Down
Loading

0 comments on commit 1750674

Please sign in to comment.