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

     1  package p2p
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"sync"
     8  	"time"
     9  
    10  	//nolint:all
    11  	"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds"
    12  
    13  	libp2p "github.com/libp2p/go-libp2p"
    14  	mplex "github.com/libp2p/go-libp2p-mplex"
    15  	lconf "github.com/libp2p/go-libp2p/config"
    16  	"github.com/libp2p/go-libp2p/core/connmgr"
    17  	"github.com/libp2p/go-libp2p/core/host"
    18  	"github.com/libp2p/go-libp2p/core/metrics"
    19  	"github.com/libp2p/go-libp2p/core/network"
    20  	"github.com/libp2p/go-libp2p/core/peer"
    21  	"github.com/libp2p/go-libp2p/core/sec/insecure"
    22  	basichost "github.com/libp2p/go-libp2p/p2p/host/basic"
    23  	"github.com/libp2p/go-libp2p/p2p/muxer/yamux"
    24  	"github.com/libp2p/go-libp2p/p2p/protocol/ping"
    25  	"github.com/libp2p/go-libp2p/p2p/security/noise"
    26  	tls "github.com/libp2p/go-libp2p/p2p/security/tls"
    27  	"github.com/libp2p/go-libp2p/p2p/transport/tcp"
    28  	ma "github.com/multiformats/go-multiaddr"
    29  	madns "github.com/multiformats/go-multiaddr-dns"
    30  
    31  	"github.com/ethereum/go-ethereum/log"
    32  
    33  	"github.com/ethereum-optimism/optimism/op-node/p2p/gating"
    34  	"github.com/ethereum-optimism/optimism/op-node/p2p/store"
    35  	"github.com/ethereum-optimism/optimism/op-service/clock"
    36  )
    37  
    38  const (
    39  	staticPeerTag = "static"
    40  )
    41  
    42  type ExtraHostFeatures interface {
    43  	host.Host
    44  	ConnectionGater() gating.BlockingConnectionGater
    45  	ConnectionManager() connmgr.ConnManager
    46  }
    47  
    48  type extraHost struct {
    49  	host.Host
    50  	gater   gating.BlockingConnectionGater
    51  	connMgr connmgr.ConnManager
    52  	log     log.Logger
    53  
    54  	staticPeers []*peer.AddrInfo
    55  
    56  	pinging *PingService
    57  
    58  	quitC chan struct{}
    59  }
    60  
    61  func (e *extraHost) ConnectionGater() gating.BlockingConnectionGater {
    62  	return e.gater
    63  }
    64  
    65  func (e *extraHost) ConnectionManager() connmgr.ConnManager {
    66  	return e.connMgr
    67  }
    68  
    69  func (e *extraHost) Close() error {
    70  	close(e.quitC)
    71  	if e.pinging != nil {
    72  		e.pinging.Close()
    73  	}
    74  	return e.Host.Close()
    75  }
    76  
    77  func (e *extraHost) initStaticPeers() {
    78  	for _, addr := range e.staticPeers {
    79  		e.Peerstore().AddAddrs(addr.ID, addr.Addrs, time.Hour*24*7)
    80  		// We protect the peer, so the connection manager doesn't decide to prune it.
    81  		// We tag it with "static" so other protects/unprotects with different tags don't affect this protection.
    82  		e.connMgr.Protect(addr.ID, staticPeerTag)
    83  		// Try to dial the node in the background
    84  		go func(addr *peer.AddrInfo) {
    85  			ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
    86  			defer cancel()
    87  			if err := e.dialStaticPeer(ctx, addr); err != nil {
    88  				e.log.Warn("error dialing static peer", "peer", addr.ID, "err", err)
    89  			}
    90  		}(addr)
    91  	}
    92  }
    93  
    94  func (e *extraHost) dialStaticPeer(ctx context.Context, addr *peer.AddrInfo) error {
    95  	e.log.Info("dialing static peer", "peer", addr.ID, "addrs", addr.Addrs)
    96  	if _, err := e.Network().DialPeer(ctx, addr.ID); err != nil {
    97  		return err
    98  	}
    99  	return nil
   100  }
   101  
   102  func (e *extraHost) monitorStaticPeers() {
   103  	tick := time.NewTicker(time.Minute)
   104  	defer tick.Stop()
   105  
   106  	for {
   107  		select {
   108  		case <-tick.C:
   109  			ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
   110  			var wg sync.WaitGroup
   111  
   112  			e.log.Debug("polling static peers", "peers", len(e.staticPeers))
   113  			for _, addr := range e.staticPeers {
   114  				connectedness := e.Network().Connectedness(addr.ID)
   115  				e.log.Trace("static peer connectedness", "peer", addr.ID, "connectedness", connectedness)
   116  
   117  				if connectedness == network.Connected {
   118  					continue
   119  				}
   120  
   121  				wg.Add(1)
   122  				go func(addr *peer.AddrInfo) {
   123  					e.log.Warn("static peer disconnected, reconnecting", "peer", addr.ID)
   124  					if err := e.dialStaticPeer(ctx, addr); err != nil {
   125  						e.log.Warn("error reconnecting to static peer", "peer", addr.ID, "err", err)
   126  					}
   127  					wg.Done()
   128  				}(addr)
   129  			}
   130  
   131  			wg.Wait()
   132  			cancel()
   133  		case <-e.quitC:
   134  			return
   135  		}
   136  	}
   137  }
   138  
   139  var _ ExtraHostFeatures = (*extraHost)(nil)
   140  
   141  func (conf *Config) Host(log log.Logger, reporter metrics.Reporter, metrics HostMetrics) (host.Host, error) {
   142  	if conf.DisableP2P {
   143  		return nil, nil
   144  	}
   145  	pub := conf.Priv.GetPublic()
   146  	pid, err := peer.IDFromPublicKey(pub)
   147  	if err != nil {
   148  		return nil, fmt.Errorf("failed to derive pubkey from network priv key: %w", err)
   149  	}
   150  
   151  	basePs, err := pstoreds.NewPeerstore(context.Background(), conf.Store, pstoreds.DefaultOpts())
   152  	if err != nil {
   153  		return nil, fmt.Errorf("failed to open peerstore: %w", err)
   154  	}
   155  
   156  	peerScoreParams := conf.PeerScoringParams()
   157  	var scoreRetention time.Duration
   158  	if peerScoreParams != nil {
   159  		// Use the same retention period as gossip will if available
   160  		scoreRetention = peerScoreParams.PeerScoring.RetainScore
   161  	} else {
   162  		// Disable score GC if peer scoring is disabled
   163  		scoreRetention = 0
   164  	}
   165  	ps, err := store.NewExtendedPeerstore(context.Background(), log, clock.SystemClock, basePs, conf.Store, scoreRetention)
   166  	if err != nil {
   167  		return nil, fmt.Errorf("failed to open extended peerstore: %w", err)
   168  	}
   169  
   170  	if err := ps.AddPrivKey(pid, conf.Priv); err != nil {
   171  		return nil, fmt.Errorf("failed to set up peerstore with priv key: %w", err)
   172  	}
   173  	if err := ps.AddPubKey(pid, pub); err != nil {
   174  		return nil, fmt.Errorf("failed to set up peerstore with pub key: %w", err)
   175  	}
   176  
   177  	var connGtr gating.BlockingConnectionGater
   178  	connGtr, err = gating.NewBlockingConnectionGater(conf.Store)
   179  	if err != nil {
   180  		return nil, fmt.Errorf("failed to open connection gater: %w", err)
   181  	}
   182  	connGtr = gating.AddBanExpiry(connGtr, ps, log, clock.SystemClock, metrics)
   183  	connGtr = gating.AddMetering(connGtr, metrics)
   184  
   185  	connMngr, err := DefaultConnManager(conf)
   186  	if err != nil {
   187  		return nil, fmt.Errorf("failed to open connection manager: %w", err)
   188  	}
   189  
   190  	listenAddr, err := addrFromIPAndPort(conf.ListenIP, conf.ListenTCPPort)
   191  	if err != nil {
   192  		return nil, fmt.Errorf("failed to make listen addr: %w", err)
   193  	}
   194  	tcpTransport := libp2p.Transport(
   195  		tcp.NewTCPTransport,
   196  		tcp.WithConnectionTimeout(time.Minute*60)) // break unused connections
   197  	// TODO: technically we can also run the node on websocket and QUIC transports. Maybe in the future?
   198  
   199  	var nat lconf.NATManagerC // disabled if nil
   200  	if conf.NAT {
   201  		nat = basichost.NewNATManager
   202  	}
   203  
   204  	opts := []libp2p.Option{
   205  		libp2p.Identity(conf.Priv),
   206  		// Explicitly set the user-agent, so we can differentiate from other Go libp2p users.
   207  		libp2p.UserAgent(conf.UserAgent),
   208  		tcpTransport,
   209  		libp2p.WithDialTimeout(conf.TimeoutDial),
   210  		// No relay services, direct connections between peers only.
   211  		libp2p.DisableRelay(),
   212  		// host will start and listen to network directly after construction from config.
   213  		libp2p.ListenAddrs(listenAddr),
   214  		libp2p.ConnectionGater(connGtr),
   215  		libp2p.ConnectionManager(connMngr),
   216  		//libp2p.ResourceManager(nil), // TODO use resource manager interface to manage resources per peer better.
   217  		libp2p.NATManager(nat),
   218  		libp2p.Peerstore(ps),
   219  		libp2p.BandwidthReporter(reporter), // may be nil if disabled
   220  		libp2p.MultiaddrResolver(madns.DefaultResolver),
   221  		// Ping is a small built-in libp2p protocol that helps us check/debug latency between peers.
   222  		libp2p.Ping(true),
   223  		// Help peers with their NAT reachability status, but throttle to avoid too much work.
   224  		libp2p.EnableNATService(),
   225  		libp2p.AutoNATServiceRateLimit(10, 5, time.Second*60),
   226  	}
   227  	opts = append(opts, conf.HostMux...)
   228  	if conf.NoTransportSecurity {
   229  		opts = append(opts, libp2p.Security(insecure.ID, insecure.NewWithIdentity))
   230  	} else {
   231  		opts = append(opts, conf.HostSecurity...)
   232  	}
   233  	h, err := libp2p.New(opts...)
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  
   238  	staticPeers := make([]*peer.AddrInfo, 0, len(conf.StaticPeers))
   239  	for _, peerAddr := range conf.StaticPeers {
   240  		addr, err := peer.AddrInfoFromP2pAddr(peerAddr)
   241  		if err != nil {
   242  			return nil, fmt.Errorf("bad peer address: %w", err)
   243  		}
   244  		if addr.ID == h.ID() {
   245  			log.Info("Static-peer list contains address of local peer, ignoring the address.", "peer_id", addr.ID, "addrs", addr.Addrs)
   246  			continue
   247  		}
   248  		staticPeers = append(staticPeers, addr)
   249  	}
   250  
   251  	out := &extraHost{
   252  		Host:        h,
   253  		connMgr:     connMngr,
   254  		log:         log,
   255  		staticPeers: staticPeers,
   256  		quitC:       make(chan struct{}),
   257  	}
   258  
   259  	if conf.EnablePingService {
   260  		out.pinging = NewPingService(log,
   261  			func(ctx context.Context, peerID peer.ID) <-chan ping.Result {
   262  				return ping.Ping(ctx, h, peerID)
   263  			}, h.Network().Peers, clock.SystemClock)
   264  	}
   265  
   266  	out.initStaticPeers()
   267  	if len(conf.StaticPeers) > 0 {
   268  		go out.monitorStaticPeers()
   269  	}
   270  
   271  	out.gater = connGtr
   272  	return out, nil
   273  }
   274  
   275  // Creates a multi-addr to bind to. Does not contain a PeerID component (required for usage by external peers)
   276  func addrFromIPAndPort(ip net.IP, port uint16) (ma.Multiaddr, error) {
   277  	ipScheme := "ip4"
   278  	if ip4 := ip.To4(); ip4 == nil {
   279  		ipScheme = "ip6"
   280  	} else {
   281  		ip = ip4
   282  	}
   283  	return ma.NewMultiaddr(fmt.Sprintf("/%s/%s/tcp/%d", ipScheme, ip.String(), port))
   284  }
   285  
   286  func YamuxC() libp2p.Option {
   287  	return libp2p.Muxer("/yamux/1.0.0", yamux.DefaultTransport)
   288  }
   289  
   290  func MplexC() libp2p.Option {
   291  	return libp2p.Muxer("/mplex/6.7.0", mplex.DefaultTransport)
   292  }
   293  
   294  func NoiseC() libp2p.Option {
   295  	return libp2p.Security(noise.ID, noise.New)
   296  }
   297  
   298  func TlsC() libp2p.Option {
   299  	return libp2p.Security(tls.ID, tls.New)
   300  }