mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 14:46:42 +00:00
internal/cli: add block tracing (#397)
* 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>
This commit is contained in:
parent
4ffdbfe60d
commit
0d2b1d0630
17 changed files with 1570 additions and 653 deletions
|
|
@ -22,6 +22,10 @@
|
||||||
|
|
||||||
- [```debug```](./debug.md)
|
- [```debug```](./debug.md)
|
||||||
|
|
||||||
|
- [```debug block```](./debug_block.md)
|
||||||
|
|
||||||
|
- [```debug pprof```](./debug_pprof.md)
|
||||||
|
|
||||||
- [```fingerprint```](./fingerprint.md)
|
- [```fingerprint```](./fingerprint.md)
|
||||||
|
|
||||||
- [```peers```](./peers.md)
|
- [```peers```](./peers.md)
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,9 @@
|
||||||
|
|
||||||
The ```bor debug``` command takes a debug dump of the running client.
|
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
|
- [```bor debug block <number>```](./debug_block.md): Dumps bor block traces.
|
||||||
|
|
||||||
- ```seconds```: seconds to trace
|
|
||||||
|
|
||||||
- ```output```: Output directory
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|
|
||||||
9
docs/cli/debug_block.md
Normal file
9
docs/cli/debug_block.md
Normal file
|
|
@ -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
docs/cli/debug_pprof.md
Normal file
11
docs/cli/debug_pprof.md
Normal file
|
|
@ -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
eth/tracers/api_bor.go
Normal file
143
eth/tracers/api_bor.go
Normal file
|
|
@ -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 := ðapi.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)
|
||||||
|
}
|
||||||
|
|
@ -84,6 +84,16 @@ func Commands() map[string]MarkDownCommandFactory {
|
||||||
},
|
},
|
||||||
"debug": func() (MarkDownCommand, error) {
|
"debug": func() (MarkDownCommand, error) {
|
||||||
return &DebugCommand{
|
return &DebugCommand{
|
||||||
|
UI: ui,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
"debug pprof": func() (MarkDownCommand, error) {
|
||||||
|
return &DebugPprofCommand{
|
||||||
|
Meta2: meta2,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
"debug block": func() (MarkDownCommand, error) {
|
||||||
|
return &DebugBlockCommand{
|
||||||
Meta2: meta2,
|
Meta2: meta2,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
// Based on https://github.com/hashicorp/nomad/blob/main/command/operator_debug.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
@ -16,20 +13,20 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/internal/cli/flagset"
|
"github.com/mitchellh/cli"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/internal/cli/server/proto"
|
"github.com/ethereum/go-ethereum/internal/cli/server/proto"
|
||||||
|
|
||||||
"github.com/golang/protobuf/jsonpb" // nolint:staticcheck
|
"github.com/golang/protobuf/jsonpb" // nolint:staticcheck
|
||||||
gproto "github.com/golang/protobuf/proto" // nolint:staticcheck
|
gproto "github.com/golang/protobuf/proto" // nolint:staticcheck
|
||||||
"github.com/golang/protobuf/ptypes/empty"
|
|
||||||
grpc_net_conn "github.com/mitchellh/go-grpc-net-conn"
|
grpc_net_conn "github.com/mitchellh/go-grpc-net-conn"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/protobuf/runtime/protoiface"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DebugCommand is the command to group the peers commands
|
||||||
type DebugCommand struct {
|
type DebugCommand struct {
|
||||||
*Meta2
|
UI cli.Ui
|
||||||
|
|
||||||
seconds uint64
|
|
||||||
output string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkDown implements cli.MarkDown interface
|
// MarkDown implements cli.MarkDown interface
|
||||||
|
|
@ -53,7 +50,8 @@ func (d *DebugCommand) MarkDown() string {
|
||||||
items := []string{
|
items := []string{
|
||||||
"# Debug",
|
"# Debug",
|
||||||
"The ```bor debug``` command takes a debug dump of the running client.",
|
"The ```bor debug``` command takes a debug dump of the running client.",
|
||||||
d.Flags().MarkDown(),
|
"- [```bor debug pprof```](./debug_pprof.md): Dumps bor pprof traces.",
|
||||||
|
"- [```bor debug block <number>```](./debug_block.md): Dumps bor block traces.",
|
||||||
}
|
}
|
||||||
items = append(items, examples...)
|
items = append(items, examples...)
|
||||||
|
|
||||||
|
|
@ -61,185 +59,141 @@ func (d *DebugCommand) MarkDown() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help implements the cli.Command interface
|
// Help implements the cli.Command interface
|
||||||
func (d *DebugCommand) Help() string {
|
func (c *DebugCommand) Help() string {
|
||||||
return `Usage: bor debug
|
return `Usage: bor debug <subcommand>
|
||||||
|
|
||||||
Build an archive containing Bor pprof traces
|
This command takes a debug dump of the running client.
|
||||||
|
|
||||||
` + d.Flags().Help()
|
Get the pprof traces:
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DebugCommand) Flags() *flagset.Flagset {
|
$ bor debug pprof <enode>
|
||||||
flags := d.NewFlagSet("debug")
|
|
||||||
|
|
||||||
flags.Uint64Flag(&flagset.Uint64Flag{
|
Get the block traces:
|
||||||
Name: "seconds",
|
|
||||||
Usage: "seconds to trace",
|
|
||||||
Value: &d.seconds,
|
|
||||||
Default: 2,
|
|
||||||
})
|
|
||||||
flags.StringFlag(&flagset.StringFlag{
|
|
||||||
Name: "output",
|
|
||||||
Value: &d.output,
|
|
||||||
Usage: "Output directory",
|
|
||||||
})
|
|
||||||
|
|
||||||
return flags
|
$ bor debug block <number>`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synopsis implements the cli.Command interface
|
// Synopsis implements the cli.Command interface
|
||||||
func (d *DebugCommand) Synopsis() string {
|
func (c *DebugCommand) Synopsis() string {
|
||||||
return "Build an archive containing Bor pprof traces"
|
return "Get traces of the running client"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run implements the cli.Command interface
|
// Run implements the cli.Command interface
|
||||||
func (d *DebugCommand) Run(args []string) int {
|
func (c *DebugCommand) Run(args []string) int {
|
||||||
flags := d.Flags()
|
return cli.RunResultHelp
|
||||||
if err := flags.Parse(args); err != nil {
|
}
|
||||||
d.UI.Error(err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
clt, err := d.BorConn()
|
type debugEnv struct {
|
||||||
if err != nil {
|
output string
|
||||||
d.UI.Error(err.Error())
|
prefix string
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
stamped := "bor-debug-" + time.Now().UTC().Format("2006-01-02-150405Z")
|
name string
|
||||||
|
dst string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *debugEnv) init() error {
|
||||||
|
d.name = d.prefix + time.Now().UTC().Format("2006-01-02-150405Z")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
// Create the output directory
|
// Create the output directory
|
||||||
var tmp string
|
var tmp string
|
||||||
if d.output != "" {
|
if d.output != "" {
|
||||||
// User specified output directory
|
// User specified output directory
|
||||||
tmp = filepath.Join(d.output, stamped)
|
tmp = filepath.Join(d.output, d.name)
|
||||||
_, err := os.Stat(tmp)
|
_, err := os.Stat(tmp)
|
||||||
|
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
d.UI.Error("Output directory already exists")
|
return fmt.Errorf("output directory already exists")
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Generate temp directory
|
// Generate temp directory
|
||||||
tmp, err = ioutil.TempDir(os.TempDir(), stamped)
|
tmp, err = ioutil.TempDir(os.TempDir(), d.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.UI.Error(fmt.Sprintf("Error creating tmp directory: %s", err.Error()))
|
return fmt.Errorf("error creating tmp directory: %s", err.Error())
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
d.UI.Output("Starting debugger...")
|
|
||||||
d.UI.Output("")
|
|
||||||
|
|
||||||
// ensure destine folder exists
|
// ensure destine folder exists
|
||||||
if err := os.MkdirAll(tmp, os.ModePerm); err != nil {
|
if err := os.MkdirAll(tmp, os.ModePerm); err != nil {
|
||||||
d.UI.Error(fmt.Sprintf("failed to create parent directory: %v", err))
|
return fmt.Errorf("failed to create parent directory: %v", err)
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pprofProfile := func(ctx context.Context, profile string, filename string) error {
|
d.dst = tmp
|
||||||
req := &proto.PprofRequest{
|
|
||||||
Seconds: int64(d.seconds),
|
|
||||||
}
|
|
||||||
|
|
||||||
switch profile {
|
return nil
|
||||||
case "cpu":
|
}
|
||||||
req.Type = proto.PprofRequest_CPU
|
|
||||||
case "trace":
|
|
||||||
req.Type = proto.PprofRequest_TRACE
|
|
||||||
default:
|
|
||||||
req.Type = proto.PprofRequest_LOOKUP
|
|
||||||
req.Profile = profile
|
|
||||||
}
|
|
||||||
|
|
||||||
stream, err := clt.Pprof(ctx, req)
|
func (d *debugEnv) tarName() string {
|
||||||
|
return d.name + ".tar.gz"
|
||||||
if err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
|
||||||
// wait for open request
|
|
||||||
msg, err := stream.Recv()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := msg.Event.(*proto.PprofResponse_Open_); !ok {
|
|
||||||
return fmt.Errorf("expected open message")
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the stream
|
|
||||||
conn := &grpc_net_conn.Conn{
|
|
||||||
Stream: stream,
|
|
||||||
Response: &proto.PprofResponse_Input{},
|
|
||||||
Decode: grpc_net_conn.SimpleDecoder(func(msg gproto.Message) *[]byte {
|
|
||||||
return &msg.(*proto.PprofResponse_Input).Data
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.OpenFile(filepath.Join(tmp, filename+".prof"), os.O_RDWR|os.O_CREATE, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(file, conn); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func (d *debugEnv) finish() error {
|
||||||
|
// Exit before archive if output directory was specified
|
||||||
|
if d.output != "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancelFn := context.WithCancel(context.Background())
|
|
||||||
trapSignal(cancelFn)
|
|
||||||
|
|
||||||
profiles := map[string]string{
|
|
||||||
"heap": "heap",
|
|
||||||
"cpu": "cpu",
|
|
||||||
"trace": "trace",
|
|
||||||
}
|
|
||||||
for profile, filename := range profiles {
|
|
||||||
if err := pprofProfile(ctx, profile, filename); err != nil {
|
|
||||||
d.UI.Error(fmt.Sprintf("Error creating profile '%s': %v", profile, err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// append the status
|
|
||||||
{
|
|
||||||
statusResp, err := clt.Status(ctx, &empty.Empty{})
|
|
||||||
if err != nil {
|
|
||||||
d.UI.Output(fmt.Sprintf("Failed to get status: %v", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
m := jsonpb.Marshaler{}
|
|
||||||
data, err := m.MarshalToString(statusResp)
|
|
||||||
if err != nil {
|
|
||||||
d.UI.Output(err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(filepath.Join(tmp, "status.json"), []byte(data), 0600); err != nil {
|
|
||||||
d.UI.Output(fmt.Sprintf("Failed to write status: %v", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit before archive if output directory was specified
|
|
||||||
if d.output != "" {
|
|
||||||
d.UI.Output(fmt.Sprintf("Created debug directory: %s", tmp))
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create archive tarball
|
// Create archive tarball
|
||||||
archiveFile := stamped + ".tar.gz"
|
archiveFile := d.tarName()
|
||||||
if err = tarCZF(archiveFile, tmp, stamped); err != nil {
|
if err := tarCZF(archiveFile, d.dst, d.name); err != nil {
|
||||||
d.UI.Error(fmt.Sprintf("Error creating archive: %s", err.Error()))
|
return fmt.Errorf("error creating archive: %s", err.Error())
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
d.UI.Output(fmt.Sprintf("Created debug archive: %s", archiveFile))
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return 0
|
type debugStream interface {
|
||||||
|
Recv() (*proto.DebugFileResponse, error)
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *debugEnv) writeFromStream(name string, stream debugStream) error {
|
||||||
|
// wait for open request
|
||||||
|
msg, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := msg.Event.(*proto.DebugFileResponse_Open_); !ok {
|
||||||
|
return fmt.Errorf("expected open message")
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the stream
|
||||||
|
conn := &grpc_net_conn.Conn{
|
||||||
|
Stream: stream,
|
||||||
|
Response: &proto.DebugFileResponse_Input{},
|
||||||
|
Decode: grpc_net_conn.SimpleDecoder(func(msg gproto.Message) *[]byte {
|
||||||
|
return &msg.(*proto.DebugFileResponse_Input).Data
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile(filepath.Join(d.dst, name), os.O_RDWR|os.O_CREATE, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(file, conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *debugEnv) writeJSON(name string, msg protoiface.MessageV1) error {
|
||||||
|
m := jsonpb.Marshaler{}
|
||||||
|
data, err := m.MarshalToString(msg)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(filepath.Join(d.dst, name), []byte(data), 0600); err != nil {
|
||||||
|
return fmt.Errorf("failed to write status: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func trapSignal(cancel func()) {
|
func trapSignal(cancel func()) {
|
||||||
|
|
|
||||||
126
internal/cli/debug_block.go
Normal file
126
internal/cli/debug_block.go
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/internal/cli/flagset"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/cli/server/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DebugBlockCommand is the command to group the peers commands
|
||||||
|
type DebugBlockCommand struct {
|
||||||
|
*Meta2
|
||||||
|
|
||||||
|
output string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DebugBlockCommand) MarkDown() string {
|
||||||
|
items := []string{
|
||||||
|
"# Debug trace",
|
||||||
|
"The ```bor debug block <number>``` command will create an archive containing traces of a bor block.",
|
||||||
|
p.Flags().MarkDown(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(items, "\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help implements the cli.Command interface
|
||||||
|
func (c *DebugBlockCommand) Help() string {
|
||||||
|
return `Usage: bor debug block <number>
|
||||||
|
|
||||||
|
This command is used get traces of a bor block`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DebugBlockCommand) Flags() *flagset.Flagset {
|
||||||
|
flags := c.NewFlagSet("trace")
|
||||||
|
|
||||||
|
flags.StringFlag(&flagset.StringFlag{
|
||||||
|
Name: "output",
|
||||||
|
Value: &c.output,
|
||||||
|
Usage: "Output directory",
|
||||||
|
})
|
||||||
|
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synopsis implements the cli.Command interface
|
||||||
|
func (c *DebugBlockCommand) Synopsis() string {
|
||||||
|
return "Get trace of a bor block"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run implements the cli.Command interface
|
||||||
|
func (c *DebugBlockCommand) Run(args []string) int {
|
||||||
|
flags := c.Flags()
|
||||||
|
|
||||||
|
var number *int64 = nil
|
||||||
|
|
||||||
|
// parse the block number (if available)
|
||||||
|
if len(args)%2 != 0 {
|
||||||
|
num, err := strconv.ParseInt(args[0], 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
number = &num
|
||||||
|
}
|
||||||
|
|
||||||
|
args = args[1:]
|
||||||
|
}
|
||||||
|
// parse output directory
|
||||||
|
if err := flags.Parse(args); err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
borClt, err := c.BorConn()
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
dEnv := &debugEnv{
|
||||||
|
output: c.output,
|
||||||
|
prefix: "bor-block-trace-",
|
||||||
|
}
|
||||||
|
if err := dEnv.init(); err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
c.UI.Output("Starting block tracer...")
|
||||||
|
c.UI.Output("")
|
||||||
|
|
||||||
|
// create a debug block request
|
||||||
|
var debugRequest *proto.DebugBlockRequest = &proto.DebugBlockRequest{}
|
||||||
|
if number != nil {
|
||||||
|
debugRequest.Number = *number
|
||||||
|
} else {
|
||||||
|
debugRequest.Number = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the request
|
||||||
|
// receives a grpc stream of debug block response
|
||||||
|
stream, err := borClt.DebugBlock(context.Background(), debugRequest)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dEnv.writeFromStream("block.json", stream); err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dEnv.finish(); err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.output != "" {
|
||||||
|
c.UI.Output(fmt.Sprintf("Created debug directory: %s", dEnv.dst))
|
||||||
|
} else {
|
||||||
|
c.UI.Output(fmt.Sprintf("Created block trace archive: %s", dEnv.tarName()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
159
internal/cli/debug_pprof.go
Normal file
159
internal/cli/debug_pprof.go
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
// Based on https://github.com/hashicorp/nomad/blob/main/command/operator_debug.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/ptypes/empty"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/internal/cli/flagset"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/cli/server/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DebugPprofCommand struct {
|
||||||
|
*Meta2
|
||||||
|
|
||||||
|
seconds uint64
|
||||||
|
output string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DebugPprofCommand) MarkDown() string {
|
||||||
|
items := []string{
|
||||||
|
"# Debug Pprof",
|
||||||
|
"The ```debug pprof <enode>``` command will create an archive containing bor pprof traces.",
|
||||||
|
p.Flags().MarkDown(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(items, "\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help implements the cli.Command interface
|
||||||
|
func (d *DebugPprofCommand) Help() string {
|
||||||
|
return `Usage: bor debug
|
||||||
|
|
||||||
|
Build an archive containing Bor pprof traces
|
||||||
|
|
||||||
|
` + d.Flags().Help()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DebugPprofCommand) Flags() *flagset.Flagset {
|
||||||
|
flags := d.NewFlagSet("debug")
|
||||||
|
|
||||||
|
flags.Uint64Flag(&flagset.Uint64Flag{
|
||||||
|
Name: "seconds",
|
||||||
|
Usage: "seconds to trace",
|
||||||
|
Value: &d.seconds,
|
||||||
|
Default: 2,
|
||||||
|
})
|
||||||
|
flags.StringFlag(&flagset.StringFlag{
|
||||||
|
Name: "output",
|
||||||
|
Value: &d.output,
|
||||||
|
Usage: "Output directory",
|
||||||
|
})
|
||||||
|
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synopsis implements the cli.Command interface
|
||||||
|
func (d *DebugPprofCommand) Synopsis() string {
|
||||||
|
return "Build an archive containing Bor pprof traces"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run implements the cli.Command interface
|
||||||
|
func (d *DebugPprofCommand) Run(args []string) int {
|
||||||
|
flags := d.Flags()
|
||||||
|
if err := flags.Parse(args); err != nil {
|
||||||
|
d.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
clt, err := d.BorConn()
|
||||||
|
if err != nil {
|
||||||
|
d.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
dEnv := &debugEnv{
|
||||||
|
output: d.output,
|
||||||
|
prefix: "bor-debug-",
|
||||||
|
}
|
||||||
|
if err := dEnv.init(); err != nil {
|
||||||
|
d.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
d.UI.Output("Starting debugger...")
|
||||||
|
d.UI.Output("")
|
||||||
|
|
||||||
|
pprofProfile := func(ctx context.Context, profile string, filename string) error {
|
||||||
|
req := &proto.DebugPprofRequest{
|
||||||
|
Seconds: int64(d.seconds),
|
||||||
|
}
|
||||||
|
|
||||||
|
switch profile {
|
||||||
|
case "cpu":
|
||||||
|
req.Type = proto.DebugPprofRequest_CPU
|
||||||
|
case "trace":
|
||||||
|
req.Type = proto.DebugPprofRequest_TRACE
|
||||||
|
default:
|
||||||
|
req.Type = proto.DebugPprofRequest_LOOKUP
|
||||||
|
req.Profile = profile
|
||||||
|
}
|
||||||
|
|
||||||
|
stream, err := clt.DebugPprof(ctx, req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dEnv.writeFromStream(filename+".prof", stream); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancelFn := context.WithCancel(context.Background())
|
||||||
|
trapSignal(cancelFn)
|
||||||
|
|
||||||
|
profiles := map[string]string{
|
||||||
|
"heap": "heap",
|
||||||
|
"cpu": "cpu",
|
||||||
|
"trace": "trace",
|
||||||
|
}
|
||||||
|
for profile, filename := range profiles {
|
||||||
|
if err := pprofProfile(ctx, profile, filename); err != nil {
|
||||||
|
d.UI.Error(fmt.Sprintf("Error creating profile '%s': %v", profile, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append the status
|
||||||
|
{
|
||||||
|
statusResp, err := clt.Status(ctx, &empty.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
d.UI.Output(fmt.Sprintf("Failed to get status: %v", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if err := dEnv.writeJSON("status.json", statusResp); err != nil {
|
||||||
|
d.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dEnv.finish(); err != nil {
|
||||||
|
d.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.output != "" {
|
||||||
|
d.UI.Output(fmt.Sprintf("Created debug directory: %s", dEnv.dst))
|
||||||
|
} else {
|
||||||
|
d.UI.Output(fmt.Sprintf("Created debug archive: %s", dEnv.tarName()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
112
internal/cli/debug_test.go
Normal file
112
internal/cli/debug_test.go
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/internal/cli/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
var currentDir string = ""
|
||||||
|
|
||||||
|
func TestCommand_DebugBlock(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Start a blockchain in developer mode and get trace of block
|
||||||
|
config := server.DefaultConfig()
|
||||||
|
|
||||||
|
// enable developer mode
|
||||||
|
config.Developer.Enabled = true
|
||||||
|
config.Developer.Period = 2 // block time
|
||||||
|
|
||||||
|
// enable archive mode for getting traces of ancient blocks
|
||||||
|
config.GcMode = "archive"
|
||||||
|
|
||||||
|
// start the mock server
|
||||||
|
srv, err := server.CreateMockServer(config)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
defer server.CloseMockServer(srv)
|
||||||
|
|
||||||
|
// get the grpc port
|
||||||
|
port := srv.GetGrpcAddr()
|
||||||
|
|
||||||
|
// wait for 4 seconds to mine a 2 blocks
|
||||||
|
time.Sleep(2 * time.Duration(config.Developer.Period) * time.Second)
|
||||||
|
|
||||||
|
// add prefix for debug trace
|
||||||
|
prefix := "bor-block-trace-"
|
||||||
|
|
||||||
|
// output dir
|
||||||
|
output := "debug_block_test"
|
||||||
|
|
||||||
|
// set current directory
|
||||||
|
currentDir, _ = os.Getwd()
|
||||||
|
|
||||||
|
// trace 1st block
|
||||||
|
start := time.Now()
|
||||||
|
dst1 := path.Join(output, prefix+time.Now().UTC().Format("2006-01-02-150405Z"), "block.json")
|
||||||
|
res := traceBlock(port, 1, output)
|
||||||
|
assert.Equal(t, 0, res)
|
||||||
|
t.Logf("Completed trace of block %d in %d ms at %s", 1, time.Since(start).Milliseconds(), dst1)
|
||||||
|
|
||||||
|
// adding this to avoid debug directory name conflicts
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
// trace last/recent block
|
||||||
|
start = time.Now()
|
||||||
|
latestBlock := srv.GetLatestBlockNumber().Int64()
|
||||||
|
dst2 := path.Join(output, prefix+time.Now().UTC().Format("2006-01-02-150405Z"), "block.json")
|
||||||
|
res = traceBlock(port, latestBlock, output)
|
||||||
|
assert.Equal(t, 0, res)
|
||||||
|
t.Logf("Completed trace of block %d in %d ms at %s", latestBlock, time.Since(start).Milliseconds(), dst2)
|
||||||
|
|
||||||
|
// verify if the trace files are created
|
||||||
|
done := verify(dst1)
|
||||||
|
assert.Equal(t, true, done)
|
||||||
|
done = verify(dst2)
|
||||||
|
assert.Equal(t, true, done)
|
||||||
|
|
||||||
|
// delete the traces
|
||||||
|
deleteTraces(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// traceBlock calls the cli command to trace a block
|
||||||
|
func traceBlock(port string, number int64, output string) int {
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
command := &DebugBlockCommand{
|
||||||
|
Meta2: &Meta2{
|
||||||
|
UI: ui,
|
||||||
|
addr: "127.0.0.1:" + port,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// run trace (by explicitly passing the output directory and grpc address)
|
||||||
|
return command.Run([]string{strconv.FormatInt(number, 10), "--output", output, "--address", command.Meta2.addr})
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify checks if the trace file is created at the destination
|
||||||
|
// directory or not
|
||||||
|
func verify(dst string) bool {
|
||||||
|
dst = path.Join(currentDir, dst)
|
||||||
|
if file, err := os.Stat(dst); err == nil {
|
||||||
|
// check if the file has content
|
||||||
|
if file.Size() > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteTraces removes the traces created during the test
|
||||||
|
func deleteTraces(dst string) {
|
||||||
|
dst = path.Join(currentDir, dst)
|
||||||
|
os.RemoveAll(dst)
|
||||||
|
}
|
||||||
72
internal/cli/server/helper.go
Normal file
72
internal/cli/server/helper.go
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var maxPortCheck int32 = 100
|
||||||
|
|
||||||
|
// findAvailablePort returns the next available port starting from `from`
|
||||||
|
func findAvailablePort(from int32, count int32) (int32, error) {
|
||||||
|
if count == maxPortCheck {
|
||||||
|
return 0, fmt.Errorf("no available port found")
|
||||||
|
}
|
||||||
|
port := atomic.AddInt32(&from, 1)
|
||||||
|
addr := fmt.Sprintf("localhost:%d", port)
|
||||||
|
lis, err := net.Listen("tcp", addr)
|
||||||
|
count++
|
||||||
|
if err == nil {
|
||||||
|
lis.Close()
|
||||||
|
return port, nil
|
||||||
|
} else {
|
||||||
|
return findAvailablePort(from, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateMockServer(config *Config) (*Server, error) {
|
||||||
|
if config == nil {
|
||||||
|
config = DefaultConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
// find available port for grpc server
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
var from int32 = 60000 // the min port to start checking from
|
||||||
|
var to int32 = 61000 // the max port to start checking from
|
||||||
|
port, err := findAvailablePort(rand.Int31n(to-from+1)+from, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// grpc port
|
||||||
|
config.GRPC.Addr = fmt.Sprintf(":%d", port)
|
||||||
|
|
||||||
|
// datadir
|
||||||
|
datadir, _ := ioutil.TempDir("/tmp", "bor-cli-test")
|
||||||
|
config.DataDir = datadir
|
||||||
|
|
||||||
|
// find available port for http server
|
||||||
|
from = 8545
|
||||||
|
to = 9545
|
||||||
|
port, err = findAvailablePort(rand.Int31n(to-from+1)+from, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.JsonRPC.Http.Port = uint64(port)
|
||||||
|
|
||||||
|
// start the server
|
||||||
|
return NewServer(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CloseMockServer(server *Server) {
|
||||||
|
// remove the contents of temp data dir
|
||||||
|
os.RemoveAll(server.config.DataDir)
|
||||||
|
|
||||||
|
// close the server
|
||||||
|
server.Stop()
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -7,8 +7,6 @@ import "google/protobuf/empty.proto";
|
||||||
option go_package = "/internal/cli/server/proto";
|
option go_package = "/internal/cli/server/proto";
|
||||||
|
|
||||||
service Bor {
|
service Bor {
|
||||||
rpc Pprof(PprofRequest) returns (stream PprofResponse);
|
|
||||||
|
|
||||||
rpc PeersAdd(PeersAddRequest) returns (PeersAddResponse);
|
rpc PeersAdd(PeersAddRequest) returns (PeersAddResponse);
|
||||||
|
|
||||||
rpc PeersRemove(PeersRemoveRequest) returns (PeersRemoveResponse);
|
rpc PeersRemove(PeersRemoveRequest) returns (PeersRemoveResponse);
|
||||||
|
|
@ -22,6 +20,18 @@ service Bor {
|
||||||
rpc Status(google.protobuf.Empty) returns (StatusResponse);
|
rpc Status(google.protobuf.Empty) returns (StatusResponse);
|
||||||
|
|
||||||
rpc ChainWatch(ChainWatchRequest) returns (stream ChainWatchResponse);
|
rpc ChainWatch(ChainWatchRequest) returns (stream ChainWatchResponse);
|
||||||
|
|
||||||
|
rpc DebugPprof(DebugPprofRequest) returns (stream DebugFileResponse);
|
||||||
|
|
||||||
|
rpc DebugBlock(DebugBlockRequest) returns (stream DebugFileResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
message TraceRequest {
|
||||||
|
int64 number = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TraceResponse {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ChainWatchRequest {
|
message ChainWatchRequest {
|
||||||
|
|
@ -113,7 +123,7 @@ message Header {
|
||||||
uint64 number = 2;
|
uint64 number = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PprofRequest {
|
message DebugPprofRequest {
|
||||||
Type type = 1;
|
Type type = 1;
|
||||||
|
|
||||||
string profile = 2;
|
string profile = 2;
|
||||||
|
|
@ -127,7 +137,11 @@ message PprofRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message PprofResponse {
|
message DebugBlockRequest {
|
||||||
|
int64 number = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DebugFileResponse {
|
||||||
oneof event {
|
oneof event {
|
||||||
Open open = 1;
|
Open open = 1;
|
||||||
Input input = 2;
|
Input input = 2;
|
||||||
|
|
@ -136,7 +150,6 @@ message PprofResponse {
|
||||||
|
|
||||||
message Open {
|
message Open {
|
||||||
map<string, string> headers = 1;
|
map<string, string> headers = 1;
|
||||||
int64 size = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Input {
|
message Input {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-grpc v1.2.0
|
||||||
|
// - protoc v3.19.3
|
||||||
|
// source: internal/cli/server/proto/server.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
|
|
||||||
|
|
@ -19,7 +23,6 @@ const _ = grpc.SupportPackageIsVersion7
|
||||||
//
|
//
|
||||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
type BorClient interface {
|
type BorClient interface {
|
||||||
Pprof(ctx context.Context, in *PprofRequest, opts ...grpc.CallOption) (Bor_PprofClient, error)
|
|
||||||
PeersAdd(ctx context.Context, in *PeersAddRequest, opts ...grpc.CallOption) (*PeersAddResponse, error)
|
PeersAdd(ctx context.Context, in *PeersAddRequest, opts ...grpc.CallOption) (*PeersAddResponse, error)
|
||||||
PeersRemove(ctx context.Context, in *PeersRemoveRequest, opts ...grpc.CallOption) (*PeersRemoveResponse, error)
|
PeersRemove(ctx context.Context, in *PeersRemoveRequest, opts ...grpc.CallOption) (*PeersRemoveResponse, error)
|
||||||
PeersList(ctx context.Context, in *PeersListRequest, opts ...grpc.CallOption) (*PeersListResponse, error)
|
PeersList(ctx context.Context, in *PeersListRequest, opts ...grpc.CallOption) (*PeersListResponse, error)
|
||||||
|
|
@ -27,6 +30,8 @@ type BorClient interface {
|
||||||
ChainSetHead(ctx context.Context, in *ChainSetHeadRequest, opts ...grpc.CallOption) (*ChainSetHeadResponse, error)
|
ChainSetHead(ctx context.Context, in *ChainSetHeadRequest, opts ...grpc.CallOption) (*ChainSetHeadResponse, error)
|
||||||
Status(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StatusResponse, error)
|
Status(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StatusResponse, error)
|
||||||
ChainWatch(ctx context.Context, in *ChainWatchRequest, opts ...grpc.CallOption) (Bor_ChainWatchClient, error)
|
ChainWatch(ctx context.Context, in *ChainWatchRequest, opts ...grpc.CallOption) (Bor_ChainWatchClient, error)
|
||||||
|
DebugPprof(ctx context.Context, in *DebugPprofRequest, opts ...grpc.CallOption) (Bor_DebugPprofClient, error)
|
||||||
|
DebugBlock(ctx context.Context, in *DebugBlockRequest, opts ...grpc.CallOption) (Bor_DebugBlockClient, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type borClient struct {
|
type borClient struct {
|
||||||
|
|
@ -37,38 +42,6 @@ func NewBorClient(cc grpc.ClientConnInterface) BorClient {
|
||||||
return &borClient{cc}
|
return &borClient{cc}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *borClient) Pprof(ctx context.Context, in *PprofRequest, opts ...grpc.CallOption) (Bor_PprofClient, error) {
|
|
||||||
stream, err := c.cc.NewStream(ctx, &Bor_ServiceDesc.Streams[0], "/proto.Bor/Pprof", opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &borPprofClient{stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bor_PprofClient interface {
|
|
||||||
Recv() (*PprofResponse, error)
|
|
||||||
grpc.ClientStream
|
|
||||||
}
|
|
||||||
|
|
||||||
type borPprofClient struct {
|
|
||||||
grpc.ClientStream
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *borPprofClient) Recv() (*PprofResponse, error) {
|
|
||||||
m := new(PprofResponse)
|
|
||||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *borClient) PeersAdd(ctx context.Context, in *PeersAddRequest, opts ...grpc.CallOption) (*PeersAddResponse, error) {
|
func (c *borClient) PeersAdd(ctx context.Context, in *PeersAddRequest, opts ...grpc.CallOption) (*PeersAddResponse, error) {
|
||||||
out := new(PeersAddResponse)
|
out := new(PeersAddResponse)
|
||||||
err := c.cc.Invoke(ctx, "/proto.Bor/PeersAdd", in, out, opts...)
|
err := c.cc.Invoke(ctx, "/proto.Bor/PeersAdd", in, out, opts...)
|
||||||
|
|
@ -124,7 +97,7 @@ func (c *borClient) Status(ctx context.Context, in *emptypb.Empty, opts ...grpc.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *borClient) ChainWatch(ctx context.Context, in *ChainWatchRequest, opts ...grpc.CallOption) (Bor_ChainWatchClient, error) {
|
func (c *borClient) ChainWatch(ctx context.Context, in *ChainWatchRequest, opts ...grpc.CallOption) (Bor_ChainWatchClient, error) {
|
||||||
stream, err := c.cc.NewStream(ctx, &Bor_ServiceDesc.Streams[1], "/proto.Bor/ChainWatch", opts...)
|
stream, err := c.cc.NewStream(ctx, &Bor_ServiceDesc.Streams[0], "/proto.Bor/ChainWatch", opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -155,11 +128,74 @@ func (x *borChainWatchClient) Recv() (*ChainWatchResponse, error) {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *borClient) DebugPprof(ctx context.Context, in *DebugPprofRequest, opts ...grpc.CallOption) (Bor_DebugPprofClient, error) {
|
||||||
|
stream, err := c.cc.NewStream(ctx, &Bor_ServiceDesc.Streams[1], "/proto.Bor/DebugPprof", opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &borDebugPprofClient{stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bor_DebugPprofClient interface {
|
||||||
|
Recv() (*DebugFileResponse, error)
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type borDebugPprofClient struct {
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *borDebugPprofClient) Recv() (*DebugFileResponse, error) {
|
||||||
|
m := new(DebugFileResponse)
|
||||||
|
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *borClient) DebugBlock(ctx context.Context, in *DebugBlockRequest, opts ...grpc.CallOption) (Bor_DebugBlockClient, error) {
|
||||||
|
stream, err := c.cc.NewStream(ctx, &Bor_ServiceDesc.Streams[2], "/proto.Bor/DebugBlock", opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &borDebugBlockClient{stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bor_DebugBlockClient interface {
|
||||||
|
Recv() (*DebugFileResponse, error)
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type borDebugBlockClient struct {
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *borDebugBlockClient) Recv() (*DebugFileResponse, error) {
|
||||||
|
m := new(DebugFileResponse)
|
||||||
|
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
// BorServer is the server API for Bor service.
|
// BorServer is the server API for Bor service.
|
||||||
// All implementations must embed UnimplementedBorServer
|
// All implementations must embed UnimplementedBorServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
type BorServer interface {
|
type BorServer interface {
|
||||||
Pprof(*PprofRequest, Bor_PprofServer) error
|
|
||||||
PeersAdd(context.Context, *PeersAddRequest) (*PeersAddResponse, error)
|
PeersAdd(context.Context, *PeersAddRequest) (*PeersAddResponse, error)
|
||||||
PeersRemove(context.Context, *PeersRemoveRequest) (*PeersRemoveResponse, error)
|
PeersRemove(context.Context, *PeersRemoveRequest) (*PeersRemoveResponse, error)
|
||||||
PeersList(context.Context, *PeersListRequest) (*PeersListResponse, error)
|
PeersList(context.Context, *PeersListRequest) (*PeersListResponse, error)
|
||||||
|
|
@ -167,6 +203,8 @@ type BorServer interface {
|
||||||
ChainSetHead(context.Context, *ChainSetHeadRequest) (*ChainSetHeadResponse, error)
|
ChainSetHead(context.Context, *ChainSetHeadRequest) (*ChainSetHeadResponse, error)
|
||||||
Status(context.Context, *emptypb.Empty) (*StatusResponse, error)
|
Status(context.Context, *emptypb.Empty) (*StatusResponse, error)
|
||||||
ChainWatch(*ChainWatchRequest, Bor_ChainWatchServer) error
|
ChainWatch(*ChainWatchRequest, Bor_ChainWatchServer) error
|
||||||
|
DebugPprof(*DebugPprofRequest, Bor_DebugPprofServer) error
|
||||||
|
DebugBlock(*DebugBlockRequest, Bor_DebugBlockServer) error
|
||||||
mustEmbedUnimplementedBorServer()
|
mustEmbedUnimplementedBorServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,9 +212,6 @@ type BorServer interface {
|
||||||
type UnimplementedBorServer struct {
|
type UnimplementedBorServer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedBorServer) Pprof(*PprofRequest, Bor_PprofServer) error {
|
|
||||||
return status.Errorf(codes.Unimplemented, "method Pprof not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedBorServer) PeersAdd(context.Context, *PeersAddRequest) (*PeersAddResponse, error) {
|
func (UnimplementedBorServer) PeersAdd(context.Context, *PeersAddRequest) (*PeersAddResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method PeersAdd not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method PeersAdd not implemented")
|
||||||
}
|
}
|
||||||
|
|
@ -198,6 +233,12 @@ func (UnimplementedBorServer) Status(context.Context, *emptypb.Empty) (*StatusRe
|
||||||
func (UnimplementedBorServer) ChainWatch(*ChainWatchRequest, Bor_ChainWatchServer) error {
|
func (UnimplementedBorServer) ChainWatch(*ChainWatchRequest, Bor_ChainWatchServer) error {
|
||||||
return status.Errorf(codes.Unimplemented, "method ChainWatch not implemented")
|
return status.Errorf(codes.Unimplemented, "method ChainWatch not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedBorServer) DebugPprof(*DebugPprofRequest, Bor_DebugPprofServer) error {
|
||||||
|
return status.Errorf(codes.Unimplemented, "method DebugPprof not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedBorServer) DebugBlock(*DebugBlockRequest, Bor_DebugBlockServer) error {
|
||||||
|
return status.Errorf(codes.Unimplemented, "method DebugBlock not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedBorServer) mustEmbedUnimplementedBorServer() {}
|
func (UnimplementedBorServer) mustEmbedUnimplementedBorServer() {}
|
||||||
|
|
||||||
// UnsafeBorServer may be embedded to opt out of forward compatibility for this service.
|
// UnsafeBorServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
|
@ -211,27 +252,6 @@ func RegisterBorServer(s grpc.ServiceRegistrar, srv BorServer) {
|
||||||
s.RegisterService(&Bor_ServiceDesc, srv)
|
s.RegisterService(&Bor_ServiceDesc, srv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _Bor_Pprof_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(PprofRequest)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return srv.(BorServer).Pprof(m, &borPprofServer{stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bor_PprofServer interface {
|
|
||||||
Send(*PprofResponse) error
|
|
||||||
grpc.ServerStream
|
|
||||||
}
|
|
||||||
|
|
||||||
type borPprofServer struct {
|
|
||||||
grpc.ServerStream
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *borPprofServer) Send(m *PprofResponse) error {
|
|
||||||
return x.ServerStream.SendMsg(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Bor_PeersAdd_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _Bor_PeersAdd_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(PeersAddRequest)
|
in := new(PeersAddRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
|
|
@ -361,6 +381,48 @@ func (x *borChainWatchServer) Send(m *ChainWatchResponse) error {
|
||||||
return x.ServerStream.SendMsg(m)
|
return x.ServerStream.SendMsg(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Bor_DebugPprof_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(DebugPprofRequest)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(BorServer).DebugPprof(m, &borDebugPprofServer{stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bor_DebugPprofServer interface {
|
||||||
|
Send(*DebugFileResponse) error
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type borDebugPprofServer struct {
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *borDebugPprofServer) Send(m *DebugFileResponse) error {
|
||||||
|
return x.ServerStream.SendMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _Bor_DebugBlock_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(DebugBlockRequest)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(BorServer).DebugBlock(m, &borDebugBlockServer{stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bor_DebugBlockServer interface {
|
||||||
|
Send(*DebugFileResponse) error
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type borDebugBlockServer struct {
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *borDebugBlockServer) Send(m *DebugFileResponse) error {
|
||||||
|
return x.ServerStream.SendMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
// Bor_ServiceDesc is the grpc.ServiceDesc for Bor service.
|
// Bor_ServiceDesc is the grpc.ServiceDesc for Bor service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
|
@ -395,13 +457,18 @@ var Bor_ServiceDesc = grpc.ServiceDesc{
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{
|
Streams: []grpc.StreamDesc{
|
||||||
{
|
{
|
||||||
StreamName: "Pprof",
|
StreamName: "ChainWatch",
|
||||||
Handler: _Bor_Pprof_Handler,
|
Handler: _Bor_ChainWatch_Handler,
|
||||||
ServerStreams: true,
|
ServerStreams: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
StreamName: "ChainWatch",
|
StreamName: "DebugPprof",
|
||||||
Handler: _Bor_ChainWatch_Handler,
|
Handler: _Bor_DebugPprof_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StreamName: "DebugBlock",
|
||||||
|
Handler: _Bor_DebugBlock_Handler,
|
||||||
ServerStreams: true,
|
ServerStreams: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -43,6 +44,9 @@ type Server struct {
|
||||||
grpcServer *grpc.Server
|
grpcServer *grpc.Server
|
||||||
tracer *sdktrace.TracerProvider
|
tracer *sdktrace.TracerProvider
|
||||||
config *Config
|
config *Config
|
||||||
|
|
||||||
|
// tracerAPI to trace block executions
|
||||||
|
tracerAPI *tracers.API
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(config *Config) (*Server, error) {
|
func NewServer(config *Config) (*Server, error) {
|
||||||
|
|
@ -162,6 +166,7 @@ func NewServer(config *Config) (*Server, error) {
|
||||||
|
|
||||||
// debug tracing is enabled by default
|
// debug tracing is enabled by default
|
||||||
stack.RegisterAPIs(tracers.APIs(srv.backend.APIBackend))
|
stack.RegisterAPIs(tracers.APIs(srv.backend.APIBackend))
|
||||||
|
srv.tracerAPI = tracers.NewAPI(srv.backend.APIBackend)
|
||||||
|
|
||||||
// graphql is started from another place
|
// graphql is started from another place
|
||||||
if config.JsonRPC.Graphql.Enabled {
|
if config.JsonRPC.Graphql.Enabled {
|
||||||
|
|
@ -200,6 +205,7 @@ func NewServer(config *Config) (*Server, error) {
|
||||||
|
|
||||||
func (s *Server) Stop() {
|
func (s *Server) Stop() {
|
||||||
s.node.Close()
|
s.node.Close()
|
||||||
|
s.grpcServer.Stop()
|
||||||
|
|
||||||
// shutdown the tracer
|
// shutdown the tracer
|
||||||
if s.tracer != nil {
|
if s.tracer != nil {
|
||||||
|
|
@ -354,3 +360,11 @@ func setupLogger(logLevel string) {
|
||||||
}
|
}
|
||||||
log.Root().SetHandler(glogger)
|
log.Root().SetHandler(glogger)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetLatestBlockNumber() *big.Int {
|
||||||
|
return s.backend.BlockChain().CurrentBlock().Number()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetGrpcAddr() string {
|
||||||
|
return s.config.GRPC.Addr[1:]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,33 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServer_DeveloperMode(t *testing.T) {
|
var initialPort uint64 = 61000
|
||||||
|
|
||||||
|
// nextPort gives the next available port starting from 60000
|
||||||
|
func nextPort() uint64 {
|
||||||
|
log.Info("Checking for new port", "current", initialPort)
|
||||||
|
port := atomic.AddUint64(&initialPort, 1)
|
||||||
|
addr := fmt.Sprintf("localhost:%d", port)
|
||||||
|
lis, err := net.Listen("tcp", addr)
|
||||||
|
if err == nil {
|
||||||
|
lis.Close()
|
||||||
|
return port
|
||||||
|
} else {
|
||||||
|
return nextPort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_DeveloperMode(t *testing.T) {
|
||||||
// get the default config
|
// get the default config
|
||||||
config := DefaultConfig()
|
config := DefaultConfig()
|
||||||
|
|
||||||
|
|
@ -16,17 +35,16 @@ func TestServer_DeveloperMode(t *testing.T) {
|
||||||
config.Developer.Enabled = true
|
config.Developer.Enabled = true
|
||||||
config.Developer.Period = 2 // block time
|
config.Developer.Period = 2 // block time
|
||||||
|
|
||||||
// start the server
|
// start the mock server
|
||||||
server, err1 := NewServer(config)
|
server, err := CreateMockServer(config)
|
||||||
if err1 != nil {
|
assert.NoError(t, err)
|
||||||
t.Fatalf("failed to start server: %v", err1)
|
defer CloseMockServer(server)
|
||||||
}
|
|
||||||
|
|
||||||
// record the initial block number
|
// record the initial block number
|
||||||
blockNumber := server.backend.BlockChain().CurrentBlock().Header().Number.Int64()
|
blockNumber := server.backend.BlockChain().CurrentBlock().Header().Number.Int64()
|
||||||
|
|
||||||
var i int64 = 0
|
var i int64 = 0
|
||||||
for i = 0; i < 10; i++ {
|
for i = 0; i < 3; i++ {
|
||||||
// We expect the node to mine blocks every `config.Developer.Period` time period
|
// We expect the node to mine blocks every `config.Developer.Period` time period
|
||||||
time.Sleep(time.Duration(config.Developer.Period) * time.Second)
|
time.Sleep(time.Duration(config.Developer.Period) * time.Second)
|
||||||
currBlock := server.backend.BlockChain().CurrentBlock().Header().Number.Int64()
|
currBlock := server.backend.BlockChain().CurrentBlock().Header().Number.Int64()
|
||||||
|
|
@ -35,7 +53,4 @@ func TestServer_DeveloperMode(t *testing.T) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop the server
|
|
||||||
server.Stop()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
@ -9,6 +10,8 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||||
"github.com/ethereum/go-ethereum/internal/cli/server/pprof"
|
"github.com/ethereum/go-ethereum/internal/cli/server/pprof"
|
||||||
"github.com/ethereum/go-ethereum/internal/cli/server/proto"
|
"github.com/ethereum/go-ethereum/internal/cli/server/proto"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
|
@ -18,30 +21,14 @@ import (
|
||||||
grpc_net_conn "github.com/mitchellh/go-grpc-net-conn"
|
grpc_net_conn "github.com/mitchellh/go-grpc-net-conn"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) Pprof(req *proto.PprofRequest, stream proto.Bor_PprofServer) error {
|
const chunkSize = 1024 * 1024 * 1024
|
||||||
var payload []byte
|
|
||||||
var headers map[string]string
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
switch req.Type {
|
|
||||||
case proto.PprofRequest_CPU:
|
|
||||||
payload, headers, err = pprof.CPUProfile(ctx, int(req.Seconds))
|
|
||||||
case proto.PprofRequest_TRACE:
|
|
||||||
payload, headers, err = pprof.Trace(ctx, int(req.Seconds))
|
|
||||||
case proto.PprofRequest_LOOKUP:
|
|
||||||
payload, headers, err = pprof.Profile(req.Profile, 0, 0)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func sendStreamDebugFile(stream proto.Bor_DebugPprofServer, headers map[string]string, data []byte) error {
|
||||||
// open the stream and send the headers
|
// open the stream and send the headers
|
||||||
err = stream.Send(&proto.PprofResponse{
|
err := stream.Send(&proto.DebugFileResponse{
|
||||||
Event: &proto.PprofResponse_Open_{
|
Event: &proto.DebugFileResponse_Open_{
|
||||||
Open: &proto.PprofResponse_Open{
|
Open: &proto.DebugFileResponse_Open{
|
||||||
Headers: headers,
|
Headers: headers,
|
||||||
Size: int64(len(payload)),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -50,24 +37,51 @@ func (s *Server) Pprof(req *proto.PprofRequest, stream proto.Bor_PprofServer) er
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap our conn around the response.
|
// Wrap our conn around the response.
|
||||||
|
encoder := grpc_net_conn.SimpleEncoder(func(msg gproto.Message) *[]byte {
|
||||||
|
return &msg.(*proto.DebugFileResponse_Input).Data
|
||||||
|
})
|
||||||
conn := &grpc_net_conn.Conn{
|
conn := &grpc_net_conn.Conn{
|
||||||
Stream: stream,
|
Stream: stream,
|
||||||
Request: &proto.PprofResponse_Input{},
|
Request: &proto.DebugFileResponse_Input{},
|
||||||
Encode: grpc_net_conn.SimpleEncoder(func(msg gproto.Message) *[]byte {
|
Encode: grpc_net_conn.ChunkedEncoder(encoder, chunkSize),
|
||||||
return &msg.(*proto.PprofResponse_Input).Data
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
if _, err := conn.Write(payload); err != nil {
|
if _, err := conn.Write(data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// send the eof
|
// send the eof
|
||||||
err = stream.Send(&proto.PprofResponse{
|
err = stream.Send(&proto.DebugFileResponse{
|
||||||
Event: &proto.PprofResponse_Eof{},
|
Event: &proto.DebugFileResponse_Eof{},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) DebugPprof(req *proto.DebugPprofRequest, stream proto.Bor_DebugPprofServer) error {
|
||||||
|
var payload []byte
|
||||||
|
var headers map[string]string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
switch req.Type {
|
||||||
|
case proto.DebugPprofRequest_CPU:
|
||||||
|
payload, headers, err = pprof.CPUProfile(ctx, int(req.Seconds))
|
||||||
|
case proto.DebugPprofRequest_TRACE:
|
||||||
|
payload, headers, err = pprof.Trace(ctx, int(req.Seconds))
|
||||||
|
case proto.DebugPprofRequest_LOOKUP:
|
||||||
|
payload, headers, err = pprof.Profile(req.Profile, 0, 0)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the file on a grpc stream
|
||||||
|
if err := sendStreamDebugFile(stream, headers, payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,6 +183,32 @@ func headerToProtoHeader(h *types.Header) *proto.Header {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) DebugBlock(req *proto.DebugBlockRequest, stream proto.Bor_DebugBlockServer) error {
|
||||||
|
traceReq := &tracers.TraceBlockRequest{
|
||||||
|
Number: req.Number,
|
||||||
|
Config: &tracers.TraceConfig{
|
||||||
|
Config: &logger.Config{
|
||||||
|
EnableMemory: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res, err := s.tracerAPI.TraceBorBlock(traceReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is memory heavy
|
||||||
|
data, err := json.Marshal(res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := sendStreamDebugFile(stream, map[string]string{}, data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var bigIntT = reflect.TypeOf(new(big.Int)).Kind()
|
var bigIntT = reflect.TypeOf(new(big.Int)).Kind()
|
||||||
|
|
||||||
// gatherForks gathers all the fork numbers via reflection
|
// gatherForks gathers all the fork numbers via reflection
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue