github.com/koko1123/flow-go-1@v0.29.6/network/p2p/p2pnode/libp2pNode.go (about)

     1  // Package p2pnode encapsulates the libp2p library
     2  package p2pnode
     3  
     4  import (
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/koko1123/flow-go-1/module/irrecoverable"
    12  
    13  	"github.com/hashicorp/go-multierror"
    14  	dht "github.com/libp2p/go-libp2p-kad-dht"
    15  	kbucket "github.com/libp2p/go-libp2p-kbucket"
    16  	"github.com/libp2p/go-libp2p/core/host"
    17  	libp2pnet "github.com/libp2p/go-libp2p/core/network"
    18  	"github.com/libp2p/go-libp2p/core/peer"
    19  	"github.com/libp2p/go-libp2p/core/protocol"
    20  	"github.com/libp2p/go-libp2p/core/routing"
    21  	"github.com/rs/zerolog"
    22  
    23  	"github.com/koko1123/flow-go-1/module/component"
    24  	flownet "github.com/koko1123/flow-go-1/network"
    25  	"github.com/koko1123/flow-go-1/network/channels"
    26  	"github.com/koko1123/flow-go-1/network/internal/p2putils"
    27  	"github.com/koko1123/flow-go-1/network/p2p"
    28  	"github.com/koko1123/flow-go-1/network/p2p/connection"
    29  	"github.com/koko1123/flow-go-1/network/p2p/unicast"
    30  )
    31  
    32  const (
    33  	_ = iota
    34  	_ = 1 << (10 * iota)
    35  	mb
    36  )
    37  
    38  const (
    39  	// MaxConnectAttempt is the maximum number of attempts to be made to connect to a remote node for 1-1 direct communication
    40  	MaxConnectAttempt = 3
    41  
    42  	// DefaultMaxPubSubMsgSize defines the maximum message size in publish and multicast modes
    43  	DefaultMaxPubSubMsgSize = 5 * mb // 5 mb
    44  
    45  	// timeout for FindPeer queries to the routing system
    46  	// TODO: is this a sensible value?
    47  	findPeerQueryTimeout = 10 * time.Second
    48  )
    49  
    50  // Node is a wrapper around the LibP2P host.
    51  type Node struct {
    52  	component.Component
    53  	sync.RWMutex
    54  	uniMgr      *unicast.Manager
    55  	host        host.Host // reference to the libp2p host (https://godoc.org/github.com/libp2p/go-libp2p/core/host)
    56  	pubSub      p2p.PubSubAdapter
    57  	logger      zerolog.Logger                      // used to provide logging
    58  	topics      map[channels.Topic]p2p.Topic        // map of a topic string to an actual topic instance
    59  	subs        map[channels.Topic]p2p.Subscription // map of a topic string to an actual subscription
    60  	routing     routing.Routing
    61  	pCache      *ProtocolPeerCache
    62  	peerManager *connection.PeerManager
    63  }
    64  
    65  // NewNode creates a new libp2p node and sets its parameters.
    66  func NewNode(
    67  	logger zerolog.Logger,
    68  	host host.Host,
    69  	pCache *ProtocolPeerCache,
    70  	uniMgr *unicast.Manager,
    71  	peerManager *connection.PeerManager,
    72  ) *Node {
    73  	return &Node{
    74  		uniMgr:      uniMgr,
    75  		host:        host,
    76  		logger:      logger.With().Str("component", "libp2p-node").Logger(),
    77  		topics:      make(map[channels.Topic]p2p.Topic),
    78  		subs:        make(map[channels.Topic]p2p.Subscription),
    79  		pCache:      pCache,
    80  		peerManager: peerManager,
    81  	}
    82  }
    83  
    84  var _ component.Component = (*Node)(nil)
    85  
    86  func (n *Node) Start(ctx irrecoverable.SignalerContext) {
    87  	n.Component.Start(ctx)
    88  }
    89  
    90  // Stop terminates the libp2p node.
    91  // All errors returned from this function can be considered benign.
    92  func (n *Node) Stop() error {
    93  	var result error
    94  
    95  	n.logger.Debug().Msg("unsubscribing from all topics")
    96  	for t := range n.topics {
    97  		err := n.UnSubscribe(t)
    98  		// context cancelled errors are expected while unsubscribing from topics during shutdown
    99  		if err != nil && !errors.Is(err, context.Canceled) {
   100  			result = multierror.Append(result, err)
   101  		}
   102  	}
   103  
   104  	n.logger.Debug().Msg("stopping libp2p node")
   105  	if err := n.host.Close(); err != nil {
   106  		result = multierror.Append(result, err)
   107  	}
   108  
   109  	n.logger.Debug().Msg("closing peer store")
   110  	// to prevent peerstore routine leak (https://github.com/libp2p/go-libp2p/issues/718)
   111  	if err := n.host.Peerstore().Close(); err != nil {
   112  		n.logger.Debug().Err(err).Msg("closing peer store")
   113  		result = multierror.Append(result, err)
   114  	}
   115  
   116  	if result != nil {
   117  		return result
   118  	}
   119  
   120  	addrs := len(n.host.Network().ListenAddresses())
   121  	ticker := time.NewTicker(time.Millisecond * 2)
   122  	defer ticker.Stop()
   123  	timeout := time.After(time.Second)
   124  	for addrs > 0 {
   125  		// wait for all listen addresses to have been removed
   126  		select {
   127  		case <-timeout:
   128  			n.logger.Error().Int("port", addrs).Msg("listen addresses still open")
   129  			return nil
   130  		case <-ticker.C:
   131  			addrs = len(n.host.Network().ListenAddresses())
   132  		}
   133  	}
   134  
   135  	n.logger.Debug().Msg("libp2p node stopped successfully")
   136  
   137  	return nil
   138  }
   139  
   140  // AddPeer adds a peer to this node by adding it to this node's peerstore and connecting to it.
   141  // All errors returned from this function can be considered benign.
   142  func (n *Node) AddPeer(ctx context.Context, peerInfo peer.AddrInfo) error {
   143  	return n.host.Connect(ctx, peerInfo)
   144  }
   145  
   146  // RemovePeer closes the connection with the peer.
   147  // All errors returned from this function can be considered benign.
   148  func (n *Node) RemovePeer(peerID peer.ID) error {
   149  	err := n.host.Network().ClosePeer(peerID)
   150  	if err != nil {
   151  		return fmt.Errorf("failed to remove peer %s: %w", peerID, err)
   152  	}
   153  	return nil
   154  }
   155  
   156  // GetPeersForProtocol returns slice peer IDs for the specified protocol ID.
   157  func (n *Node) GetPeersForProtocol(pid protocol.ID) peer.IDSlice {
   158  	pMap := n.pCache.GetPeers(pid)
   159  	peers := make(peer.IDSlice, 0, len(pMap))
   160  	for p := range pMap {
   161  		peers = append(peers, p)
   162  	}
   163  	return peers
   164  }
   165  
   166  // CreateStream returns an existing stream connected to the peer if it exists, or creates a new stream with it.
   167  // All errors returned from this function can be considered benign.
   168  func (n *Node) CreateStream(ctx context.Context, peerID peer.ID) (libp2pnet.Stream, error) {
   169  	lg := n.logger.With().Str("peer_id", peerID.String()).Logger()
   170  
   171  	// If we do not currently have any addresses for the given peer, stream creation will almost
   172  	// certainly fail. If this Node was configured with a routing system, we can try to use it to
   173  	// look up the address of the peer.
   174  	if len(n.host.Peerstore().Addrs(peerID)) == 0 && n.routing != nil {
   175  		lg.Info().Msg("address not found in peer store, searching for peer in routing system")
   176  
   177  		var err error
   178  		func() {
   179  			timedCtx, cancel := context.WithTimeout(ctx, findPeerQueryTimeout)
   180  			defer cancel()
   181  			// try to find the peer using the routing system
   182  			_, err = n.routing.FindPeer(timedCtx, peerID)
   183  		}()
   184  
   185  		if err != nil {
   186  			lg.Warn().Err(err).Msg("address not found in both peer store and routing system")
   187  		} else {
   188  			lg.Debug().Msg("address not found in peer store, but found in routing system search")
   189  		}
   190  	}
   191  	stream, dialAddrs, err := n.uniMgr.CreateStream(ctx, peerID, MaxConnectAttempt)
   192  	if err != nil {
   193  		return nil, flownet.NewPeerUnreachableError(fmt.Errorf("could not create stream (peer_id: %s, dialing address(s): %v): %w", peerID,
   194  			dialAddrs, err))
   195  	}
   196  
   197  	lg.Info().
   198  		Str("networking_protocol_id", string(stream.Protocol())).
   199  		Str("dial_address", fmt.Sprintf("%v", dialAddrs)).
   200  		Msg("stream successfully created to remote peer")
   201  	return stream, nil
   202  }
   203  
   204  // GetIPPort returns the IP and Port the libp2p node is listening on.
   205  // All errors returned from this function can be considered benign.
   206  func (n *Node) GetIPPort() (string, string, error) {
   207  	return p2putils.IPPortFromMultiAddress(n.host.Network().ListenAddresses()...)
   208  }
   209  
   210  // RoutingTable returns the node routing table
   211  func (n *Node) RoutingTable() *kbucket.RoutingTable {
   212  	return n.routing.(*dht.IpfsDHT).RoutingTable()
   213  }
   214  
   215  // ListPeers returns list of peer IDs for peers subscribed to the topic.
   216  func (n *Node) ListPeers(topic string) []peer.ID {
   217  	return n.pubSub.ListPeers(topic)
   218  }
   219  
   220  // Subscribe subscribes the node to the given topic and returns the subscription
   221  // All errors returned from this function can be considered benign.
   222  func (n *Node) Subscribe(topic channels.Topic, topicValidator p2p.TopicValidatorFunc) (p2p.Subscription, error) {
   223  	n.Lock()
   224  	defer n.Unlock()
   225  
   226  	// Check if the topic has been already created and is in the cache
   227  	n.pubSub.GetTopics()
   228  	tp, found := n.topics[topic]
   229  	var err error
   230  	if !found {
   231  		if err := n.pubSub.RegisterTopicValidator(topic.String(), topicValidator); err != nil {
   232  			n.logger.Err(err).Str("topic", topic.String()).Msg("failed to register topic validator, aborting subscription")
   233  			return nil, fmt.Errorf("failed to register topic validator: %w", err)
   234  		}
   235  
   236  		tp, err = n.pubSub.Join(topic.String())
   237  		if err != nil {
   238  			if err := n.pubSub.UnregisterTopicValidator(topic.String()); err != nil {
   239  				n.logger.Err(err).Str("topic", topic.String()).Msg("failed to unregister topic validator")
   240  			}
   241  
   242  			return nil, fmt.Errorf("could not join topic (%s): %w", topic, err)
   243  		}
   244  
   245  		n.topics[topic] = tp
   246  	}
   247  
   248  	// Create a new subscription
   249  	s, err := tp.Subscribe()
   250  	if err != nil {
   251  		return s, fmt.Errorf("could not subscribe to topic (%s): %w", topic, err)
   252  	}
   253  
   254  	// Add the subscription to the cache
   255  	n.subs[topic] = s
   256  
   257  	n.logger.Debug().
   258  		Str("topic", topic.String()).
   259  		Msg("subscribed to topic")
   260  	return s, err
   261  }
   262  
   263  // UnSubscribe cancels the subscriber and closes the topic.
   264  // All errors returned from this function can be considered benign.
   265  func (n *Node) UnSubscribe(topic channels.Topic) error {
   266  	n.Lock()
   267  	defer n.Unlock()
   268  	// Remove the Subscriber from the cache
   269  	if s, found := n.subs[topic]; found {
   270  		s.Cancel()
   271  		n.subs[topic] = nil
   272  		delete(n.subs, topic)
   273  	}
   274  
   275  	tp, found := n.topics[topic]
   276  	if !found {
   277  		err := fmt.Errorf("could not find topic (%s)", topic)
   278  		return err
   279  	}
   280  
   281  	if err := n.pubSub.UnregisterTopicValidator(topic.String()); err != nil {
   282  		n.logger.Err(err).Str("topic", topic.String()).Msg("failed to unregister topic validator")
   283  	}
   284  
   285  	// attempt to close the topic
   286  	err := tp.Close()
   287  	if err != nil {
   288  		err = fmt.Errorf("could not close topic (%s): %w", topic, err)
   289  		return err
   290  	}
   291  	n.topics[topic] = nil
   292  	delete(n.topics, topic)
   293  
   294  	n.logger.Debug().
   295  		Str("topic", topic.String()).
   296  		Msg("unsubscribed from topic")
   297  	return err
   298  }
   299  
   300  // Publish publishes the given payload on the topic.
   301  // All errors returned from this function can be considered benign.
   302  func (n *Node) Publish(ctx context.Context, topic channels.Topic, data []byte) error {
   303  	ps, found := n.topics[topic]
   304  	if !found {
   305  		return fmt.Errorf("could not find topic (%s)", topic)
   306  	}
   307  	err := ps.Publish(ctx, data)
   308  	if err != nil {
   309  		return fmt.Errorf("could not publish to topic (%s): %w", topic, err)
   310  	}
   311  	return nil
   312  }
   313  
   314  // HasSubscription returns true if the node currently has an active subscription to the topic.
   315  func (n *Node) HasSubscription(topic channels.Topic) bool {
   316  	n.RLock()
   317  	defer n.RUnlock()
   318  	_, ok := n.subs[topic]
   319  	return ok
   320  }
   321  
   322  // Host returns pointer to host object of node.
   323  func (n *Node) Host() host.Host {
   324  	return n.host
   325  }
   326  
   327  // WithDefaultUnicastProtocol overrides the default handler of the unicast manager and registers all preferred protocols.
   328  func (n *Node) WithDefaultUnicastProtocol(defaultHandler libp2pnet.StreamHandler, preferred []unicast.ProtocolName) error {
   329  	n.uniMgr.WithDefaultHandler(defaultHandler)
   330  	for _, p := range preferred {
   331  		err := n.uniMgr.Register(p)
   332  		if err != nil {
   333  			return fmt.Errorf("could not register unicast protocls: %w", err)
   334  		}
   335  	}
   336  
   337  	return nil
   338  }
   339  
   340  // WithPeersProvider sets the PeersProvider for the peer manager.
   341  // If a peer manager factory is set, this method will set the peer manager's PeersProvider.
   342  func (n *Node) WithPeersProvider(peersProvider p2p.PeersProvider) {
   343  	if n.peerManager != nil {
   344  		n.peerManager.SetPeersProvider(peersProvider)
   345  	}
   346  }
   347  
   348  // PeerManagerComponent returns the component interface of the peer manager.
   349  func (n *Node) PeerManagerComponent() component.Component {
   350  	return n.peerManager
   351  }
   352  
   353  // RequestPeerUpdate requests an update to the peer connections of this node using the peer manager.
   354  func (n *Node) RequestPeerUpdate() {
   355  	if n.peerManager != nil {
   356  		n.peerManager.RequestPeerUpdate()
   357  	}
   358  }
   359  
   360  // IsConnected returns true is address is a direct peer of this node else false
   361  func (n *Node) IsConnected(peerID peer.ID) (bool, error) {
   362  	isConnected := n.host.Network().Connectedness(peerID) == libp2pnet.Connected
   363  	return isConnected, nil
   364  }
   365  
   366  // SetRouting sets the node's routing implementation.
   367  // SetRouting may be called at most once.
   368  func (n *Node) SetRouting(r routing.Routing) {
   369  	if n.routing != nil {
   370  		n.logger.Fatal().Msg("routing already set")
   371  	}
   372  
   373  	n.routing = r
   374  }
   375  
   376  // Routing returns the node's routing implementation.
   377  func (n *Node) Routing() routing.Routing {
   378  	return n.routing
   379  }
   380  
   381  // SetPubSub sets the node's pubsub implementation.
   382  // SetPubSub may be called at most once.
   383  func (n *Node) SetPubSub(ps p2p.PubSubAdapter) {
   384  	if n.pubSub != nil {
   385  		n.logger.Fatal().Msg("pubSub already set")
   386  	}
   387  
   388  	n.pubSub = ps
   389  }
   390  
   391  // SetComponentManager sets the component manager for the node.
   392  // SetComponentManager may be called at most once.
   393  func (n *Node) SetComponentManager(cm *component.ComponentManager) {
   394  	if n.Component != nil {
   395  		n.logger.Fatal().Msg("component already set")
   396  	}
   397  
   398  	n.Component = cm
   399  }