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  }