mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 23:26:44 +00:00
Merge pull request #208 from maticnetwork/feature-grpc-server
Feature grpc server
This commit is contained in:
commit
026d0ded8b
15 changed files with 1097 additions and 221 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -48,3 +48,4 @@ profile.cov
|
|||
|
||||
**/yarn-error.log
|
||||
./test
|
||||
./bor-debug-*
|
||||
|
|
|
|||
3
Makefile
3
Makefile
|
|
@ -13,6 +13,9 @@ GO ?= latest
|
|||
GORUN = env GO111MODULE=on go run
|
||||
GOPATH = $(shell go env GOPATH)
|
||||
|
||||
protoc:
|
||||
protoc --go_out=. --go-grpc_out=. ./command/server/proto/*.proto
|
||||
|
||||
bor:
|
||||
$(GORUN) build/ci.go install ./cmd/geth
|
||||
mkdir -p $(GOPATH)/bin/
|
||||
|
|
|
|||
242
command/debug.go
Normal file
242
command/debug.go
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
package main
|
||||
|
||||
// Based on https://github.com/hashicorp/nomad/blob/main/command/operator_debug.go
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/command/flagset"
|
||||
"github.com/ethereum/go-ethereum/command/server/proto"
|
||||
)
|
||||
|
||||
type DebugCommand struct {
|
||||
*Meta2
|
||||
|
||||
seconds uint64
|
||||
output string
|
||||
}
|
||||
|
||||
// Help implements the cli.Command interface
|
||||
func (d *DebugCommand) Help() string {
|
||||
return `Usage: bor debug
|
||||
|
||||
Build an archive containing Bor pprof traces
|
||||
|
||||
` + d.Flags().Help()
|
||||
}
|
||||
|
||||
func (d *DebugCommand) Flags() *flagset.Flagset {
|
||||
flags := d.NewFlagSet("debug")
|
||||
|
||||
flags.Uint64Flag(&flagset.Uint64Flag{
|
||||
Name: "seconds",
|
||||
Usage: "seconds to trace",
|
||||
Value: &d.seconds,
|
||||
Default: 5,
|
||||
})
|
||||
flags.StringFlag(&flagset.StringFlag{
|
||||
Name: "output",
|
||||
Value: &d.output,
|
||||
Usage: "Output directory",
|
||||
})
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
// Synopsis implements the cli.Command interface
|
||||
func (d *DebugCommand) Synopsis() string {
|
||||
return "Build an archive containing Bor pprof traces"
|
||||
}
|
||||
|
||||
// Run implements the cli.Command interface
|
||||
func (d *DebugCommand) 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
|
||||
}
|
||||
|
||||
stamped := "bor-debug-" + time.Now().UTC().Format("2006-01-02-150405Z")
|
||||
|
||||
// Create the output directory
|
||||
var tmp string
|
||||
if d.output != "" {
|
||||
// User specified output directory
|
||||
tmp = filepath.Join(d.output, stamped)
|
||||
_, err := os.Stat(tmp)
|
||||
if !os.IsNotExist(err) {
|
||||
d.UI.Error("Output directory already exists")
|
||||
return 1
|
||||
}
|
||||
} else {
|
||||
// Generate temp directory
|
||||
tmp, err = ioutil.TempDir(os.TempDir(), stamped)
|
||||
if err != nil {
|
||||
d.UI.Error(fmt.Sprintf("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
|
||||
if err := os.MkdirAll(tmp, os.ModePerm); err != nil {
|
||||
d.UI.Error(fmt.Sprintf("failed to create parent directory: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
pprofProfile := func(ctx context.Context, profile string, filename string) error {
|
||||
req := &proto.PprofRequest{
|
||||
Seconds: int64(d.seconds),
|
||||
}
|
||||
switch profile {
|
||||
case "cpu":
|
||||
req.Type = proto.PprofRequest_CPU
|
||||
case "trace":
|
||||
req.Type = proto.PprofRequest_TRACE
|
||||
default:
|
||||
req.Type = proto.PprofRequest_LOOKUP
|
||||
req.Profile = profile
|
||||
}
|
||||
resp, err := clt.Pprof(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// write file
|
||||
raw, err := hex.DecodeString(resp.Payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(tmp, filename+".prof"), raw, 0755); 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
archiveFile := stamped + ".tar.gz"
|
||||
if err = tarCZF(archiveFile, tmp, stamped); err != nil {
|
||||
d.UI.Error(fmt.Sprintf("Error creating archive: %s", err.Error()))
|
||||
return 1
|
||||
}
|
||||
|
||||
d.UI.Output(fmt.Sprintf("Created debug archive: %s", archiveFile))
|
||||
return 0
|
||||
}
|
||||
|
||||
func trapSignal(cancel func()) {
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh,
|
||||
syscall.SIGHUP,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGQUIT)
|
||||
|
||||
go func() {
|
||||
<-sigCh
|
||||
cancel()
|
||||
}()
|
||||
}
|
||||
|
||||
func tarCZF(archive string, src, target string) error {
|
||||
// ensure the src actually exists before trying to tar it
|
||||
if _, err := os.Stat(src); err != nil {
|
||||
return fmt.Errorf("unable to tar files - %v", err.Error())
|
||||
}
|
||||
|
||||
// create the archive
|
||||
fh, err := os.Create(archive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
zz := gzip.NewWriter(fh)
|
||||
defer zz.Close()
|
||||
|
||||
tw := tar.NewWriter(zz)
|
||||
defer tw.Close()
|
||||
|
||||
// tar
|
||||
return filepath.Walk(src, func(file string, fi os.FileInfo, err error) error {
|
||||
// return on any error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !fi.Mode().IsRegular() {
|
||||
return nil
|
||||
}
|
||||
|
||||
header, err := tar.FileInfoHeader(fi, fi.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// remove leading path to the src, so files are relative to the archive
|
||||
path := strings.ReplaceAll(file, src, "")
|
||||
if target != "" {
|
||||
path = filepath.Join([]string{target, path}...)
|
||||
}
|
||||
path = strings.TrimPrefix(path, string(filepath.Separator))
|
||||
|
||||
header.Name = path
|
||||
|
||||
if err := tw.WriteHeader(header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// copy the file contents
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(tw, f); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.Close()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
@ -7,9 +7,11 @@ import (
|
|||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/command/flagset"
|
||||
"github.com/ethereum/go-ethereum/command/server"
|
||||
"github.com/ethereum/go-ethereum/command/server/proto"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/ryanuber/columnize"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
@ -40,6 +42,9 @@ func commands() map[string]cli.CommandFactory {
|
|||
ErrorWriter: os.Stderr,
|
||||
}
|
||||
|
||||
meta2 := &Meta2{
|
||||
UI: ui,
|
||||
}
|
||||
meta := &Meta{
|
||||
UI: ui,
|
||||
}
|
||||
|
|
@ -54,6 +59,11 @@ func commands() map[string]cli.CommandFactory {
|
|||
UI: ui,
|
||||
}, nil
|
||||
},
|
||||
"debug": func() (cli.Command, error) {
|
||||
return &DebugCommand{
|
||||
Meta2: meta2,
|
||||
}, nil
|
||||
},
|
||||
"account": func() (cli.Command, error) {
|
||||
return &Account{
|
||||
UI: ui,
|
||||
|
|
@ -77,6 +87,40 @@ func commands() map[string]cli.CommandFactory {
|
|||
}
|
||||
}
|
||||
|
||||
type Meta2 struct {
|
||||
UI cli.Ui
|
||||
|
||||
addr string
|
||||
}
|
||||
|
||||
func (m *Meta2) NewFlagSet(n string) *flagset.Flagset {
|
||||
f := flagset.NewFlagSet(n)
|
||||
|
||||
f.StringFlag(&flagset.StringFlag{
|
||||
Name: "address",
|
||||
Value: &m.addr,
|
||||
Usage: "Address of the grpc endpoint",
|
||||
Default: "127.0.0.1:3131",
|
||||
})
|
||||
return f
|
||||
}
|
||||
|
||||
func (m *Meta2) Conn() (*grpc.ClientConn, error) {
|
||||
conn, err := grpc.Dial(m.addr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to server: %v", err)
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (m *Meta2) BorConn() (proto.BorClient, error) {
|
||||
conn, err := m.Conn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proto.NewBorClient(conn), nil
|
||||
}
|
||||
|
||||
// Meta is a helper utility for the commands
|
||||
type Meta struct {
|
||||
UI cli.Ui
|
||||
|
|
|
|||
|
|
@ -2,27 +2,11 @@ package server
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/ethstats"
|
||||
"github.com/ethereum/go-ethereum/graphql"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/metrics/influxdb"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
|
|
@ -38,8 +22,7 @@ type Command struct {
|
|||
|
||||
configFile []string
|
||||
|
||||
// bor node
|
||||
node *node.Node
|
||||
srv *Server
|
||||
}
|
||||
|
||||
// Help implements the cli.Command interface
|
||||
|
|
@ -82,182 +65,16 @@ func (c *Command) Run(args []string) int {
|
|||
}
|
||||
c.config = config
|
||||
|
||||
// start the logger
|
||||
setupLogger(config.LogLevel)
|
||||
|
||||
// load the chain genesis
|
||||
if err := config.loadChain(); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
// create the node/stack
|
||||
nodeCfg, err := config.buildNode()
|
||||
srv, err := NewServer(config)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
stack, err := node.New(nodeCfg)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
c.node = stack
|
||||
c.srv = srv
|
||||
|
||||
// register the ethereum backend
|
||||
ethCfg, err := config.buildEth()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
backend, err := eth.New(stack, ethCfg)
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
log.Info("Heimdall setup", "url", ethCfg.HeimdallURL)
|
||||
|
||||
// debug tracing is enabled by default
|
||||
stack.RegisterAPIs(tracers.APIs(backend.APIBackend))
|
||||
|
||||
// graphql is started from another place
|
||||
if config.JsonRPC.Graphql.Enabled {
|
||||
if err := graphql.New(stack, backend.APIBackend, config.JsonRPC.Cors, config.JsonRPC.Modules); err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Failed to register the GraphQL service: %v", err))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// register ethash service
|
||||
if config.Ethstats != "" {
|
||||
if err := ethstats.New(stack, backend.APIBackend, backend.Engine(), config.Ethstats); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// setup account manager (only keystore)
|
||||
var borKeystore *keystore.KeyStore
|
||||
{
|
||||
keydir := stack.KeyStoreDir()
|
||||
n, p := keystore.StandardScryptN, keystore.StandardScryptP
|
||||
if config.Accounts.UseLightweightKDF {
|
||||
n, p = keystore.LightScryptN, keystore.LightScryptP
|
||||
}
|
||||
borKeystore = keystore.NewKeyStore(keydir, n, p)
|
||||
stack.AccountManager().AddBackend(borKeystore)
|
||||
}
|
||||
|
||||
// unlock accounts if necessary
|
||||
if len(config.Accounts.Unlock) != 0 {
|
||||
if err := c.unlockAccounts(borKeystore); err != nil {
|
||||
c.UI.Error(fmt.Sprintf("failed to unlock: %v", err))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// sealing (if enabled)
|
||||
if config.Sealer.Enabled {
|
||||
if err := backend.StartMining(1); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.setupTelemetry(config.Telemetry); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
// start the node
|
||||
if err := c.node.Start(); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
return c.handleSignals()
|
||||
}
|
||||
|
||||
func (c *Command) unlockAccounts(borKeystore *keystore.KeyStore) error {
|
||||
// If insecure account unlocking is not allowed if node's APIs are exposed to external.
|
||||
if !c.node.Config().InsecureUnlockAllowed && c.node.Config().ExtRPCEnabled() {
|
||||
return fmt.Errorf("account unlock with HTTP access is forbidden")
|
||||
}
|
||||
|
||||
// read passwords from file if possible
|
||||
passwords := []string{}
|
||||
if c.config.Accounts.PasswordFile != "" {
|
||||
var err error
|
||||
if passwords, err = readMultilineFile(c.config.Accounts.PasswordFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
decodePassword := func(addr common.Address, index int) (string, error) {
|
||||
if len(passwords) > 0 {
|
||||
if index < len(passwords) {
|
||||
return passwords[index], nil
|
||||
}
|
||||
return passwords[len(passwords)-1], nil
|
||||
}
|
||||
// ask for the password
|
||||
return c.UI.AskSecret(fmt.Sprintf("Please give a password to unlock '%s'", addr.String()))
|
||||
}
|
||||
|
||||
for index, addrStr := range c.config.Accounts.Unlock {
|
||||
if !common.IsHexAddress(addrStr) {
|
||||
return fmt.Errorf("unlock value '%s' is not an address", addrStr)
|
||||
}
|
||||
acct := accounts.Account{Address: common.HexToAddress(addrStr)}
|
||||
|
||||
password, err := decodePassword(acct.Address, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := borKeystore.Unlock(acct, password); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Unlocked account", "address", acct.Address.Hex())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Command) setupTelemetry(config *TelemetryConfig) error {
|
||||
metrics.Enabled = config.Enabled
|
||||
metrics.EnabledExpensive = config.Expensive
|
||||
|
||||
if !metrics.Enabled {
|
||||
// metrics are disabled, do not set up any sink
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("Enabling metrics collection")
|
||||
|
||||
// influxdb
|
||||
if v1Enabled, v2Enabled := (config.InfluxDB.V1Enabled), (config.InfluxDB.V2Enabled); v1Enabled || v2Enabled {
|
||||
if v1Enabled && v2Enabled {
|
||||
return fmt.Errorf("both influx v1 and influx v2 cannot be enabled")
|
||||
}
|
||||
|
||||
cfg := config.InfluxDB
|
||||
tags := cfg.Tags
|
||||
endpoint := cfg.Endpoint
|
||||
|
||||
if v1Enabled {
|
||||
log.Info("Enabling metrics export to InfluxDB (v1)")
|
||||
go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, cfg.Database, cfg.Username, cfg.Password, "geth.", tags)
|
||||
}
|
||||
if v2Enabled {
|
||||
log.Info("Enabling metrics export to InfluxDB (v2)")
|
||||
go influxdb.InfluxDBV2WithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, cfg.Token, cfg.Bucket, cfg.Organization, "geth.", tags)
|
||||
}
|
||||
}
|
||||
|
||||
// Start system runtime metrics collection
|
||||
go metrics.CollectProcessMetrics(3 * time.Second)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Command) handleSignals() int {
|
||||
signalCh := make(chan os.Signal, 4)
|
||||
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
|
||||
|
|
@ -269,8 +86,7 @@ func (c *Command) handleSignals() int {
|
|||
|
||||
gracefulCh := make(chan struct{})
|
||||
go func() {
|
||||
c.node.Close()
|
||||
c.node.Wait()
|
||||
c.srv.Stop()
|
||||
close(gracefulCh)
|
||||
}()
|
||||
|
||||
|
|
@ -284,35 +100,3 @@ func (c *Command) handleSignals() int {
|
|||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func setupLogger(logLevel string) {
|
||||
output := io.Writer(os.Stderr)
|
||||
usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb"
|
||||
if usecolor {
|
||||
output = colorable.NewColorableStderr()
|
||||
}
|
||||
ostream := log.StreamHandler(output, log.TerminalFormat(usecolor))
|
||||
glogger := log.NewGlogHandler(ostream)
|
||||
|
||||
// logging
|
||||
lvl, err := log.LvlFromString(strings.ToLower(logLevel))
|
||||
if err == nil {
|
||||
glogger.Verbosity(lvl)
|
||||
} else {
|
||||
glogger.Verbosity(log.LvlInfo)
|
||||
}
|
||||
log.Root().SetHandler(glogger)
|
||||
}
|
||||
|
||||
func readMultilineFile(path string) ([]string, error) {
|
||||
text, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(string(text), "\n")
|
||||
// Sanitise DOS line endings.
|
||||
for i := range lines {
|
||||
lines[i] = strings.TrimRight(lines[i], "\r")
|
||||
}
|
||||
return lines, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,9 @@ type Config struct {
|
|||
|
||||
// Account has the validator account related settings
|
||||
Accounts *AccountsConfig `hcl:"accounts,block"`
|
||||
|
||||
// GRPC has the grpc server related settings
|
||||
GRPC *GRPCConfig
|
||||
}
|
||||
|
||||
type P2PConfig struct {
|
||||
|
|
@ -226,6 +229,11 @@ type JsonRPCConfig struct {
|
|||
Graphql *APIConfig `hcl:"graphql,block"`
|
||||
}
|
||||
|
||||
type GRPCConfig struct {
|
||||
// Addr is the bind address for the grpc rpc server
|
||||
Addr string
|
||||
}
|
||||
|
||||
type APIConfig struct {
|
||||
// Enabled selects whether the api is enabled
|
||||
Enabled bool `hcl:"enabled,optional"`
|
||||
|
|
@ -459,6 +467,9 @@ func DefaultConfig() *Config {
|
|||
AllowInsecureUnlock: false,
|
||||
UseLightweightKDF: false,
|
||||
},
|
||||
GRPC: &GRPCConfig{
|
||||
Addr: ":3131",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -440,5 +440,12 @@ func (c *Command) Flags() *flagset.Flagset {
|
|||
Value: &c.cliConfig.Accounts.UseLightweightKDF,
|
||||
})
|
||||
|
||||
// grpc
|
||||
f.StringFlag(&flagset.StringFlag{
|
||||
Name: "grpc.addr",
|
||||
Usage: "Address and port to bind the GRPC server",
|
||||
Value: &c.cliConfig.GRPC.Addr,
|
||||
})
|
||||
|
||||
return f
|
||||
}
|
||||
|
|
|
|||
93
command/server/pprof/pprof.go
Normal file
93
command/server/pprof/pprof.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package pprof
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"runtime/trace"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Profile generates a pprof.Profile report for the given profile name.
|
||||
func Profile(profile string, debug, gc int) ([]byte, map[string]string, error) {
|
||||
p := pprof.Lookup(profile)
|
||||
if p == nil {
|
||||
return nil, nil, fmt.Errorf("profile '%s' not found", profile)
|
||||
}
|
||||
|
||||
if profile == "heap" && gc > 0 {
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := p.WriteTo(&buf, debug); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
headers := map[string]string{
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
}
|
||||
if debug != 0 {
|
||||
headers["Content-Type"] = "text/plain; charset=utf-8"
|
||||
} else {
|
||||
headers["Content-Type"] = "application/octet-stream"
|
||||
headers["Content-Disposition"] = fmt.Sprintf(`attachment; filename="%s"`, profile)
|
||||
}
|
||||
return buf.Bytes(), headers, nil
|
||||
}
|
||||
|
||||
// CPUProfile generates a CPU Profile for a given duration
|
||||
func CPUProfile(ctx context.Context, sec int) ([]byte, map[string]string, error) {
|
||||
if sec <= 0 {
|
||||
sec = 1
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := pprof.StartCPUProfile(&buf); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sleep(ctx, time.Duration(sec)*time.Second)
|
||||
|
||||
pprof.StopCPUProfile()
|
||||
|
||||
return buf.Bytes(),
|
||||
map[string]string{
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"Content-Type": "application/octet-stream",
|
||||
"Content-Disposition": `attachment; filename="profile"`,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Trace runs a trace profile for a given duration
|
||||
func Trace(ctx context.Context, sec int) ([]byte, map[string]string, error) {
|
||||
if sec <= 0 {
|
||||
sec = 1
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := trace.Start(&buf); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sleep(ctx, time.Duration(sec)*time.Second)
|
||||
|
||||
trace.Stop()
|
||||
|
||||
return buf.Bytes(),
|
||||
map[string]string{
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"Content-Type": "application/octet-stream",
|
||||
"Content-Disposition": `attachment; filename="trace"`,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func sleep(ctx context.Context, d time.Duration) {
|
||||
// Sleep until duration is met or ctx is cancelled
|
||||
select {
|
||||
case <-time.After(d):
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
311
command/server/proto/server.pb.go
Normal file
311
command/server/proto/server.pb.go
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.25.0
|
||||
// protoc v3.12.0
|
||||
// source: command/server/proto/server.proto
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
type PprofRequest_Type int32
|
||||
|
||||
const (
|
||||
PprofRequest_LOOKUP PprofRequest_Type = 0
|
||||
PprofRequest_CPU PprofRequest_Type = 1
|
||||
PprofRequest_TRACE PprofRequest_Type = 2
|
||||
)
|
||||
|
||||
// Enum value maps for PprofRequest_Type.
|
||||
var (
|
||||
PprofRequest_Type_name = map[int32]string{
|
||||
0: "LOOKUP",
|
||||
1: "CPU",
|
||||
2: "TRACE",
|
||||
}
|
||||
PprofRequest_Type_value = map[string]int32{
|
||||
"LOOKUP": 0,
|
||||
"CPU": 1,
|
||||
"TRACE": 2,
|
||||
}
|
||||
)
|
||||
|
||||
func (x PprofRequest_Type) Enum() *PprofRequest_Type {
|
||||
p := new(PprofRequest_Type)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x PprofRequest_Type) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (PprofRequest_Type) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_command_server_proto_server_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (PprofRequest_Type) Type() protoreflect.EnumType {
|
||||
return &file_command_server_proto_server_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x PprofRequest_Type) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PprofRequest_Type.Descriptor instead.
|
||||
func (PprofRequest_Type) EnumDescriptor() ([]byte, []int) {
|
||||
return file_command_server_proto_server_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
type PprofRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Type PprofRequest_Type `protobuf:"varint,1,opt,name=type,proto3,enum=proto.PprofRequest_Type" json:"type,omitempty"`
|
||||
Profile string `protobuf:"bytes,2,opt,name=profile,proto3" json:"profile,omitempty"`
|
||||
Seconds int64 `protobuf:"varint,3,opt,name=seconds,proto3" json:"seconds,omitempty"`
|
||||
}
|
||||
|
||||
func (x *PprofRequest) Reset() {
|
||||
*x = PprofRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_command_server_proto_server_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *PprofRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PprofRequest) ProtoMessage() {}
|
||||
|
||||
func (x *PprofRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_command_server_proto_server_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PprofRequest.ProtoReflect.Descriptor instead.
|
||||
func (*PprofRequest) Descriptor() ([]byte, []int) {
|
||||
return file_command_server_proto_server_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *PprofRequest) GetType() PprofRequest_Type {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return PprofRequest_LOOKUP
|
||||
}
|
||||
|
||||
func (x *PprofRequest) GetProfile() string {
|
||||
if x != nil {
|
||||
return x.Profile
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PprofRequest) GetSeconds() int64 {
|
||||
if x != nil {
|
||||
return x.Seconds
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type PprofResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Payload string `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
|
||||
Headers map[string]string `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
}
|
||||
|
||||
func (x *PprofResponse) Reset() {
|
||||
*x = PprofResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_command_server_proto_server_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *PprofResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PprofResponse) ProtoMessage() {}
|
||||
|
||||
func (x *PprofResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_command_server_proto_server_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PprofResponse.ProtoReflect.Descriptor instead.
|
||||
func (*PprofResponse) Descriptor() ([]byte, []int) {
|
||||
return file_command_server_proto_server_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *PprofResponse) GetPayload() string {
|
||||
if x != nil {
|
||||
return x.Payload
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PprofResponse) GetHeaders() map[string]string {
|
||||
if x != nil {
|
||||
return x.Headers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_command_server_proto_server_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_command_server_proto_server_proto_rawDesc = []byte{
|
||||
0x0a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x98, 0x01, 0x0a, 0x0c, 0x50,
|
||||
0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x04, 0x74,
|
||||
0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x2e, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x54,
|
||||
0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f,
|
||||
0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66,
|
||||
0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x22, 0x26, 0x0a,
|
||||
0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x4f, 0x4f, 0x4b, 0x55, 0x50, 0x10,
|
||||
0x00, 0x12, 0x07, 0x0a, 0x03, 0x43, 0x50, 0x55, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52,
|
||||
0x41, 0x43, 0x45, 0x10, 0x02, 0x22, 0xa2, 0x01, 0x0a, 0x0d, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f,
|
||||
0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61,
|
||||
0x64, 0x12, 0x3b, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03,
|
||||
0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x70, 0x72, 0x6f, 0x66,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73,
|
||||
0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x3a,
|
||||
0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
|
||||
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
|
||||
0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0x39, 0x0a, 0x03, 0x42, 0x6f,
|
||||
0x72, 0x12, 0x32, 0x0a, 0x05, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x2e, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x17, 0x5a, 0x15, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
|
||||
0x64, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_command_server_proto_server_proto_rawDescOnce sync.Once
|
||||
file_command_server_proto_server_proto_rawDescData = file_command_server_proto_server_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_command_server_proto_server_proto_rawDescGZIP() []byte {
|
||||
file_command_server_proto_server_proto_rawDescOnce.Do(func() {
|
||||
file_command_server_proto_server_proto_rawDescData = protoimpl.X.CompressGZIP(file_command_server_proto_server_proto_rawDescData)
|
||||
})
|
||||
return file_command_server_proto_server_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_command_server_proto_server_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_command_server_proto_server_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_command_server_proto_server_proto_goTypes = []interface{}{
|
||||
(PprofRequest_Type)(0), // 0: proto.PprofRequest.Type
|
||||
(*PprofRequest)(nil), // 1: proto.PprofRequest
|
||||
(*PprofResponse)(nil), // 2: proto.PprofResponse
|
||||
nil, // 3: proto.PprofResponse.HeadersEntry
|
||||
}
|
||||
var file_command_server_proto_server_proto_depIdxs = []int32{
|
||||
0, // 0: proto.PprofRequest.type:type_name -> proto.PprofRequest.Type
|
||||
3, // 1: proto.PprofResponse.headers:type_name -> proto.PprofResponse.HeadersEntry
|
||||
1, // 2: proto.Bor.Pprof:input_type -> proto.PprofRequest
|
||||
2, // 3: proto.Bor.Pprof:output_type -> proto.PprofResponse
|
||||
3, // [3:4] is the sub-list for method output_type
|
||||
2, // [2:3] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
2, // [2:2] is the sub-list for extension extendee
|
||||
0, // [0:2] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_command_server_proto_server_proto_init() }
|
||||
func file_command_server_proto_server_proto_init() {
|
||||
if File_command_server_proto_server_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_command_server_proto_server_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*PprofRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_command_server_proto_server_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*PprofResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_command_server_proto_server_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_command_server_proto_server_proto_goTypes,
|
||||
DependencyIndexes: file_command_server_proto_server_proto_depIdxs,
|
||||
EnumInfos: file_command_server_proto_server_proto_enumTypes,
|
||||
MessageInfos: file_command_server_proto_server_proto_msgTypes,
|
||||
}.Build()
|
||||
File_command_server_proto_server_proto = out.File
|
||||
file_command_server_proto_server_proto_rawDesc = nil
|
||||
file_command_server_proto_server_proto_goTypes = nil
|
||||
file_command_server_proto_server_proto_depIdxs = nil
|
||||
}
|
||||
28
command/server/proto/server.proto
Normal file
28
command/server/proto/server.proto
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package proto;
|
||||
|
||||
option go_package = "/command/server/proto";
|
||||
|
||||
service Bor {
|
||||
rpc Pprof(PprofRequest) returns (PprofResponse);
|
||||
}
|
||||
|
||||
message PprofRequest {
|
||||
Type type = 1;
|
||||
|
||||
string profile = 2;
|
||||
|
||||
int64 seconds = 3;
|
||||
|
||||
enum Type {
|
||||
LOOKUP = 0;
|
||||
CPU = 1;
|
||||
TRACE = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message PprofResponse {
|
||||
string payload = 1;
|
||||
map<string, string> headers = 2;
|
||||
}
|
||||
101
command/server/proto/server_grpc.pb.go
Normal file
101
command/server/proto/server_grpc.pb.go
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// BorClient is the client API for Bor service.
|
||||
//
|
||||
// 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 {
|
||||
Pprof(ctx context.Context, in *PprofRequest, opts ...grpc.CallOption) (*PprofResponse, error)
|
||||
}
|
||||
|
||||
type borClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewBorClient(cc grpc.ClientConnInterface) BorClient {
|
||||
return &borClient{cc}
|
||||
}
|
||||
|
||||
func (c *borClient) Pprof(ctx context.Context, in *PprofRequest, opts ...grpc.CallOption) (*PprofResponse, error) {
|
||||
out := new(PprofResponse)
|
||||
err := c.cc.Invoke(ctx, "/proto.Bor/Pprof", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// BorServer is the server API for Bor service.
|
||||
// All implementations must embed UnimplementedBorServer
|
||||
// for forward compatibility
|
||||
type BorServer interface {
|
||||
Pprof(context.Context, *PprofRequest) (*PprofResponse, error)
|
||||
mustEmbedUnimplementedBorServer()
|
||||
}
|
||||
|
||||
// UnimplementedBorServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedBorServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedBorServer) Pprof(context.Context, *PprofRequest) (*PprofResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Pprof not implemented")
|
||||
}
|
||||
func (UnimplementedBorServer) mustEmbedUnimplementedBorServer() {}
|
||||
|
||||
// UnsafeBorServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to BorServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeBorServer interface {
|
||||
mustEmbedUnimplementedBorServer()
|
||||
}
|
||||
|
||||
func RegisterBorServer(s grpc.ServiceRegistrar, srv BorServer) {
|
||||
s.RegisterService(&Bor_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _Bor_Pprof_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PprofRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BorServer).Pprof(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/proto.Bor/Pprof",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BorServer).Pprof(ctx, req.(*PprofRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// Bor_ServiceDesc is the grpc.ServiceDesc for Bor service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Bor_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "proto.Bor",
|
||||
HandlerType: (*BorServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Pprof",
|
||||
Handler: _Bor_Pprof_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "command/server/proto/server.proto",
|
||||
}
|
||||
202
command/server/server.go
Normal file
202
command/server/server.go
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/command/server/proto"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/ethstats"
|
||||
"github.com/ethereum/go-ethereum/graphql"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/metrics/influxdb"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
proto.UnimplementedBorServer
|
||||
node *node.Node
|
||||
grpcServer *grpc.Server
|
||||
}
|
||||
|
||||
func NewServer(config *Config) (*Server, error) {
|
||||
srv := &Server{}
|
||||
|
||||
// start the logger
|
||||
setupLogger(config.LogLevel)
|
||||
|
||||
if err := srv.setupGRPCServer(config.GRPC.Addr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// load the chain genesis
|
||||
if err := config.loadChain(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create the node/stack
|
||||
nodeCfg, err := config.buildNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stack, err := node.New(nodeCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srv.node = stack
|
||||
|
||||
// register the ethereum backend
|
||||
ethCfg, err := config.buildEth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backend, err := eth.New(stack, ethCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// debug tracing is enabled by default
|
||||
stack.RegisterAPIs(tracers.APIs(backend.APIBackend))
|
||||
|
||||
// graphql is started from another place
|
||||
if config.JsonRPC.Graphql.Enabled {
|
||||
if err := graphql.New(stack, backend.APIBackend, config.JsonRPC.Cors, config.JsonRPC.Modules); err != nil {
|
||||
return nil, fmt.Errorf("failed to register the GraphQL service: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// register ethash service
|
||||
if config.Ethstats != "" {
|
||||
if err := ethstats.New(stack, backend.APIBackend, backend.Engine(), config.Ethstats); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// setup account manager (only keystore)
|
||||
{
|
||||
keydir := stack.KeyStoreDir()
|
||||
n, p := keystore.StandardScryptN, keystore.StandardScryptP
|
||||
if config.Accounts.UseLightweightKDF {
|
||||
n, p = keystore.LightScryptN, keystore.LightScryptP
|
||||
}
|
||||
stack.AccountManager().AddBackend(keystore.NewKeyStore(keydir, n, p))
|
||||
}
|
||||
|
||||
// sealing (if enabled)
|
||||
if config.Sealer.Enabled {
|
||||
if err := backend.StartMining(1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := srv.setupMetrics(config.Telemetry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// start the node
|
||||
if err := srv.node.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
func (s *Server) Stop() {
|
||||
s.node.Close()
|
||||
}
|
||||
|
||||
func (s *Server) setupMetrics(config *TelemetryConfig) error {
|
||||
metrics.Enabled = config.Enabled
|
||||
metrics.EnabledExpensive = config.Expensive
|
||||
|
||||
if !metrics.Enabled {
|
||||
// metrics are disabled, do not set up any sink
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("Enabling metrics collection")
|
||||
|
||||
// influxdb
|
||||
if v1Enabled, v2Enabled := config.InfluxDB.V1Enabled, config.InfluxDB.V2Enabled; v1Enabled || v2Enabled {
|
||||
if v1Enabled && v2Enabled {
|
||||
return fmt.Errorf("both influx v1 and influx v2 cannot be enabled")
|
||||
}
|
||||
|
||||
cfg := config.InfluxDB
|
||||
tags := cfg.Tags
|
||||
endpoint := cfg.Endpoint
|
||||
|
||||
if v1Enabled {
|
||||
log.Info("Enabling metrics export to InfluxDB (v1)")
|
||||
go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, cfg.Database, cfg.Username, cfg.Password, "geth.", tags)
|
||||
}
|
||||
if v2Enabled {
|
||||
log.Info("Enabling metrics export to InfluxDB (v2)")
|
||||
go influxdb.InfluxDBV2WithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, cfg.Token, cfg.Bucket, cfg.Organization, "geth.", tags)
|
||||
}
|
||||
}
|
||||
|
||||
// Start system runtime metrics collection
|
||||
go metrics.CollectProcessMetrics(3 * time.Second)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) setupGRPCServer(addr string) error {
|
||||
s.grpcServer = grpc.NewServer(s.withLoggingUnaryInterceptor())
|
||||
proto.RegisterBorServer(s.grpcServer, s)
|
||||
|
||||
lis, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := s.grpcServer.Serve(lis); err != nil {
|
||||
log.Error("failed to serve grpc server", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Info("GRPC Server started", "addr", addr)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) withLoggingUnaryInterceptor() grpc.ServerOption {
|
||||
return grpc.UnaryInterceptor(s.loggingServerInterceptor)
|
||||
}
|
||||
|
||||
func (s *Server) loggingServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
start := time.Now()
|
||||
h, err := handler(ctx, req)
|
||||
log.Trace("Request", "method", info.FullMethod, "duration", time.Since(start), "error", err)
|
||||
return h, err
|
||||
}
|
||||
|
||||
func setupLogger(logLevel string) {
|
||||
output := io.Writer(os.Stderr)
|
||||
usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb"
|
||||
if usecolor {
|
||||
output = colorable.NewColorableStderr()
|
||||
}
|
||||
ostream := log.StreamHandler(output, log.TerminalFormat(usecolor))
|
||||
glogger := log.NewGlogHandler(ostream)
|
||||
|
||||
// logging
|
||||
lvl, err := log.LvlFromString(strings.ToLower(logLevel))
|
||||
if err == nil {
|
||||
glogger.Verbosity(lvl)
|
||||
} else {
|
||||
glogger.Verbosity(log.LvlInfo)
|
||||
}
|
||||
log.Root().SetHandler(glogger)
|
||||
}
|
||||
33
command/server/service.go
Normal file
33
command/server/service.go
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/ethereum/go-ethereum/command/server/pprof"
|
||||
"github.com/ethereum/go-ethereum/command/server/proto"
|
||||
)
|
||||
|
||||
func (s *Server) Pprof(ctx context.Context, req *proto.PprofRequest) (*proto.PprofResponse, error) {
|
||||
var payload []byte
|
||||
var headers map[string]string
|
||||
var err error
|
||||
|
||||
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 nil, err
|
||||
}
|
||||
|
||||
resp := &proto.PprofResponse{
|
||||
Payload: hex.EncodeToString(payload),
|
||||
Headers: headers,
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
2
go.mod
2
go.mod
|
|
@ -74,6 +74,8 @@ require (
|
|||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912
|
||||
golang.org/x/text v0.3.6
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
||||
google.golang.org/grpc v1.37.0
|
||||
google.golang.org/protobuf v1.25.0
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
|
||||
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6
|
||||
gopkg.in/urfave/cli.v1 v1.20.0
|
||||
|
|
|
|||
16
go.sum
16
go.sum
|
|
@ -116,6 +116,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
|||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.14.0 h1:gFqGlGl/5f9UGXAaKapCGUfaTCgRKKnzu2VvzMZlOFA=
|
||||
github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ=
|
||||
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f h1:C43yEtQ6NIf4ftFXD/V55gnGFgPbMQobd//YlnLjUJ8=
|
||||
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q=
|
||||
|
|
@ -144,7 +145,9 @@ github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbT
|
|||
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
|
||||
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
|
|
@ -201,6 +204,7 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
|
|||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
|
|
@ -217,6 +221,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64=
|
||||
|
|
@ -672,18 +677,27 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx
|
|||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
|
|
|
|||
Loading…
Reference in a new issue