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

     1  package p2p
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/hashicorp/go-multierror"
    12  	pubsub "github.com/libp2p/go-libp2p-pubsub"
    13  	"github.com/libp2p/go-libp2p/core/connmgr"
    14  	"github.com/libp2p/go-libp2p/core/host"
    15  	p2pmetrics "github.com/libp2p/go-libp2p/core/metrics"
    16  	"github.com/libp2p/go-libp2p/core/network"
    17  	"github.com/libp2p/go-libp2p/core/peer"
    18  	ma "github.com/multiformats/go-multiaddr"
    19  	manet "github.com/multiformats/go-multiaddr/net"
    20  
    21  	"github.com/ethereum/go-ethereum/log"
    22  	"github.com/ethereum/go-ethereum/p2p/discover"
    23  	"github.com/ethereum/go-ethereum/p2p/enode"
    24  
    25  	"github.com/ethereum-optimism/optimism/op-node/metrics"
    26  	"github.com/ethereum-optimism/optimism/op-node/p2p/gating"
    27  	"github.com/ethereum-optimism/optimism/op-node/p2p/monitor"
    28  	"github.com/ethereum-optimism/optimism/op-node/p2p/store"
    29  	"github.com/ethereum-optimism/optimism/op-node/rollup"
    30  	"github.com/ethereum-optimism/optimism/op-service/clock"
    31  	"github.com/ethereum-optimism/optimism/op-service/eth"
    32  )
    33  
    34  // NodeP2P is a p2p node, which can be used to gossip messages.
    35  type NodeP2P struct {
    36  	host        host.Host                      // p2p host (optional, may be nil)
    37  	gater       gating.BlockingConnectionGater // p2p gater, to ban/unban peers with, may be nil even with p2p enabled
    38  	scorer      Scorer                         // writes score-updates to the peerstore and keeps metrics of score changes
    39  	connMgr     connmgr.ConnManager            // p2p conn manager, to keep a reliable number of peers, may be nil even with p2p enabled
    40  	peerMonitor *monitor.PeerMonitor           // peer monitor to disconnect bad peers, may be nil even with p2p enabled
    41  	store       store.ExtendedPeerstore        // peerstore of host, with extra bindings for scoring and banning
    42  	appScorer   ApplicationScorer
    43  	log         log.Logger
    44  	// the below components are all optional, and may be nil. They require the host to not be nil.
    45  	dv5Local *enode.LocalNode // p2p discovery identity
    46  	dv5Udp   *discover.UDPv5  // p2p discovery service
    47  	gs       *pubsub.PubSub   // p2p gossip router
    48  	gsOut    GossipOut        // p2p gossip application interface for publishing
    49  	syncCl   *SyncClient
    50  	syncSrv  *ReqRespServer
    51  }
    52  
    53  // NewNodeP2P creates a new p2p node, and returns a reference to it. If the p2p is disabled, it returns nil.
    54  // If metrics are configured, a bandwidth monitor will be spawned in a goroutine.
    55  func NewNodeP2P(resourcesCtx context.Context, rollupCfg *rollup.Config, log log.Logger, setup SetupP2P, gossipIn GossipIn, l2Chain L2Chain, runCfg GossipRuntimeConfig, metrics metrics.Metricer, elSyncEnabled bool) (*NodeP2P, error) {
    56  	if setup == nil {
    57  		return nil, errors.New("p2p node cannot be created without setup")
    58  	}
    59  	var n NodeP2P
    60  	if err := n.init(resourcesCtx, rollupCfg, log, setup, gossipIn, l2Chain, runCfg, metrics, elSyncEnabled); err != nil {
    61  		closeErr := n.Close()
    62  		if closeErr != nil {
    63  			log.Error("failed to close p2p after starting with err", "closeErr", closeErr, "err", err)
    64  		}
    65  		return nil, err
    66  	}
    67  	if n.host == nil {
    68  		return nil, nil
    69  	}
    70  	return &n, nil
    71  }
    72  
    73  func (n *NodeP2P) init(resourcesCtx context.Context, rollupCfg *rollup.Config, log log.Logger, setup SetupP2P, gossipIn GossipIn, l2Chain L2Chain, runCfg GossipRuntimeConfig, metrics metrics.Metricer, elSyncEnabled bool) error {
    74  	bwc := p2pmetrics.NewBandwidthCounter()
    75  
    76  	n.log = log
    77  
    78  	var err error
    79  	// nil if disabled.
    80  	n.host, err = setup.Host(log, bwc, metrics)
    81  	if err != nil {
    82  		if n.dv5Udp != nil {
    83  			n.dv5Udp.Close()
    84  		}
    85  		return fmt.Errorf("failed to start p2p host: %w", err)
    86  	}
    87  
    88  	// TODO(CLI-4016): host is not optional, NodeP2P as a whole is. This if statement is wrong
    89  	if n.host != nil {
    90  		// Enable extra features, if any. During testing we don't setup the most advanced host all the time.
    91  		if extra, ok := n.host.(ExtraHostFeatures); ok {
    92  			n.gater = extra.ConnectionGater()
    93  			n.connMgr = extra.ConnectionManager()
    94  		}
    95  		eps, ok := n.host.Peerstore().(store.ExtendedPeerstore)
    96  		if !ok {
    97  			return fmt.Errorf("cannot init without extended peerstore: %w", err)
    98  		}
    99  		n.store = eps
   100  		scoreParams := setup.PeerScoringParams()
   101  
   102  		if scoreParams != nil {
   103  			n.appScorer = newPeerApplicationScorer(resourcesCtx, log, clock.SystemClock, &scoreParams.ApplicationScoring, eps, n.host.Network().Peers)
   104  		} else {
   105  			n.appScorer = &NoopApplicationScorer{}
   106  		}
   107  		// Activate the P2P req-resp sync if enabled by feature-flag.
   108  		if setup.ReqRespSyncEnabled() && !elSyncEnabled {
   109  			n.syncCl = NewSyncClient(log, rollupCfg, n.host.NewStream, gossipIn.OnUnsafeL2Payload, metrics, n.appScorer)
   110  			n.host.Network().Notify(&network.NotifyBundle{
   111  				ConnectedF: func(nw network.Network, conn network.Conn) {
   112  					n.syncCl.AddPeer(conn.RemotePeer())
   113  				},
   114  				DisconnectedF: func(nw network.Network, conn network.Conn) {
   115  					// only when no connection is available, we can remove the peer
   116  					if nw.Connectedness(conn.RemotePeer()) == network.NotConnected {
   117  						n.syncCl.RemovePeer(conn.RemotePeer())
   118  					}
   119  				},
   120  			})
   121  			n.syncCl.Start()
   122  			// the host may already be connected to peers, add them all to the sync client
   123  			for _, peerID := range n.host.Network().Peers() {
   124  				n.syncCl.AddPeer(peerID)
   125  			}
   126  			if l2Chain != nil { // Only enable serving side of req-resp sync if we have a data-source, to make minimal P2P testing easy
   127  				n.syncSrv = NewReqRespServer(rollupCfg, l2Chain, metrics)
   128  				// register the sync protocol with libp2p host
   129  				payloadByNumber := MakeStreamHandler(resourcesCtx, log.New("serve", "payloads_by_number"), n.syncSrv.HandleSyncRequest)
   130  				n.host.SetStreamHandler(PayloadByNumberProtocolID(rollupCfg.L2ChainID), payloadByNumber)
   131  			}
   132  		}
   133  		n.scorer = NewScorer(rollupCfg, eps, metrics, n.appScorer, log)
   134  		// notify of any new connections/streams/etc.
   135  		n.host.Network().Notify(NewNetworkNotifier(log, metrics))
   136  		// note: the IDDelta functionality was removed from libP2P, and no longer needs to be explicitly disabled.
   137  		n.gs, err = NewGossipSub(resourcesCtx, n.host, rollupCfg, setup, n.scorer, metrics, log)
   138  		if err != nil {
   139  			return fmt.Errorf("failed to start gossipsub router: %w", err)
   140  		}
   141  		n.gsOut, err = JoinGossip(n.host.ID(), n.gs, log, rollupCfg, runCfg, gossipIn)
   142  		if err != nil {
   143  			return fmt.Errorf("failed to join blocks gossip topic: %w", err)
   144  		}
   145  		log.Info("started p2p host", "addrs", n.host.Addrs(), "peerID", n.host.ID().String())
   146  
   147  		tcpPort, err := FindActiveTCPPort(n.host)
   148  		if err != nil {
   149  			log.Warn("failed to find what TCP port p2p is binded to", "err", err)
   150  		}
   151  
   152  		// All nil if disabled.
   153  		n.dv5Local, n.dv5Udp, err = setup.Discovery(log.New("p2p", "discv5"), rollupCfg, tcpPort)
   154  		if err != nil {
   155  			return fmt.Errorf("failed to start discv5: %w", err)
   156  		}
   157  
   158  		if metrics != nil {
   159  			go metrics.RecordBandwidth(resourcesCtx, bwc)
   160  		}
   161  
   162  		if setup.BanPeers() {
   163  			n.peerMonitor = monitor.NewPeerMonitor(resourcesCtx, log, clock.SystemClock, n, setup.BanThreshold(), setup.BanDuration())
   164  			n.peerMonitor.Start()
   165  		}
   166  		n.appScorer.start()
   167  	}
   168  	return nil
   169  }
   170  
   171  func (n *NodeP2P) AltSyncEnabled() bool {
   172  	return n.syncCl != nil
   173  }
   174  
   175  func (n *NodeP2P) RequestL2Range(ctx context.Context, start, end eth.L2BlockRef) error {
   176  	if !n.AltSyncEnabled() {
   177  		return fmt.Errorf("cannot request range %s - %s, req-resp sync is not enabled", start, end)
   178  	}
   179  	return n.syncCl.RequestL2Range(ctx, start, end)
   180  }
   181  
   182  func (n *NodeP2P) Host() host.Host {
   183  	return n.host
   184  }
   185  
   186  func (n *NodeP2P) Dv5Local() *enode.LocalNode {
   187  	return n.dv5Local
   188  }
   189  
   190  func (n *NodeP2P) Dv5Udp() *discover.UDPv5 {
   191  	return n.dv5Udp
   192  }
   193  
   194  func (n *NodeP2P) GossipSub() *pubsub.PubSub {
   195  	return n.gs
   196  }
   197  
   198  func (n *NodeP2P) GossipOut() GossipOut {
   199  	return n.gsOut
   200  }
   201  
   202  func (n *NodeP2P) ConnectionGater() gating.BlockingConnectionGater {
   203  	return n.gater
   204  }
   205  
   206  func (n *NodeP2P) ConnectionManager() connmgr.ConnManager {
   207  	return n.connMgr
   208  }
   209  
   210  func (n *NodeP2P) Peers() []peer.ID {
   211  	return n.host.Network().Peers()
   212  }
   213  
   214  func (n *NodeP2P) GetPeerScore(id peer.ID) (float64, error) {
   215  	return n.store.GetPeerScore(id)
   216  }
   217  
   218  func (n *NodeP2P) IsStatic(id peer.ID) bool {
   219  	return n.connMgr != nil && n.connMgr.IsProtected(id, staticPeerTag)
   220  }
   221  
   222  func (n *NodeP2P) BanPeer(id peer.ID, expiration time.Time) error {
   223  	if err := n.store.SetPeerBanExpiration(id, expiration); err != nil {
   224  		return fmt.Errorf("failed to set peer ban expiry: %w", err)
   225  	}
   226  	if err := n.host.Network().ClosePeer(id); err != nil {
   227  		return fmt.Errorf("failed to close peer connection: %w", err)
   228  	}
   229  	return nil
   230  }
   231  
   232  func (n *NodeP2P) BanIP(ip net.IP, expiration time.Time) error {
   233  	if err := n.store.SetIPBanExpiration(ip, expiration); err != nil {
   234  		return fmt.Errorf("failed to set IP ban expiry: %w", err)
   235  	}
   236  	// kick all peers that match this IP
   237  	for _, conn := range n.host.Network().Conns() {
   238  		addr := conn.RemoteMultiaddr()
   239  		remoteIP, err := manet.ToIP(addr)
   240  		if err != nil {
   241  			continue
   242  		}
   243  		if remoteIP.Equal(ip) {
   244  			if err := conn.Close(); err != nil {
   245  				n.log.Error("failed to close connection to peer with banned IP", "peer", conn.RemotePeer(), "ip", ip)
   246  			}
   247  		}
   248  	}
   249  	return nil
   250  }
   251  
   252  func (n *NodeP2P) Close() error {
   253  	var result *multierror.Error
   254  	if n.peerMonitor != nil {
   255  		n.peerMonitor.Stop()
   256  	}
   257  	if n.dv5Udp != nil {
   258  		n.dv5Udp.Close()
   259  	}
   260  	if n.gsOut != nil {
   261  		if err := n.gsOut.Close(); err != nil {
   262  			result = multierror.Append(result, fmt.Errorf("failed to close gossip cleanly: %w", err))
   263  		}
   264  	}
   265  	if n.host != nil {
   266  		if err := n.host.Close(); err != nil {
   267  			result = multierror.Append(result, fmt.Errorf("failed to close p2p host cleanly: %w", err))
   268  		}
   269  		if n.syncCl != nil {
   270  			if err := n.syncCl.Close(); err != nil {
   271  				result = multierror.Append(result, fmt.Errorf("failed to close p2p sync client cleanly: %w", err))
   272  			}
   273  		}
   274  	}
   275  	if n.appScorer != nil {
   276  		n.appScorer.stop()
   277  	}
   278  	return result.ErrorOrNil()
   279  }
   280  
   281  func FindActiveTCPPort(h host.Host) (uint16, error) {
   282  	var tcpPort uint16
   283  	for _, addr := range h.Addrs() {
   284  		tcpPortStr, err := addr.ValueForProtocol(ma.P_TCP)
   285  		if err != nil {
   286  			continue
   287  		}
   288  		v, err := strconv.ParseUint(tcpPortStr, 10, 16)
   289  		if err != nil {
   290  			continue
   291  		}
   292  		tcpPort = uint16(v)
   293  		break
   294  	}
   295  	return tcpPort, nil
   296  }