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 }