Skip to content

Commit

Permalink
Debug tracing (#836)
Browse files Browse the repository at this point in the history
* Add draft EVM tracer

* Add draft block tracer

* Add draft debug_traceCall endpoint

* Add helper struct for common methods among jsonrpc endpoints

* Format struct logs in EVM tracer

* Fix lint error

* Fix lint error

* Revert super struct of jsonrpc endpoint and add helper methods to debug endpoint

* Remove Tracer argument from Apply method and add SetTracer method in Transition object

* Merge CallStart & InnerCallStart, CallEnd & InnerCallEnd

Fix lint error

* Merge CpatureMemory, CaptureStack, and CaptureStorage into CaptureState

* Cleanup the flow with tracer in VM loop

* Clean up the flow with Tracer in Transition

* Reconstruct tracer package

* Fix comment

* Create helper functions in jsonrpc package

* Remove dependency of TracerConfig in debug endpoint

* Add TestDebugTraceConfigDecode

* Rename tracing methods in store

* Add timeout config in tracer

* Remove Limit from Tracer configuration

* [WIP] Reverting some missing changes from previous merge commit

* Fix build & test error due to wrong resolving of merge conflicts

* Add unit tests in JSON-RPC layer

* Add hash or number to error message in debug endpoint

* Remove inline instanizing error object

* Add unit tests of StructTracer

* Add unit tests of EVM with Tracer

* Fix minor issue that passing nil to captureCallEnd

* Fix lint error

* Remove comment

* Add cancelLock instead of sync.Atomic to protect reason against concurrent accesses

* Fix typo

* Remove context argument from newTracer

* Fix error message of ErrNegativeBlockNumber

* Export cancel func to terminate context from newTracer

* Add explain for ErrTraceGenesisBlock

* Add nil check for latest header in GetNumericBlockNumber

* Fix lint error

* Remove resolveNum from getLogsFromBlocks

* Update go.sum

* Fix comment

* Removed unused local variable

Revert gasCopy

* Set empty array in struct tracer result to adjust some tools (Hardhat, Truffle)

* Ran go mod tidy

Co-authored-by: Lazar Travica <lazar.travica@mvpworkshop.co>
  • Loading branch information
Kourin1996 and lazartravica authored Nov 28, 2022
1 parent e22c827 commit 316ce9b
Show file tree
Hide file tree
Showing 26 changed files with 4,076 additions and 249 deletions.
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT
github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c=
github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/bwesterb/go-ristretto v1.2.0 h1:xxWOVbN5m8NNKiSDZXE1jtZvZnC6JSJ9cYFADiZcWtw=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
Expand Down Expand Up @@ -668,9 +667,6 @@ github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cb
github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/umbracle/ethgo v0.1.4-0.20220722090909-c8ac32939570 h1:/KyTftQQhxq0iRIVRocn0F2D4zoHmstIfB4FTDjsZbw=
github.com/umbracle/ethgo v0.1.4-0.20220722090909-c8ac32939570/go.mod h1:g9zclCLixH8liBI27Py82klDkW7Oo33AxUOr+M9lzrU=
github.com/umbracle/ethgo v0.1.4-0.20221117101647-b81ef2f07953 h1:ep+lwZyyeh1iw8UojTxslDsqPw0305Vu6lOiEebWS8k=
github.com/umbracle/ethgo v0.1.4-0.20221117101647-b81ef2f07953/go.mod h1:8QIHEG/YfGnW4I5AND2Znl9W0LU3tXR9IGqgmSieiGo=
github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722 h1:10Nbw6cACsnQm7r34zlpJky+IzxVLRk6MKTS2d3Vp0E=
Expand Down
230 changes: 230 additions & 0 deletions jsonrpc/debug_endpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
package jsonrpc

import (
"context"
"errors"
"fmt"
"time"

"github.com/0xPolygon/polygon-edge/helper/hex"
"github.com/0xPolygon/polygon-edge/state/runtime/tracer"
"github.com/0xPolygon/polygon-edge/state/runtime/tracer/structtracer"
"github.com/0xPolygon/polygon-edge/types"
)

var (
defaultTraceTimeout = 5 * time.Second

// ErrExecutionTimeout indicates the execution was terminated due to timeout
ErrExecutionTimeout = errors.New("execution timeout")
// ErrTraceGenesisBlock is an error returned when tracing genesis block which can't be traced
ErrTraceGenesisBlock = errors.New("genesis is not traceable")
)

type debugBlockchainStore interface {
// Header returns the current header of the chain (genesis if empty)
Header() *types.Header

// GetHeaderByNumber gets a header using the provided number
GetHeaderByNumber(uint64) (*types.Header, bool)

// ReadTxLookup returns a block hash in which a given txn was mined
ReadTxLookup(txnHash types.Hash) (types.Hash, bool)

// GetBlockByHash gets a block using the provided hash
GetBlockByHash(hash types.Hash, full bool) (*types.Block, bool)

// GetBlockByNumber gets a block using the provided height
GetBlockByNumber(num uint64, full bool) (*types.Block, bool)

// TraceBlock traces all transactions in the given block
TraceBlock(*types.Block, tracer.Tracer) ([]interface{}, error)

// TraceTxn traces a transaction in the block, associated with the given hash
TraceTxn(*types.Block, types.Hash, tracer.Tracer) (interface{}, error)

// TraceCall traces a single call at the point when the given header is mined
TraceCall(*types.Transaction, *types.Header, tracer.Tracer) (interface{}, error)
}

type debugTxPoolStore interface {
GetNonce(types.Address) uint64
}

type debugStateStore interface {
GetAccount(root types.Hash, addr types.Address) (*Account, error)
}

type debugStore interface {
debugBlockchainStore
debugTxPoolStore
debugStateStore
}

// Debug is the debug jsonrpc endpoint
type Debug struct {
store debugStore
}

type TraceConfig struct {
EnableMemory bool `json:"enableMemory"`
DisableStack bool `json:"disableStack"`
DisableStorage bool `json:"disableStorage"`
EnableReturnData bool `json:"enableReturnData"`
Timeout *string `json:"timeout"`
}

func (d *Debug) TraceBlockByNumber(
blockNumber BlockNumber,
config *TraceConfig,
) (interface{}, error) {
num, err := GetNumericBlockNumber(blockNumber, d.store)
if err != nil {
return nil, err
}

block, ok := d.store.GetBlockByNumber(num, true)
if !ok {
return nil, fmt.Errorf("block %d not found", num)
}

return d.traceBlock(block, config)
}

func (d *Debug) TraceBlockByHash(
blockHash types.Hash,
config *TraceConfig,
) (interface{}, error) {
block, ok := d.store.GetBlockByHash(blockHash, true)
if !ok {
return nil, fmt.Errorf("block %s not found", blockHash)
}

return d.traceBlock(block, config)
}

func (d *Debug) TraceBlock(
input string,
config *TraceConfig,
) (interface{}, error) {
blockByte, decodeErr := hex.DecodeHex(input)
if decodeErr != nil {
return nil, fmt.Errorf("unable to decode block, %w", decodeErr)
}

block := &types.Block{}
if err := block.UnmarshalRLP(blockByte); err != nil {
return nil, err
}

return d.traceBlock(block, config)
}

func (d *Debug) TraceTransaction(
txHash types.Hash,
config *TraceConfig,
) (interface{}, error) {
tx, block := GetTxAndBlockByTxHash(txHash, d.store)
if tx == nil {
return nil, fmt.Errorf("tx %s not found", txHash.String())
}

if block.Number() == 0 {
return nil, ErrTraceGenesisBlock
}

tracer, cancel, err := newTracer(config)
defer cancel()

if err != nil {
return nil, err
}

return d.store.TraceTxn(block, tx.Hash, tracer)
}

func (d *Debug) TraceCall(
arg *txnArgs,
filter BlockNumberOrHash,
config *TraceConfig,
) (interface{}, error) {
header, err := GetHeaderFromBlockNumberOrHash(filter, d.store)
if err != nil {
return nil, ErrHeaderNotFound
}

tx, err := DecodeTxn(arg, d.store)
if err != nil {
return nil, err
}

// If the caller didn't supply the gas limit in the message, then we set it to maximum possible => block gas limit
if tx.Gas == 0 {
tx.Gas = header.GasLimit
}

tracer, cancel, err := newTracer(config)
defer cancel()

if err != nil {
return nil, err
}

return d.store.TraceCall(tx, header, tracer)
}

func (d *Debug) traceBlock(
block *types.Block,
config *TraceConfig,
) (interface{}, error) {
if block.Number() == 0 {
return nil, ErrTraceGenesisBlock
}

tracer, cancel, err := newTracer(config)
defer cancel()

if err != nil {
return nil, err
}

return d.store.TraceBlock(block, tracer)
}

// newTracer creates new tracer by config
func newTracer(config *TraceConfig) (
tracer.Tracer,
context.CancelFunc,
error,
) {
var (
timeout = defaultTraceTimeout
err error
)

if config.Timeout != nil {
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
return nil, nil, err
}
}

tracer := structtracer.NewStructTracer(structtracer.Config{
EnableMemory: config.EnableMemory,
EnableStack: !config.DisableStack,
EnableStorage: !config.DisableStorage,
EnableReturnData: config.EnableReturnData,
})

timeoutCtx, cancel := context.WithTimeout(context.Background(), timeout)

go func() {
<-timeoutCtx.Done()

if errors.Is(timeoutCtx.Err(), context.DeadlineExceeded) {
tracer.Cancel(ErrExecutionTimeout)
}
}()

// cancellation of context is done by caller
return tracer, cancel, nil
}
Loading

0 comments on commit 316ce9b

Please sign in to comment.