mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 06:36:43 +00:00
287 lines
7.2 KiB
Go
287 lines
7.2 KiB
Go
package cli
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/internal/cli/flagset"
|
|
"github.com/ethereum/go-ethereum/internal/cli/server"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/ethereum/go-ethereum/metrics"
|
|
"github.com/ethereum/go-ethereum/metrics/prometheus"
|
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
|
|
|
"github.com/mitchellh/cli"
|
|
)
|
|
|
|
type BootnodeCommand struct {
|
|
UI cli.Ui
|
|
|
|
listenAddr string
|
|
enableMetrics bool
|
|
prometheusAddr string
|
|
v5 bool
|
|
verbosity int
|
|
logLevel string
|
|
nat string
|
|
nodeKey string
|
|
saveKey string
|
|
dryRun bool
|
|
}
|
|
|
|
// Help implements the cli.Command interface
|
|
func (b *BootnodeCommand) Help() string {
|
|
return `Usage: bor bootnode`
|
|
}
|
|
|
|
// MarkDown implements cli.MarkDown interface
|
|
func (c *BootnodeCommand) MarkDown() string {
|
|
items := []string{
|
|
"# Bootnode",
|
|
c.Flags().MarkDown(),
|
|
}
|
|
|
|
return strings.Join(items, "\n\n")
|
|
}
|
|
|
|
func (b *BootnodeCommand) Flags() *flagset.Flagset {
|
|
flags := flagset.NewFlagSet("bootnode")
|
|
|
|
flags.StringFlag(&flagset.StringFlag{
|
|
Name: "listen-addr",
|
|
Default: "0.0.0.0:30303",
|
|
Usage: "listening address of bootnode (<ip>:<port>)",
|
|
Value: &b.listenAddr,
|
|
})
|
|
flags.BoolFlag(&flagset.BoolFlag{
|
|
Name: "metrics",
|
|
Usage: "Enable metrics collection and reporting",
|
|
Value: &b.enableMetrics,
|
|
Default: true,
|
|
})
|
|
flags.StringFlag(&flagset.StringFlag{
|
|
Name: "prometheus-addr",
|
|
Default: "127.0.0.1:7071",
|
|
Usage: "listening address of bootnode (<ip>:<port>)",
|
|
Value: &b.prometheusAddr,
|
|
})
|
|
flags.BoolFlag(&flagset.BoolFlag{
|
|
Name: "v5",
|
|
Default: false,
|
|
Usage: "Enable UDP v5",
|
|
Value: &b.v5,
|
|
})
|
|
flags.IntFlag(&flagset.IntFlag{
|
|
Name: "verbosity",
|
|
Default: 3,
|
|
Usage: "Logging verbosity (5=trace|4=debug|3=info|2=warn|1=error|0=crit)",
|
|
Value: &b.verbosity,
|
|
})
|
|
flags.StringFlag(&flagset.StringFlag{
|
|
Name: "log-level",
|
|
Default: "info",
|
|
Usage: "log level (trace|debug|info|warn|error|crit), will be deprecated soon. Use verbosity instead",
|
|
Value: &b.logLevel,
|
|
})
|
|
flags.StringFlag(&flagset.StringFlag{
|
|
Name: "nat",
|
|
Default: "none",
|
|
Usage: "port mapping mechanism (any|none|upnp|pmp|extip:<IP>)",
|
|
Value: &b.nat,
|
|
})
|
|
flags.StringFlag(&flagset.StringFlag{
|
|
Name: "node-key",
|
|
Default: "",
|
|
Usage: "file or hex node key",
|
|
Value: &b.nodeKey,
|
|
})
|
|
flags.StringFlag(&flagset.StringFlag{
|
|
Name: "save-key",
|
|
Default: "",
|
|
Usage: "path to save the ecdsa private key",
|
|
Value: &b.saveKey,
|
|
})
|
|
flags.BoolFlag(&flagset.BoolFlag{
|
|
Name: "dry-run",
|
|
Default: false,
|
|
Usage: "validates parameters and prints bootnode configurations, but does not start bootnode",
|
|
Value: &b.dryRun,
|
|
})
|
|
|
|
return flags
|
|
}
|
|
|
|
// Synopsis implements the cli.Command interface
|
|
func (b *BootnodeCommand) Synopsis() string {
|
|
return "Start a bootnode"
|
|
}
|
|
|
|
// Run implements the cli.Command interface
|
|
// nolint: gocognit
|
|
func (b *BootnodeCommand) Run(args []string) int {
|
|
flags := b.Flags()
|
|
if err := flags.Parse(args); err != nil {
|
|
b.UI.Error(err.Error())
|
|
return 1
|
|
}
|
|
|
|
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
|
|
|
|
var logInfo string
|
|
|
|
if b.verbosity != 0 && b.logLevel != "" {
|
|
b.UI.Warn(fmt.Sprintf("Both verbosity and log-level provided, using verbosity: %v", b.verbosity))
|
|
logInfo = server.VerbosityIntToString(b.verbosity)
|
|
} else if b.verbosity != 0 {
|
|
logInfo = server.VerbosityIntToString(b.verbosity)
|
|
} else {
|
|
logInfo = b.logLevel
|
|
}
|
|
|
|
lvl, err := log.LvlFromString(strings.ToLower(logInfo))
|
|
if err == nil {
|
|
glogger.Verbosity(lvl)
|
|
} else {
|
|
glogger.Verbosity(log.LvlInfo)
|
|
}
|
|
|
|
log.Root().SetHandler(glogger)
|
|
|
|
natm, err := nat.Parse(b.nat)
|
|
if err != nil {
|
|
b.UI.Error(fmt.Sprintf("failed to parse nat: %v", err))
|
|
return 1
|
|
}
|
|
|
|
// create a one time key
|
|
var nodeKey *ecdsa.PrivateKey
|
|
// nolint: nestif
|
|
if b.nodeKey != "" {
|
|
// try to read the key either from file or command line
|
|
if _, err := os.Stat(b.nodeKey); errors.Is(err, os.ErrNotExist) {
|
|
if nodeKey, err = crypto.HexToECDSA(b.nodeKey); err != nil {
|
|
b.UI.Error(fmt.Sprintf("failed to parse hex address: %v", err))
|
|
return 1
|
|
}
|
|
} else {
|
|
if nodeKey, err = crypto.LoadECDSA(b.nodeKey); err != nil {
|
|
b.UI.Error(fmt.Sprintf("failed to load node key: %v", err))
|
|
return 1
|
|
}
|
|
}
|
|
} else {
|
|
// generate a new temporal key
|
|
if nodeKey, err = crypto.GenerateKey(); err != nil {
|
|
b.UI.Error(fmt.Sprintf("could not generate key: %v", err))
|
|
return 1
|
|
}
|
|
|
|
if b.saveKey != "" {
|
|
path := b.saveKey
|
|
|
|
// save the private key
|
|
if err = crypto.SaveECDSA(filepath.Join(path, "priv.key"), nodeKey); err != nil {
|
|
b.UI.Error(fmt.Sprintf("failed to write node priv key: %v", err))
|
|
return 1
|
|
}
|
|
// save the public key
|
|
pubRaw := fmt.Sprintf("%x", crypto.FromECDSAPub(&nodeKey.PublicKey)[1:])
|
|
if err := os.WriteFile(filepath.Join(path, "pub.key"), []byte(pubRaw), 0600); err != nil {
|
|
b.UI.Error(fmt.Sprintf("failed to write node pub key: %v", err))
|
|
return 1
|
|
}
|
|
}
|
|
}
|
|
|
|
addr, err := net.ResolveUDPAddr("udp", b.listenAddr)
|
|
if err != nil {
|
|
b.UI.Error(fmt.Sprintf("could not resolve udp addr '%s': %v", b.listenAddr, err))
|
|
return 1
|
|
}
|
|
|
|
conn, err := net.ListenUDP("udp", addr)
|
|
|
|
if err != nil {
|
|
b.UI.Error(fmt.Sprintf("failed to listen udp addr '%s': %v", b.listenAddr, err))
|
|
return 1
|
|
}
|
|
|
|
realaddr := conn.LocalAddr().(*net.UDPAddr)
|
|
if natm != nil {
|
|
if !realaddr.IP.IsLoopback() {
|
|
go nat.Map(natm, nil, "udp", realaddr.Port, realaddr.Port, "ethereum discovery")
|
|
}
|
|
|
|
if ext, err := natm.ExternalIP(); err == nil {
|
|
// nolint: govet
|
|
realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port}
|
|
}
|
|
}
|
|
|
|
n := enode.NewV4(&nodeKey.PublicKey, addr.IP, addr.Port, addr.Port)
|
|
b.UI.Info(n.String())
|
|
|
|
if b.dryRun {
|
|
return 0
|
|
}
|
|
|
|
db, _ := enode.OpenDB("")
|
|
ln := enode.NewLocalNode(db, nodeKey)
|
|
cfg := discover.Config{
|
|
PrivateKey: nodeKey,
|
|
Log: log.Root(),
|
|
}
|
|
|
|
if b.v5 {
|
|
if _, err := discover.ListenV5(conn, ln, cfg); err != nil {
|
|
utils.Fatalf("%v", err)
|
|
}
|
|
} else {
|
|
if _, err := discover.ListenUDP(conn, ln, cfg); err != nil {
|
|
utils.Fatalf("%v", err)
|
|
}
|
|
}
|
|
|
|
if b.enableMetrics {
|
|
prometheusMux := http.NewServeMux()
|
|
|
|
prometheusMux.Handle("/debug/metrics/prometheus", prometheus.Handler(metrics.DefaultRegistry))
|
|
|
|
promServer := &http.Server{
|
|
Addr: b.prometheusAddr,
|
|
Handler: prometheusMux,
|
|
ReadHeaderTimeout: 30 * time.Second,
|
|
}
|
|
|
|
go func() {
|
|
if err := promServer.ListenAndServe(); err != nil {
|
|
log.Error("Failure in running Prometheus server", "err", err)
|
|
}
|
|
}()
|
|
|
|
log.Info("Enabling metrics export to prometheus", "path", fmt.Sprintf("http://%s/debug/metrics/prometheus", b.prometheusAddr))
|
|
}
|
|
|
|
signalCh := make(chan os.Signal, 4)
|
|
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
|
|
|
|
sig := <-signalCh
|
|
|
|
b.UI.Output(fmt.Sprintf("Caught signal: %v", sig))
|
|
b.UI.Output("Gracefully shutting down agent...")
|
|
|
|
return 0
|
|
}
|