github.com/prysmaticlabs/prysm@v1.4.4/tools/bootnode/bootnode.go (about) 1 /** 2 * Bootnode 3 * 4 * A node which implements the DiscoveryV5 protocol for peer 5 * discovery. The purpose of this service is to provide a starting point for 6 * newly connected services to find other peers outside of their network. 7 * 8 * Usage: Run bootnode --help for flag options. 9 */ 10 package main 11 12 import ( 13 "context" 14 "crypto/ecdsa" 15 "crypto/rand" 16 "encoding/hex" 17 "flag" 18 "fmt" 19 "io" 20 "net" 21 "net/http" 22 "os" 23 "time" 24 25 gcrypto "github.com/ethereum/go-ethereum/crypto" 26 gethlog "github.com/ethereum/go-ethereum/log" 27 "github.com/ethereum/go-ethereum/p2p/discover" 28 "github.com/ethereum/go-ethereum/p2p/enode" 29 "github.com/ethereum/go-ethereum/p2p/enr" 30 "github.com/libp2p/go-libp2p-core/crypto" 31 "github.com/pkg/errors" 32 "github.com/prometheus/client_golang/prometheus" 33 "github.com/prometheus/client_golang/prometheus/promauto" 34 "github.com/prysmaticlabs/go-bitfield" 35 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 36 pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" 37 "github.com/prysmaticlabs/prysm/shared/bytesutil" 38 "github.com/prysmaticlabs/prysm/shared/iputils" 39 "github.com/prysmaticlabs/prysm/shared/logutil" 40 _ "github.com/prysmaticlabs/prysm/shared/maxprocs" 41 "github.com/prysmaticlabs/prysm/shared/params" 42 "github.com/prysmaticlabs/prysm/shared/runutil" 43 "github.com/prysmaticlabs/prysm/shared/version" 44 "github.com/sirupsen/logrus" 45 ) 46 47 var ( 48 debug = flag.Bool("debug", false, "Enable debug logging") 49 logFileName = flag.String("log-file", "", "Specify log filename, relative or absolute") 50 privateKey = flag.String("private", "", "Private key to use for peer ID") 51 discv5port = flag.Int("discv5-port", 4000, "Port to listen for discv5 connections") 52 metricsPort = flag.Int("metrics-port", 5000, "Port to listen for connections") 53 externalIP = flag.String("external-ip", "", "External IP for the bootnode") 54 forkVersion = flag.String("fork-version", "", "Fork Version that the bootnode uses") 55 genesisValidatorRoot = flag.String("genesis-root", "", "Genesis Validator Root the beacon node uses") 56 seedNode = flag.String("seed-node", "", "External node to connect to") 57 log = logrus.WithField("prefix", "bootnode") 58 discv5PeersCount = promauto.NewGauge(prometheus.GaugeOpts{ 59 Name: "bootstrap_node_discv5_peers", 60 Help: "The current number of discv5 peers of the bootstrap node", 61 }) 62 ) 63 64 type handler struct { 65 listener *discover.UDPv5 66 } 67 68 func main() { 69 flag.Parse() 70 71 if *logFileName != "" { 72 if err := logutil.ConfigurePersistentLogging(*logFileName); err != nil { 73 log.WithError(err).Error("Failed to configuring logging to disk.") 74 } 75 } 76 77 fmt.Printf("Starting bootnode. Version: %s\n", version.Version()) 78 79 if *debug { 80 logrus.SetLevel(logrus.DebugLevel) 81 82 // Geth specific logging. 83 glogger := gethlog.NewGlogHandler(gethlog.StreamHandler(os.Stderr, gethlog.TerminalFormat(false))) 84 glogger.Verbosity(gethlog.LvlTrace) 85 gethlog.Root().SetHandler(glogger) 86 87 log.Debug("Debug logging enabled.") 88 } 89 privKey := extractPrivateKey() 90 cfg := discover.Config{ 91 PrivateKey: privKey, 92 } 93 if *seedNode != "" { 94 log.Debugf("Adding seed node %s", *seedNode) 95 node, err := enode.Parse(enode.ValidSchemes, *seedNode) 96 if err != nil { 97 log.Fatal(err) 98 } 99 cfg.Bootnodes = []*enode.Node{node} 100 } 101 ipAddr, err := iputils.ExternalIP() 102 if err != nil { 103 log.Fatal(err) 104 } 105 listener := createListener(ipAddr, *discv5port, cfg) 106 107 node := listener.Self() 108 log.Infof("Running bootnode: %s", node.String()) 109 110 handler := &handler{ 111 listener: listener, 112 } 113 mux := http.NewServeMux() 114 mux.HandleFunc("/p2p", handler.httpHandler) 115 116 if err := http.ListenAndServe(fmt.Sprintf(":%d", *metricsPort), mux); err != nil { 117 log.Fatalf("Failed to start server %v", err) 118 } 119 120 // Update metrics once per slot. 121 slotDuration := time.Duration(params.BeaconConfig().SecondsPerSlot) 122 runutil.RunEvery(context.Background(), slotDuration*time.Second, func() { 123 updateMetrics(listener) 124 }) 125 126 select {} 127 } 128 129 func createListener(ipAddr string, port int, cfg discover.Config) *discover.UDPv5 { 130 ip := net.ParseIP(ipAddr) 131 if ip.To4() == nil { 132 log.Fatalf("IPV4 address not provided instead %s was provided", ipAddr) 133 } 134 var bindIP net.IP 135 var networkVersion string 136 switch { 137 case ip.To16() != nil && ip.To4() == nil: 138 bindIP = net.IPv6zero 139 networkVersion = "udp6" 140 case ip.To4() != nil: 141 bindIP = net.IPv4zero 142 networkVersion = "udp4" 143 default: 144 log.Fatalf("Valid ip address not provided instead %s was provided", ipAddr) 145 } 146 udpAddr := &net.UDPAddr{ 147 IP: bindIP, 148 Port: port, 149 } 150 conn, err := net.ListenUDP(networkVersion, udpAddr) 151 if err != nil { 152 log.Fatal(err) 153 } 154 localNode, err := createLocalNode(cfg.PrivateKey, ip, port) 155 if err != nil { 156 log.Fatal(err) 157 } 158 159 network, err := discover.ListenV5(conn, localNode, cfg) 160 if err != nil { 161 log.Fatal(err) 162 } 163 return network 164 } 165 166 func (h *handler) httpHandler(w http.ResponseWriter, _ *http.Request) { 167 w.WriteHeader(http.StatusOK) 168 write := func(w io.Writer, b []byte) { 169 if _, err := w.Write(b); err != nil { 170 log.WithError(err).Error("Failed to write to http response") 171 } 172 } 173 allNodes := h.listener.AllNodes() 174 write(w, []byte("Nodes stored in the table:\n")) 175 for i, n := range allNodes { 176 write(w, []byte(fmt.Sprintf("Node %d\n", i))) 177 write(w, []byte(n.String()+"\n")) 178 write(w, []byte("Node ID: "+n.ID().String()+"\n")) 179 write(w, []byte("IP: "+n.IP().String()+"\n")) 180 write(w, []byte(fmt.Sprintf("UDP Port: %d", n.UDP())+"\n")) 181 write(w, []byte(fmt.Sprintf("TCP Port: %d", n.UDP())+"\n\n")) 182 } 183 } 184 185 func createLocalNode(privKey *ecdsa.PrivateKey, ipAddr net.IP, port int) (*enode.LocalNode, error) { 186 db, err := enode.OpenDB("") 187 if err != nil { 188 return nil, errors.Wrap(err, "Could not open node's peer database") 189 } 190 external := net.ParseIP(*externalIP) 191 if *externalIP == "" { 192 external = ipAddr 193 } 194 fVersion := params.BeaconConfig().GenesisForkVersion 195 if *forkVersion != "" { 196 fVersion, err = hex.DecodeString(*forkVersion) 197 if err != nil { 198 return nil, errors.Wrap(err, "Could not retrieve fork version") 199 } 200 if len(fVersion) != 4 { 201 return nil, errors.Errorf("Invalid fork version size expected %d but got %d", 4, len(fVersion)) 202 } 203 } 204 genRoot := params.BeaconConfig().ZeroHash 205 if *genesisValidatorRoot != "" { 206 retRoot, err := hex.DecodeString(*genesisValidatorRoot) 207 if err != nil { 208 return nil, errors.Wrap(err, "Could not retrieve genesis validator root") 209 } 210 if len(retRoot) != 32 { 211 return nil, errors.Errorf("Invalid root size, expected 32 but got %d", len(retRoot)) 212 } 213 genRoot = bytesutil.ToBytes32(retRoot) 214 } 215 digest, err := helpers.ComputeForkDigest(fVersion, genRoot[:]) 216 if err != nil { 217 return nil, errors.Wrap(err, "Could not compute fork digest") 218 } 219 220 forkID := &pb.ENRForkID{ 221 CurrentForkDigest: digest[:], 222 NextForkVersion: fVersion, 223 NextForkEpoch: params.BeaconConfig().FarFutureEpoch, 224 } 225 forkEntry, err := forkID.MarshalSSZ() 226 if err != nil { 227 return nil, errors.Wrap(err, "Could not marshal fork id") 228 } 229 230 localNode := enode.NewLocalNode(db, privKey) 231 localNode.Set(enr.WithEntry("eth2", forkEntry)) 232 localNode.Set(enr.WithEntry("attnets", bitfield.NewBitvector64())) 233 localNode.SetFallbackIP(external) 234 localNode.SetFallbackUDP(port) 235 236 return localNode, nil 237 } 238 239 func extractPrivateKey() *ecdsa.PrivateKey { 240 var privKey *ecdsa.PrivateKey 241 if *privateKey != "" { 242 dst, err := hex.DecodeString(*privateKey) 243 if err != nil { 244 panic(err) 245 } 246 unmarshalledKey, err := crypto.UnmarshalSecp256k1PrivateKey(dst) 247 if err != nil { 248 panic(err) 249 } 250 privKey = (*ecdsa.PrivateKey)(unmarshalledKey.(*crypto.Secp256k1PrivateKey)) 251 252 } else { 253 privInterfaceKey, _, err := crypto.GenerateSecp256k1Key(rand.Reader) 254 if err != nil { 255 panic(err) 256 } 257 privKey = (*ecdsa.PrivateKey)(privInterfaceKey.(*crypto.Secp256k1PrivateKey)) 258 log.Warning("No private key was provided. Using default/random private key") 259 b, err := privInterfaceKey.Raw() 260 if err != nil { 261 panic(err) 262 } 263 log.Debugf("Private key %x", b) 264 } 265 privKey.Curve = gcrypto.S256() 266 267 return privKey 268 } 269 270 func updateMetrics(listener *discover.UDPv5) { 271 if listener != nil { 272 discv5PeersCount.Set(float64(len(listener.AllNodes()))) 273 } 274 }