github.com/ethereum-optimism/optimism@v1.7.2/op-node/p2p/cli/load_config.go (about)

     1  package cli
     2  
     3  import (
     4  	"crypto/rand"
     5  	"encoding/hex"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"net"
    10  	"os"
    11  	"strings"
    12  
    13  	"github.com/ethereum-optimism/optimism/op-node/rollup"
    14  	ds "github.com/ipfs/go-datastore"
    15  	"github.com/ipfs/go-datastore/sync"
    16  	leveldb "github.com/ipfs/go-ds-leveldb"
    17  	"github.com/libp2p/go-libp2p/core/crypto"
    18  	"github.com/multiformats/go-multiaddr"
    19  
    20  	"github.com/ethereum-optimism/optimism/op-node/flags"
    21  	"github.com/ethereum-optimism/optimism/op-node/p2p"
    22  
    23  	"github.com/urfave/cli/v2"
    24  
    25  	"github.com/ethereum/go-ethereum/p2p/enode"
    26  	"github.com/ethereum/go-ethereum/p2p/netutil"
    27  )
    28  
    29  func NewConfig(ctx *cli.Context, rollupCfg *rollup.Config) (*p2p.Config, error) {
    30  	conf := &p2p.Config{}
    31  
    32  	if ctx.Bool(flags.DisableP2PName) {
    33  		conf.DisableP2P = true
    34  		return conf, nil
    35  	}
    36  
    37  	p, err := loadNetworkPrivKey(ctx)
    38  	if err != nil {
    39  		return nil, fmt.Errorf("failed to load p2p priv key: %w", err)
    40  	}
    41  	conf.Priv = p
    42  
    43  	if err := loadListenOpts(conf, ctx); err != nil {
    44  		return nil, fmt.Errorf("failed to load p2p listen options: %w", err)
    45  	}
    46  
    47  	if err := loadDiscoveryOpts(conf, ctx); err != nil {
    48  		return nil, fmt.Errorf("failed to load p2p discovery options: %w", err)
    49  	}
    50  
    51  	if err := loadLibp2pOpts(conf, ctx); err != nil {
    52  		return nil, fmt.Errorf("failed to load p2p options: %w", err)
    53  	}
    54  
    55  	if err := loadGossipOptions(conf, ctx); err != nil {
    56  		return nil, fmt.Errorf("failed to load p2p gossip options: %w", err)
    57  	}
    58  
    59  	if err := loadScoringParams(conf, ctx, rollupCfg); err != nil {
    60  		return nil, fmt.Errorf("failed to load p2p peer scoring options: %w", err)
    61  	}
    62  
    63  	if err := loadBanningOptions(conf, ctx); err != nil {
    64  		return nil, fmt.Errorf("failed to load banning option: %w", err)
    65  	}
    66  
    67  	conf.EnableReqRespSync = ctx.Bool(flags.SyncReqRespName)
    68  	conf.EnablePingService = ctx.Bool(flags.P2PPingName)
    69  
    70  	return conf, nil
    71  }
    72  
    73  func validatePort(p uint) (uint16, error) {
    74  	if p == 0 {
    75  		return 0, nil
    76  	}
    77  	if p >= (1 << 16) {
    78  		return 0, fmt.Errorf("port out of range: %d", p)
    79  	}
    80  	if p < 1024 {
    81  		return 0, fmt.Errorf("port is reserved for system: %d", p)
    82  	}
    83  	return uint16(p), nil
    84  }
    85  
    86  // loadScoringParams loads the peer scoring options from the CLI context.
    87  func loadScoringParams(conf *p2p.Config, ctx *cli.Context, rollupCfg *rollup.Config) error {
    88  	scoringLevel := ctx.String(flags.ScoringName)
    89  	// Check old names for backwards compatibility
    90  	if scoringLevel == "" {
    91  		scoringLevel = ctx.String(flags.PeerScoringName)
    92  	}
    93  	if scoringLevel == "" {
    94  		scoringLevel = ctx.String(flags.TopicScoringName)
    95  	}
    96  	if scoringLevel != "" {
    97  		params, err := p2p.GetScoringParams(scoringLevel, rollupCfg)
    98  		if err != nil {
    99  			return err
   100  		}
   101  		conf.ScoringParams = params
   102  	}
   103  
   104  	return nil
   105  }
   106  
   107  // loadBanningOptions loads whether or not to ban peers from the CLI context.
   108  func loadBanningOptions(conf *p2p.Config, ctx *cli.Context) error {
   109  	conf.BanningEnabled = ctx.Bool(flags.BanningName)
   110  	conf.BanningThreshold = ctx.Float64(flags.BanningThresholdName)
   111  	conf.BanningDuration = ctx.Duration(flags.BanningDurationName)
   112  	return nil
   113  }
   114  
   115  func loadListenOpts(conf *p2p.Config, ctx *cli.Context) error {
   116  	listenIP := ctx.String(flags.ListenIPName)
   117  	if listenIP != "" { // optional
   118  		conf.ListenIP = net.ParseIP(listenIP)
   119  		if conf.ListenIP == nil {
   120  			return fmt.Errorf("failed to parse IP %q", listenIP)
   121  		}
   122  	}
   123  	var err error
   124  	conf.ListenTCPPort, err = validatePort(ctx.Uint(flags.ListenTCPPortName))
   125  	if err != nil {
   126  		return fmt.Errorf("bad listen TCP port: %w", err)
   127  	}
   128  	conf.ListenUDPPort, err = validatePort(ctx.Uint(flags.ListenUDPPortName))
   129  	if err != nil {
   130  		return fmt.Errorf("bad listen UDP port: %w", err)
   131  	}
   132  	return nil
   133  }
   134  
   135  func loadDiscoveryOpts(conf *p2p.Config, ctx *cli.Context) error {
   136  	if ctx.Bool(flags.NoDiscoveryName) {
   137  		conf.NoDiscovery = true
   138  	}
   139  
   140  	var err error
   141  	conf.AdvertiseTCPPort, err = validatePort(ctx.Uint(flags.AdvertiseTCPPortName))
   142  	if err != nil {
   143  		return fmt.Errorf("bad advertised TCP port: %w", err)
   144  	}
   145  	conf.AdvertiseUDPPort, err = validatePort(ctx.Uint(flags.AdvertiseUDPPortName))
   146  	if err != nil {
   147  		return fmt.Errorf("bad advertised UDP port: %w", err)
   148  	}
   149  	adIP := ctx.String(flags.AdvertiseIPName)
   150  	if adIP != "" { // optional
   151  		ips, err := net.LookupIP(adIP)
   152  		if err != nil {
   153  			return fmt.Errorf("failed to lookup IP of %q to advertise in ENR: %w", adIP, err)
   154  		}
   155  		// Find the first v4 IP it resolves to
   156  		for _, ip := range ips {
   157  			if ipv4 := ip.To4(); ipv4 != nil {
   158  				conf.AdvertiseIP = ipv4
   159  				break
   160  			}
   161  		}
   162  		if conf.AdvertiseIP == nil {
   163  			return fmt.Errorf("failed to parse IP %q", adIP)
   164  		}
   165  	}
   166  
   167  	dbPath := ctx.String(flags.DiscoveryPathName)
   168  	if dbPath == "" {
   169  		dbPath = "opnode_discovery_db"
   170  	}
   171  	if dbPath == "memory" {
   172  		dbPath = ""
   173  	}
   174  	conf.DiscoveryDB, err = enode.OpenDB(dbPath)
   175  	if err != nil {
   176  		return fmt.Errorf("failed to open discovery db: %w", err)
   177  	}
   178  
   179  	bootnodes := make([]*enode.Node, 0)
   180  	records := strings.Split(ctx.String(flags.BootnodesName), ",")
   181  	for i, recordB64 := range records {
   182  		recordB64 = strings.TrimSpace(recordB64)
   183  		if recordB64 == "" { // ignore empty records
   184  			continue
   185  		}
   186  		nodeRecord, err := enode.Parse(enode.ValidSchemes, recordB64)
   187  		if err != nil {
   188  			return fmt.Errorf("bootnode record %d (of %d) is invalid: %q err: %w", i, len(records), recordB64, err)
   189  		}
   190  		bootnodes = append(bootnodes, nodeRecord)
   191  	}
   192  	if len(bootnodes) > 0 {
   193  		conf.Bootnodes = bootnodes
   194  	} else {
   195  		conf.Bootnodes = p2p.DefaultBootnodes
   196  	}
   197  
   198  	if ctx.IsSet(flags.NetRestrictName) {
   199  		netRestrict, err := netutil.ParseNetlist(ctx.String(flags.NetRestrictName))
   200  		if err != nil {
   201  			return fmt.Errorf("failed to parse net list: %w", err)
   202  		}
   203  		conf.NetRestrict = netRestrict
   204  	}
   205  
   206  	return nil
   207  }
   208  
   209  func loadLibp2pOpts(conf *p2p.Config, ctx *cli.Context) error {
   210  	addrs := strings.Split(ctx.String(flags.StaticPeersName), ",")
   211  	for i, addr := range addrs {
   212  		addr = strings.TrimSpace(addr)
   213  		if addr == "" {
   214  			continue // skip empty multi addrs
   215  		}
   216  		a, err := multiaddr.NewMultiaddr(addr)
   217  		if err != nil {
   218  			return fmt.Errorf("failed to parse multi addr of static peer %d (out of %d): %q err: %w", i, len(addrs), addr, err)
   219  		}
   220  		conf.StaticPeers = append(conf.StaticPeers, a)
   221  	}
   222  
   223  	for _, v := range strings.Split(ctx.String(flags.HostMuxName), ",") {
   224  		v = strings.ToLower(strings.TrimSpace(v))
   225  		switch v {
   226  		case "yamux":
   227  			conf.HostMux = append(conf.HostMux, p2p.YamuxC())
   228  		case "mplex":
   229  			conf.HostMux = append(conf.HostMux, p2p.MplexC())
   230  		default:
   231  			return fmt.Errorf("could not recognize mux %s", v)
   232  		}
   233  	}
   234  
   235  	secArr := strings.Split(ctx.String(flags.HostSecurityName), ",")
   236  	for _, v := range secArr {
   237  		v = strings.ToLower(strings.TrimSpace(v))
   238  		switch v {
   239  		case "none": // no security, for debugging etc.
   240  			if len(conf.HostSecurity) > 0 || len(secArr) > 1 {
   241  				return errors.New("cannot mix secure transport protocols with no-security")
   242  			}
   243  			conf.NoTransportSecurity = true
   244  		case "noise":
   245  			conf.HostSecurity = append(conf.HostSecurity, p2p.NoiseC())
   246  		case "tls":
   247  			conf.HostSecurity = append(conf.HostSecurity, p2p.TlsC())
   248  		default:
   249  			return fmt.Errorf("could not recognize security %s", v)
   250  		}
   251  	}
   252  
   253  	conf.PeersLo = ctx.Uint(flags.PeersLoName)
   254  	conf.PeersHi = ctx.Uint(flags.PeersHiName)
   255  	conf.PeersGrace = ctx.Duration(flags.PeersGraceName)
   256  	conf.NAT = ctx.Bool(flags.NATName)
   257  	conf.UserAgent = ctx.String(flags.UserAgentName)
   258  	conf.TimeoutNegotiation = ctx.Duration(flags.TimeoutNegotiationName)
   259  	conf.TimeoutAccept = ctx.Duration(flags.TimeoutAcceptName)
   260  	conf.TimeoutDial = ctx.Duration(flags.TimeoutDialName)
   261  
   262  	peerstorePath := ctx.String(flags.PeerstorePathName)
   263  	if peerstorePath == "" {
   264  		return errors.New("peerstore path must be specified, use 'memory' to explicitly not persist peer records")
   265  	}
   266  
   267  	var err error
   268  	var store ds.Batching
   269  	if peerstorePath == "memory" {
   270  		store = sync.MutexWrap(ds.NewMapDatastore())
   271  	} else {
   272  		store, err = leveldb.NewDatastore(peerstorePath, nil) // default leveldb options are fine
   273  		if err != nil {
   274  			return fmt.Errorf("failed to open leveldb db for peerstore: %w", err)
   275  		}
   276  	}
   277  	conf.Store = store
   278  
   279  	return nil
   280  }
   281  
   282  func loadNetworkPrivKey(ctx *cli.Context) (*crypto.Secp256k1PrivateKey, error) {
   283  	raw := ctx.String(flags.P2PPrivRawName)
   284  	if raw != "" {
   285  		return parsePriv(raw)
   286  	}
   287  	keyPath := ctx.String(flags.P2PPrivPathName)
   288  	if keyPath == "" {
   289  		return nil, errors.New("no p2p private key path specified, cannot auto-generate key without path")
   290  	}
   291  	f, err := os.OpenFile(keyPath, os.O_RDONLY, 0600)
   292  	if os.IsNotExist(err) {
   293  		p, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
   294  		if err != nil {
   295  			return nil, fmt.Errorf("failed to generate new p2p priv key: %w", err)
   296  		}
   297  		b, err := p.Raw()
   298  		if err != nil {
   299  			return nil, fmt.Errorf("failed to encode new p2p priv key: %w", err)
   300  		}
   301  		f, err := os.OpenFile(keyPath, os.O_CREATE|os.O_WRONLY, 0600)
   302  		if err != nil {
   303  			return nil, fmt.Errorf("failed to store new p2p priv key: %w", err)
   304  		}
   305  		defer f.Close()
   306  		if _, err := f.WriteString(hex.EncodeToString(b)); err != nil {
   307  			return nil, fmt.Errorf("failed to write new p2p priv key: %w", err)
   308  		}
   309  		return (p).(*crypto.Secp256k1PrivateKey), nil
   310  	} else {
   311  		defer f.Close()
   312  		data, err := io.ReadAll(f)
   313  		if err != nil {
   314  			return nil, fmt.Errorf("failed to read priv key file: %w", err)
   315  		}
   316  		return parsePriv(strings.TrimSpace(string(data)))
   317  	}
   318  }
   319  
   320  func parsePriv(data string) (*crypto.Secp256k1PrivateKey, error) {
   321  	if len(data) > 2 && data[:2] == "0x" {
   322  		data = data[2:]
   323  	}
   324  	b, err := hex.DecodeString(data)
   325  	if err != nil {
   326  		return nil, errors.New("p2p priv key is not formatted in hex chars")
   327  	}
   328  	p, err := crypto.UnmarshalSecp256k1PrivateKey(b)
   329  	if err != nil {
   330  		// avoid logging the priv key in the error, but hint at likely input length problem
   331  		return nil, fmt.Errorf("failed to parse priv key from %d bytes", len(b))
   332  	}
   333  	return (p).(*crypto.Secp256k1PrivateKey), nil
   334  }
   335  
   336  func loadGossipOptions(conf *p2p.Config, ctx *cli.Context) error {
   337  	conf.MeshD = ctx.Int(flags.GossipMeshDName)
   338  	conf.MeshDLo = ctx.Int(flags.GossipMeshDloName)
   339  	conf.MeshDHi = ctx.Int(flags.GossipMeshDhiName)
   340  	conf.MeshDLazy = ctx.Int(flags.GossipMeshDlazyName)
   341  	conf.FloodPublish = ctx.Bool(flags.GossipFloodPublishName)
   342  	return nil
   343  }