github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/p2p/node.go (about)

     1  package p2p
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	core "github.com/ipfs/go-ipfs/core"
    10  	coreiface "github.com/ipfs/interface-go-ipfs-core"
    11  	libp2p "github.com/libp2p/go-libp2p"
    12  	circuit "github.com/libp2p/go-libp2p-circuit"
    13  	connmgr "github.com/libp2p/go-libp2p-connmgr"
    14  	crypto "github.com/libp2p/go-libp2p-core/crypto"
    15  	libp2pevent "github.com/libp2p/go-libp2p-core/event"
    16  	host "github.com/libp2p/go-libp2p-core/host"
    17  	net "github.com/libp2p/go-libp2p-core/network"
    18  	peer "github.com/libp2p/go-libp2p-core/peer"
    19  	peerstore "github.com/libp2p/go-libp2p-peerstore"
    20  	pstoremem "github.com/libp2p/go-libp2p-peerstore/pstoremem"
    21  	discovery "github.com/libp2p/go-libp2p/p2p/discovery"
    22  	ma "github.com/multiformats/go-multiaddr"
    23  	"github.com/qri-io/ioes"
    24  	"github.com/qri-io/qfs/qipfs"
    25  	"github.com/qri-io/qri/auth/key"
    26  	"github.com/qri-io/qri/config"
    27  	"github.com/qri-io/qri/dsref"
    28  	"github.com/qri-io/qri/event"
    29  	p2putil "github.com/qri-io/qri/p2p/p2putil"
    30  	p2ptest "github.com/qri-io/qri/p2p/test"
    31  	"github.com/qri-io/qri/repo"
    32  )
    33  
    34  // QriNode encapsulates a qri peer-2-peer node
    35  type QriNode struct {
    36  	// ID is the node's identifier both locally & on the network
    37  	// Identity has a relationship to privateKey (hash of PublicKey)
    38  	ID peer.ID
    39  	// private key for encrypted communication & verifying identity
    40  	privateKey crypto.PrivKey
    41  
    42  	cfg *config.P2P
    43  
    44  	// Online indicates weather this is node is connected to the p2p network
    45  	Online bool
    46  	// Host for p2p connections. can be provided by an ipfs node
    47  	host host.Host
    48  	// Discovery service, can be provided by an ipfs node
    49  	Discovery discovery.Service
    50  
    51  	// Repo is a repository of this node's qri data
    52  	// note that repo's are built upon a qfs.MuxFS, which
    53  	// may contain a reference to a functioning IPFS node. In that case
    54  	// QriNode should piggyback non-qri-specific p2p functionality on the
    55  	// ipfs node provided by repo
    56  	Repo repo.Repo
    57  
    58  	// QriProfileService allows your node to examine a peer & protect the
    59  	// connections to that peer if it "speaks" the qri protocol
    60  	// it also manages requests for that peer's qri profile and handles
    61  	// requests for this node's profile
    62  	qis *QriProfileService
    63  
    64  	// localResolver allows the node to resolve local dataset references
    65  	localResolver dsref.Resolver
    66  
    67  	// msgState keeps a "scratch pad" of message IDS & timeouts
    68  	msgState *sync.Map
    69  	// receivers is a list of anyone who wants to be notifed on new
    70  	// message arrival
    71  	receivers []chan p2putil.Message
    72  	// receiversMu is the lock for the receivers list
    73  	receiversMu sync.Mutex
    74  
    75  	// pub is the event publisher on which to publish p2p events
    76  	pub     event.Publisher
    77  	notifee *net.NotifyBundle
    78  
    79  	// node keeps a set of IOStreams for "node local" io, often to the
    80  	// command line, to give feedback to the user. These may be piped to
    81  	// local http handlers/websockets/stdio, but these streams are meant for
    82  	// local feedback as opposed to p2p connections
    83  	LocalStreams ioes.IOStreams
    84  
    85  	// shutdown is the function used to cancel the context of the qri node
    86  	shutdown context.CancelFunc
    87  }
    88  
    89  // Assert that conversions needed by the tests are valid.
    90  var _ p2ptest.TestablePeerNode = (*QriNode)(nil)
    91  var _ p2ptest.NodeMakerFunc = NewTestableQriNode
    92  
    93  // NewTestableQriNode creates a new node, as a TestablePeerNode, usable by testing utilities.
    94  func NewTestableQriNode(r repo.Repo, p2pconf *config.P2P, pub event.Publisher) (p2ptest.TestablePeerNode, error) {
    95  	localResolver := dsref.SequentialResolver(r.Dscache(), r)
    96  	return NewQriNode(r, p2pconf, pub, localResolver)
    97  }
    98  
    99  // NewQriNode creates a new node from a configuration. To get a fully connected
   100  // node that's searching for peers call:
   101  // n, _ := NewQriNode(r, cfg)
   102  // n.GoOnline()
   103  func NewQriNode(r repo.Repo, p2pconf *config.P2P, pub event.Publisher, localResolver dsref.Resolver) (node *QriNode, err error) {
   104  	pid, err := p2pconf.DecodePeerID()
   105  	if err != nil {
   106  		return nil, fmt.Errorf("error decoding peer id: %s", err.Error())
   107  	}
   108  
   109  	node = &QriNode{
   110  		ID:            pid,
   111  		cfg:           p2pconf,
   112  		Repo:          r,
   113  		msgState:      &sync.Map{},
   114  		pub:           pub,
   115  		receiversMu:   sync.Mutex{},
   116  		localResolver: localResolver,
   117  		// Make sure we always have proper IOStreams, this can be set later
   118  		LocalStreams: ioes.NewDiscardIOStreams(),
   119  	}
   120  
   121  	node.notifee = &net.NotifyBundle{
   122  		ConnectedF:    node.connected,
   123  		DisconnectedF: node.disconnected,
   124  	}
   125  
   126  	node.qis = NewQriProfileService(node.Repo.Profiles(), node.pub)
   127  	return node, nil
   128  }
   129  
   130  // Host returns the node's Host
   131  func (n *QriNode) Host() host.Host {
   132  	return n.host
   133  }
   134  
   135  // GoOnline puts QriNode on the distributed web, ensuring there's an active peer-2-peer host
   136  // participating in a peer-2-peer network, and kicks off requests to connect to known bootstrap
   137  // peers that support the QriProtocol
   138  func (n *QriNode) GoOnline(c context.Context) (err error) {
   139  	ctx, cancel := context.WithCancel(c)
   140  	n.shutdown = cancel
   141  
   142  	log.Debugf("going online")
   143  	if !n.cfg.Enabled {
   144  		cancel()
   145  		return fmt.Errorf("p2p connection is disabled")
   146  	}
   147  	if n.Online {
   148  		cancel()
   149  		return nil
   150  	}
   151  
   152  	// If the underlying content-addressed-filestore is an ipfs
   153  	// node, it has built-in p2p, overlay the qri protocol
   154  	// on the ipfs node's p2p connections.
   155  	if ipfsfs, ok := n.Repo.Filesystem().Filesystem("ipfs").(*qipfs.Filestore); ok {
   156  		log.Debugf("using IPFS p2p Host")
   157  		if !ipfsfs.Online() {
   158  			if err := ipfsfs.GoOnline(); err != nil {
   159  				cancel()
   160  				return err
   161  			}
   162  		}
   163  
   164  		ipfsnode := ipfsfs.Node()
   165  		if ipfsnode.PeerHost != nil {
   166  			n.host = ipfsnode.PeerHost
   167  		}
   168  
   169  		if ipfsnode.Discovery != nil {
   170  			n.Discovery = ipfsnode.Discovery
   171  		}
   172  	} else if n.host == nil {
   173  		log.Debugf("creating p2p Host")
   174  		ps := pstoremem.NewPeerstore()
   175  		n.host, err = makeBasicHost(ctx, ps, n.cfg)
   176  		if err != nil {
   177  			cancel()
   178  			return fmt.Errorf("error creating host: %s", err.Error())
   179  		}
   180  
   181  		// we need to BYO discovery service when working without IPFS
   182  		if err := n.setupDiscovery(ctx); err != nil {
   183  			// we don't want to fail completely if discovery services like mdns aren't
   184  			// supported. Otherwise routers with mdns turned off would break p2p entirely
   185  			log.Errorf("couldn't start discovery: %s", err)
   186  		}
   187  	}
   188  
   189  	n.qis.Start(n.host)
   190  
   191  	// add ref resolution capabilities:
   192  	n.host.SetStreamHandler(ResolveRefProtocolID, n.resolveRefHandler)
   193  
   194  	// register ourselves as a notifee on connected
   195  	n.host.Network().Notify(n.notifee)
   196  	if err := n.libp2pSubscribe(ctx); err != nil {
   197  		cancel()
   198  		return err
   199  	}
   200  
   201  	p := n.Repo.Profiles().Owner(ctx)
   202  	p.PeerIDs = []peer.ID{n.host.ID()}
   203  
   204  	// update profile with our p2p addresses
   205  	if err := n.Repo.Profiles().SetOwner(ctx, p); err != nil {
   206  		cancel()
   207  		return err
   208  	}
   209  
   210  	n.Online = true
   211  	n.pub.Publish(ctx, event.ETP2PGoneOnline, n.EncapsulatedAddresses())
   212  
   213  	return n.startOnlineServices(ctx)
   214  }
   215  
   216  // startOnlineServices bootstraps the node to qri & IPFS networks
   217  // and begins NAT discovery
   218  func (n *QriNode) startOnlineServices(ctx context.Context) error {
   219  	if !n.Online {
   220  		return nil
   221  	}
   222  	log.Debugf("starting online services")
   223  
   224  	// Boostrap off of default addresses
   225  	go n.Bootstrap(n.cfg.QriBootstrapAddrs)
   226  	// Bootstrap to IPFS network if this node is using an IPFS fs
   227  	go n.BootstrapIPFS()
   228  	return nil
   229  }
   230  
   231  // GoOffline takes the peer offline and shuts it down
   232  func (n *QriNode) GoOffline() error {
   233  	if n != nil && n.Online {
   234  		err := n.Host().Close()
   235  		// clean up the "GoOnline" context
   236  		n.shutdown()
   237  		ctx, cancel := context.WithCancel(context.Background())
   238  		defer cancel()
   239  		n.pub.Publish(ctx, event.ETP2PGoneOffline, nil)
   240  		n.Online = false
   241  		return err
   242  	}
   243  	return nil
   244  }
   245  
   246  // IPFS exposes the core.IPFS node if one exists.
   247  // This is currently required by things like remoteClient in other packages,
   248  // which don't work properly with the CoreAPI implementation
   249  func (n *QriNode) IPFS() (*core.IpfsNode, error) {
   250  	if ipfsfs, ok := n.Repo.Filesystem().Filesystem("ipfs").(*qipfs.Filestore); ok {
   251  		return ipfsfs.Node(), nil
   252  	}
   253  	return nil, fmt.Errorf("not using IPFS")
   254  }
   255  
   256  // note: both qipfs and ipfs_http have this method
   257  type ipfsApier interface {
   258  	CoreAPI() coreiface.CoreAPI
   259  }
   260  
   261  // IPFSCoreAPI returns a IPFS API interface instance
   262  func (n *QriNode) IPFSCoreAPI() (coreiface.CoreAPI, error) {
   263  	if n == nil {
   264  		return nil, ErrNoQriNode
   265  	}
   266  	if ipfsfs, ok := n.Repo.Filesystem().Filesystem("ipfs").(ipfsApier); ok {
   267  		return ipfsfs.CoreAPI(), nil
   268  	}
   269  	return nil, fmt.Errorf("not using IPFS")
   270  }
   271  
   272  // ListenAddresses gives the listening addresses of this node on the p2p network as
   273  // a slice of strings
   274  func (n *QriNode) ListenAddresses() ([]string, error) {
   275  	maddrs := n.EncapsulatedAddresses()
   276  	addrs := make([]string, len(maddrs))
   277  	for i, maddr := range maddrs {
   278  		addrs[i] = maddr.String()
   279  	}
   280  	return addrs, nil
   281  }
   282  
   283  // EncapsulatedAddresses returns a slice of full multaddrs for this node
   284  func (n *QriNode) EncapsulatedAddresses() []ma.Multiaddr {
   285  	// Build host multiaddress
   286  	hostAddr, err := ma.NewMultiaddr(fmt.Sprintf("/p2p/%s", n.host.ID().Pretty()))
   287  	if err != nil {
   288  		fmt.Println(err.Error())
   289  		return nil
   290  	}
   291  
   292  	res := make([]ma.Multiaddr, len(n.host.Addrs()))
   293  	for i, a := range n.host.Addrs() {
   294  		res[i] = a.Encapsulate(hostAddr)
   295  	}
   296  
   297  	return res
   298  }
   299  
   300  // makeBasicHost creates a LibP2P host from a NodeCfg
   301  func makeBasicHost(ctx context.Context, ps peerstore.Peerstore, p2pconf *config.P2P) (host.Host, error) {
   302  	pk, err := key.DecodeB64PrivKey(p2pconf.PrivKey)
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  
   307  	pid, err := p2pconf.DecodePeerID()
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  
   312  	ps.AddPrivKey(pid, pk)
   313  	ps.AddPubKey(pid, pk.GetPublic())
   314  
   315  	opts := []libp2p.Option{
   316  		libp2p.ListenAddrs(p2pconf.Addrs...),
   317  		libp2p.Identity(pk),
   318  		libp2p.Peerstore(ps),
   319  		libp2p.EnableRelay(circuit.OptHop),
   320  	}
   321  
   322  	// Let's talk about these options a bit. Most of the time, we will never
   323  	// follow the code path that takes us to makeBasicHost. Usually, we will be
   324  	// using the Host that comes with the ipfs node. But, let's say we want to not
   325  	// use that ipfs host, or, we are in a testing situation, we will need to
   326  	// create our own host. If we do not explicitly pass the host the options
   327  	// for a ConnManager, it will use the NullConnManager, which doesn't actually
   328  	// tag or manage any conns.
   329  	// So instead, we pass in the libp2p basic ConnManager:
   330  	opts = append(opts, libp2p.ConnectionManager(connmgr.NewConnManager(1000, 0, time.Millisecond)))
   331  
   332  	return libp2p.New(ctx, opts...)
   333  }
   334  
   335  // connected is called when a connection opened via the network notifee bundle
   336  func (n *QriNode) connected(_ net.Network, conn net.Conn) {
   337  	log.Debugf("connected to peer: %s", conn.RemotePeer())
   338  	pi := n.Host().Peerstore().PeerInfo(conn.RemotePeer())
   339  	n.pub.Publish(context.Background(), event.ETP2PPeerConnected, pi)
   340  }
   341  
   342  func (n *QriNode) disconnected(_ net.Network, conn net.Conn) {
   343  	pi := n.Host().Peerstore().PeerInfo(conn.RemotePeer())
   344  	n.pub.Publish(context.Background(), event.ETP2PPeerDisconnected, pi)
   345  
   346  	n.qis.HandleQriPeerDisconnect(pi.ID)
   347  }
   348  
   349  func (n *QriNode) libp2pSubscribe(ctx context.Context) error {
   350  	host := n.host
   351  	sub, err := host.EventBus().Subscribe([]interface{}{
   352  		new(libp2pevent.EvtPeerIdentificationCompleted),
   353  		new(libp2pevent.EvtPeerIdentificationFailed),
   354  	},
   355  	// libp2peventbus.BufSize(1024),
   356  	)
   357  	if err != nil {
   358  		return fmt.Errorf("failed to subscribe to identify notifications: %w", err)
   359  	}
   360  	go func() {
   361  		defer sub.Close()
   362  		for e := range sub.Out() {
   363  			switch e := e.(type) {
   364  			case libp2pevent.EvtPeerIdentificationCompleted:
   365  				log.Debugf("libp2p identified peer: %s", e.Peer)
   366  				n.qis.QriProfileRequest(ctx, e.Peer)
   367  			case libp2pevent.EvtPeerIdentificationFailed:
   368  				log.Debugf("libp2p failed to identify peer %s: %s", e.Peer, e.Reason)
   369  			}
   370  		}
   371  	}()
   372  	return nil
   373  }
   374  
   375  // Keys returns the KeyBook for the node.
   376  func (n *QriNode) Keys() peerstore.KeyBook {
   377  	return n.host.Peerstore()
   378  }
   379  
   380  // Addrs returns the AddrBook for the node.
   381  func (n *QriNode) Addrs() peerstore.AddrBook {
   382  	return n.host.Peerstore()
   383  }
   384  
   385  // SimpleAddrInfo returns a PeerInfo with just the ID and Addresses.
   386  func (n *QriNode) SimpleAddrInfo() peer.AddrInfo {
   387  	return peer.AddrInfo{
   388  		ID:    n.host.ID(),
   389  		Addrs: n.host.Addrs(),
   390  	}
   391  }