mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 23:26:44 +00:00
Add bootnode cli
This commit is contained in:
parent
a131e427f6
commit
ad8bede955
4 changed files with 248 additions and 0 deletions
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
- [```attach```](./attach.md)
|
||||
|
||||
- [```bootnode```](./bootnode.md)
|
||||
|
||||
- [```chain```](./chain.md)
|
||||
|
||||
- [```chain sethead```](./chain_sethead.md)
|
||||
|
|
|
|||
17
docs/cli/bootnode.md
Normal file
17
docs/cli/bootnode.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Bootnode
|
||||
|
||||
## Options
|
||||
|
||||
- ```listen-addr```: listening address of bootnode (<ip>:<port>)
|
||||
|
||||
- ```v5```: Enable UDP v5
|
||||
|
||||
- ```log-level```: Log level (trace|debug|info|warn|error|crit)
|
||||
|
||||
- ```nat```: port mapping mechanism (any|none|upnp|pmp|extip:<IP>)
|
||||
|
||||
- ```node-key```: file or hex node key
|
||||
|
||||
- ```save-key```: path to save the ecdsa private key
|
||||
|
||||
- ```dry-run```
|
||||
220
internal/cli/bootnode.go
Normal file
220
internal/cli/bootnode.go
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"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/log"
|
||||
"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
|
||||
v5 bool
|
||||
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: "v5",
|
||||
Default: false,
|
||||
Usage: "Enable UDP v5",
|
||||
Value: &b.v5,
|
||||
})
|
||||
flags.StringFlag(&flagset.StringFlag{
|
||||
Name: "log-level",
|
||||
Default: "info",
|
||||
Usage: "Log level (trace|debug|info|warn|error|crit)",
|
||||
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
|
||||
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)))
|
||||
|
||||
lvl, err := log.LvlFromString(strings.ToLower(b.logLevel))
|
||||
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
|
||||
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 := ioutil.WriteFile(filepath.Join(path, "pub.key"), []byte(pubRaw), 0755); 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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -27,6 +27,10 @@ func Run(args []string) int {
|
|||
mappedCommands := make(map[string]cli.CommandFactory)
|
||||
|
||||
for k, v := range commands {
|
||||
// Declare a new v to limit the scope of v to inside the block, so the anonymous function below
|
||||
// can get the "current" value of v, instead of the value of last v in the loop.
|
||||
// See this post: https://stackoverflow.com/questions/10116507/go-transfer-var-into-anonymous-function for more explanation
|
||||
v := v
|
||||
mappedCommands[k] = func() (cli.Command, error) {
|
||||
cmd, err := v()
|
||||
return cmd.(cli.Command), err
|
||||
|
|
@ -153,6 +157,11 @@ func Commands() map[string]MarkDownCommandFactory {
|
|||
Meta2: meta2,
|
||||
}, nil
|
||||
},
|
||||
"bootnode": func() (MarkDownCommand, error) {
|
||||
return &BootnodeCommand{
|
||||
UI: ui,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue