Skip to content

Commit

Permalink
internal/cli: add block tracing (ethereum#397)
Browse files Browse the repository at this point in the history
* bad block tracing

* Add debug env framework

* add implementation of custom block tracing

* add few comments

* use tar name fn

* wip: add unit tests for block tracing

* complete test for block tracer

* fix: close grpc server

* refactor cli tests

* fix: change condition for parsing args

* Fix port binding for test server

* add helper for creating and closing mock server for tests

* consume mock server in tests

* fixes due to geth merge

* fix: handle port selection for http server

* update help and markdown for debug command

* update docs

* update debug synopsis

* fix: use chunked encoder to handle large data over grpc

* fix prints

* lint

* rm unused function, rename fn to TraceBorBlock

Co-authored-by: Ferran Borreguero <ferranbt@protonmail.com>
  • Loading branch information
manav2401 and ferranbt authored Jun 6, 2022
1 parent 4ffdbfe commit 0d2b1d0
Show file tree
Hide file tree
Showing 17 changed files with 1,560 additions and 643 deletions.
4 changes: 4 additions & 0 deletions docs/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@

- [```debug```](./debug.md)

- [```debug block```](./debug_block.md)

- [```debug pprof```](./debug_pprof.md)

- [```fingerprint```](./fingerprint.md)

- [```peers```](./peers.md)
Expand Down
8 changes: 2 additions & 6 deletions docs/cli/debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@

The ```bor debug``` command takes a debug dump of the running client.

## Options
- [```bor debug pprof```](./debug_pprof.md): Dumps bor pprof traces.

- ```address```: Address of the grpc endpoint

- ```seconds```: seconds to trace

- ```output```: Output directory
- [```bor debug block <number>```](./debug_block.md): Dumps bor block traces.

## Examples

Expand Down
9 changes: 9 additions & 0 deletions docs/cli/debug_block.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Debug trace

The ```bor debug block <number>``` command will create an archive containing traces of a bor block.

## Options

- ```address```: Address of the grpc endpoint

- ```output```: Output directory
11 changes: 11 additions & 0 deletions docs/cli/debug_pprof.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Debug Pprof

The ```debug pprof <enode>``` command will create an archive containing bor pprof traces.

## Options

- ```address```: Address of the grpc endpoint

- ```seconds```: seconds to trace

- ```output```: Output directory
143 changes: 143 additions & 0 deletions eth/tracers/api_bor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package tracers

import (
"context"
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
)

type BlockTraceResult struct {
// Trace of each transaction executed
Transactions []*TxTraceResult `json:"transactions,omitempty"`

// Block that we are executing on the trace
Block interface{} `json:"block"`
}

type TxTraceResult struct {
// Trace results produced by the tracer
Result interface{} `json:"result,omitempty"`

// Trace failure produced by the tracer
Error string `json:"error,omitempty"`

// IntermediateHash of the execution if succeeds
IntermediateHash common.Hash `json:"intermediatehash"`
}

func (api *API) traceBorBlock(ctx context.Context, block *types.Block, config *TraceConfig) (*BlockTraceResult, error) {
if block.NumberU64() == 0 {
return nil, fmt.Errorf("genesis is not traceable")
}

res := &BlockTraceResult{
Block: block,
}

// block object cannot be converted to JSON since much of the fields are non-public
blockFields, err := ethapi.RPCMarshalBlock(block, true, true, api.backend.ChainConfig())
if err != nil {
return nil, err
}
res.Block = blockFields

parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash())
if err != nil {
return nil, err
}
reexec := defaultTraceReexec
if config != nil && config.Reexec != nil {
reexec = *config.Reexec
}
// TODO: discuss consequences of setting preferDisk false.
statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false)
if err != nil {
return nil, err
}

// Execute all the transaction contained within the block concurrently
var (
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
txs = block.Transactions()
deleteEmptyObjects = api.backend.ChainConfig().IsEIP158(block.Number())
)

blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)

traceTxn := func(indx int, tx *types.Transaction) *TxTraceResult {
message, _ := tx.AsMessage(signer, block.BaseFee())
txContext := core.NewEVMTxContext(message)

tracer := logger.NewStructLogger(config.Config)

// Run the transaction with tracing enabled.
vmenv := vm.NewEVM(blockCtx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true})

// Call Prepare to clear out the statedb access list
// Not sure if we need to do this
statedb.Prepare(tx.Hash(), indx)

execRes, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
if err != nil {
return &TxTraceResult{
Error: err.Error(),
}
}

returnVal := fmt.Sprintf("%x", execRes.Return())
if len(execRes.Revert()) > 0 {
returnVal = fmt.Sprintf("%x", execRes.Revert())
}
result := &ethapi.ExecutionResult{
Gas: execRes.UsedGas,
Failed: execRes.Failed(),
ReturnValue: returnVal,
StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
}
res := &TxTraceResult{
Result: result,
IntermediateHash: statedb.IntermediateRoot(deleteEmptyObjects),
}
return res
}

for indx, tx := range txs {
res.Transactions = append(res.Transactions, traceTxn(indx, tx))
}
return res, nil
}

type TraceBlockRequest struct {
Number int64
Hash string
IsBadBlock bool
Config *TraceConfig
}

// If you use context as first parameter this function gets exposed automaticall on rpc endpoint
func (api *API) TraceBorBlock(req *TraceBlockRequest) (*BlockTraceResult, error) {
ctx := context.Background()

var blockNumber rpc.BlockNumber
if req.Number == -1 {
blockNumber = rpc.LatestBlockNumber
} else {
blockNumber = rpc.BlockNumber(req.Number)
}

log.Debug("Tracing Bor Block", "block number", blockNumber)

block, err := api.blockByNumber(ctx, blockNumber)
if err != nil {
return nil, err
}
return api.traceBorBlock(ctx, block, req.Config)
}
10 changes: 10 additions & 0 deletions internal/cli/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ func Commands() map[string]MarkDownCommandFactory {
},
"debug": func() (MarkDownCommand, error) {
return &DebugCommand{
UI: ui,
}, nil
},
"debug pprof": func() (MarkDownCommand, error) {
return &DebugPprofCommand{
Meta2: meta2,
}, nil
},
"debug block": func() (MarkDownCommand, error) {
return &DebugBlockCommand{
Meta2: meta2,
}, nil
},
Expand Down
Loading

0 comments on commit 0d2b1d0

Please sign in to comment.