github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/internal/cli/bootnode.go (about) 1 package cli 2 3 import ( 4 "crypto/ecdsa" 5 "errors" 6 "fmt" 7 "net" 8 "os" 9 "os/signal" 10 "path/filepath" 11 "strings" 12 "syscall" 13 14 "github.com/ethereum/go-ethereum/cmd/utils" 15 "github.com/ethereum/go-ethereum/crypto" 16 "github.com/ethereum/go-ethereum/internal/cli/flagset" 17 "github.com/ethereum/go-ethereum/internal/cli/server" 18 "github.com/ethereum/go-ethereum/log" 19 "github.com/ethereum/go-ethereum/p2p/discover" 20 "github.com/ethereum/go-ethereum/p2p/enode" 21 "github.com/ethereum/go-ethereum/p2p/nat" 22 23 "github.com/mitchellh/cli" 24 ) 25 26 type BootnodeCommand struct { 27 UI cli.Ui 28 29 listenAddr string 30 v5 bool 31 verbosity int 32 logLevel string 33 nat string 34 nodeKey string 35 saveKey string 36 dryRun bool 37 } 38 39 // Help implements the cli.Command interface 40 func (b *BootnodeCommand) Help() string { 41 return `Usage: bor bootnode` 42 } 43 44 // MarkDown implements cli.MarkDown interface 45 func (c *BootnodeCommand) MarkDown() string { 46 items := []string{ 47 "# Bootnode", 48 c.Flags().MarkDown(), 49 } 50 51 return strings.Join(items, "\n\n") 52 } 53 54 func (b *BootnodeCommand) Flags() *flagset.Flagset { 55 flags := flagset.NewFlagSet("bootnode") 56 57 flags.StringFlag(&flagset.StringFlag{ 58 Name: "listen-addr", 59 Default: "0.0.0.0:30303", 60 Usage: "listening address of bootnode (<ip>:<port>)", 61 Value: &b.listenAddr, 62 }) 63 flags.BoolFlag(&flagset.BoolFlag{ 64 Name: "v5", 65 Default: false, 66 Usage: "Enable UDP v5", 67 Value: &b.v5, 68 }) 69 flags.IntFlag(&flagset.IntFlag{ 70 Name: "verbosity", 71 Default: 3, 72 Usage: "Logging verbosity (5=trace|4=debug|3=info|2=warn|1=error|0=crit)", 73 Value: &b.verbosity, 74 }) 75 flags.StringFlag(&flagset.StringFlag{ 76 Name: "log-level", 77 Default: "info", 78 Usage: "log level (trace|debug|info|warn|error|crit), will be deprecated soon. Use verbosity instead", 79 Value: &b.logLevel, 80 }) 81 flags.StringFlag(&flagset.StringFlag{ 82 Name: "nat", 83 Default: "none", 84 Usage: "port mapping mechanism (any|none|upnp|pmp|extip:<IP>)", 85 Value: &b.nat, 86 }) 87 flags.StringFlag(&flagset.StringFlag{ 88 Name: "node-key", 89 Default: "", 90 Usage: "file or hex node key", 91 Value: &b.nodeKey, 92 }) 93 flags.StringFlag(&flagset.StringFlag{ 94 Name: "save-key", 95 Default: "", 96 Usage: "path to save the ecdsa private key", 97 Value: &b.saveKey, 98 }) 99 flags.BoolFlag(&flagset.BoolFlag{ 100 Name: "dry-run", 101 Default: false, 102 Usage: "validates parameters and prints bootnode configurations, but does not start bootnode", 103 Value: &b.dryRun, 104 }) 105 106 return flags 107 } 108 109 // Synopsis implements the cli.Command interface 110 func (b *BootnodeCommand) Synopsis() string { 111 return "Start a bootnode" 112 } 113 114 // Run implements the cli.Command interface 115 // nolint: gocognit 116 func (b *BootnodeCommand) Run(args []string) int { 117 flags := b.Flags() 118 if err := flags.Parse(args); err != nil { 119 b.UI.Error(err.Error()) 120 return 1 121 } 122 123 glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) 124 125 var logInfo string 126 127 if b.verbosity != 0 && b.logLevel != "" { 128 b.UI.Warn(fmt.Sprintf("Both verbosity and log-level provided, using verbosity: %v", b.verbosity)) 129 logInfo = server.VerbosityIntToString(b.verbosity) 130 } else if b.verbosity != 0 { 131 logInfo = server.VerbosityIntToString(b.verbosity) 132 } else { 133 logInfo = b.logLevel 134 } 135 136 lvl, err := log.LvlFromString(strings.ToLower(logInfo)) 137 if err == nil { 138 glogger.Verbosity(lvl) 139 } else { 140 glogger.Verbosity(log.LvlInfo) 141 } 142 143 log.Root().SetHandler(glogger) 144 145 natm, err := nat.Parse(b.nat) 146 if err != nil { 147 b.UI.Error(fmt.Sprintf("failed to parse nat: %v", err)) 148 return 1 149 } 150 151 // create a one time key 152 var nodeKey *ecdsa.PrivateKey 153 // nolint: nestif 154 if b.nodeKey != "" { 155 // try to read the key either from file or command line 156 if _, err := os.Stat(b.nodeKey); errors.Is(err, os.ErrNotExist) { 157 if nodeKey, err = crypto.HexToECDSA(b.nodeKey); err != nil { 158 b.UI.Error(fmt.Sprintf("failed to parse hex address: %v", err)) 159 return 1 160 } 161 } else { 162 if nodeKey, err = crypto.LoadECDSA(b.nodeKey); err != nil { 163 b.UI.Error(fmt.Sprintf("failed to load node key: %v", err)) 164 return 1 165 } 166 } 167 } else { 168 // generate a new temporal key 169 if nodeKey, err = crypto.GenerateKey(); err != nil { 170 b.UI.Error(fmt.Sprintf("could not generate key: %v", err)) 171 return 1 172 } 173 if b.saveKey != "" { 174 path := b.saveKey 175 176 // save the private key 177 if err = crypto.SaveECDSA(filepath.Join(path, "priv.key"), nodeKey); err != nil { 178 b.UI.Error(fmt.Sprintf("failed to write node priv key: %v", err)) 179 return 1 180 } 181 // save the public key 182 pubRaw := fmt.Sprintf("%x", crypto.FromECDSAPub(&nodeKey.PublicKey)[1:]) 183 if err := os.WriteFile(filepath.Join(path, "pub.key"), []byte(pubRaw), 0600); err != nil { 184 b.UI.Error(fmt.Sprintf("failed to write node pub key: %v", err)) 185 return 1 186 } 187 } 188 } 189 190 addr, err := net.ResolveUDPAddr("udp", b.listenAddr) 191 if err != nil { 192 b.UI.Error(fmt.Sprintf("could not resolve udp addr '%s': %v", b.listenAddr, err)) 193 return 1 194 } 195 196 conn, err := net.ListenUDP("udp", addr) 197 198 if err != nil { 199 b.UI.Error(fmt.Sprintf("failed to listen udp addr '%s': %v", b.listenAddr, err)) 200 return 1 201 } 202 203 realaddr := conn.LocalAddr().(*net.UDPAddr) 204 if natm != nil { 205 if !realaddr.IP.IsLoopback() { 206 go nat.Map(natm, nil, "udp", realaddr.Port, realaddr.Port, "ethereum discovery") 207 } 208 209 if ext, err := natm.ExternalIP(); err == nil { 210 // nolint: govet 211 realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port} 212 } 213 } 214 215 n := enode.NewV4(&nodeKey.PublicKey, addr.IP, addr.Port, addr.Port) 216 b.UI.Info(n.String()) 217 218 if b.dryRun { 219 return 0 220 } 221 222 db, _ := enode.OpenDB("") 223 ln := enode.NewLocalNode(db, nodeKey) 224 cfg := discover.Config{ 225 PrivateKey: nodeKey, 226 Log: log.Root(), 227 } 228 229 if b.v5 { 230 if _, err := discover.ListenV5(conn, ln, cfg); err != nil { 231 utils.Fatalf("%v", err) 232 } 233 } else { 234 if _, err := discover.ListenUDP(conn, ln, cfg); err != nil { 235 utils.Fatalf("%v", err) 236 } 237 } 238 239 signalCh := make(chan os.Signal, 4) 240 signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) 241 242 sig := <-signalCh 243 244 b.UI.Output(fmt.Sprintf("Caught signal: %v", sig)) 245 b.UI.Output("Gracefully shutting down agent...") 246 247 return 0 248 }