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  }