diff --git a/.aegir.js b/.aegir.js index 9935399389..678bffee11 100644 --- a/.aegir.js +++ b/.aegir.js @@ -17,6 +17,9 @@ export default { const { plaintext } = await import('./dist/src/insecure/index.js') const { default: Peers } = await import('./dist/test/fixtures/peers.js') const { circuitRelayServer, circuitRelayTransport } = await import('./dist/src/circuit-relay/index.js') + const { identifyService } = await import('./dist/src/identify/index.js') + const { pingService } = await import('./dist/src/ping/index.js') + const { fetchService } = await import('./dist/src/fetch/index.js') // Use the last peer const peerId = await createFromJSON(Peers[Peers.length - 1]) @@ -39,9 +42,15 @@ export default { noise(), plaintext() ], - relay: circuitRelayServer(), - nat: { - enabled: false + services: { + identify: identifyService(), + ping: pingService(), + fetch: fetchService(), + relay: circuitRelayServer({ + reservations: { + maxReservations: Infinity + } + }) } }) // Add the echo protocol diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 1bafb01128..f0a2daf302 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -30,7 +30,7 @@ - [Configuring Metrics](#configuring-metrics) - [Configuring PeerStore](#configuring-peerstore) - [Customizing Transports](#customizing-transports) - - [Configuring the NAT Manager](#configuring-the-nat-manager) + - [Configuring UPnP NAT Traversal](#configuring-upnp-nat-traversal) - [Browser support](#browser-support) - [UPnP and NAT-PMP](#upnp-and-nat-pmp) - [Configuring protocol name](#configuring-protocol-name) @@ -142,8 +142,9 @@ Some available content routing modules are: - [@libp2p/kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) - [@libp2p/delegated-content-routing](https://github.com/libp2p/js-libp2p-delegated-content-routing) +- [@libp2p/ipni-content-routing](https://github.com/libp2p/js-ipni-content-routing) -If none of the available content routing protocols fulfills your needs, you can create a libp2p compatible one. A libp2p content routing protocol just needs to be compliant with the [Content Routing Interface](https://github.com/libp2p/js-interfaces/tree/master/src/content-routing). **(WIP: This module is not yet implemented)** +If none of the available content routing protocols fulfil your needs, you can create a libp2p compatible one. A libp2p content routing protocol just needs to be compliant with the [Content Routing Interface](https://github.com/libp2p/js-interfaces/tree/master/src/content-routing). If you want to know more about libp2p content routing, you should read the following content: @@ -205,8 +206,9 @@ const modules = { contentRouting: [], peerRouting: [], peerDiscovery: [], - dht: dhtImplementation, - pubsub: pubsubImplementation + services: { + serviceKey: serviceImplementation + } } ``` @@ -252,8 +254,10 @@ const node = await createLibp2p({ streamMuxers: [mplex()], connectionEncryption: [noise()], peerDiscovery: [MulticastDNS], - dht: kadDHT(), - pubsub: gossipsub() + services: { + dht: kadDHT(), + pubsub: gossipsub() + } }) ``` @@ -335,10 +339,12 @@ const node = await createLibp2p({ connectionEncryption: [ noise() ], - pubsub: gossipsub({ - emitSelf: false, // whether the node should emit to self on publish - globalSignaturePolicy: SignaturePolicy.StrictSign // message signing policy - }) + services: { + pubsub: gossipsub({ + emitSelf: false, // whether the node should emit to self on publish + globalSignaturePolicy: SignaturePolicy.StrictSign // message signing policy + }) + } } }) ``` @@ -362,10 +368,12 @@ const node = await createLibp2p({ connectionEncryption: [ noise() ], - dht: kadDHT({ - kBucketSize: 20, - clientMode: false // Whether to run the WAN DHT in client or server mode (default: client mode) - }) + services: { + dht: kadDHT({ + kBucketSize: 20, + clientMode: false // Whether to run the WAN DHT in client or server mode (default: client mode) + }) + } }) ``` @@ -435,21 +443,6 @@ const node = await createLibp2p({ ], streamMuxers: [mplex()], connectionEncryption: [noise()], - relay: circuitRelayServer({ // makes the node function as a relay server - hopTimeout: 30 * 1000, // incoming relay requests must be resolved within this time limit - advertise: { // if set, use content routing to broadcast availability of this relay - bootDelay: 30 * 1000 // how long to wait after startup before broadcast - }, - reservations: { - maxReservations: 15 // how many peers are allowed to reserve relay slots on this server - reservationClearInterval: 300 * 1000 // how often to reclaim stale reservations - applyDefaultLimit: true // whether to apply default data/duration limits to each relayed connection - defaultDurationLimit: 2 * 60 * 1000 // the default maximum amount of time a relayed connection can be open for - defaultDataLimit: BigInt(2 << 7) // the default maximum number of bytes that can be transferred over a relayed connection - maxInboundHopStreams: 32 // how many inbound HOP streams are allow simultaneously - maxOutboundHopStreams: 64 // how many outbound HOP streams are allow simultaneously - } - }), connectionGater: { // used by the server - return true to deny a reservation to the remote peer denyInboundRelayReservation: (source: PeerId) => Promise @@ -459,6 +452,23 @@ const node = await createLibp2p({ // used by the client - return true to deny a relay connection from the remote relay and peer denyInboundRelayedConnection: (relay: PeerId, remotePeer: PeerId) => Promise + }, + services: { + relay: circuitRelayServer({ // makes the node function as a relay server + hopTimeout: 30 * 1000, // incoming relay requests must be resolved within this time limit + advertise: { // if set, use content routing to broadcast availability of this relay + bootDelay: 30 * 1000 // how long to wait after startup before broadcast + }, + reservations: { + maxReservations: 15 // how many peers are allowed to reserve relay slots on this server + reservationClearInterval: 300 * 1000 // how often to reclaim stale reservations + applyDefaultLimit: true // whether to apply default data/duration limits to each relayed connection + defaultDurationLimit: 2 * 60 * 1000 // the default maximum amount of time a relayed connection can be open for + defaultDataLimit: BigInt(2 << 7) // the default maximum number of bytes that can be transferred over a relayed connection + maxInboundHopStreams: 32 // how many inbound HOP streams are allow simultaneously + maxOutboundHopStreams: 64 // how many outbound HOP streams are allow simultaneously + } + }), } }) ``` @@ -844,24 +854,28 @@ const node = await createLibp2p({ }) ``` -#### Configuring the NAT Manager +#### Configuring UPnP NAT Traversal Network Address Translation (NAT) is a function performed by your router to enable multiple devices on your local network to share a single IPv4 address. It's done transparently for outgoing connections, ensuring the correct response traffic is routed to your computer, but if you wish to accept incoming connections some configuration is necessary. -The NAT manager can be configured as follows: +Some home routers support [UPnP NAT](https://en.wikipedia.org/wiki/Universal_Plug_and_Play) which allows network devices to request traffic to be forwarded from public facing ports that would otherwise be firewalled. + +If your router supports this, libp2p can be configured to use it as follows: ```js +import { createLibp2p } from 'libp2p' +import { uPnPNAT } from 'libp2p/upnp-nat' + const node = await createLibp2p({ - config: { - nat: { - enabled: true, // defaults to true + services: { + nat: uPnPNAT({ description: 'my-node', // set as the port mapping description on the router, defaults the current libp2p version and your peer id gateway: '192.168.1.1', // leave unset to auto-discover externalIp: '80.1.1.1', // leave unset to auto-discover localAddress: '129.168.1.123', // leave unset to auto-discover ttl: 7200, // TTL for port mappings (min 20 minutes) keepAlive: true, // Refresh port mapping after TTL expires - } + }) } }) ``` @@ -881,12 +895,18 @@ By default under nodejs libp2p will attempt to use [UPnP](https://en.wikipedia.o Changing the protocol name prefix can isolate default public network (IPFS) for custom purposes. ```js +import { createLibp2p } from 'libp2p' +import { identifyService } from 'libp2p/identify' +import { pingService } from 'libp2p/ping' + const node = await createLibp2p({ - identify: { - protocolPrefix: 'ipfs' // default - }, - ping: { - protocolPrefix: 'ipfs' // default + services: { + identify: identifyService({ + protocolPrefix: 'ipfs' // default + }), + ping: pingService({ + protocolPrefix: 'ipfs' // default + }) } }) /* diff --git a/doc/LIMITS.md b/doc/LIMITS.md index f7b5fb6625..3b6e3f362c 100644 --- a/doc/LIMITS.md +++ b/doc/LIMITS.md @@ -29,7 +29,7 @@ We can also limit the number of connections in a "pending" state. These connecti All fields are optional. The default values are defined in [src/connection-manager/index.ts](https://github.com/libp2p/js-libp2p/blob/master/src/connection-manager/index.ts) - please see that file for the current values. ```ts -const node = await createLibp2pNode({ +const node = await createLibp2p({ connectionManager: { /** * The total number of connections allowed to be open at one time @@ -69,7 +69,7 @@ To prevent individual peers from opening multiple connections to a node, an `inb All fields are optional. The default values are defined in [src/connection-manager/index.ts](https://github.com/libp2p/js-libp2p/blob/master/src/connection-manager/index.ts) - please see that file for the current values. ```ts -const node = await createLibp2pNode({ +const node = await createLibp2p({ connectionManager: { /** * A remote peer may attempt to open up to this many connections per second, @@ -93,7 +93,7 @@ These settings are done on a per-muxer basis, please see the README of the relev All fields are optional. The default values are defined in [@libp2p/mplex/src/mplex.ts](https://github.com/libp2p/js-libp2p-mplex/blob/master/src/mplex.ts) - please see that file for the current values. ```ts -const node = await createLibp2pNode({ +const node = await createLibp2p({ muxers: [ mplex({ /** @@ -133,7 +133,7 @@ const node = await createLibp2pNode({ All fields are optional. The default values are defined in [@chainsafe/libp2p-yamux/src/config.ts](https://github.com/ChainSafe/js-libp2p-yamux/blob/master/src/config.ts) - please see that file for the current values. ```ts -const node = await createLibp2pNode({ +const node = await createLibp2p({ muxers: [ yamux({ /** @@ -186,7 +186,7 @@ The [@libp2p/tcp](https://github.com/libp2p/js-libp2p-tcp) transport allows addi All fields are optional. The full list of options is defined in [@libp2p/tcp/src/index.ts](https://github.com/libp2p/js-libp2p-tcp/blob/master/src/index.ts) - please see that file for more details. ```ts -const node = await createLibp2pNode({ +const node = await createLibp2p({ transports: [ tcp({ /** @@ -215,7 +215,7 @@ const node = await createLibp2pNode({ It is possible to configure some hosts to always accept connections from and some to always reject connections from. ```js -const node = await createLibp2pNode({ +const node = await createLibp2p({ connectionManager: { /** * A list of multiaddrs, any connection with a `remoteAddress` property diff --git a/doc/METRICS.md b/doc/METRICS.md index 72ea0785bd..2fb8eb4226 100644 --- a/doc/METRICS.md +++ b/doc/METRICS.md @@ -32,10 +32,10 @@ Although designed to primarily integrate with tools such as [Prometheus](https:/ First enable metrics tracking by supplying a [Metrics](https://www.npmjs.com/package/@libp2p/interface-metrics) implementation: ```js -import { createLibp2pNode } from 'libp2p' +import { createLibp2p } from 'libp2p' import { prometheusMetrics } from '@libp2p/prometheus-metrics' -const node = await createLibp2pNode({ +const node = await createLibp2p({ metrics: prometheusMetrics() //... other config }) @@ -164,7 +164,7 @@ Metrics implementations will allow extracting the values for presentation in an import { prometheusMetrics } from '@libp2p/prometheus-metrics' import client from 'prom-client' -const libp2p = createLibp2pNode({ +const libp2p = createLibp2p({ metrics: prometheusMetrics() //... other config }) diff --git a/doc/migrations/v0.44-v0.45.md b/doc/migrations/v0.44-v0.45.md index 53c9864aae..c8b1aa8365 100644 --- a/doc/migrations/v0.44-v0.45.md +++ b/doc/migrations/v0.44-v0.45.md @@ -4,6 +4,7 @@ A migration guide for refactoring your application code from libp2p v0.44.x to v ## Table of Contents +- [Services](#services) - [Events](#events) - [Emitters](#emitters) - [Event changes](#event-changes) @@ -13,6 +14,75 @@ A migration guide for refactoring your application code from libp2p v0.44.x to v - [`self:peer:update`](#selfpeerupdate) - [Atomic peer store methods](#atomic-peer-store-methods) +## Services + +libp2p now accepts arbitrary service modules that can use internal components to fulfil their functions. + +This reduces the attack surface area of libp2p nodes as less functionality is enabled by default, and with tree shaking less code will be included in bundles making for faster downloads when used in browsers. + +Several optional modules have been removed and must now be configured as services: + +**Before** + +```js +import { createLibp2p } from 'libp2p' +import { circuitRelayServer } from 'libp2p/circuit-relay' +import { kadDHT } from '@libp2p/kad-dht' +import { gossipSub } from '@ChainSafe/libp2p-gossipsub' + +const node = createLibp2p({ + // ... other options here + identify: { + /** identify options **/ + }, + ping: { + /** ping options **/ + }, + fetch: { + /** fetch options **/ + }, + pubsub: gossipSub(), + dht: kadDHT(), + relay: circuitRelayServer() +}) +``` + +**After** + +```js +import { createLibp2p } from 'libp2p' +import { circuitRelayServer } from 'libp2p/circuit-relay' +import { identifyService } from 'libp2p/identify' +import { pingService } from 'libp2p/ping' +import { fetchService } from 'libp2p/fetch' +import { kadDHT } from '@libp2p/kad-dht' +import { gossipSub } from '@ChainSafe/libp2p-gossipsub' + +const node = createLibp2p({ + // ... other options here + services: { + identify: identifyService({ + /** identify options **/ + }), + ping: pingService({ + /** ping options **/ + }), + fetch: fetchService({ + /** fetch options **/ + }), + pubsub: gossipSub(), + dht: kadDHT(), + relay: circuitRelayServer() + } +}) +``` + +Configured services can be accessed via the `.services` key: + +```js +const result = await node.services.ping.ping(multiaddr('...')) +``` + ## Events The events emitted by libp2p have been refactored to be more consistent and to give more insight into the inner workings of libp2p. diff --git a/examples/auto-relay/dialer.js b/examples/auto-relay/dialer.js index 8089bc7cb3..08f4aaafb7 100644 --- a/examples/auto-relay/dialer.js +++ b/examples/auto-relay/dialer.js @@ -4,6 +4,7 @@ import { noise } from '@chainsafe/libp2p-noise' import { mplex } from '@libp2p/mplex' import { multiaddr } from '@multiformats/multiaddr' import { circuitRelayTransport } from 'libp2p/circuit-relay' +import { identifyService } from 'libp2p/identify' async function main () { const autoRelayNodeAddr = process.argv[2] @@ -21,7 +22,10 @@ async function main () { ], streamMuxers: [ mplex() - ] + ], + services: { + identify: identifyService() + } }) console.log(`Node started with id ${node.peerId.toString()}`) diff --git a/examples/auto-relay/listener.js b/examples/auto-relay/listener.js index 24ee8da64c..0b3ed17452 100644 --- a/examples/auto-relay/listener.js +++ b/examples/auto-relay/listener.js @@ -4,6 +4,7 @@ import { noise } from '@chainsafe/libp2p-noise' import { mplex } from '@libp2p/mplex' import { multiaddr } from '@multiformats/multiaddr' import { circuitRelayTransport } from 'libp2p/circuit-relay' +import { identifyService } from 'libp2p/identify' async function main () { const relayAddr = process.argv[2] @@ -23,7 +24,10 @@ async function main () { ], streamMuxers: [ mplex() - ] + ], + services: { + identify: identifyService() + } }) console.log(`Node started with id ${node.peerId.toString()}`) diff --git a/examples/auto-relay/relay.js b/examples/auto-relay/relay.js index a153e41fd7..5633668dcd 100644 --- a/examples/auto-relay/relay.js +++ b/examples/auto-relay/relay.js @@ -3,6 +3,7 @@ import { webSockets } from '@libp2p/websockets' import { noise } from '@chainsafe/libp2p-noise' import { mplex } from '@libp2p/mplex' import { circuitRelayServer } from 'libp2p/circuit-relay' +import { identifyService } from 'libp2p/identify' async function main () { const node = await createLibp2p({ @@ -20,7 +21,10 @@ async function main () { streamMuxers: [ mplex() ], - relay: circuitRelayServer() + services: { + identify: identifyService(), + relay: circuitRelayServer() + } }) console.log(`Node started with id ${node.peerId.toString()}`) diff --git a/examples/chat/src/listener.js b/examples/chat/src/listener.js index f97b534d57..e3bb355ebf 100644 --- a/examples/chat/src/listener.js +++ b/examples/chat/src/listener.js @@ -17,8 +17,8 @@ async function run () { // Log a message when a remote peer connects to us nodeListener.addEventListener('peer:connect', (evt) => { - const connection = evt.detail - console.log('connected to: ', connection.remotePeer.toString()) + const remotePeer = evt.detail + console.log('connected to: ', remotePeer.toString()) }) // Handle messages for the protocol diff --git a/examples/delegated-routing/package.json b/examples/delegated-routing/package.json index 1235ca22ca..1478d8570b 100644 --- a/examples/delegated-routing/package.json +++ b/examples/delegated-routing/package.json @@ -9,7 +9,7 @@ "libp2p": "file:../../", "@libp2p/delegated-content-routing": "^4.0.0", "@libp2p/delegated-peer-routing": "^4.0.0", - "@libp2p/kad-dht": "^9.0.0", + "@libp2p/kad-dht": "^9.1.0", "@libp2p/mplex": "^8.0.1", "@libp2p/webrtc-star": "^7.0.0", "@libp2p/websockets": "^6.0.1", diff --git a/examples/discovery-mechanisms/1.js b/examples/discovery-mechanisms/1.js index 1292818fea..abba6d8ddc 100644 --- a/examples/discovery-mechanisms/1.js +++ b/examples/discovery-mechanisms/1.js @@ -28,8 +28,8 @@ import bootstrappers from './bootstrappers.js' }) node.addEventListener('peer:discovery', (evt) => { - const peerId = evt.detail + const peerInfo = evt.detail - console.log('Discovered:', peerId.toString()) + console.log('Discovered:', peerInfo.id.toString()) }) })(); diff --git a/examples/discovery-mechanisms/3.js b/examples/discovery-mechanisms/3.js index 559babea1a..f181b16814 100644 --- a/examples/discovery-mechanisms/3.js +++ b/examples/discovery-mechanisms/3.js @@ -8,6 +8,7 @@ import { floodsub } from '@libp2p/floodsub' import { bootstrap } from '@libp2p/bootstrap' import { pubsubPeerDiscovery } from '@libp2p/pubsub-peer-discovery' import { circuitRelayTransport, circuitRelayServer } from 'libp2p/circuit-relay' +import { identifyService } from 'libp2p/identify' const createNode = async (bootstrappers) => { const node = await createLibp2p({ @@ -17,7 +18,6 @@ const createNode = async (bootstrappers) => { transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], - pubsub: floodsub(), peerDiscovery: [ bootstrap({ list: bootstrappers @@ -25,7 +25,11 @@ const createNode = async (bootstrappers) => { pubsubPeerDiscovery({ interval: 1000 }) - ] + ], + services: { + pubsub: floodsub(), + identify: identifyService() + } }) return node @@ -41,13 +45,16 @@ const createNode = async (bootstrappers) => { transports: [tcp(), circuitRelayTransport()], streamMuxers: [mplex()], connectionEncryption: [noise()], - pubsub: floodsub(), peerDiscovery: [ pubsubPeerDiscovery({ interval: 1000 }) ], - relay: circuitRelayServer() + services: { + relay: circuitRelayServer(), + identify: identifyService(), + pubsub: floodsub() + } }) console.log(`libp2p relay started with id: ${relay.peerId.toString()}`) diff --git a/examples/echo/src/listener.js b/examples/echo/src/listener.js index 559ea1c9b4..86a65e1be7 100644 --- a/examples/echo/src/listener.js +++ b/examples/echo/src/listener.js @@ -22,8 +22,8 @@ async function run() { // Log a message when we receive a connection listenerNode.addEventListener('peer:connect', (evt) => { - const connection = evt.detail - console.log('received dial to me from:', connection.remotePeer.toString()) + const remotePeer = evt.detail + console.log('received dial to me from:', remotePeer.toString()) }) // Handle incoming connections for the protocol by piping from the stream diff --git a/examples/package.json b/examples/package.json index 4537b2b2d2..2c7d9dec0b 100644 --- a/examples/package.json +++ b/examples/package.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@libp2p/floodsub": "^7.0.1", - "@libp2p/pubsub-peer-discovery": "^8.0.0", + "@libp2p/pubsub-peer-discovery": "^8.0.4", "@nodeutils/defaults-deep": "^1.1.0", "execa": "^6.1.0", "fs-extra": "^10.1.0", diff --git a/examples/peer-and-content-routing/1.js b/examples/peer-and-content-routing/1.js index aa48549d26..252d910331 100644 --- a/examples/peer-and-content-routing/1.js +++ b/examples/peer-and-content-routing/1.js @@ -1,6 +1,7 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' +import { identifyService } from 'libp2p/identify' import { tcp } from '@libp2p/tcp' import { mplex } from '@libp2p/mplex' import { noise } from '@chainsafe/libp2p-noise' @@ -15,7 +16,10 @@ const createNode = async () => { transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], - dht: kadDHT() + services: { + dht: kadDHT(), + identify: identifyService() + } }) return node diff --git a/examples/peer-and-content-routing/2.js b/examples/peer-and-content-routing/2.js index 9b494fcfbd..c7d67fad64 100644 --- a/examples/peer-and-content-routing/2.js +++ b/examples/peer-and-content-routing/2.js @@ -1,6 +1,7 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' +import { identifyService } from 'libp2p/identify' import { tcp } from '@libp2p/tcp' import { mplex } from '@libp2p/mplex' import { noise } from '@chainsafe/libp2p-noise' @@ -17,7 +18,10 @@ const createNode = async () => { transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], - dht: kadDHT() + services: { + dht: kadDHT(), + identify: identifyService() + } }) return node diff --git a/examples/pubsub/1.js b/examples/pubsub/1.js index a7b8589b46..883439fa8d 100644 --- a/examples/pubsub/1.js +++ b/examples/pubsub/1.js @@ -1,6 +1,7 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' +import { identifyService } from 'libp2p/identify' import { tcp } from '@libp2p/tcp' import { mplex } from '@libp2p/mplex' import { noise } from '@chainsafe/libp2p-noise' @@ -16,7 +17,10 @@ const createNode = async () => { transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], - pubsub: floodsub() + services: { + pubsub: floodsub(), + identify: identifyService() + } }) return node @@ -36,20 +40,20 @@ const createNode = async () => { }) await node1.dial(node2.peerId) - node1.pubsub.subscribe(topic) - node1.pubsub.addEventListener('message', (evt) => { + node1.services.pubsub.subscribe(topic) + node1.services.pubsub.addEventListener('message', (evt) => { console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`) }) // Will not receive own published messages by default - node2.pubsub.subscribe(topic) - node2.pubsub.addEventListener('message', (evt) => { + node2.services.pubsub.subscribe(topic) + node2.services.pubsub.addEventListener('message', (evt) => { console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`) }) // node2 publishes "news" every second setInterval(() => { - node2.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!')).catch(err => { + node2.services.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!')).catch(err => { console.error(err) }) }, 1000) diff --git a/examples/pubsub/message-filtering/1.js b/examples/pubsub/message-filtering/1.js index 45a4125c30..d919b5197e 100644 --- a/examples/pubsub/message-filtering/1.js +++ b/examples/pubsub/message-filtering/1.js @@ -1,6 +1,7 @@ /* eslint-disable no-console */ import { createLibp2p } from 'libp2p' +import { identifyService } from 'libp2p/identify' import { tcp } from '@libp2p/tcp' import { mplex } from '@libp2p/mplex' import { noise } from '@chainsafe/libp2p-noise' @@ -16,7 +17,10 @@ const createNode = async () => { transports: [tcp()], streamMuxers: [mplex()], connectionEncryption: [noise()], - pubsub: floodsub() + services: { + pubsub: floodsub(), + identify: identifyService() + } }) return node @@ -43,7 +47,7 @@ const createNode = async () => { await node2.dial(node3.peerId) //subscribe - node1.pubsub.addEventListener('message', (evt) => { + node1.services.pubsub.addEventListener('message', (evt) => { if (evt.detail.topic !== topic) { return } @@ -51,25 +55,25 @@ const createNode = async () => { // Will not receive own published messages by default console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)}`) }) - node1.pubsub.subscribe(topic) + node1.services.pubsub.subscribe(topic) - node2.pubsub.addEventListener('message', (evt) => { + node2.services.pubsub.addEventListener('message', (evt) => { if (evt.detail.topic !== topic) { return } console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)}`) }) - node2.pubsub.subscribe(topic) + node2.services.pubsub.subscribe(topic) - node3.pubsub.addEventListener('message', (evt) => { + node3.services.pubsub.addEventListener('message', (evt) => { if (evt.detail.topic !== topic) { return } console.log(`node3 received: ${uint8ArrayToString(evt.detail.data)}`) }) - node3.pubsub.subscribe(topic) + node3.services.pubsub.subscribe(topic) // wait for subscriptions to propagate await hasSubscription(node1, node2, topic) @@ -86,14 +90,14 @@ const createNode = async () => { } //validate fruit - node1.pubsub.topicValidators.set(topic, validateFruit) - node2.pubsub.topicValidators.set(topic, validateFruit) - node3.pubsub.topicValidators.set(topic, validateFruit) + node1.services.pubsub.topicValidators.set(topic, validateFruit) + node2.services.pubsub.topicValidators.set(topic, validateFruit) + node3.services.pubsub.topicValidators.set(topic, validateFruit) // node1 publishes "fruits" for (const fruit of ['banana', 'apple', 'car', 'orange']) { console.log('############## fruit ' + fruit + ' ##############') - await node1.pubsub.publish(topic, uint8ArrayFromString(fruit)) + await node1.services.pubsub.publish(topic, uint8ArrayFromString(fruit)) } console.log('############## all messages sent ##############') @@ -110,7 +114,7 @@ async function delay (ms) { */ async function hasSubscription (node1, node2, topic) { while (true) { - const subs = await node1.pubsub.getSubscribers(topic) + const subs = await node1.services.pubsub.getSubscribers(topic) if (subs.map(peer => peer.toString()).includes(node2.peerId.toString())) { return diff --git a/package.json b/package.json index b5df58e0b2..8f51935fe7 100644 --- a/package.json +++ b/package.json @@ -52,14 +52,34 @@ "types": "./dist/src/index.d.ts", "import": "./dist/src/index.js" }, + "./autonat": { + "types": "./dist/src/autonat/index.d.ts", + "import": "./dist/src/autonat/index.js" + }, "./circuit-relay": { "types": "./dist/src/circuit-relay/index.d.ts", "import": "./dist/src/circuit-relay/index.js" }, + "./fetch": { + "types": "./dist/src/fetch/index.d.ts", + "import": "./dist/src/fetch/index.js" + }, + "./identify": { + "types": "./dist/src/identify/index.d.ts", + "import": "./dist/src/identify/index.js" + }, "./insecure": { "types": "./dist/src/insecure/index.d.ts", "import": "./dist/src/insecure/index.js" }, + "./upnp-nat": { + "types": "./dist/src/upnp-nat/index.d.ts", + "import": "./dist/src/upnp-nat/index.js" + }, + "./ping": { + "types": "./dist/src/ping/index.d.ts", + "import": "./dist/src/ping/index.js" + }, "./pnet": { "types": "./dist/src/pnet/index.d.ts", "import": "./dist/src/pnet/index.js" @@ -106,15 +126,15 @@ "@libp2p/interface-connection-encrypter": "^4.0.0", "@libp2p/interface-connection-gater": "^3.0.0", "@libp2p/interface-connection-manager": "^3.0.0", - "@libp2p/interface-content-routing": "^2.0.0", - "@libp2p/interface-dht": "^2.0.0", + "@libp2p/interface-content-routing": "^2.1.0", + "@libp2p/interface-dht": "^2.0.1", "@libp2p/interface-keychain": "^2.0.4", - "@libp2p/interface-libp2p": "^2.0.0", + "@libp2p/interface-libp2p": "^3.0.0", "@libp2p/interface-metrics": "^4.0.0", - "@libp2p/interface-peer-discovery": "^1.0.1", + "@libp2p/interface-peer-discovery": "^1.1.0", "@libp2p/interface-peer-id": "^2.0.1", "@libp2p/interface-peer-info": "^1.0.3", - "@libp2p/interface-peer-routing": "^1.0.1", + "@libp2p/interface-peer-routing": "^1.1.0", "@libp2p/interface-peer-store": "^2.0.0", "@libp2p/interface-pubsub": "^4.0.0", "@libp2p/interface-record": "^2.0.6", @@ -155,19 +175,16 @@ "merge-options": "^3.0.4", "multiformats": "^11.0.0", "p-defer": "^4.0.0", - "p-fifo": "^1.0.0", "p-queue": "^7.3.4", "p-retry": "^5.0.0", "private-ip": "^3.0.0", "protons-runtime": "^5.0.0", "rate-limiter-flexible": "^2.3.11", - "retimer": "^3.0.0", "set-delayed-interval": "^1.0.0", "timeout-abort-controller": "^3.0.0", "uint8arraylist": "^2.3.2", "uint8arrays": "^4.0.2", "wherearewe": "^2.0.0", - "why-is-node-running": "^2.2.2", "xsalsa20": "^1.1.0" }, "devDependencies": { @@ -175,21 +192,20 @@ "@chainsafe/libp2p-noise": "^11.0.0", "@chainsafe/libp2p-yamux": "^4.0.0", "@libp2p/bootstrap": "^7.0.0", - "@libp2p/daemon-client": "^6.0.0", - "@libp2p/daemon-server": "^5.0.0", + "@libp2p/daemon-client": "^6.0.2", + "@libp2p/daemon-server": "^5.0.2", "@libp2p/floodsub": "^7.0.1", "@libp2p/interface-compliance-tests": "^3.0.6", "@libp2p/interface-connection-compliance-tests": "^2.0.8", "@libp2p/interface-connection-encrypter-compliance-tests": "^5.0.0", "@libp2p/interface-mocks": "^11.0.0", "@libp2p/interop": "^8.0.0", - "@libp2p/kad-dht": "^9.0.0", + "@libp2p/kad-dht": "^9.1.0", "@libp2p/mdns": "^7.0.0", "@libp2p/mplex": "^8.0.1", "@libp2p/pubsub": "^7.0.1", "@libp2p/tcp": "^7.0.1", "@libp2p/websockets": "^6.0.1", - "@types/p-fifo": "^1.0.0", "@types/varint": "^6.0.0", "@types/xsalsa20": "^1.1.0", "aegir": "^38.1.7", diff --git a/src/components.ts b/src/components.ts index 4a8f5243ac..1312832a31 100644 --- a/src/components.ts +++ b/src/components.ts @@ -10,14 +10,12 @@ import type { PeerStore } from '@libp2p/interface-peer-store' import type { Registrar } from '@libp2p/interface-registrar' import type { TransportManager, Upgrader } from '@libp2p/interface-transport' import type { Datastore } from 'interface-datastore' -import type { PubSub } from '@libp2p/interface-pubsub' -import type { DualDHT } from '@libp2p/interface-dht' import type { ConnectionManager } from '@libp2p/interface-connection-manager' import type { ConnectionGater } from '@libp2p/interface-connection-gater' import type { Libp2pEvents } from '@libp2p/interface-libp2p' import type { EventEmitter } from '@libp2p/interfaces/events' -export interface Components { +export interface Components extends Record, Startable { peerId: PeerId events: EventEmitter addressManager: AddressManager @@ -32,8 +30,6 @@ export interface Components { datastore: Datastore connectionProtector?: ConnectionProtector metrics?: Metrics - dht?: DualDHT - pubsub?: PubSub } export interface ComponentsInit { @@ -51,46 +47,18 @@ export interface ComponentsInit { peerRouting?: PeerRouting datastore?: Datastore connectionProtector?: ConnectionProtector - dht?: DualDHT - pubsub?: PubSub } -export class DefaultComponents implements Components, Startable { - private _peerId?: PeerId - private _events?: EventEmitter - private _addressManager?: AddressManager - private _peerStore?: PeerStore - private _upgrader?: Upgrader - private _metrics?: Metrics - private _registrar?: Registrar - private _connectionManager?: ConnectionManager - private _transportManager?: TransportManager - private _connectionGater?: ConnectionGater - private _contentRouting?: ContentRouting - private _peerRouting?: PeerRouting - private _datastore?: Datastore - private _connectionProtector?: ConnectionProtector - private _dht?: DualDHT - private _pubsub?: PubSub +class DefaultComponents implements Startable { + public components: Record = {} private _started = false constructor (init: ComponentsInit = {}) { - this._peerId = init.peerId - this._events = init.events - this._addressManager = init.addressManager - this._peerStore = init.peerStore - this._upgrader = init.upgrader - this._metrics = init.metrics - this._registrar = init.registrar - this._connectionManager = init.connectionManager - this._transportManager = init.transportManager - this._connectionGater = init.connectionGater - this._contentRouting = init.contentRouting - this._peerRouting = init.peerRouting - this._datastore = init.datastore - this._connectionProtector = init.connectionProtector - this._dht = init.dht - this._pubsub = init.pubsub + this.components = {} + + for (const [key, value] of Object.entries(init)) { + this.components[key] = value + } } isStarted (): boolean { @@ -99,19 +67,23 @@ export class DefaultComponents implements Components, Startable { async beforeStart (): Promise { await Promise.all( - Object.values(this).filter(obj => isStartable(obj)).map(async (startable: Startable) => { - if (startable.beforeStart != null) { - await startable.beforeStart() - } - }) + Object.values(this.components) + .filter(obj => isStartable(obj)) + .map(async (startable: Startable) => { + if (startable.beforeStart != null) { + await startable.beforeStart() + } + }) ) } async start (): Promise { await Promise.all( - Object.values(this).filter(obj => isStartable(obj)).map(async (startable: Startable) => { - await startable.start() - }) + Object.values(this.components) + .filter(obj => isStartable(obj)) + .map(async (startable: Startable) => { + await startable.start() + }) ) this._started = true @@ -119,29 +91,35 @@ export class DefaultComponents implements Components, Startable { async afterStart (): Promise { await Promise.all( - Object.values(this).filter(obj => isStartable(obj)).map(async (startable: Startable) => { - if (startable.afterStart != null) { - await startable.afterStart() - } - }) + Object.values(this.components) + .filter(obj => isStartable(obj)) + .map(async (startable: Startable) => { + if (startable.afterStart != null) { + await startable.afterStart() + } + }) ) } async beforeStop (): Promise { await Promise.all( - Object.values(this).filter(obj => isStartable(obj)).map(async (startable: Startable) => { - if (startable.beforeStop != null) { - await startable.beforeStop() - } - }) + Object.values(this.components) + .filter(obj => isStartable(obj)) + .map(async (startable: Startable) => { + if (startable.beforeStop != null) { + await startable.beforeStop() + } + }) ) } async stop (): Promise { await Promise.all( - Object.values(this).filter(obj => isStartable(obj)).map(async (startable: Startable) => { - await startable.stop() - }) + Object.values(this.components) + .filter(obj => isStartable(obj)) + .map(async (startable: Startable) => { + await startable.stop() + }) ) this._started = false @@ -149,187 +127,63 @@ export class DefaultComponents implements Components, Startable { async afterStop (): Promise { await Promise.all( - Object.values(this).filter(obj => isStartable(obj)).map(async (startable: Startable) => { - if (startable.afterStop != null) { - await startable.afterStop() - } - }) + Object.values(this.components) + .filter(obj => isStartable(obj)) + .map(async (startable: Startable) => { + if (startable.afterStop != null) { + await startable.afterStop() + } + }) ) } +} - get peerId (): PeerId { - if (this._peerId == null) { - throw new CodeError('peerId not set', 'ERR_SERVICE_MISSING') - } - - return this._peerId - } - - set peerId (peerId: PeerId) { - this._peerId = peerId - } - - get events (): EventEmitter { - if (this._events == null) { - throw new CodeError('events not set', 'ERR_SERVICE_MISSING') - } - - return this._events - } - - set events (events: EventEmitter) { - this._events = events - } - - get addressManager (): AddressManager { - if (this._addressManager == null) { - throw new CodeError('addressManager not set', 'ERR_SERVICE_MISSING') - } - - return this._addressManager - } - - set addressManager (addressManager: AddressManager) { - this._addressManager = addressManager - } - - get peerStore (): PeerStore { - if (this._peerStore == null) { - throw new CodeError('peerStore not set', 'ERR_SERVICE_MISSING') - } - - return this._peerStore - } - - set peerStore (peerStore: PeerStore) { - this._peerStore = peerStore - } - - get upgrader (): Upgrader { - if (this._upgrader == null) { - throw new CodeError('upgrader not set', 'ERR_SERVICE_MISSING') - } - - return this._upgrader - } - - set upgrader (upgrader: Upgrader) { - this._upgrader = upgrader - } - - get registrar (): Registrar { - if (this._registrar == null) { - throw new CodeError('registrar not set', 'ERR_SERVICE_MISSING') - } - - return this._registrar - } - - set registrar (registrar: Registrar) { - this._registrar = registrar - } - - get connectionManager (): ConnectionManager { - if (this._connectionManager == null) { - throw new CodeError('connectionManager not set', 'ERR_SERVICE_MISSING') - } - - return this._connectionManager - } - - set connectionManager (connectionManager: ConnectionManager) { - this._connectionManager = connectionManager - } - - get transportManager (): TransportManager { - if (this._transportManager == null) { - throw new CodeError('transportManager not set', 'ERR_SERVICE_MISSING') - } - - return this._transportManager - } - - set transportManager (transportManager: TransportManager) { - this._transportManager = transportManager - } - - get connectionGater (): ConnectionGater { - if (this._connectionGater == null) { - throw new CodeError('connectionGater not set', 'ERR_SERVICE_MISSING') - } - - return this._connectionGater - } - - set connectionGater (connectionGater: ConnectionGater) { - this._connectionGater = connectionGater - } - - get contentRouting (): ContentRouting { - if (this._contentRouting == null) { - throw new CodeError('contentRouting not set', 'ERR_SERVICE_MISSING') - } - - return this._contentRouting - } - - set contentRouting (contentRouting: ContentRouting) { - this._contentRouting = contentRouting - } +const OPTIONAL_SERVICES = [ + 'metrics', + 'connectionProtector' +] + +const NON_SERVICE_PROPERTIES = [ + 'components', + 'isStarted', + 'beforeStart', + 'start', + 'afterStart', + 'beforeStop', + 'stop', + 'afterStop', + 'then' +] + +export function defaultComponents (init: ComponentsInit = {}): Components { + const components = new DefaultComponents(init) + + const proxy = new Proxy(components, { + get (target, prop, receiver) { + if (typeof prop === 'string' && !NON_SERVICE_PROPERTIES.includes(prop)) { + const service = components.components[prop] + + if (service == null && !OPTIONAL_SERVICES.includes(prop)) { + throw new CodeError(`${prop} not set`, 'ERR_SERVICE_MISSING') + } - get peerRouting (): PeerRouting { - if (this._peerRouting == null) { - throw new CodeError('peerRouting not set', 'ERR_SERVICE_MISSING') - } + return service + } - return this._peerRouting - } + return Reflect.get(target, prop, receiver) + }, - set peerRouting (peerRouting: PeerRouting) { - this._peerRouting = peerRouting - } + set (target, prop, value) { + if (typeof prop === 'string') { + components.components[prop] = value + } else { + Reflect.set(target, prop, value) + } - get datastore (): Datastore { - if (this._datastore == null) { - throw new CodeError('datastore not set', 'ERR_SERVICE_MISSING') + return true } + }) - return this._datastore - } - - set datastore (datastore: Datastore) { - this._datastore = datastore - } - - get connectionProtector (): ConnectionProtector | undefined { - return this._connectionProtector - } - - set connectionProtector (connectionProtector: ConnectionProtector | undefined) { - this._connectionProtector = connectionProtector - } - - get metrics (): Metrics | undefined { - return this._metrics - } - - set metrics (metrics: Metrics | undefined) { - this._metrics = metrics - } - - get dht (): DualDHT | undefined { - return this._dht - } - - set dht (dht: DualDHT | undefined) { - this._dht = dht - } - - get pubsub (): PubSub | undefined { - return this._pubsub - } - - set pubsub (pubsub: PubSub | undefined) { - this._pubsub = pubsub - } + // @ts-expect-error component keys are proxied + return proxy } diff --git a/src/config.ts b/src/config.ts index 7d6d7fc150..104386c344 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,5 @@ import mergeOptions from 'merge-options' import { dnsaddrResolver } from '@multiformats/multiaddr/resolvers' -import { AGENT_VERSION } from './identify/consts.js' import { publicAddressesFirst } from '@libp2p/utils/address-sort' import { FaultTolerance } from '@libp2p/interface-transport' import type { Multiaddr } from '@multiformats/multiaddr' @@ -8,7 +7,7 @@ import type { Libp2pInit } from './index.js' import { codes, messages } from './errors.js' import { CodeError } from '@libp2p/interfaces/errors' import type { RecursivePartial } from '@libp2p/interfaces' -import { isNode, isBrowser, isWebWorker, isElectronMain, isElectronRenderer, isReactNative } from 'wherearewe' +import type { ServiceMap } from '@libp2p/interface-libp2p' const DefaultConfig: Partial = { addresses: { @@ -32,7 +31,9 @@ const DefaultConfig: Partial = { interval: 6e5, bootDelay: 10e3 } - }, + } +/* + nat: { enabled: true, ttl: 7200, @@ -77,10 +78,12 @@ const DefaultConfig: Partial = { startupDelay: 5000, refreshInterval: 60000 } + +*/ } -export function validateConfig (opts: RecursivePartial): Libp2pInit { - const resultingOptions: Libp2pInit = mergeOptions(DefaultConfig, opts) +export function validateConfig (opts: RecursivePartial>): Libp2pInit { + const resultingOptions: Libp2pInit = mergeOptions(DefaultConfig, opts) if (resultingOptions.transports == null || resultingOptions.transports.length < 1) { throw new CodeError(messages.ERR_TRANSPORTS_REQUIRED, codes.ERR_TRANSPORTS_REQUIRED) @@ -94,14 +97,5 @@ export function validateConfig (opts: RecursivePartial): Libp2pInit throw new CodeError(messages.ERR_PROTECTOR_REQUIRED, codes.ERR_PROTECTOR_REQUIRED) } - // Append user agent version to default AGENT_VERSION depending on the environment - if (resultingOptions.identify.host.agentVersion === AGENT_VERSION) { - if (isNode || isElectronMain) { - resultingOptions.identify.host.agentVersion += ` UserAgent=${globalThis.process.version}` - } else if (isBrowser || isWebWorker || isElectronRenderer || isReactNative) { - resultingOptions.identify.host.agentVersion += ` UserAgent=${globalThis.navigator.userAgent}` - } - } - return resultingOptions } diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index cc064aa5da..8ae27edfc3 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -479,6 +479,10 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { } async openConnection (peerIdOrMultiaddr: PeerId | Multiaddr | Multiaddr[], options: OpenConnectionOptions = {}): Promise { + if (!this.isStarted()) { + throw new CodeError('Not started', codes.ERR_NODE_NOT_STARTED) + } + const { peerId } = getPeerAddress(peerIdOrMultiaddr) if (peerId != null) { diff --git a/src/content-routing/index.ts b/src/content-routing/index.ts index 5d5603d344..c3e0ed75a7 100644 --- a/src/content-routing/index.ts +++ b/src/content-routing/index.ts @@ -5,7 +5,6 @@ import { uniquePeers, requirePeers } from './utils.js' -import drain from 'it-drain' import merge from 'it-merge' import { pipe } from 'it-pipe' import type { ContentRouting } from '@libp2p/interface-content-routing' @@ -13,9 +12,7 @@ import type { AbortOptions } from '@libp2p/interfaces' import type { Startable } from '@libp2p/interfaces/startable' import type { CID } from 'multiformats/cid' import type { PeerStore } from '@libp2p/interface-peer-store' -import type { DualDHT } from '@libp2p/interface-dht' import type { PeerInfo } from '@libp2p/interface-peer-info' -import type { PeerId } from '@libp2p/interface-peer-id' export interface CompoundContentRoutingInit { routers: ContentRouting[] @@ -23,7 +20,6 @@ export interface CompoundContentRoutingInit { export interface CompoundContentRoutingComponents { peerStore: PeerStore - dht?: DualDHT } export class CompoundContentRouting implements ContentRouting, Startable { @@ -87,11 +83,9 @@ export class CompoundContentRouting implements ContentRouting, Startable { throw new CodeError(messages.NOT_STARTED_YET, codes.DHT_NOT_STARTED) } - const dht = this.components.dht - - if (dht != null) { - await drain(dht.put(key, value, options)) - } + await Promise.all(this.routers.map(async (router) => { + await router.put(key, value, options) + })) } /** @@ -103,50 +97,8 @@ export class CompoundContentRouting implements ContentRouting, Startable { throw new CodeError(messages.NOT_STARTED_YET, codes.DHT_NOT_STARTED) } - const dht = this.components.dht - - if (dht != null) { - for await (const event of dht.get(key, options)) { - if (event.name === 'VALUE') { - return event.value - } - } - } - - throw new CodeError(messages.NOT_FOUND, codes.ERR_NOT_FOUND) - } - - /** - * Get the `n` values to the given key without sorting - */ - async * getMany (key: Uint8Array, nVals: number, options: AbortOptions): AsyncIterable<{ from: PeerId, val: Uint8Array }> { - if (!this.isStarted()) { - throw new CodeError(messages.NOT_STARTED_YET, codes.DHT_NOT_STARTED) - } - - if (nVals == null || nVals === 0) { - return - } - - let gotValues = 0 - const dht = this.components.dht - - if (dht != null) { - for await (const event of dht.get(key, options)) { - if (event.name === 'VALUE') { - yield { from: event.from, val: event.value } - - gotValues++ - - if (gotValues === nVals) { - break - } - } - } - } - - if (gotValues === 0) { - throw new CodeError(messages.NOT_FOUND, codes.ERR_NOT_FOUND) - } + return await Promise.any(this.routers.map(async (router) => { + return await router.get(key, options) + })) } } diff --git a/src/dht/dht-content-routing.ts b/src/dht/dht-content-routing.ts deleted file mode 100644 index 4c10d5bcf0..0000000000 --- a/src/dht/dht-content-routing.ts +++ /dev/null @@ -1,44 +0,0 @@ -import drain from 'it-drain' -import { CodeError } from '@libp2p/interfaces/errors' -import type { DHT } from '@libp2p/interface-dht' -import type { ContentRouting } from '@libp2p/interface-content-routing' -import type { CID } from 'multiformats/cid' -import type { AbortOptions } from '@libp2p/interfaces' -import type { PeerInfo } from '@libp2p/interface-peer-info' - -/** - * Wrapper class to convert events into returned values - */ -export class DHTContentRouting implements ContentRouting { - private readonly dht: DHT - - constructor (dht: DHT) { - this.dht = dht - } - - async provide (cid: CID): Promise { - await drain(this.dht.provide(cid)) - } - - async * findProviders (cid: CID, options: AbortOptions = {}): AsyncGenerator { - for await (const event of this.dht.findProviders(cid, options)) { - if (event.name === 'PROVIDER') { - yield * event.providers - } - } - } - - async put (key: Uint8Array, value: Uint8Array, options?: AbortOptions): Promise { - await drain(this.dht.put(key, value, options)) - } - - async get (key: Uint8Array, options?: AbortOptions): Promise { - for await (const event of this.dht.get(key, options)) { - if (event.name === 'VALUE') { - return event.value - } - } - - throw new CodeError('Not found', 'ERR_NOT_FOUND') - } -} diff --git a/src/dht/dht-peer-routing.ts b/src/dht/dht-peer-routing.ts deleted file mode 100644 index 61ff4f4820..0000000000 --- a/src/dht/dht-peer-routing.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { CodeError } from '@libp2p/interfaces/errors' -import { messages, codes } from '../errors.js' -import type { PeerRouting } from '@libp2p/interface-peer-routing' -import type { DHT } from '@libp2p/interface-dht' -import type { PeerId } from '@libp2p/interface-peer-id' -import type { AbortOptions } from '@libp2p/interfaces' -import type { PeerInfo } from '@libp2p/interface-peer-info' - -/** - * Wrapper class to convert events into returned values - */ -export class DHTPeerRouting implements PeerRouting { - private readonly dht: DHT - - constructor (dht: DHT) { - this.dht = dht - } - - async findPeer (peerId: PeerId, options: AbortOptions = {}): Promise { - for await (const event of this.dht.findPeer(peerId, options)) { - if (event.name === 'FINAL_PEER') { - return event.peer - } - } - - throw new CodeError(messages.NOT_FOUND, codes.ERR_NOT_FOUND) - } - - async * getClosestPeers (key: Uint8Array, options: AbortOptions = {}): AsyncIterable { - for await (const event of this.dht.getClosestPeers(key, options)) { - if (event.name === 'FINAL_PEER') { - yield event.peer - } - } - } -} diff --git a/src/dht/dummy-dht.ts b/src/dht/dummy-dht.ts deleted file mode 100644 index 19e56f65df..0000000000 --- a/src/dht/dummy-dht.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { DualDHT, QueryEvent, SingleDHT } from '@libp2p/interface-dht' -import type { PeerDiscoveryEvents } from '@libp2p/interface-peer-discovery' -import { CodeError } from '@libp2p/interfaces/errors' -import { messages, codes } from '../errors.js' -import { EventEmitter } from '@libp2p/interfaces/events' -import { symbol } from '@libp2p/interface-peer-discovery' - -export class DummyDHT extends EventEmitter implements DualDHT { - get [symbol] (): true { - return true - } - - get [Symbol.toStringTag] (): '@libp2p/dummy-dht' { - return '@libp2p/dummy-dht' - } - - get wan (): SingleDHT { - throw new CodeError(messages.DHT_DISABLED, codes.DHT_DISABLED) - } - - get lan (): SingleDHT { - throw new CodeError(messages.DHT_DISABLED, codes.DHT_DISABLED) - } - - get (): AsyncIterable { - throw new CodeError(messages.DHT_DISABLED, codes.DHT_DISABLED) - } - - findProviders (): AsyncIterable { - throw new CodeError(messages.DHT_DISABLED, codes.DHT_DISABLED) - } - - findPeer (): AsyncIterable { - throw new CodeError(messages.DHT_DISABLED, codes.DHT_DISABLED) - } - - getClosestPeers (): AsyncIterable { - throw new CodeError(messages.DHT_DISABLED, codes.DHT_DISABLED) - } - - provide (): AsyncIterable { - throw new CodeError(messages.DHT_DISABLED, codes.DHT_DISABLED) - } - - put (): AsyncIterable { - throw new CodeError(messages.DHT_DISABLED, codes.DHT_DISABLED) - } - - async getMode (): Promise<'client' | 'server'> { - throw new CodeError(messages.DHT_DISABLED, codes.DHT_DISABLED) - } - - async setMode (): Promise { - throw new CodeError(messages.DHT_DISABLED, codes.DHT_DISABLED) - } - - async refreshRoutingTable (): Promise { - throw new CodeError(messages.DHT_DISABLED, codes.DHT_DISABLED) - } -} diff --git a/src/fetch/index.ts b/src/fetch/index.ts index b3ae99ca0a..23b851fb43 100644 --- a/src/fetch/index.ts +++ b/src/fetch/index.ts @@ -20,15 +20,17 @@ import type { ConnectionManager } from '@libp2p/interface-connection-manager' const log = logger('libp2p:fetch') +const DEFAULT_TIMEOUT = 10000 + export interface FetchServiceInit { - protocolPrefix: string - maxInboundStreams: number - maxOutboundStreams: number + protocolPrefix?: string + maxInboundStreams?: number + maxOutboundStreams?: number /** * How long we should wait for a remote peer to send any data */ - timeout: number + timeout?: number } export interface HandleMessageOptions { @@ -45,13 +47,51 @@ export interface FetchServiceComponents { connectionManager: ConnectionManager } +export interface FetchService { + /** + * The protocol name used by this fetch service + */ + protocol: string + + /** + * Sends a request to fetch the value associated with the given key from the given peer + */ + fetch: (peer: PeerId, key: string, options?: AbortOptions) => Promise + + /** + * Registers a new lookup callback that can map keys to values, for a given set of keys that + * share the same prefix + * + * @example + * + * ```js + * // ... + * libp2p.fetchService.registerLookupFunction('/prefix', (key) => { ... }) + * ``` + */ + registerLookupFunction: (prefix: string, lookup: LookupFunction) => void + + /** + * Registers a new lookup callback that can map keys to values, for a given set of keys that + * share the same prefix. + * + * @example + * + * ```js + * // ... + * libp2p.fetchService.unregisterLookupFunction('/prefix') + * ``` + */ + unregisterLookupFunction: (prefix: string, lookup?: LookupFunction) => void +} + /** * A simple libp2p protocol for requesting a value corresponding to a key from a peer. * Developers can register one or more lookup function for retrieving the value corresponding to * a given key. Each lookup function must act on a distinct part of the overall key space, defined * by a fixed prefix that all keys that should be routed to that lookup function will start with. */ -export class FetchService implements Startable { +class DefaultFetchService implements Startable, FetchService { public readonly protocol: string private readonly components: FetchServiceComponents private readonly lookupFunctions: Map @@ -106,7 +146,7 @@ export class FetchService implements Startable { // create a timeout if no abort signal passed if (signal == null) { log('using default timeout of %d ms', this.init.timeout) - timeoutController = new TimeoutController(this.init.timeout) + timeoutController = new TimeoutController(this.init.timeout ?? DEFAULT_TIMEOUT) signal = timeoutController.signal try { @@ -274,3 +314,7 @@ export class FetchService implements Startable { this.lookupFunctions.delete(prefix) } } + +export function fetchService (init: FetchServiceInit = {}): (components: FetchServiceComponents) => FetchService { + return (components) => new DefaultFetchService(components, init) +} diff --git a/src/identify/index.ts b/src/identify/index.ts index 2ec3fbd85a..e1db6c21b7 100644 --- a/src/identify/index.ts +++ b/src/identify/index.ts @@ -8,6 +8,7 @@ import { Multiaddr, multiaddr, protocols } from '@multiformats/multiaddr' import { Identify } from './pb/message.js' import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record' import { + AGENT_VERSION, MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_PUSH, IDENTIFY_PROTOCOL_VERSION, @@ -33,42 +34,39 @@ import type { EventEmitter } from '@libp2p/interfaces/events' import type { Libp2pEvents } from '@libp2p/interface-libp2p' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { pbStream } from 'it-pb-stream' +import { isNode, isBrowser, isWebWorker, isElectronMain, isElectronRenderer, isReactNative } from 'wherearewe' const log = logger('libp2p:identify') // https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L52 const MAX_IDENTIFY_MESSAGE_SIZE = 1024 * 8 -export interface HostProperties { - agentVersion: string -} - export interface IdentifyServiceInit { /** * The prefix to use for the protocol (default: 'ipfs') */ - protocolPrefix: string + protocolPrefix?: string /** * What details we should send as part of an identify message */ - host: HostProperties + agentVersion?: string /** * How long we should wait for a remote peer to send their identify response */ - timeout: number + timeout?: number /** * Identify responses larger than this in bytes will be rejected (default: 8192) */ maxIdentifyMessageSize?: number - maxInboundStreams: number - maxOutboundStreams: number + maxInboundStreams?: number + maxOutboundStreams?: number - maxPushIncomingStreams: number - maxPushOutgoingStreams: number + maxPushIncomingStreams?: number + maxPushOutgoingStreams?: number maxObservedAddresses?: number } @@ -81,8 +79,34 @@ export interface IdentifyServiceComponents { events: EventEmitter } -export class IdentifyService implements Startable { - private readonly components: IdentifyServiceComponents +export interface IdentifyService { + /** + * Requests the `Identify` message from peer associated with the given `connection`. + * If the identified peer does not match the `PeerId` associated with the connection, + * an error will be thrown. + */ + identify: (connection: Connection, options?: AbortOptions) => Promise + + /** + * Calls `push` on all peer connections + */ + push: () => Promise +} + +const defaultValues = { + protocolPrefix: 'ipfs', + agentVersion: AGENT_VERSION, + // https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L48 + timeout: 60000, + maxInboundStreams: 1, + maxOutboundStreams: 1, + maxPushIncomingStreams: 1, + maxPushOutgoingStreams: 1, + maxObservedAddresses: 10, + maxIdentifyMessageSize: 8192 +} + +class DefaultIdentifyService implements Startable, IdentifyService { private readonly identifyProtocolStr: string private readonly identifyPushProtocolStr: string public readonly host: { @@ -90,33 +114,63 @@ export class IdentifyService implements Startable { agentVersion: string } - private readonly init: IdentifyServiceInit private started: boolean + private readonly timeout: number + private readonly peerId: PeerId + private readonly peerStore: PeerStore + private readonly registrar: Registrar + private readonly connectionManager: ConnectionManager + private readonly addressManager: AddressManager + private readonly maxInboundStreams: number + private readonly maxOutboundStreams: number + private readonly maxPushIncomingStreams: number + private readonly maxPushOutgoingStreams: number + private readonly maxIdentifyMessageSize: number + private readonly maxObservedAddresses: number constructor (components: IdentifyServiceComponents, init: IdentifyServiceInit) { - this.components = components this.started = false - this.init = init - - this.identifyProtocolStr = `/${init.protocolPrefix}/${MULTICODEC_IDENTIFY_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PROTOCOL_VERSION}` - this.identifyPushProtocolStr = `/${init.protocolPrefix}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION}` + this.peerId = components.peerId + this.peerStore = components.peerStore + this.registrar = components.registrar + this.addressManager = components.addressManager + this.connectionManager = components.connectionManager + + this.identifyProtocolStr = `/${init.protocolPrefix ?? defaultValues.protocolPrefix}/${MULTICODEC_IDENTIFY_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PROTOCOL_VERSION}` + this.identifyPushProtocolStr = `/${init.protocolPrefix ?? defaultValues.protocolPrefix}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION}` + this.timeout = init.timeout ?? defaultValues.timeout + this.maxInboundStreams = init.maxInboundStreams ?? defaultValues.maxInboundStreams + this.maxOutboundStreams = init.maxOutboundStreams ?? defaultValues.maxOutboundStreams + this.maxPushIncomingStreams = init.maxPushIncomingStreams ?? defaultValues.maxPushIncomingStreams + this.maxPushOutgoingStreams = init.maxPushOutgoingStreams ?? defaultValues.maxPushOutgoingStreams + this.maxIdentifyMessageSize = init.maxIdentifyMessageSize ?? defaultValues.maxIdentifyMessageSize + this.maxObservedAddresses = init.maxObservedAddresses ?? defaultValues.maxObservedAddresses // Store self host metadata this.host = { - protocolVersion: `${init.protocolPrefix}/${IDENTIFY_PROTOCOL_VERSION}`, - ...init.host + protocolVersion: `${init.protocolPrefix ?? defaultValues.protocolPrefix}/${IDENTIFY_PROTOCOL_VERSION}`, + agentVersion: init.agentVersion ?? defaultValues.agentVersion } // When a new connection happens, trigger identify - this.components.events.addEventListener('connection:open', (evt) => { + components.events.addEventListener('connection:open', (evt) => { const connection = evt.detail this.identify(connection).catch(err => { log.error('error during identify trigged by connection:open', err) }) }) // When self peer record changes, trigger identify-push - this.components.events.addEventListener('self:peer:update', (evt) => { - void this.pushToPeerStore().catch(err => { log.error(err) }) + components.events.addEventListener('self:peer:update', (evt) => { + void this.push().catch(err => { log.error(err) }) }) + + // Append user agent version to default AGENT_VERSION depending on the environment + if (this.host.agentVersion === AGENT_VERSION) { + if (isNode || isElectronMain) { + this.host.agentVersion += ` UserAgent=${globalThis.process.version}` + } else if (isBrowser || isWebWorker || isElectronRenderer || isReactNative) { + this.host.agentVersion += ` UserAgent=${globalThis.navigator.userAgent}` + } + } } isStarted (): boolean { @@ -128,36 +182,36 @@ export class IdentifyService implements Startable { return } - await this.components.peerStore.merge(this.components.peerId, { + await this.peerStore.merge(this.peerId, { metadata: { AgentVersion: uint8ArrayFromString(this.host.agentVersion), ProtocolVersion: uint8ArrayFromString(this.host.protocolVersion) } }) - await this.components.registrar.handle(this.identifyProtocolStr, (data) => { + await this.registrar.handle(this.identifyProtocolStr, (data) => { void this._handleIdentify(data).catch(err => { log.error(err) }) }, { - maxInboundStreams: this.init.maxInboundStreams, - maxOutboundStreams: this.init.maxOutboundStreams + maxInboundStreams: this.maxInboundStreams, + maxOutboundStreams: this.maxOutboundStreams }) - await this.components.registrar.handle(this.identifyPushProtocolStr, (data) => { + await this.registrar.handle(this.identifyPushProtocolStr, (data) => { void this._handlePush(data).catch(err => { log.error(err) }) }, { - maxInboundStreams: this.init.maxPushIncomingStreams, - maxOutboundStreams: this.init.maxPushOutgoingStreams + maxInboundStreams: this.maxPushIncomingStreams, + maxOutboundStreams: this.maxPushOutgoingStreams }) this.started = true } async stop (): Promise { - await this.components.registrar.unhandle(this.identifyProtocolStr) - await this.components.registrar.unhandle(this.identifyPushProtocolStr) + await this.registrar.unhandle(this.identifyProtocolStr) + await this.registrar.unhandle(this.identifyPushProtocolStr) this.started = false } @@ -165,21 +219,21 @@ export class IdentifyService implements Startable { /** * Send an Identify Push update to the list of connections */ - async push (connections: Connection[]): Promise { - const listenAddresses = this.components.addressManager.getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code)) + async pushToConnections (connections: Connection[]): Promise { + const listenAddresses = this.addressManager.getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code)) const peerRecord = new PeerRecord({ - peerId: this.components.peerId, + peerId: this.peerId, multiaddrs: listenAddresses }) - const signedPeerRecord = await RecordEnvelope.seal(peerRecord, this.components.peerId) - const supportedProtocols = this.components.registrar.getProtocols() - const peer = await this.components.peerStore.get(this.components.peerId) + const signedPeerRecord = await RecordEnvelope.seal(peerRecord, this.peerId) + const supportedProtocols = this.registrar.getProtocols() + const peer = await this.peerStore.get(this.peerId) const agentVersion = uint8ArrayToString(peer.metadata.get('AgentVersion') ?? uint8ArrayFromString(this.host.agentVersion)) const protocolVersion = uint8ArrayToString(peer.metadata.get('ProtocolVersion') ?? uint8ArrayFromString(this.host.protocolVersion)) const pushes = connections.map(async connection => { let stream: Stream | undefined - const timeoutController = new TimeoutController(this.init.timeout) + const timeoutController = new TimeoutController(this.timeout) try { // fails on node < 15.4 @@ -222,7 +276,7 @@ export class IdentifyService implements Startable { /** * Calls `push` on all peer connections */ - async pushToPeerStore (): Promise { + async push (): Promise { // Do not try to push if we are not running if (!this.isStarted()) { return @@ -230,18 +284,25 @@ export class IdentifyService implements Startable { const connections: Connection[] = [] - for (const conn of this.components.connectionManager.getConnections()) { - const peerId = conn.remotePeer - const peer = await this.components.peerStore.get(peerId) + await Promise.all( + this.connectionManager.getConnections().map(async conn => { + try { + const peer = await this.peerStore.get(conn.remotePeer) - if (!peer.protocols.includes(this.identifyPushProtocolStr)) { - continue - } + if (!peer.protocols.includes(this.identifyPushProtocolStr)) { + return + } - connections.push(conn) - } + connections.push(conn) + } catch (err: any) { + if (err.code !== codes.ERR_NOT_FOUND) { + throw err + } + } + }) + ) - await this.push(connections) + await this.pushToConnections(connections) } async _identify (connection: Connection, options: AbortOptions = {}): Promise { @@ -251,7 +312,7 @@ export class IdentifyService implements Startable { // create a timeout if no abort signal passed if (signal == null) { - timeoutController = new TimeoutController(this.init.timeout) + timeoutController = new TimeoutController(this.timeout) signal = timeoutController.signal try { @@ -272,7 +333,7 @@ export class IdentifyService implements Startable { [], source, (source) => lp.decode(source, { - maxDataLength: this.init.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE + maxDataLength: this.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE }), async (source) => await first(source) ) @@ -297,11 +358,6 @@ export class IdentifyService implements Startable { } } - /** - * Requests the `Identify` message from peer associated with the given `connection`. - * If the identified peer does not match the `PeerId` associated with the connection, - * an error will be thrown. - */ async identify (connection: Connection, options: AbortOptions = {}): Promise { const message = await this._identify(connection, options) const { @@ -320,20 +376,20 @@ export class IdentifyService implements Startable { throw new CodeError('identified peer does not match the expected peer', codes.ERR_INVALID_PEER) } - if (this.components.peerId.equals(id)) { + if (this.peerId.equals(id)) { throw new CodeError('identified peer is our own peer id?', codes.ERR_INVALID_PEER) } // Get the observedAddr if there is one - const cleanObservedAddr = IdentifyService.getCleanMultiaddr(observedAddr) + const cleanObservedAddr = getCleanMultiaddr(observedAddr) log('identify completed for peer %p and protocols %o', id, protocols) log('our observed address is %s', cleanObservedAddr) if (cleanObservedAddr != null && - this.components.addressManager.getObservedAddrs().length < (this.init.maxObservedAddresses ?? Infinity)) { + this.addressManager.getObservedAddrs().length < (this.maxObservedAddresses ?? Infinity)) { log('storing our observed address %s', cleanObservedAddr?.toString()) - this.components.addressManager.addObservedAddr(cleanObservedAddr) + this.addressManager.addObservedAddr(cleanObservedAddr) } await this.#consumeIdentifyMessage(connection.remotePeer, message) @@ -345,7 +401,7 @@ export class IdentifyService implements Startable { */ async _handleIdentify (data: IncomingStreamData): Promise { const { connection, stream } = data - const timeoutController = new TimeoutController(this.init.timeout) + const timeoutController = new TimeoutController(this.timeout) try { // fails on node < 15.4 @@ -353,18 +409,18 @@ export class IdentifyService implements Startable { } catch {} try { - const publicKey = this.components.peerId.publicKey ?? new Uint8Array(0) - const peerData = await this.components.peerStore.get(this.components.peerId) - const multiaddrs = this.components.addressManager.getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code)) + const publicKey = this.peerId.publicKey ?? new Uint8Array(0) + const peerData = await this.peerStore.get(this.peerId) + const multiaddrs = this.addressManager.getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code)) let signedPeerRecord = peerData.peerRecordEnvelope if (multiaddrs.length > 0 && signedPeerRecord == null) { const peerRecord = new PeerRecord({ - peerId: this.components.peerId, + peerId: this.peerId, multiaddrs }) - const envelope = await RecordEnvelope.seal(peerRecord, this.components.peerId) + const envelope = await RecordEnvelope.seal(peerRecord, this.peerId) signedPeerRecord = envelope.marshal().subarray() } @@ -398,14 +454,14 @@ export class IdentifyService implements Startable { const { connection, stream } = data try { - if (this.components.peerId.equals(connection.remotePeer)) { + if (this.peerId.equals(connection.remotePeer)) { throw new Error('received push from ourselves?') } // make stream abortable - const source = abortableDuplex(stream, AbortSignal.timeout(this.init.timeout)) + const source = abortableDuplex(stream, AbortSignal.timeout(this.timeout)) const pb = pbStream(source, { - maxDataLength: this.init.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE + maxDataLength: this.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE }) const message = await pb.readPB(Identify) @@ -447,7 +503,7 @@ export class IdentifyService implements Startable { let peer: Peer | undefined try { - peer = await this.components.peerStore.get(peerRecord.peerId) + peer = await this.peerStore.get(peerRecord.peerId) } catch (err: any) { if (err.code !== 'ERR_NOT_FOUND') { throw err @@ -477,7 +533,7 @@ export class IdentifyService implements Startable { metadata.set('ProtocolVersion', uint8ArrayFromString(message.protocolVersion)) } - await this.components.peerStore.patch(peerRecord.peerId, { + await this.peerStore.patch(peerRecord.peerId, { peerRecordEnvelope: message.signedPeerRecord, protocols: message.protocols, addresses: peerRecord.multiaddrs.map(multiaddr => ({ @@ -489,17 +545,17 @@ export class IdentifyService implements Startable { log('consumed signedPeerRecord sent in push from %p', remotePeer) } +} - /** - * Takes the `addr` and converts it to a Multiaddr if possible - */ - static getCleanMultiaddr (addr: Uint8Array | string | null | undefined): Multiaddr | undefined { - if (addr != null && addr.length > 0) { - try { - return multiaddr(addr) - } catch { +/** + * Takes the `addr` and converts it to a Multiaddr if possible + */ +function getCleanMultiaddr (addr: Uint8Array | string | null | undefined): Multiaddr | undefined { + if (addr != null && addr.length > 0) { + try { + return multiaddr(addr) + } catch { - } } } } @@ -513,3 +569,7 @@ export const multicodecs = { } export const Message = { Identify } + +export function identifyService (init: IdentifyServiceInit = {}): (components: IdentifyServiceComponents) => IdentifyService { + return (components) => new DefaultIdentifyService(components, init) +} diff --git a/src/index.ts b/src/index.ts index bba959a515..154586ea15 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,8 +17,6 @@ import { createLibp2pNode } from './libp2p.js' import type { RecursivePartial } from '@libp2p/interfaces' import type { TransportManagerInit } from './transport-manager.js' -import type { IdentifyServiceInit } from './identify/index.js' -import type { DualDHT } from '@libp2p/interface-dht' import type { Datastore } from 'interface-datastore' import type { PeerId } from '@libp2p/interface-peer-id' import type { PeerDiscovery } from '@libp2p/interface-peer-discovery' @@ -29,25 +27,23 @@ import type { StreamMuxerFactory } from '@libp2p/interface-stream-muxer' import type { ConnectionEncrypter } from '@libp2p/interface-connection-encrypter' import type { PeerRouting } from '@libp2p/interface-peer-routing' import type { ContentRouting } from '@libp2p/interface-content-routing' -import type { PubSub } from '@libp2p/interface-pubsub' import type { Metrics } from '@libp2p/interface-metrics' -import type { PingServiceInit } from './ping/index.js' -import type { FetchServiceInit } from './fetch/index.js' -import type { AutonatServiceInit } from './autonat/index.js' import type { Components } from './components.js' -import type { Libp2p } from '@libp2p/interface-libp2p' +import type { Libp2p, ServiceMap } from '@libp2p/interface-libp2p' import type { KeyChainInit } from '@libp2p/keychain' -import type { NatManagerInit } from './nat-manager.js' import type { AddressManagerInit } from './address-manager/index.js' import type { PeerRoutingInit } from './peer-routing.js' import type { ConnectionManagerInit } from './connection-manager/index.js' -import type { CircuitRelayService } from './circuit-relay/index.js' import type { PersistentPeerStoreInit } from '@libp2p/peer-store' +export type ServiceFactoryMap = Record> = { + [Property in keyof T]: (components: Components) => T[Property] +} + /** * For Libp2p configurations and modules details read the [Configuration Document](./CONFIGURATION.md). */ -export interface Libp2pInit { +export interface Libp2pInit { /** * peerId instance (it will be created if not provided) */ @@ -95,37 +91,6 @@ export interface Libp2pInit { */ keychain: KeyChainInit - /** - * The NAT manager controls uPNP hole punching - */ - nat: NatManagerInit - - /** - * If configured as a relay this node will relay certain - * types of traffic for other peers - */ - relay: (components: Components) => CircuitRelayService - - /** - * identify protocol options - */ - identify: IdentifyServiceInit - - /** - * ping protocol options - */ - ping: PingServiceInit - - /** - * fetch protocol options - */ - fetch: FetchServiceInit - - /** - * autonat protocol options - */ - autonat: AutonatServiceInit - /** * An array that must include at least 1 compliant transport */ @@ -136,30 +101,25 @@ export interface Libp2pInit { peerRouters?: Array<(components: Components) => PeerRouting> contentRouters?: Array<(components: Components) => ContentRouting> - /** - * Pass a DHT implementation to enable DHT operations - */ - dht?: (components: Components) => DualDHT - /** * A Metrics implementation can be supplied to collect metrics on this node */ metrics?: (components: Components) => Metrics /** - * If a PubSub implmentation is supplied, PubSub operations will become available + * A ConnectionProtector can be used to create a secure overlay on top of the network using pre-shared keys */ - pubsub?: (components: Components) => PubSub + connectionProtector?: (components: Components) => ConnectionProtector /** - * A ConnectionProtector can be used to create a secure overlay on top of the network using pre-shared keys + * Arbitrary libp2p modules */ - connectionProtector?: (components: Components) => ConnectionProtector + services: ServiceFactoryMap } export type { Libp2p } -export type Libp2pOptions = RecursivePartial & { start?: boolean } +export type Libp2pOptions = RecursivePartial> & { start?: boolean } /** * Returns a new instance of the Libp2p interface, generating a new PeerId @@ -186,7 +146,7 @@ export type Libp2pOptions = RecursivePartial & { start?: boolean } * const libp2p = await createLibp2p(options) * ``` */ -export async function createLibp2p (options: Libp2pOptions): Promise { +export async function createLibp2p (options: Libp2pOptions): Promise> { const node = await createLibp2pNode(options) if (options.start !== false) { diff --git a/src/libp2p.ts b/src/libp2p.ts index 1ab8222349..565bc8a623 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -1,7 +1,6 @@ import { logger } from '@libp2p/logger' import type { AbortOptions } from '@libp2p/interfaces' import { EventEmitter, CustomEvent } from '@libp2p/interfaces/events' -import { Startable, isStartable } from '@libp2p/interfaces/startable' import { isMultiaddr, Multiaddr } from '@multiformats/multiaddr' import { MemoryDatastore } from 'datastore-core/memory' import { DefaultPeerRouting } from './peer-routing.js' @@ -13,68 +12,48 @@ import { DefaultKeyChain } from '@libp2p/keychain' import { DefaultTransportManager } from './transport-manager.js' import { DefaultUpgrader } from './upgrader.js' import { DefaultRegistrar } from './registrar.js' -import { IdentifyService } from './identify/index.js' -import { FetchService } from './fetch/index.js' -import { PingService } from './ping/index.js' -import { NatManager } from './nat-manager.js' -import { DHTPeerRouting } from './dht/dht-peer-routing.js' import { PersistentPeerStore } from '@libp2p/peer-store' -import { DHTContentRouting } from './dht/dht-content-routing.js' -import { AutonatService } from './autonat/index.js' -import { DefaultComponents } from './components.js' +import { defaultComponents } from './components.js' import type { Components } from './components.js' import type { PeerId } from '@libp2p/interface-peer-id' import type { Connection, Stream } from '@libp2p/interface-connection' -import type { PeerRouting } from '@libp2p/interface-peer-routing' -import type { ContentRouting } from '@libp2p/interface-content-routing' -import type { PubSub } from '@libp2p/interface-pubsub' -import type { Registrar, StreamHandler, StreamHandlerOptions, Topology } from '@libp2p/interface-registrar' -import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import type { StreamHandler, StreamHandlerOptions, Topology } from '@libp2p/interface-registrar' import type { PeerInfo } from '@libp2p/interface-peer-info' import type { Libp2p, Libp2pInit, Libp2pOptions } from './index.js' -import { validateConfig } from './config.js' -import { createEd25519PeerId } from '@libp2p/peer-id-factory' import type { PeerStore } from '@libp2p/interface-peer-store' -import type { DualDHT } from '@libp2p/interface-dht' import { concat as uint8ArrayConcat } from 'uint8arrays/concat' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { CodeError } from '@libp2p/interfaces/errors' import type { Metrics } from '@libp2p/interface-metrics' -import { DummyDHT } from './dht/dummy-dht.js' -import { DummyPubSub } from './pubsub/dummy-pubsub.js' import { PeerSet } from '@libp2p/peer-collections' +import type { KeyChain } from '@libp2p/interface-keychain' +import type { Libp2pEvents, PendingDial, ServiceMap } from '@libp2p/interface-libp2p' +import { setMaxListeners } from 'events' +import { unmarshalPublicKey } from '@libp2p/crypto/keys' import { peerIdFromString } from '@libp2p/peer-id' import type { Datastore } from 'interface-datastore' -import type { KeyChain } from '@libp2p/interface-keychain' import mergeOptions from 'merge-options' -import type { CircuitRelayService } from './circuit-relay/index.js' -import type { Libp2pEvents, PendingDial } from '@libp2p/interface-libp2p' -import { setMaxListeners } from 'events' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { validateConfig } from './config.js' +import { ContentRouting, contentRouting } from '@libp2p/interface-content-routing' +import { PeerRouting, peerRouting } from '@libp2p/interface-peer-routing' +import { peerDiscovery } from '@libp2p/interface-peer-discovery' const log = logger('libp2p') -export class Libp2pNode extends EventEmitter implements Libp2p { +export class Libp2pNode extends EventEmitter implements Libp2p { public peerId: PeerId - public dht: DualDHT - public pubsub: PubSub - public identifyService: IdentifyService - public circuitService?: CircuitRelayService - public fetchService: FetchService - public pingService: PingService - public autonatService: AutonatService - public components: Components public peerStore: PeerStore public contentRouting: ContentRouting public peerRouting: PeerRouting public keychain: KeyChain - public connectionManager: ConnectionManager - public registrar: Registrar public metrics?: Metrics + public services: T - private started: boolean - private readonly services: Startable[] + public components: Components + #started: boolean - constructor (init: Libp2pInit) { + constructor (init: Libp2pInit) { super() // event bus - components can listen to this emitter to be notified of system events @@ -95,9 +74,11 @@ export class Libp2pNode extends EventEmitter implements Libp2p { setMaxListeners?.(Infinity, events) } catch {} - this.started = false + this.#started = false this.peerId = init.peerId - const components = this.components = new DefaultComponents({ + // @ts-expect-error {} may not be of type T + this.services = {} + const components = this.components = defaultComponents({ peerId: init.peerId, events, datastore: init.datastore ?? new MemoryDatastore(), @@ -114,23 +95,18 @@ export class Libp2pNode extends EventEmitter implements Libp2p { ...init.connectionGater } }) - components.peerStore = new PersistentPeerStore(components, { + + this.peerStore = this.configureComponent('peerStore', new PersistentPeerStore(components, { addressFilter: this.components.connectionGater.filterMultiaddrForPeer, ...init.peerStore - }) - - this.services = [ - components - ] + })) // Create Metrics if (init.metrics != null) { - this.metrics = this.components.metrics = this.configureComponent(init.metrics(this.components)) + this.metrics = this.configureComponent('metrics', init.metrics(this.components)) } - this.peerStore = this.components.peerStore - - this.components.events.addEventListener('peer:update', evt => { + components.events.addEventListener('peer:update', evt => { // if there was no peer previously in the peer store this is a new peer if (evt.detail.previous == null) { this.safeDispatchEvent('peer:discovery', { detail: evt.detail.peer }) @@ -139,124 +115,101 @@ export class Libp2pNode extends EventEmitter implements Libp2p { // Set up connection protector if configured if (init.connectionProtector != null) { - this.components.connectionProtector = init.connectionProtector(components) + this.configureComponent('connectionProtector', init.connectionProtector(components)) } // Set up the Upgrader this.components.upgrader = new DefaultUpgrader(this.components, { - connectionEncryption: (init.connectionEncryption ?? []).map(fn => this.configureComponent(fn(this.components))), - muxers: (init.streamMuxers ?? []).map(fn => this.configureComponent(fn(this.components))), + connectionEncryption: (init.connectionEncryption ?? []).map((fn, index) => this.configureComponent(`connection-encryption-${index}`, fn(this.components))), + muxers: (init.streamMuxers ?? []).map((fn, index) => this.configureComponent(`stream-muxers-${index}`, fn(this.components))), inboundUpgradeTimeout: init.connectionManager.inboundUpgradeTimeout }) // Setup the transport manager - this.components.transportManager = new DefaultTransportManager(this.components, init.transportManager) + this.configureComponent('transportManager', new DefaultTransportManager(this.components, init.transportManager)) // Create the Connection Manager - this.connectionManager = this.components.connectionManager = new DefaultConnectionManager(this.components, init.connectionManager) + this.configureComponent('connectionManager', new DefaultConnectionManager(this.components, init.connectionManager)) // Create the Registrar - this.registrar = this.components.registrar = new DefaultRegistrar(this.components) + this.configureComponent('registrar', new DefaultRegistrar(this.components)) // Addresses {listen, announce, noAnnounce} - this.components.addressManager = new DefaultAddressManager(this.components, init.addresses) + this.configureComponent('addressManager', new DefaultAddressManager(this.components, init.addresses)) // Create keychain const keychainOpts = DefaultKeyChain.generateOptions() - this.keychain = this.configureComponent(new DefaultKeyChain(this.components, { + this.keychain = this.configureComponent('keyChain', new DefaultKeyChain(this.components, { ...keychainOpts, ...init.keychain })) - // Create the Nat Manager - this.services.push(new NatManager(this.components, init.nat)) - - // Add the identify service - this.identifyService = new IdentifyService(this.components, { - ...init.identify - }) - this.configureComponent(this.identifyService) - - // dht provided components (peerRouting, contentRouting, dht) - if (init.dht != null) { - this.dht = this.components.dht = init.dht(this.components) - } else { - this.dht = new DummyDHT() - } - - // Create pubsub if provided - if (init.pubsub != null) { - this.pubsub = this.components.pubsub = init.pubsub(this.components) - } else { - this.pubsub = new DummyPubSub() - } - - // Attach remaining APIs - // peer and content routing will automatically get modules from _modules and _dht - - const peerRouters: PeerRouting[] = (init.peerRouters ?? []).map(fn => this.configureComponent(fn(this.components))) - - if (init.dht != null) { - // add dht to routers - peerRouters.push(this.configureComponent(new DHTPeerRouting(this.dht))) - - // use dht for peer discovery - this.dht.addEventListener('peer', (evt) => { - this.onDiscoveryPeer(evt) - }) - } - - this.peerRouting = this.components.peerRouting = this.configureComponent(new DefaultPeerRouting(this.components, { + // Peer routers + const peerRouters: PeerRouting[] = (init.peerRouters ?? []).map((fn, index) => this.configureComponent(`peer-router-${index}`, fn(this.components))) + this.peerRouting = this.components.peerRouting = this.configureComponent('peerRouting', new DefaultPeerRouting(this.components, { ...init.peerRouting, routers: peerRouters })) - const contentRouters: ContentRouting[] = (init.contentRouters ?? []).map(fn => this.configureComponent(fn(this.components))) - - if (init.dht != null) { - // add dht to routers - contentRouters.push(this.configureComponent(new DHTContentRouting(this.dht))) - } - - this.contentRouting = this.components.contentRouting = this.configureComponent(new CompoundContentRouting(this.components, { + // Content routers + const contentRouters: ContentRouting[] = (init.contentRouters ?? []).map((fn, index) => this.configureComponent(`content-router-${index}`, fn(this.components))) + this.contentRouting = this.components.contentRouting = this.configureComponent('contentRouting', new CompoundContentRouting(this.components, { routers: contentRouters })) - this.fetchService = this.configureComponent(new FetchService(this.components, { - ...init.fetch - })) - - this.pingService = this.configureComponent(new PingService(this.components, { - ...init.ping - })) - - this.autonatService = this.configureComponent(new AutonatService(this.components, { - ...init.autonat - })) - - if (init.relay != null) { - this.circuitService = this.configureComponent(init.relay(this.components)) - } - // Discovery modules - for (const fn of init.peerDiscovery ?? []) { - const service = this.configureComponent(fn(this.components)) + ;(init.peerDiscovery ?? []).forEach((fn, index) => { + const service = this.configureComponent(`peer-discovery-${index}`, fn(this.components)) service.addEventListener('peer', (evt) => { - this.onDiscoveryPeer(evt) + this.#onDiscoveryPeer(evt) }) - } + }) // Transport modules - init.transports.forEach((fn) => { - this.components.transportManager.add(this.configureComponent(fn(this.components))) + init.transports.forEach((fn, index) => { + this.components.transportManager.add(this.configureComponent(`transport-${index}`, fn(this.components))) }) - } - private configureComponent (component: T): T { - if (isStartable(component)) { - this.services.push(component) + // User defined modules + if (init.services != null) { + for (const name of Object.keys(init.services)) { + const createService = init.services[name] + const service: any = createService(this.components) + + if (service == null) { + continue + } + + this.services[name as keyof T] = service + this.configureComponent(name, service) + + if (service == null) { + throw new Error('wtf ' + name) + } + + if (service[contentRouting] != null) { + log('registering service %s for content routing', name) + contentRouters.push(service[contentRouting]) + } + + if (service[peerRouting] != null) { + log('registering service %s for peer routing', name) + peerRouters.push(service[peerRouting]) + } + + if (service[peerDiscovery] != null) { + log('registering service %s for peer discovery', name) + service[peerDiscovery].addEventListener('peer', (evt: CustomEvent) => { + this.#onDiscoveryPeer(evt) + }) + } + } } + } + + private configureComponent (name: string, component: T): T { + this.components[name] = component return component } @@ -265,11 +218,11 @@ export class Libp2pNode extends EventEmitter implements Libp2p { * Starts the libp2p node and all its subsystems */ async start (): Promise { - if (this.started) { + if (this.#started) { return } - this.started = true + this.#started = true log('libp2p is starting') @@ -281,27 +234,9 @@ export class Libp2pNode extends EventEmitter implements Libp2p { } try { - await Promise.all( - this.services.map(async service => { - if (service.beforeStart != null) { - await service.beforeStart() - } - }) - ) - - // start any startables - await Promise.all( - this.services.map(async service => { await service.start() }) - ) - - await Promise.all( - this.services.map(async service => { - if (service.afterStart != null) { - await service.afterStart() - } - }) - ) - + await this.components.beforeStart?.() + await this.components.start() + await this.components.afterStart?.() log('libp2p has started') } catch (err: any) { log.error('An error occurred starting libp2p', err) @@ -314,39 +249,23 @@ export class Libp2pNode extends EventEmitter implements Libp2p { * Stop the libp2p node by closing its listeners and open connections */ async stop (): Promise { - if (!this.started) { + if (!this.#started) { return } log('libp2p is stopping') - this.started = false + this.#started = false - await Promise.all( - this.services.map(async service => { - if (service.beforeStop != null) { - await service.beforeStop() - } - }) - ) - - await Promise.all( - this.services.map(async service => { await service.stop() }) - ) - - await Promise.all( - this.services.map(async service => { - if (service.afterStop != null) { - await service.afterStop() - } - }) - ) + await this.components.beforeStop?.() + await this.components.stop() + await this.components.afterStop?.() log('libp2p has stopped') } isStarted (): boolean { - return this.started + return this.#started } getConnections (peerId?: PeerId): Connection[] { @@ -426,6 +345,8 @@ export class Libp2pNode extends EventEmitter implements Libp2p { // search any available content routing methods const bytes = await this.contentRouting.get(peerKey, options) + // ensure the returned key is valid + unmarshalPublicKey(bytes) await this.peerStore.patch(peer, { publicKey: bytes @@ -434,30 +355,6 @@ export class Libp2pNode extends EventEmitter implements Libp2p { return bytes } - async fetch (peer: PeerId | Multiaddr, key: string, options: AbortOptions = {}): Promise { - if (isMultiaddr(peer)) { - const peerId = peerIdFromString(peer.getPeerId() ?? '') - await this.components.peerStore.merge(peerId, { - multiaddrs: [peer] - }) - peer = peerId - } - - return await this.fetchService.fetch(peer, key, options) - } - - async ping (peer: PeerId | Multiaddr, options: AbortOptions = {}): Promise { - if (isMultiaddr(peer)) { - const peerId = peerIdFromString(peer.getPeerId() ?? '') - await this.components.peerStore.merge(peerId, { - multiaddrs: [peer] - }) - peer = peerId - } - - return await this.pingService.ping(peer, options) - } - async handle (protocols: string | string[], handler: StreamHandler, options?: StreamHandlerOptions): Promise { if (!Array.isArray(protocols)) { protocols = [protocols] @@ -483,18 +380,18 @@ export class Libp2pNode extends EventEmitter implements Libp2p { } async register (protocol: string, topology: Topology): Promise { - return await this.registrar.register(protocol, topology) + return await this.components.registrar.register(protocol, topology) } unregister (id: string): void { - this.registrar.unregister(id) + this.components.registrar.unregister(id) } /** * Called whenever peer discovery services emit `peer` events and adds peers * to the peer store. */ - onDiscoveryPeer (evt: CustomEvent): void { + #onDiscoveryPeer (evt: CustomEvent): void { const { detail: peer } = evt if (peer.id.toString() === this.peerId.toString()) { @@ -514,7 +411,7 @@ export class Libp2pNode extends EventEmitter implements Libp2p { * Returns a new Libp2pNode instance - this exposes more of the internals than the * libp2p interface and is useful for testing and debugging. */ -export async function createLibp2pNode (options: Libp2pOptions): Promise { +export async function createLibp2pNode (options: Libp2pOptions): Promise> { if (options.peerId == null) { const datastore = options.datastore as Datastore | undefined diff --git a/src/ping/constants.ts b/src/ping/constants.ts index 9411e75c6f..deff92c9ce 100644 --- a/src/ping/constants.ts +++ b/src/ping/constants.ts @@ -3,3 +3,14 @@ export const PROTOCOL = '/ipfs/ping/1.0.0' export const PING_LENGTH = 32 export const PROTOCOL_VERSION = '1.0.0' export const PROTOCOL_NAME = 'ping' +export const PROTOCOL_PREFIX = 'ipfs' +export const TIMEOUT = 10000 + +// See https://github.com/libp2p/specs/blob/d4b5fb0152a6bb86cfd9ea/ping/ping.md?plain=1#L38-L43 +// The dialing peer MUST NOT keep more than one outbound stream for the ping protocol per peer. +// The listening peer SHOULD accept at most two streams per peer since cross-stream behavior is +// non-linear and stream writes occur asynchronously. The listening peer may perceive the +// dialing peer closing and opening the wrong streams (for instance, closing stream B and +// opening stream A even though the dialing peer is opening stream B and closing stream A). +export const MAX_INBOUND_STREAMS = 2 +export const MAX_OUTBOUND_STREAMS = 1 diff --git a/src/ping/index.ts b/src/ping/index.ts index 7f233298d2..2e38c2f331 100644 --- a/src/ping/index.ts +++ b/src/ping/index.ts @@ -5,28 +5,32 @@ import { randomBytes } from '@libp2p/crypto' import { pipe } from 'it-pipe' import first from 'it-first' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION } from './constants.js' +import { PROTOCOL_PREFIX, PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION, TIMEOUT, MAX_INBOUND_STREAMS, MAX_OUTBOUND_STREAMS } from './constants.js' import type { IncomingStreamData, Registrar } from '@libp2p/interface-registrar' import type { PeerId } from '@libp2p/interface-peer-id' import type { Startable } from '@libp2p/interfaces/startable' import type { AbortOptions } from '@libp2p/interfaces' import { abortableDuplex } from 'abortable-iterator' -import { TimeoutController } from 'timeout-abort-controller' import type { Stream } from '@libp2p/interface-connection' import { setMaxListeners } from 'events' import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import type { Multiaddr } from '@multiformats/multiaddr' const log = logger('libp2p:ping') +export interface PingService { + ping: (peer: PeerId | Multiaddr | Multiaddr[], options?: AbortOptions) => Promise +} + export interface PingServiceInit { - protocolPrefix: string - maxInboundStreams: number - maxOutboundStreams: number + protocolPrefix?: string + maxInboundStreams?: number + maxOutboundStreams?: number /** * How long we should wait for a ping response */ - timeout: number + timeout?: number } export interface PingServiceComponents { @@ -34,23 +38,27 @@ export interface PingServiceComponents { connectionManager: ConnectionManager } -export class PingService implements Startable { +class DefaultPingService implements Startable, PingService { public readonly protocol: string private readonly components: PingServiceComponents private started: boolean - private readonly init: PingServiceInit + private readonly timeout: number + private readonly maxInboundStreams: number + private readonly maxOutboundStreams: number constructor (components: PingServiceComponents, init: PingServiceInit) { this.components = components this.started = false - this.protocol = `/${init.protocolPrefix}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` - this.init = init + this.protocol = `/${init.protocolPrefix ?? PROTOCOL_PREFIX}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}` + this.timeout = init.timeout ?? TIMEOUT + this.maxInboundStreams = init.maxInboundStreams ?? MAX_INBOUND_STREAMS + this.maxOutboundStreams = init.maxOutboundStreams ?? MAX_OUTBOUND_STREAMS } async start (): Promise { await this.components.registrar.handle(this.protocol, this.handleMessage, { - maxInboundStreams: this.init.maxInboundStreams, - maxOutboundStreams: this.init.maxOutboundStreams + maxInboundStreams: this.maxInboundStreams, + maxOutboundStreams: this.maxOutboundStreams }) this.started = true } @@ -82,24 +90,22 @@ export class PingService implements Startable { * @param {PeerId|Multiaddr} peer * @returns {Promise} */ - async ping (peer: PeerId, options: AbortOptions = {}): Promise { + async ping (peer: PeerId | Multiaddr | Multiaddr[], options: AbortOptions = {}): Promise { log('dialing %s to %p', this.protocol, peer) const start = Date.now() const data = randomBytes(PING_LENGTH) const connection = await this.components.connectionManager.openConnection(peer, options) - let timeoutController let signal = options.signal let stream: Stream | undefined // create a timeout if no abort signal passed if (signal == null) { - timeoutController = new TimeoutController(this.init.timeout) - signal = timeoutController.signal + signal = AbortSignal.timeout(this.timeout) try { // fails on node < 15.4 - setMaxListeners?.(Infinity, timeoutController.signal) + setMaxListeners?.(Infinity, signal) } catch {} } @@ -124,13 +130,13 @@ export class PingService implements Startable { return end - start } finally { - if (timeoutController != null) { - timeoutController.clear() - } - if (stream != null) { stream.close() } } } } + +export function pingService (init: PingServiceInit = {}): (components: PingServiceComponents) => PingService { + return (components) => new DefaultPingService(components, init) +} diff --git a/src/registrar.ts b/src/registrar.ts index b5d0acf949..e024e79391 100644 --- a/src/registrar.ts +++ b/src/registrar.ts @@ -178,7 +178,7 @@ export class DefaultRegistrar implements Registrar { } }) .catch(err => { - log.error(err) + log.error('could not inform topologies of disconnecting peer %p', connection.remotePeer, err) }) } @@ -205,7 +205,7 @@ export class DefaultRegistrar implements Registrar { } }) .catch(err => { - log.error(err) + log.error('could not inform topologies of connecting peer %p', connection.remotePeer, err) }) } diff --git a/src/transport-manager.ts b/src/transport-manager.ts index c4ebd7a9c3..0178715bfa 100644 --- a/src/transport-manager.ts +++ b/src/transport-manager.ts @@ -75,13 +75,15 @@ export class DefaultTransportManager implements TransportManager, Startable { return this.started } - async start (): Promise { + start (): void { + this.started = true + } + + async afterStart (): Promise { // Listen on the provided transports for the provided addresses const addrs = this.components.addressManager.getListenAddrs() await this.listen(addrs) - - this.started = true } /** diff --git a/src/nat-manager.ts b/src/upnp-nat/index.ts similarity index 90% rename from src/nat-manager.ts rename to src/upnp-nat/index.ts index 65af5bb5e7..21cc184775 100644 --- a/src/nat-manager.ts +++ b/src/upnp-nat/index.ts @@ -3,16 +3,16 @@ import { logger } from '@libp2p/logger' import { fromNodeAddress } from '@multiformats/multiaddr' import { isBrowser } from 'wherearewe' import isPrivateIp from 'private-ip' -import * as pkg from './version.js' +import * as pkg from '../version.js' import { CodeError } from '@libp2p/interfaces/errors' -import { codes } from './errors.js' +import { codes } from '../errors.js' import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback' import type { Startable } from '@libp2p/interfaces/startable' import type { TransportManager } from '@libp2p/interface-transport' import type { PeerId } from '@libp2p/interface-peer-id' import type { AddressManager } from '@libp2p/interface-address-manager' -const log = logger('libp2p:nat') +const log = logger('libp2p:upnp-nat') const DEFAULT_TTL = 7200 function highPort (min = 1024, max = 65535): number { @@ -26,12 +26,7 @@ export interface PMPOptions { enabled?: boolean } -export interface NatManagerInit { - /** - * Whether to enable the NAT manager - */ - enabled: boolean - +export interface UPnPNATInit { /** * Pass a value to use instead of auto-detection */ @@ -63,15 +58,14 @@ export interface NatManagerInit { gateway?: string } -export interface NatManagerComponents { +export interface UPnPNATComponents { peerId: PeerId transportManager: TransportManager addressManager: AddressManager } -export class NatManager implements Startable { - private readonly components: NatManagerComponents - private readonly enabled: boolean +class UPnPNAT implements Startable { + private readonly components: UPnPNATComponents private readonly externalAddress?: string private readonly localAddress?: string private readonly description: string @@ -81,11 +75,10 @@ export class NatManager implements Startable { private started: boolean private client?: NatAPI - constructor (components: NatManagerComponents, init: NatManagerInit) { + constructor (components: UPnPNATComponents, init: UPnPNATInit) { this.components = components this.started = false - this.enabled = init.enabled this.externalAddress = init.externalAddress this.localAddress = init.localAddress this.description = init.description ?? `${pkg.name}@${pkg.version} ${this.components.peerId.toString()}` @@ -112,7 +105,7 @@ export class NatManager implements Startable { * Run after start to ensure the transport manager has all addresses configured. */ afterStart (): void { - if (isBrowser || !this.enabled || this.started) { + if (isBrowser || this.started) { return } @@ -211,3 +204,9 @@ export class NatManager implements Startable { } } } + +export function uPnPNAT (init: UPnPNATInit): (components: UPnPNATComponents) => {} { + return (components: UPnPNATComponents) => { + return new UPnPNAT(components, init) + } +} diff --git a/test/addresses/address-manager.spec.ts b/test/addresses/address-manager.spec.ts index 278275fda6..3493f2d449 100644 --- a/test/addresses/address-manager.spec.ts +++ b/test/addresses/address-manager.spec.ts @@ -3,7 +3,7 @@ import { expect } from 'aegir/chai' import { multiaddr, protocols } from '@multiformats/multiaddr' import { AddressFilter, DefaultAddressManager } from '../../src/address-manager/index.js' -import { createNode } from '../utils/creators/peer.js' +import { createLibp2p } from '../../src/index.js' import { createFromJSON } from '@libp2p/peer-id-factory' import Peers from '../fixtures/peers.js' import { StubbedInstance, stubInterface } from 'sinon-ts' @@ -11,6 +11,8 @@ import type { TransportManager } from '@libp2p/interface-transport' import type { PeerId } from '@libp2p/interface-peer-id' import type { Libp2p } from '../../src/index.js' import type { PeerStore } from '@libp2p/interface-peer-store' +import { webSockets } from '@libp2p/websockets' +import { plaintext } from '../../src/insecure/index.js' const listenAddresses = ['/ip4/127.0.0.1/tcp/15006/ws', '/ip4/127.0.0.1/tcp/15008/ws'] const announceAddreses = ['/dns4/peer.io'] @@ -215,14 +217,18 @@ describe('libp2p.addressManager', () => { }) it('should populate the AddressManager from the config', async () => { - libp2p = await createNode({ - started: false, - config: { - addresses: { - listen: listenAddresses, - announce: announceAddreses - } - } + libp2p = await createLibp2p({ + start: false, + addresses: { + listen: listenAddresses, + announce: announceAddreses + }, + transports: [ + webSockets() + ], + connectionEncryption: [ + plaintext() + ] }) expect(libp2p.getMultiaddrs().map(ma => ma.decapsulateCode(protocols('p2p').code).toString())).to.have.members(announceAddreses) diff --git a/test/autonat/index.spec.ts b/test/autonat/index.spec.ts index 3d97e0b15d..3a3cd7b242 100644 --- a/test/autonat/index.spec.ts +++ b/test/autonat/index.spec.ts @@ -22,7 +22,7 @@ import type { DefaultConnectionManager } from '../../src/connection-manager/inde import * as lp from 'it-length-prefixed' import all from 'it-all' import { pipe } from 'it-pipe' -import { Components, DefaultComponents } from '../../src/components.js' +import { Components, defaultComponents } from '../../src/components.js' import { Uint8ArrayList } from 'uint8arraylist' import type { PeerInfo } from '@libp2p/interface-peer-info' @@ -55,7 +55,7 @@ describe('autonat', () => { transportManager = stubInterface() peerStore = stubInterface() - components = new DefaultComponents({ + components = defaultComponents({ peerId: await createEd25519PeerId(), peerRouting, registrar, diff --git a/test/circuit-relay/discovery.node.ts b/test/circuit-relay/discovery.node.ts index ac57e0c858..c83136cfdd 100644 --- a/test/circuit-relay/discovery.node.ts +++ b/test/circuit-relay/discovery.node.ts @@ -1,68 +1,91 @@ /* eslint-env mocha */ import { expect } from 'aegir/chai' -import { createNode } from '../utils/creators/peer.js' -import type { Libp2pNode } from '../../src/libp2p.js' -import { createNodeOptions, getRelayAddress, hasRelay, MockContentRouting, mockContentRouting } from './utils.js' -import { circuitRelayServer, circuitRelayTransport } from '../../src/circuit-relay/index.js' +import { getRelayAddress, hasRelay, MockContentRouting, mockContentRouting } from './utils.js' +import { circuitRelayServer, CircuitRelayService, circuitRelayTransport } from '../../src/circuit-relay/index.js' import { tcp } from '@libp2p/tcp' import { pEvent } from 'p-event' +import type { Libp2p } from '@libp2p/interface-libp2p' +import { createLibp2p } from '../../src/index.js' +import { plaintext } from '../../src/insecure/index.js' +import { yamux } from '@chainsafe/libp2p-yamux' describe('circuit-relay discovery', () => { - let local: Libp2pNode - let remote: Libp2pNode - let relay: Libp2pNode + let local: Libp2p + let remote: Libp2p + let relay: Libp2p<{ relay: CircuitRelayService }> beforeEach(async () => { // create relay first so it has time to advertise itself via content routing - relay = await createNode({ - config: createNodeOptions({ - transports: [ - tcp() - ], + relay = await createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp() + ], + streamMuxers: [ + yamux() + ], + connectionEncryption: [ + plaintext() + ], + contentRouters: [ + mockContentRouting() + ], + services: { relay: circuitRelayServer({ advertise: { bootDelay: 10 } - }), - contentRouters: [ - mockContentRouting() - ] - }) + }) + } }) - if (relay.circuitService != null) { - // wait for relay to advertise service successfully - await pEvent(relay.circuitService, 'relay:advert:success') - } + // wait for relay to advertise service successfully + await pEvent(relay.services.relay, 'relay:advert:success') // now create client nodes - [local, remote] = await Promise.all([ - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ], - contentRouters: [ - mockContentRouting() - ] - }) + ;[local, remote] = await Promise.all([ + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + yamux() + ], + connectionEncryption: [ + plaintext() + ], + contentRouters: [ + mockContentRouting() + ] }), - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ], - contentRouters: [ - mockContentRouting() - ] - }) + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + yamux() + ], + connectionEncryption: [ + plaintext() + ], + contentRouters: [ + mockContentRouting() + ] }) ]) }) diff --git a/test/circuit-relay/relay.node.ts b/test/circuit-relay/relay.node.ts index a8d1013bb1..b61b8c04b2 100644 --- a/test/circuit-relay/relay.node.ts +++ b/test/circuit-relay/relay.node.ts @@ -6,10 +6,8 @@ import defer from 'p-defer' import pWaitFor from 'p-wait-for' import sinon from 'sinon' import { RELAY_V2_HOP_CODEC } from '../../src/circuit-relay/constants.js' -import { createNode } from '../utils/creators/peer.js' -import type { Libp2pNode } from '../../src/libp2p.js' -import { createNodeOptions, discoveredRelayConfig, getRelayAddress, hasRelay, usingAsRelay } from './utils.js' -import { circuitRelayServer, circuitRelayTransport } from '../../src/circuit-relay/index.js' +import { discoveredRelayConfig, getRelayAddress, hasRelay, usingAsRelay } from './utils.js' +import { circuitRelayServer, CircuitRelayService, circuitRelayTransport } from '../../src/circuit-relay/index.js' import { tcp } from '@libp2p/tcp' import { Uint8ArrayList } from 'uint8arraylist' import delay from 'delay' @@ -18,59 +16,103 @@ import { pbStream } from 'it-pb-stream' import { HopMessage, Status } from '../../src/circuit-relay/pb/index.js' import { Circuit } from '@multiformats/mafmt' import { multiaddr } from '@multiformats/multiaddr' +import { createLibp2p } from '../../src/index.js' +import { plaintext } from '../../src/insecure/index.js' +import { mplex } from '@libp2p/mplex' +import { identifyService } from '../../src/identify/index.js' describe('circuit-relay', () => { describe('flows with 1 listener', () => { let local: Libp2p - let relay1: Libp2p - let relay2: Libp2p - let relay3: Libp2p + let relay1: Libp2p<{ relay: CircuitRelayService }> + let relay2: Libp2p<{ relay: CircuitRelayService }> + let relay3: Libp2p<{ relay: CircuitRelayService }> beforeEach(async () => { // create 1 node and 3 relays [local, relay1, relay2, relay3] = await Promise.all([ - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ] - }) + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + identify: identifyService() + } }), - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ], - relay: circuitRelayServer() - }) + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + relay: circuitRelayServer(), + identify: identifyService() + } }), - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ], - relay: circuitRelayServer() - }) + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + relay: circuitRelayServer(), + identify: identifyService() + } }), - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ], - relay: circuitRelayServer() - }) + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + relay: circuitRelayServer(), + identify: identifyService() + } }) ]) }) @@ -292,64 +334,114 @@ describe('circuit-relay', () => { describe('flows with 2 listeners', () => { let local: Libp2p let remote: Libp2p - let relay1: Libp2p - let relay2: Libp2p - let relay3: Libp2p + let relay1: Libp2p<{ relay: CircuitRelayService }> + let relay2: Libp2p<{ relay: CircuitRelayService }> + let relay3: Libp2p<{ relay: CircuitRelayService }> beforeEach(async () => { [local, remote, relay1, relay2, relay3] = await Promise.all([ - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ] - }) + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + identify: identifyService() + } }), - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ] - }) + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + identify: identifyService() + } }), - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ], - relay: circuitRelayServer() - }) + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + relay: circuitRelayServer(), + identify: identifyService() + } }), - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ], - relay: circuitRelayServer() - }) + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + relay: circuitRelayServer(), + identify: identifyService() + } }), - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ], - relay: circuitRelayServer() - }) + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + relay: circuitRelayServer(), + identify: identifyService() + } }) ]) }) @@ -515,43 +607,76 @@ describe('circuit-relay', () => { }) describe('flows with data limit', () => { - let local: Libp2pNode - let remote: Libp2pNode - let relay: Libp2pNode + let local: Libp2p + let remote: Libp2p + let relay: Libp2p<{ relay: CircuitRelayService }> beforeEach(async () => { [local, remote, relay] = await Promise.all([ - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ] - }) + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + identify: identifyService() + } }), - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ] - }) + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + identify: identifyService() + } }), - createNode({ - config: createNodeOptions({ - transports: [ - tcp() - ], + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { relay: circuitRelayServer({ reservations: { defaultDataLimit: 1024n } - }) - }) + }), + identify: identifyService() + } }) ]) }) @@ -610,43 +735,76 @@ describe('circuit-relay', () => { }) describe('flows with duration limit', () => { - let local: Libp2pNode - let remote: Libp2pNode - let relay: Libp2pNode + let local: Libp2p + let remote: Libp2p + let relay: Libp2p<{ relay: CircuitRelayService }> beforeEach(async () => { [local, remote, relay] = await Promise.all([ - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ] - }) + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + identify: identifyService() + } }), - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport({ - discoverRelays: 1 - }) - ] - }) + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + identify: identifyService() + } }), - createNode({ - config: createNodeOptions({ - transports: [ - tcp() - ], + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport({ + discoverRelays: 1 + }) + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { relay: circuitRelayServer({ reservations: { defaultDurationLimit: 1000 } - }) - }) + }), + identify: identifyService() + } }) ]) }) @@ -706,40 +864,67 @@ describe('circuit-relay', () => { describe('preconfigured relay address', () => { let local: Libp2p let remote: Libp2p - let relay: Libp2p + let relay: Libp2p<{ relay: CircuitRelayService }> beforeEach(async () => { - relay = await createNode({ - config: createNodeOptions({ + relay = await createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport() + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + relay: circuitRelayServer(), + identify: identifyService() + } + }) + + ;[local, remote] = await Promise.all([ + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, transports: [ tcp(), circuitRelayTransport() ], - relay: circuitRelayServer() - }) - }) - - ;[local, remote] = await Promise.all([ - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport() - ] - }) + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + identify: identifyService() + } }), - createNode({ - config: createNodeOptions({ - addresses: { - listen: [ - `${relay.getMultiaddrs()[0].toString()}/p2p-circuit` - ] - }, - transports: [ - tcp(), - circuitRelayTransport() + createLibp2p({ + addresses: { + listen: [ + `${relay.getMultiaddrs()[0].toString()}/p2p-circuit` ] - }) + }, + transports: [ + tcp(), + circuitRelayTransport() + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + identify: identifyService() + } }) ]) }) @@ -763,40 +948,67 @@ describe('circuit-relay', () => { describe('preconfigured relay without a peer id', () => { let local: Libp2p let remote: Libp2p - let relay: Libp2p + let relay: Libp2p<{ relay: CircuitRelayService }> beforeEach(async () => { - relay = await createNode({ - config: createNodeOptions({ + relay = await createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, + transports: [ + tcp(), + circuitRelayTransport() + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + relay: circuitRelayServer(), + identify: identifyService() + } + }) + + ;[local, remote] = await Promise.all([ + createLibp2p({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/0'] + }, transports: [ tcp(), circuitRelayTransport() ], - relay: circuitRelayServer() - }) - }) - - ;[local, remote] = await Promise.all([ - createNode({ - config: createNodeOptions({ - transports: [ - tcp(), - circuitRelayTransport() - ] - }) + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + identify: identifyService() + } }), - createNode({ - config: createNodeOptions({ - addresses: { - listen: [ - `${relay.getMultiaddrs()[0].toString().split('/p2p')[0]}/p2p-circuit` - ] - }, - transports: [ - tcp(), - circuitRelayTransport() + createLibp2p({ + addresses: { + listen: [ + `${relay.getMultiaddrs()[0].toString().split('/p2p')[0]}/p2p-circuit` ] - }) + }, + transports: [ + tcp(), + circuitRelayTransport() + ], + streamMuxers: [ + mplex() + ], + connectionEncryption: [ + plaintext() + ], + services: { + identify: identifyService() + } }) ]) }) diff --git a/test/circuit-relay/utils.ts b/test/circuit-relay/utils.ts index fc69e77439..9688e3fbb8 100644 --- a/test/circuit-relay/utils.ts +++ b/test/circuit-relay/utils.ts @@ -3,8 +3,6 @@ import type { PeerId } from '@libp2p/interface-peer-id' import type { PeerInfo } from '@libp2p/interface-peer-info' import type { AbortOptions } from '@libp2p/interfaces' import type { CID, Version } from 'multiformats' -import type { Libp2pOptions } from '../../src/index.js' -import { createBaseOptions } from '../utils/base-options.js' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import type { AddressManager } from '@libp2p/interface-address-manager' import type { Libp2p } from '@libp2p/interface-libp2p' @@ -14,16 +12,6 @@ import { peerIdFromString } from '@libp2p/peer-id' import { RELAY_V2_HOP_CODEC } from '../../src/circuit-relay/constants.js' import type { Multiaddr } from '@multiformats/multiaddr' -const listenAddr = '/ip4/127.0.0.1/tcp/0' - -export function createNodeOptions (...overrides: Libp2pOptions[]): Libp2pOptions { - return createBaseOptions({ - addresses: { - listen: [listenAddr] - } - }, ...overrides) -} - export async function usingAsRelay (node: Libp2p, relay: Libp2p, opts?: PWaitForOptions): Promise { // Wait for peer to be used as a relay await pWaitFor(() => { diff --git a/test/configuration/protocol-prefix.node.ts b/test/configuration/protocol-prefix.node.ts index 3fdab4fedc..b69bf105aa 100644 --- a/test/configuration/protocol-prefix.node.ts +++ b/test/configuration/protocol-prefix.node.ts @@ -1,13 +1,17 @@ /* eslint-env mocha */ +import type { Libp2p } from '@libp2p/interface-libp2p' import { expect } from 'aegir/chai' import mergeOptions from 'merge-options' -import { validateConfig } from '../../src/config.js' -import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import { pEvent } from 'p-event' +import { FetchService, fetchService } from '../../src/fetch/index.js' +import { IdentifyService, identifyService } from '../../src/identify/index.js' +import { createLibp2p } from '../../src/index.js' +import { PingService, pingService } from '../../src/ping/index.js' import { baseOptions } from './utils.js' describe('Protocol prefix is configurable', () => { - let libp2p: Libp2pNode + let libp2p: Libp2p<{ identify: IdentifyService, ping: PingService, fetch: FetchService }> afterEach(async () => { if (libp2p != null) { @@ -17,18 +21,24 @@ describe('Protocol prefix is configurable', () => { it('protocolPrefix is provided', async () => { const testProtocol = 'test-protocol' - libp2p = await createLibp2pNode(mergeOptions(baseOptions, { - identify: { - protocolPrefix: testProtocol + libp2p = await createLibp2p(mergeOptions(baseOptions, { + services: { + identify: identifyService({ + protocolPrefix: testProtocol + }), + ping: pingService({ + protocolPrefix: testProtocol + }), + fetch: fetchService({ + protocolPrefix: testProtocol + }) }, - ping: { - protocolPrefix: testProtocol - }, - fetch: { - protocolPrefix: testProtocol - } + start: false })) + + const eventPromise = pEvent(libp2p, 'self:peer:update') await libp2p.start() + await eventPromise const peer = await libp2p.peerStore.get(libp2p.peerId) expect(peer.protocols).to.include.members([ @@ -40,8 +50,18 @@ describe('Protocol prefix is configurable', () => { }) it('protocolPrefix is not provided', async () => { - libp2p = await createLibp2pNode(validateConfig(baseOptions)) + libp2p = await createLibp2p(mergeOptions(baseOptions, { + services: { + identify: identifyService(), + ping: pingService(), + fetch: fetchService() + }, + start: false + })) + + const eventPromise = pEvent(libp2p, 'self:peer:update') await libp2p.start() + await eventPromise const peer = await libp2p.peerStore.get(libp2p.peerId) expect(peer.protocols).to.include.members([ diff --git a/test/configuration/pubsub.spec.ts b/test/configuration/pubsub.spec.ts index e46ec92c30..4ce9e174a4 100644 --- a/test/configuration/pubsub.spec.ts +++ b/test/configuration/pubsub.spec.ts @@ -5,14 +5,14 @@ import mergeOptions from 'merge-options' import pDefer from 'p-defer' import delay from 'delay' import { createLibp2p, Libp2p } from '../../src/index.js' -import { baseOptions, pubsubSubsystemOptions } from './utils.js' +import { pubsubSubsystemOptions } from './utils.js' import { createPeerId } from '../utils/creators/peer.js' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { floodsub } from '@libp2p/floodsub' import type { PubSub } from '@libp2p/interface-pubsub' describe('Pubsub subsystem is configurable', () => { - let libp2p: Libp2p + let libp2p: Libp2p<{ pubsub: PubSub }> afterEach(async () => { if (libp2p != null) { @@ -20,16 +20,10 @@ describe('Pubsub subsystem is configurable', () => { } }) - it('should throw if no module is provided', async () => { - libp2p = await createLibp2p(baseOptions) - await libp2p.start() - expect(() => libp2p.pubsub.getTopics()).to.throw() - }) - it('should not throw if the module is provided', async () => { libp2p = await createLibp2p(pubsubSubsystemOptions) await libp2p.start() - expect(libp2p.pubsub.getTopics()).to.be.empty() + expect(libp2p.services.pubsub.getTopics()).to.be.empty() }) it('should start and stop by default once libp2p starts', async () => { @@ -42,29 +36,31 @@ describe('Pubsub subsystem is configurable', () => { libp2p = await createLibp2p(customOptions) // @ts-expect-error not part of interface - expect(libp2p.pubsub.isStarted()).to.equal(false) + expect(libp2p.services.pubsub.isStarted()).to.equal(false) await libp2p.start() // @ts-expect-error not part of interface - expect(libp2p.pubsub.isStarted()).to.equal(true) + expect(libp2p.services.pubsub.isStarted()).to.equal(true) await libp2p.stop() // @ts-expect-error not part of interface - expect(libp2p.pubsub.isStarted()).to.equal(false) + expect(libp2p.services.pubsub.isStarted()).to.equal(false) }) }) describe('Pubsub subscription handlers adapter', () => { - let libp2p: Libp2p + let libp2p: Libp2p<{ pubsub: PubSub }> beforeEach(async () => { const peerId = await createPeerId() libp2p = await createLibp2p(mergeOptions(pubsubSubsystemOptions, { peerId, - pubsub: floodsub({ - emitSelf: true - }) + services: { + pubsub: floodsub({ + emitSelf: true + }) + } })) await libp2p.start() @@ -86,7 +82,7 @@ describe('Pubsub subscription handlers adapter', () => { defer.resolve() } - const pubsub: PubSub | undefined = libp2p.pubsub + const pubsub: PubSub | undefined = libp2p.services.pubsub if (pubsub == null) { throw new Error('Pubsub was not enabled') diff --git a/test/configuration/utils.ts b/test/configuration/utils.ts index 45f005ccd1..3862147c9e 100644 --- a/test/configuration/utils.ts +++ b/test/configuration/utils.ts @@ -5,7 +5,7 @@ import { webSockets } from '@libp2p/websockets' import * as filters from '@libp2p/websockets/filters' import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import mergeOptions from 'merge-options' -import type { Message, PublishResult, PubSubInit, PubSubRPC, PubSubRPCMessage } from '@libp2p/interface-pubsub' +import type { Message, PublishResult, PubSub, PubSubInit, PubSubRPC, PubSubRPCMessage } from '@libp2p/interface-pubsub' import type { Libp2pInit, Libp2pOptions } from '../../src/index.js' import type { PeerId } from '@libp2p/interface-peer-id' import * as cborg from 'cborg' @@ -13,8 +13,18 @@ import { circuitRelayTransport } from '../../src/circuit-relay/index.js' const relayAddr = MULTIADDRS_WEBSOCKETS[0] -export const baseOptions: Partial = { - transports: [webSockets()], +export const baseOptions: Partial> = { + addresses: { + listen: [ + `${relayAddr}/p2p-circuit` + ] + }, + transports: [ + webSockets({ + filter: filters.all + }), + circuitRelayTransport() + ], streamMuxers: [mplex()], connectionEncryption: [plaintext()] } @@ -68,13 +78,15 @@ class MockPubSub extends PubSubBaseProtocol { } } -export const pubsubSubsystemOptions: Libp2pOptions = mergeOptions(baseOptions, { - pubsub: (components: PubSubComponents) => new MockPubSub(components), +export const pubsubSubsystemOptions: Libp2pOptions<{ pubsub: PubSub }> = mergeOptions(baseOptions, { addresses: { listen: [`${relayAddr.toString()}/p2p-circuit`] }, transports: [ webSockets({ filter: filters.all }), circuitRelayTransport() - ] + ], + services: { + pubsub: (components: PubSubComponents) => new MockPubSub(components) + } }) diff --git a/test/connection-manager/direct.node.ts b/test/connection-manager/direct.node.ts index d66e096ef0..245422d49f 100644 --- a/test/connection-manager/direct.node.ts +++ b/test/connection-manager/direct.node.ts @@ -8,7 +8,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' import { plaintext } from '../../src/insecure/index.js' -import { Connection, isConnection } from '@libp2p/interface-connection' +import { Connection, ConnectionProtector, isConnection } from '@libp2p/interface-connection' import { mockConnection, mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader } from '@libp2p/interface-mocks' import type { PeerId } from '@libp2p/interface-peer-id' import { AbortError } from '@libp2p/interfaces/errors' @@ -22,11 +22,10 @@ import pDefer from 'p-defer' import pWaitFor from 'p-wait-for' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { DefaultAddressManager } from '../../src/address-manager/index.js' -import { DefaultComponents } from '../../src/components.js' +import { defaultComponents, Components } from '../../src/components.js' import { DialQueue } from '../../src/connection-manager/dial-queue.js' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { codes as ErrorCodes } from '../../src/errors.js' -import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { preSharedKey } from '../../src/pnet/index.js' import { DefaultTransportManager } from '../../src/transport-manager.js' import Peers from '../fixtures/peers.js' @@ -38,6 +37,7 @@ import { peerIdFromString } from '@libp2p/peer-id' import { stubInterface } from 'sinon-ts' import type { TransportManager } from '@libp2p/interface-transport' import { EventEmitter } from '@libp2p/interfaces/events' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' const swarmKeyBuffer = uint8ArrayFromString(swarmKey) const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') @@ -47,8 +47,8 @@ describe('dialing (direct, TCP)', () => { let remoteTM: DefaultTransportManager let localTM: DefaultTransportManager let remoteAddr: Multiaddr - let remoteComponents: DefaultComponents - let localComponents: DefaultComponents + let remoteComponents: Components + let localComponents: Components let resolver: sinon.SinonStub<[Multiaddr], Promise> beforeEach(async () => { @@ -59,7 +59,7 @@ describe('dialing (direct, TCP)', () => { ]) const remoteEvents = new EventEmitter() - remoteComponents = new DefaultComponents({ + remoteComponents = defaultComponents({ peerId: remotePeerId, events: remoteEvents, datastore: new MemoryDatastore(), @@ -76,7 +76,7 @@ describe('dialing (direct, TCP)', () => { remoteTM.add(tcp()()) const localEvents = new EventEmitter() - localComponents = new DefaultComponents({ + localComponents = defaultComponents({ peerId: localPeerId, events: localEvents, datastore: new MemoryDatastore(), @@ -295,7 +295,8 @@ describe('dialing (direct, TCP)', () => { }) describe('libp2p.dialer (direct, TCP)', () => { - let peerId: PeerId, remotePeerId: PeerId + let peerId: PeerId + let remotePeerId: PeerId let libp2p: Libp2pNode let remoteLibp2p: Libp2pNode let remoteAddr: Multiaddr @@ -326,7 +327,7 @@ describe('libp2p.dialer (direct, TCP)', () => { }) await remoteLibp2p.start() - remoteAddr = remoteLibp2p.components.transportManager.getAddrs()[0].encapsulate(`/p2p/${remotePeerId.toString()}`) + remoteAddr = remoteLibp2p.getMultiaddrs()[0] }) afterEach(async () => { @@ -341,33 +342,6 @@ describe('libp2p.dialer (direct, TCP)', () => { } }) - it('should use the dialer for connecting to a multiaddr', async () => { - libp2p = await createLibp2pNode({ - peerId, - transports: [ - tcp() - ], - streamMuxers: [ - mplex() - ], - connectionEncryption: [ - plaintext() - ] - }) - - await libp2p.start() - - const dialerDialSpy = sinon.spy(libp2p.connectionManager, 'openConnection') - - const connection = await libp2p.dial(remoteAddr) - expect(connection).to.exist() - const stream = await connection.newStream(['/echo/1.0.0']) - expect(stream).to.exist() - expect(stream).to.have.nested.property('stat.protocol', '/echo/1.0.0') - expect(dialerDialSpy.callCount).to.be.greaterThan(0) - await connection.close() - }) - it('should use the dialer for connecting to a peer', async () => { libp2p = await createLibp2pNode({ peerId, @@ -384,9 +358,7 @@ describe('libp2p.dialer (direct, TCP)', () => { await libp2p.start() - const dialerDialSpy = sinon.spy(libp2p.connectionManager, 'openConnection') - - await libp2p.components.peerStore.patch(remotePeerId, { + await libp2p.peerStore.patch(remotePeerId, { multiaddrs: remoteLibp2p.getMultiaddrs() }) @@ -396,7 +368,6 @@ describe('libp2p.dialer (direct, TCP)', () => { expect(stream).to.exist() expect(stream).to.have.nested.property('stat.protocol', '/echo/1.0.0') await connection.close() - expect(dialerDialSpy.callCount).to.be.greaterThan(0) }) it('should close all streams when the connection closes', async () => { @@ -429,7 +400,7 @@ describe('libp2p.dialer (direct, TCP)', () => { void pipe(stream, stream) }) - await libp2p.components.peerStore.patch(remotePeerId, { + await libp2p.peerStore.patch(remotePeerId, { multiaddrs: remoteLibp2p.getMultiaddrs() }) const connection = await libp2p.dial(remotePeerId) @@ -515,6 +486,10 @@ describe('libp2p.dialer (direct, TCP)', () => { }) it('should use the protectors when provided for connecting', async () => { + const protector: ConnectionProtector = preSharedKey({ + psk: swarmKeyBuffer + })() + libp2p = await createLibp2pNode({ peerId, transports: [ @@ -526,20 +501,12 @@ describe('libp2p.dialer (direct, TCP)', () => { connectionEncryption: [ plaintext() ], - connectionProtector: preSharedKey({ - psk: swarmKeyBuffer - }) + connectionProtector: () => protector }) - const protector = libp2p.components.connectionProtector - - if (protector == null) { - throw new Error('No protector was configured') - } - const protectorProtectSpy = sinon.spy(protector, 'protect') - remoteLibp2p.components.connectionProtector = preSharedKey({ enabled: true, psk: swarmKeyBuffer })() + remoteLibp2p.components.connectionProtector = preSharedKey({ psk: swarmKeyBuffer })() await libp2p.start() @@ -572,7 +539,7 @@ describe('libp2p.dialer (direct, TCP)', () => { // PeerId should be in multiaddr expect(remoteAddr.getPeerId()).to.equal(remoteLibp2p.peerId.toString()) - await libp2p.components.peerStore.patch(remotePeerId, { + await libp2p.peerStore.patch(remotePeerId, { multiaddrs: remoteLibp2p.getMultiaddrs() }) const dialResults = await Promise.all([...new Array(dials)].map(async (_, index) => { @@ -611,7 +578,7 @@ describe('libp2p.dialer (direct, TCP)', () => { const error = new Error('Boom') sinon.stub(libp2p.components.transportManager, 'dial').callsFake(async () => await Promise.reject(error)) - await libp2p.components.peerStore.patch(remotePeerId, { + await libp2p.peerStore.patch(remotePeerId, { multiaddrs: remoteLibp2p.getMultiaddrs() }) const dialResults = await Promise.allSettled([...new Array(dials)].map(async (_, index) => { diff --git a/test/connection-manager/direct.spec.ts b/test/connection-manager/direct.spec.ts index c13bc57c53..a365a4c32b 100644 --- a/test/connection-manager/direct.spec.ts +++ b/test/connection-manager/direct.spec.ts @@ -21,28 +21,31 @@ import { createPeerId } from '../utils/creators/peer.js' import type { TransportManager } from '@libp2p/interface-transport' import { peerIdFromString } from '@libp2p/peer-id' import type { Connection } from '@libp2p/interface-connection' -import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import { createLibp2p } from '../../src/index.js' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { createFromJSON } from '@libp2p/peer-id-factory' import Peers from '../fixtures/peers.js' import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import type { PeerId } from '@libp2p/interface-peer-id' import { pEvent } from 'p-event' -import { DefaultComponents } from '../../src/components.js' +import { defaultComponents, Components } from '../../src/components.js' import { stubInterface } from 'sinon-ts' import { EventEmitter } from '@libp2p/interfaces/events' +import type { Libp2p } from '@libp2p/interface-libp2p' +import { IdentifyService, identifyService } from '../../src/identify/index.js' const unsupportedAddr = multiaddr('/ip4/127.0.0.1/tcp/9999') describe('dialing (direct, WebSockets)', () => { let localTM: TransportManager - let localComponents: DefaultComponents + let localComponents: Components let remoteAddr: Multiaddr - let remoteComponents: DefaultComponents + let remoteComponents: Components + let connectionManager: DefaultConnectionManager beforeEach(async () => { const localEvents = new EventEmitter() - localComponents = new DefaultComponents({ + localComponents = defaultComponents({ peerId: await createFromJSON(Peers[0]), datastore: new MemoryDatastore(), upgrader: mockUpgrader({ events: localEvents }), @@ -65,17 +68,22 @@ describe('dialing (direct, WebSockets)', () => { // this peer is spun up in .aegir.cjs remoteAddr = MULTIADDRS_WEBSOCKETS[0] - remoteComponents = new DefaultComponents({ + remoteComponents = defaultComponents({ peerId: peerIdFromString(remoteAddr.getPeerId() ?? '') }) }) afterEach(async () => { sinon.restore() + + if (connectionManager != null) { + await connectionManager.stop() + } }) it('should be able to connect to a remote node via its multiaddr', async () => { - const connectionManager = new DefaultConnectionManager(localComponents) + connectionManager = new DefaultConnectionManager(localComponents) + await connectionManager.start() const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.peerStore.patch(remotePeerId, { @@ -88,7 +96,8 @@ describe('dialing (direct, WebSockets)', () => { }) it('should fail to connect to an unsupported multiaddr', async () => { - const connectionManager = new DefaultConnectionManager(localComponents) + connectionManager = new DefaultConnectionManager(localComponents) + await connectionManager.start() await expect(connectionManager.openConnection(unsupportedAddr.encapsulate(`/p2p/${remoteComponents.peerId.toString()}`))) .to.eventually.be.rejectedWith(Error) @@ -96,7 +105,8 @@ describe('dialing (direct, WebSockets)', () => { }) it('should be able to connect to a given peer', async () => { - const connectionManager = new DefaultConnectionManager(localComponents) + connectionManager = new DefaultConnectionManager(localComponents) + await connectionManager.start() const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.peerStore.patch(remotePeerId, { @@ -109,7 +119,8 @@ describe('dialing (direct, WebSockets)', () => { }) it('should fail to connect to a given peer with unsupported addresses', async () => { - const connectionManager = new DefaultConnectionManager(localComponents) + connectionManager = new DefaultConnectionManager(localComponents) + await connectionManager.start() const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.peerStore.patch(remotePeerId, { @@ -122,9 +133,10 @@ describe('dialing (direct, WebSockets)', () => { }) it('should abort dials on queue task timeout', async () => { - const connectionManager = new DefaultConnectionManager(localComponents, { + connectionManager = new DefaultConnectionManager(localComponents, { dialTimeout: 50 }) + await connectionManager.start() const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.peerStore.patch(remotePeerId, { @@ -146,9 +158,10 @@ describe('dialing (direct, WebSockets)', () => { }) it('should throw when a peer advertises more than the allowed number of addresses', async () => { - const connectionManager = new DefaultConnectionManager(localComponents, { + connectionManager = new DefaultConnectionManager(localComponents, { maxPeerAddrsToDial: 10 }) + await connectionManager.start() const remotePeerId = peerIdFromString(remoteAddr.getPeerId() ?? '') await localComponents.peerStore.patch(remotePeerId, { @@ -170,10 +183,11 @@ describe('dialing (direct, WebSockets)', () => { const publicAddressesFirstSpy = sinon.spy(publicAddressesFirst) const localTMDialStub = sinon.stub(localTM, 'dial').callsFake(async (ma) => mockConnection(mockMultiaddrConnection(mockDuplex(), remoteComponents.peerId))) - const connectionManager = new DefaultConnectionManager(localComponents, { + connectionManager = new DefaultConnectionManager(localComponents, { addressSorter: publicAddressesFirstSpy, maxParallelDials: 3 }) + await connectionManager.start() // Inject data into the AddressBook await localComponents.peerStore.merge(remoteComponents.peerId, { @@ -198,9 +212,10 @@ describe('dialing (direct, WebSockets)', () => { multiaddr('/ip4/0.0.0.0/tcp/8001/ws'), multiaddr('/ip4/0.0.0.0/tcp/8002/ws') ] - const connectionManager = new DefaultConnectionManager(localComponents, { + connectionManager = new DefaultConnectionManager(localComponents, { maxParallelDials: 2 }) + await connectionManager.start() // Inject data into the AddressBook await localComponents.peerStore.merge(remoteComponents.peerId, { @@ -244,14 +259,14 @@ describe('dialing (direct, WebSockets)', () => { multiaddrs: addrs }) - const connectionManager = new DefaultConnectionManager(localComponents) + connectionManager = new DefaultConnectionManager(localComponents) + await connectionManager.start() const transactionManagerDialStub = sinon.stub(localTM, 'dial') transactionManagerDialStub.callsFake(async (ma) => mockConnection(mockMultiaddrConnection(mockDuplex(), remoteComponents.peerId))) // Perform dial await connectionManager.openConnection(remoteComponents.peerId) - await connectionManager.stop() expect(transactionManagerDialStub).to.have.property('callCount', 3) }) @@ -271,31 +286,31 @@ describe('dialing (direct, WebSockets)', () => { // different address not in the address book, same peer id const dialMultiaddr = multiaddr(`/ip4/0.0.0.0/tcp/8003/ws/p2p/${remoteComponents.peerId.toString()}`) - const connectionManager = new DefaultConnectionManager(localComponents) + connectionManager = new DefaultConnectionManager(localComponents) + await connectionManager.start() const transactionManagerDialStub = sinon.stub(localTM, 'dial') transactionManagerDialStub.callsFake(async (ma) => mockConnection(mockMultiaddrConnection(mockDuplex(), remoteComponents.peerId))) // Perform dial await connectionManager.openConnection(dialMultiaddr) - await connectionManager.stop() expect(transactionManagerDialStub).to.have.property('callCount', 1) expect(transactionManagerDialStub.getCall(0).args[0].toString()).to.equal(dialMultiaddr.toString()) }) it('should throw if dialling an empty array is attempted', async () => { - const connectionManager = new DefaultConnectionManager(localComponents) + connectionManager = new DefaultConnectionManager(localComponents) + await connectionManager.start() // Perform dial await expect(connectionManager.openConnection([])).to.eventually.rejected .with.property('code', 'ERR_NO_VALID_ADDRESSES') - - await connectionManager.stop() }) it('should throw if dialling multiaddrs with mismatched peer ids', async () => { - const connectionManager = new DefaultConnectionManager(localComponents) + connectionManager = new DefaultConnectionManager(localComponents) + await connectionManager.start() // Perform dial await expect(connectionManager.openConnection([ @@ -303,12 +318,11 @@ describe('dialing (direct, WebSockets)', () => { multiaddr(`/ip4/0.0.0.0/tcp/8001/ws/p2p/${(await createPeerId()).toString()}`) ])).to.eventually.rejected .with.property('code', 'ERR_INVALID_PARAMETERS') - - await connectionManager.stop() }) it('should throw if dialling multiaddrs with inconsistent peer ids', async () => { - const connectionManager = new DefaultConnectionManager(localComponents) + connectionManager = new DefaultConnectionManager(localComponents) + await connectionManager.start() // Perform dial await expect(connectionManager.openConnection([ @@ -323,14 +337,12 @@ describe('dialing (direct, WebSockets)', () => { multiaddr(`/ip4/0.0.0.0/tcp/8000/ws/p2p/${(await createPeerId()).toString()}`) ])).to.eventually.rejected .with.property('code', 'ERR_INVALID_PARAMETERS') - - await connectionManager.stop() }) }) describe('libp2p.dialer (direct, WebSockets)', () => { // const connectionGater = mockConnectionGater() - let libp2p: Libp2pNode + let libp2p: Libp2p<{ identify: IdentifyService }> let peerId: PeerId beforeEach(async () => { @@ -345,39 +357,8 @@ describe('libp2p.dialer (direct, WebSockets)', () => { } }) - it('should use the connection manager for connecting', async () => { - libp2p = await createLibp2pNode({ - peerId, - transports: [ - webSockets({ - filter: filters.all - }) - ], - streamMuxers: [ - mplex() - ], - connectionEncryption: [ - plaintext() - ] - }) - - const dialerDialSpy = sinon.spy(libp2p.components.connectionManager, 'openConnection') - - await libp2p.start() - - const connection = await libp2p.dial(MULTIADDRS_WEBSOCKETS[0]) - expect(connection).to.exist() - const stream = await connection.newStream('/echo/1.0.0') - expect(stream).to.exist() - expect(stream).to.have.nested.property('stat.protocol', '/echo/1.0.0') - await connection.close() - expect(dialerDialSpy.callCount).to.be.at.least(1) - - await libp2p.stop() - }) - it('should run identify automatically after connecting', async () => { - libp2p = await createLibp2pNode({ + libp2p = await createLibp2p({ peerId, transports: [ webSockets({ @@ -389,15 +370,18 @@ describe('libp2p.dialer (direct, WebSockets)', () => { ], connectionEncryption: [ plaintext() - ] + ], + services: { + identify: identifyService() + } }) - if (libp2p.identifyService == null) { + if (libp2p.services.identify == null) { throw new Error('Identify service missing') } - const identifySpy = sinon.spy(libp2p.identifyService, 'identify') - const peerStorePatchSpy = sinon.spy(libp2p.components.peerStore, 'patch') + const identifySpy = sinon.spy(libp2p.services.identify, 'identify') + const peerStorePatchSpy = sinon.spy(libp2p.peerStore, 'patch') const connectionPromise = pEvent(libp2p, 'connection:open') await libp2p.start() @@ -417,7 +401,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { }) it('should be able to use hangup to close connections', async () => { - libp2p = await createLibp2pNode({ + libp2p = await createLibp2p({ peerId, transports: [ webSockets({ @@ -445,7 +429,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { }) it('should be able to use hangup when no connection exists', async () => { - libp2p = await createLibp2pNode({ + libp2p = await createLibp2p({ peerId, transports: [ webSockets({ @@ -463,34 +447,8 @@ describe('libp2p.dialer (direct, WebSockets)', () => { await libp2p.hangUp(MULTIADDRS_WEBSOCKETS[0]) }) - it('should abort pending dials on stop', async () => { - libp2p = await createLibp2pNode({ - peerId, - transports: [ - webSockets({ - filter: filters.all - }) - ], - streamMuxers: [ - mplex() - ], - connectionEncryption: [ - plaintext() - ] - }) - - await libp2p.start() - - const connectionManager = libp2p.components.connectionManager as DefaultConnectionManager - const dialerDestroyStub = sinon.spy(connectionManager.dialQueue, 'stop') - - await libp2p.stop() - - expect(dialerDestroyStub.callCount).to.equal(1) - }) - it('should fail to dial self', async () => { - libp2p = await createLibp2pNode({ + libp2p = await createLibp2p({ peerId, transports: [ webSockets({ diff --git a/test/connection-manager/index.node.ts b/test/connection-manager/index.node.ts index a69258e087..a4619d5310 100644 --- a/test/connection-manager/index.node.ts +++ b/test/connection-manager/index.node.ts @@ -19,7 +19,7 @@ import { codes } from '../../src/errors.js' import { start } from '@libp2p/interfaces/startable' import type { TransportManager } from '@libp2p/interface-transport' import type { ConnectionGater } from '@libp2p/interface-connection-gater' -import { DefaultComponents } from '../../src/components.js' +import { defaultComponents } from '../../src/components.js' describe('Connection Manager', () => { let libp2p: Libp2p @@ -49,7 +49,7 @@ describe('Connection Manager', () => { it('should filter connections on disconnect, removing the closed one', async () => { const peerStore = stubInterface() - const components = new DefaultComponents({ + const components = defaultComponents({ peerId: peerIds[0], peerStore, transportManager: stubInterface(), @@ -87,7 +87,7 @@ describe('Connection Manager', () => { it('should close connections on stop', async () => { const peerStore = stubInterface() - const components = new DefaultComponents({ + const components = defaultComponents({ peerId: peerIds[0], peerStore, transportManager: stubInterface(), @@ -252,11 +252,11 @@ describe('libp2p.connections', () => { await libp2p.start() // Wait for peer to connect - await pWaitFor(() => libp2p.connectionManager.getConnections().length === minConnections) + await pWaitFor(() => libp2p.components.connectionManager.getConnections().length === minConnections) // Wait more time to guarantee no other connection happened await delay(200) - expect(libp2p.connectionManager.getConnections().length).to.eql(minConnections) + expect(libp2p.components.connectionManager.getConnections().length).to.eql(minConnections) await libp2p.stop() }) @@ -289,11 +289,11 @@ describe('libp2p.connections', () => { await libp2p.start() // Wait for peer to connect - await pWaitFor(() => libp2p.connectionManager.getConnections().length === minConnections) + await pWaitFor(() => libp2p.components.connectionManager.getConnections().length === minConnections) // Should have connected to the peer with protocols - expect(libp2p.connectionManager.getConnections(nodes[0].peerId)).to.be.empty() - expect(libp2p.connectionManager.getConnections(nodes[1].peerId)).to.not.be.empty() + expect(libp2p.components.connectionManager.getConnections(nodes[0].peerId)).to.be.empty() + expect(libp2p.components.connectionManager.getConnections(nodes[1].peerId)).to.not.be.empty() await libp2p.stop() }) @@ -319,15 +319,15 @@ describe('libp2p.connections', () => { // Wait for peer to connect const conn = await libp2p.dial(nodes[0].peerId) - expect(libp2p.connectionManager.getConnections(nodes[0].peerId)).to.not.be.empty() + expect(libp2p.components.connectionManager.getConnections(nodes[0].peerId)).to.not.be.empty() await conn.close() // Closed - await pWaitFor(() => libp2p.connectionManager.getConnections().length === 0) + await pWaitFor(() => libp2p.components.connectionManager.getConnections().length === 0) // Connected - await pWaitFor(() => libp2p.connectionManager.getConnections().length === 1) + await pWaitFor(() => libp2p.components.connectionManager.getConnections().length === 1) - expect(libp2p.connectionManager.getConnections(nodes[0].peerId)).to.not.be.empty() + expect(libp2p.components.connectionManager.getConnections(nodes[0].peerId)).to.not.be.empty() await libp2p.stop() }) @@ -355,7 +355,7 @@ describe('libp2p.connections', () => { }) await libp2p.dial(remoteLibp2p.peerId) - const conns = libp2p.connectionManager.getConnections() + const conns = libp2p.components.connectionManager.getConnections() expect(conns.length).to.eql(1) const conn = conns[0] @@ -430,7 +430,7 @@ describe('libp2p.connections', () => { await libp2p.peerStore.patch(remoteLibp2p.peerId, { multiaddrs: remoteLibp2p.getMultiaddrs() }) - await libp2p.connectionManager.openConnection(remoteLibp2p.peerId) + await libp2p.components.connectionManager.openConnection(remoteLibp2p.peerId) for (const multiaddr of remoteLibp2p.getMultiaddrs()) { expect(denyDialMultiaddr.calledWith(multiaddr)).to.be.true() diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index 3d095548bb..86b6bf9c05 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -28,10 +28,15 @@ const defaultOptions = { describe('Connection Manager', () => { let libp2p: Libp2pNode + let connectionManager: DefaultConnectionManager afterEach(async () => { sinon.restore() + if (connectionManager != null) { + await connectionManager.stop() + } + if (libp2p != null) { await libp2p.stop() } @@ -43,7 +48,7 @@ describe('Connection Manager', () => { started: false }) - const spy = sinon.spy(libp2p.connectionManager as DefaultConnectionManager, 'start') + const spy = sinon.spy(libp2p.components.connectionManager as DefaultConnectionManager, 'start') await libp2p.start() expect(spy).to.have.property('callCount', 1) @@ -58,7 +63,7 @@ describe('Connection Manager', () => { started: false }) - const spy = sinon.spy(libp2p.connectionManager as DefaultConnectionManager, 'start') + const spy = sinon.spy(libp2p.components.connectionManager as DefaultConnectionManager, 'start') await libp2p.start() expect(spy).to.have.property('callCount', 1) @@ -79,7 +84,7 @@ describe('Connection Manager', () => { await libp2p.start() - const connectionManager = libp2p.connectionManager as DefaultConnectionManager + const connectionManager = libp2p.components.connectionManager as DefaultConnectionManager const connectionManagerMaybePruneConnectionsSpy = sinon.spy(connectionManager.connectionPruner, 'maybePruneConnections') const spies = new Map>>() @@ -138,7 +143,7 @@ describe('Connection Manager', () => { await libp2p.start() - const connectionManager = libp2p.connectionManager as DefaultConnectionManager + const connectionManager = libp2p.components.connectionManager as DefaultConnectionManager const connectionManagerMaybePruneConnectionsSpy = sinon.spy(connectionManager.connectionPruner, 'maybePruneConnections') const spies = new Map>>() const eventPromise = pEvent(libp2p, 'connection:prune') @@ -206,7 +211,7 @@ describe('Connection Manager', () => { await libp2p.start() - const connectionManager = libp2p.connectionManager as DefaultConnectionManager + const connectionManager = libp2p.components.connectionManager as DefaultConnectionManager const connectionManagerMaybePruneConnectionsSpy = sinon.spy(connectionManager.connectionPruner, 'maybePruneConnections') const spies = new Map>>() const eventPromise = pEvent(libp2p, 'connection:prune') @@ -288,7 +293,7 @@ describe('Connection Manager', () => { await libp2p.start() - const connectionManager = libp2p.connectionManager as DefaultConnectionManager + const connectionManager = libp2p.components.connectionManager as DefaultConnectionManager const connectionManagerMaybePruneConnectionsSpy = sinon.spy(connectionManager.connectionPruner, 'maybePruneConnections') const eventPromise = pEvent(libp2p, 'connection:prune') @@ -328,7 +333,7 @@ describe('Connection Manager', () => { started: false }) - const connectionManager = libp2p.connectionManager as DefaultConnectionManager + const connectionManager = libp2p.components.connectionManager as DefaultConnectionManager const connectionManagerOpenConnectionSpy = sinon.spy(connectionManager, 'openConnection') await libp2p.start() @@ -354,7 +359,7 @@ describe('Connection Manager', () => { it('should deny connections from denylist multiaddrs', async () => { const remoteAddr = multiaddr('/ip4/83.13.55.32/tcp/59283') - const connectionManager = new DefaultConnectionManager({ + connectionManager = new DefaultConnectionManager({ peerId: libp2p.peerId, peerStore: stubInterface(), transportManager: stubInterface(), @@ -366,6 +371,7 @@ describe('Connection Manager', () => { '/ip4/83.13.55.32' ] }) + await connectionManager.start() const remotePeer = await createEd25519PeerId() const maConn = mockMultiaddrConnection({ @@ -381,7 +387,7 @@ describe('Connection Manager', () => { }) it('should deny connections when maxConnections is exceeded', async () => { - const connectionManager = new DefaultConnectionManager({ + connectionManager = new DefaultConnectionManager({ peerId: libp2p.peerId, peerStore: stubInterface(), transportManager: stubInterface(), @@ -391,6 +397,7 @@ describe('Connection Manager', () => { ...defaultOptions, maxConnections: 1 }) + await connectionManager.start() sinon.stub(connectionManager.dialQueue, 'dial').resolves(stubInterface()) @@ -412,7 +419,7 @@ describe('Connection Manager', () => { }) it('should deny connections from peers that connect too frequently', async () => { - const connectionManager = new DefaultConnectionManager({ + connectionManager = new DefaultConnectionManager({ peerId: libp2p.peerId, peerStore: stubInterface(), transportManager: stubInterface(), @@ -422,6 +429,7 @@ describe('Connection Manager', () => { ...defaultOptions, inboundConnectionThreshold: 1 }) + await connectionManager.start() sinon.stub(connectionManager.dialQueue, 'dial').resolves(stubInterface()) @@ -447,8 +455,7 @@ describe('Connection Manager', () => { it('should allow connections from allowlist multiaddrs', async () => { const remoteAddr = multiaddr('/ip4/83.13.55.32/tcp/59283') - - const connectionManager = new DefaultConnectionManager({ + connectionManager = new DefaultConnectionManager({ peerId: libp2p.peerId, peerStore: stubInterface(), transportManager: stubInterface(), @@ -461,6 +468,7 @@ describe('Connection Manager', () => { '/ip4/83.13.55.32' ] }) + await connectionManager.start() sinon.stub(connectionManager.dialQueue, 'dial').resolves(stubInterface()) @@ -483,7 +491,7 @@ describe('Connection Manager', () => { }) it('should limit the number of inbound pending connections', async () => { - const connectionManager = new DefaultConnectionManager({ + connectionManager = new DefaultConnectionManager({ peerId: await createEd25519PeerId(), peerStore: stubInterface(), transportManager: stubInterface(), @@ -493,6 +501,7 @@ describe('Connection Manager', () => { ...defaultOptions, maxIncomingPendingConnections: 1 }) + await connectionManager.start() sinon.stub(connectionManager.dialQueue, 'dial').resolves(stubInterface()) diff --git a/test/connection-manager/resolver.spec.ts b/test/connection-manager/resolver.spec.ts index e10c371faa..5640920685 100644 --- a/test/connection-manager/resolver.spec.ts +++ b/test/connection-manager/resolver.spec.ts @@ -5,18 +5,20 @@ import sinon from 'sinon' import type { Multiaddr } from '@multiformats/multiaddr' import { multiaddr } from '@multiformats/multiaddr' import { codes as ErrorCodes } from '../../src/errors.js' -import { createNode } from '../utils/creators/peer.js' -import { createBaseOptions } from '../utils/base-options.browser.js' import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import type { PeerId } from '@libp2p/interface-peer-id' -import type { Libp2pNode } from '../../src/libp2p.js' import pDefer from 'p-defer' import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-mocks' import { peerIdFromString } from '@libp2p/peer-id' import { createFromJSON } from '@libp2p/peer-id-factory' import { RELAY_V2_HOP_CODEC } from '../../src/circuit-relay/constants.js' -import { circuitRelayServer } from '../../src/circuit-relay/index.js' +import { circuitRelayServer, CircuitRelayService, circuitRelayTransport } from '../../src/circuit-relay/index.js' import type { Transport } from '@libp2p/interface-transport' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import { webSockets } from '@libp2p/websockets' +import * as filters from '@libp2p/websockets/filters' +import { mplex } from '@libp2p/mplex' +import { plaintext } from '../../src/insecure/index.js' const relayAddr = MULTIADDRS_WEBSOCKETS[0] @@ -36,39 +38,67 @@ const getDnsRelayedAddrStub = (peerId: PeerId): string[] => [ ] describe('dialing (resolvable addresses)', () => { - let libp2p: Libp2pNode, remoteLibp2p: Libp2pNode + let libp2p: Libp2pNode + let remoteLibp2p: Libp2pNode<{ relay: CircuitRelayService }> let resolver: sinon.SinonStub<[Multiaddr], Promise> beforeEach(async () => { resolver = sinon.stub<[Multiaddr], Promise>(); [libp2p, remoteLibp2p] = await Promise.all([ - createNode({ - config: createBaseOptions({ - addresses: { - listen: [`${relayAddr.toString()}/p2p-circuit`] - }, - connectionManager: { - resolvers: { - dnsaddr: resolver - } + createLibp2pNode({ + addresses: { + listen: [`${relayAddr.toString()}/p2p-circuit`] + }, + transports: [ + circuitRelayTransport(), + webSockets({ + filter: filters.all + }) + ], + streamMuxers: [ + mplex() + ], + connectionManager: { + resolvers: { + dnsaddr: resolver } - }) + }, + connectionEncryption: [ + plaintext() + ] }), - createNode({ - config: createBaseOptions({ - addresses: { - listen: [`${relayAddr.toString()}/p2p-circuit`] - }, - connectionManager: { - resolvers: { - dnsaddr: resolver - } - }, + createLibp2pNode({ + addresses: { + listen: [`${relayAddr.toString()}/p2p-circuit`] + }, + transports: [ + circuitRelayTransport(), + webSockets({ + filter: filters.all + }) + ], + streamMuxers: [ + mplex() + ], + connectionManager: { + resolvers: { + dnsaddr: resolver + } + }, + connectionEncryption: [ + plaintext() + ], + services: { relay: circuitRelayServer() - }) + } }) ]) + + await Promise.all([ + libp2p.start(), + remoteLibp2p.start() + ]) }) afterEach(async () => { @@ -87,7 +117,7 @@ describe('dialing (resolvable addresses)', () => { // Use the last peer const peerId = await createFromJSON(Peers[Peers.length - 1]) // ensure remote libp2p creates reservation on relay - await remoteLibp2p.components.peerStore.merge(peerId, { + await remoteLibp2p.peerStore.merge(peerId, { protocols: [RELAY_V2_HOP_CODEC] }) const remoteId = remoteLibp2p.peerId @@ -120,7 +150,7 @@ describe('dialing (resolvable addresses)', () => { // Use the last peer const relayId = await createFromJSON(Peers[Peers.length - 1]) // ensure remote libp2p creates reservation on relay - await remoteLibp2p.components.peerStore.merge(relayId, { + await remoteLibp2p.peerStore.merge(relayId, { protocols: [RELAY_V2_HOP_CODEC] }) @@ -188,7 +218,7 @@ describe('dialing (resolvable addresses)', () => { // Use the last peer const relayId = await createFromJSON(Peers[Peers.length - 1]) // ensure remote libp2p creates reservation on relay - await remoteLibp2p.components.peerStore.merge(relayId, { + await remoteLibp2p.peerStore.merge(relayId, { protocols: [RELAY_V2_HOP_CODEC] }) diff --git a/test/content-routing/content-routing.node.ts b/test/content-routing/content-routing.node.ts index e99703ea49..79cf47b4a7 100644 --- a/test/content-routing/content-routing.node.ts +++ b/test/content-routing/content-routing.node.ts @@ -10,12 +10,12 @@ import all from 'it-all' import { createNode, createPeerId, populateAddressBooks } from '../utils/creators/peer.js' import { createBaseOptions } from '../utils/base-options.js' import { createRoutingOptions } from './utils.js' -import type { Libp2p } from '../../src/index.js' +import { createLibp2p, Libp2p } from '../../src/index.js' import type { PeerInfo } from '@libp2p/interface-peer-info' -import type { Libp2pNode } from '../../src/libp2p.js' import type { ContentRouting } from '@libp2p/interface-content-routing' import { StubbedInstance, stubInterface } from 'sinon-ts' import { peerIdFromString } from '@libp2p/peer-id' +import type { DHT } from '@libp2p/interface-dht' describe('content-routing', () => { describe('no routers', () => { @@ -50,15 +50,15 @@ describe('content-routing', () => { describe('via dht router', () => { const number = 5 - let nodes: Libp2pNode[] + let nodes: Array> before(async () => { nodes = await Promise.all([ - createNode({ config: createRoutingOptions() }), - createNode({ config: createRoutingOptions() }), - createNode({ config: createRoutingOptions() }), - createNode({ config: createRoutingOptions() }), - createNode({ config: createRoutingOptions() }) + createLibp2p(createRoutingOptions()), + createLibp2p(createRoutingOptions()), + createLibp2p(createRoutingOptions()), + createLibp2p(createRoutingOptions()), + createLibp2p(createRoutingOptions()) ]) await populateAddressBooks(nodes) @@ -77,11 +77,11 @@ describe('content-routing', () => { it('should use the nodes dht to provide', async () => { const deferred = pDefer() - if (nodes[0].dht == null) { + if (nodes[0].services.dht == null) { throw new Error('DHT was not configured') } - sinon.stub(nodes[0].dht, 'provide').callsFake(async function * () { // eslint-disable-line require-yield + sinon.stub(nodes[0].services.dht, 'provide').callsFake(async function * () { // eslint-disable-line require-yield deferred.resolve() }) @@ -93,11 +93,11 @@ describe('content-routing', () => { it('should use the nodes dht to find providers', async () => { const deferred = pDefer() - if (nodes[0].dht == null) { + if (nodes[0].services.dht == null) { throw new Error('DHT was not configured') } - sinon.stub(nodes[0].dht, 'findProviders').callsFake(async function * () { + sinon.stub(nodes[0].services.dht, 'findProviders').callsFake(async function * () { yield { from: nodes[0].peerId, type: 0, @@ -118,7 +118,7 @@ describe('content-routing', () => { }) describe('via delegate router', () => { - let node: Libp2pNode + let node: Libp2p let delegate: StubbedInstance beforeEach(async () => { @@ -130,8 +130,7 @@ describe('content-routing', () => { config: createBaseOptions({ contentRouters: [ () => delegate - ], - dht: undefined + ] }) }) }) @@ -223,7 +222,7 @@ describe('content-routing', () => { }) describe('via dht and delegate routers', () => { - let node: Libp2pNode + let node: Libp2p<{ dht: DHT }> let delegate: StubbedInstance beforeEach(async () => { @@ -256,11 +255,11 @@ describe('content-routing', () => { protocols: [] } - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT was not configured') } - sinon.stub(node.dht, 'findProviders').callsFake(async function * () {}) + sinon.stub(node.services.dht, 'findProviders').callsFake(async function * () {}) delegate.findProviders.callsFake(async function * () { yield result }) @@ -285,13 +284,13 @@ describe('content-routing', () => { protocols: [] } - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT was not configured') } const defer = pDefer() - sinon.stub(node.dht, 'findProviders').callsFake(async function * () { // eslint-disable-line require-yield + sinon.stub(node.services.dht, 'findProviders').callsFake(async function * () { // eslint-disable-line require-yield await defer.promise }) delegate.findProviders.callsFake(async function * () { @@ -316,11 +315,11 @@ describe('content-routing', () => { protocols: [] } - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT was not configured') } - sinon.stub(node.dht, 'findProviders').callsFake(async function * () { + sinon.stub(node.services.dht, 'findProviders').callsFake(async function * () { yield { from: providerPeerId, type: 0, @@ -356,11 +355,11 @@ describe('content-routing', () => { protocols: [] } - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT was not configured') } - sinon.stub(node.dht, 'findProviders').callsFake(async function * () { + sinon.stub(node.services.dht, 'findProviders').callsFake(async function * () { yield { from: providerPeerId, type: 0, @@ -389,11 +388,11 @@ describe('content-routing', () => { const dhtDeferred = pDefer() const delegatedDeferred = pDefer() - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT was not configured') } - sinon.stub(node.dht, 'provide').callsFake(async function * () { // eslint-disable-line require-yield + sinon.stub(node.services.dht, 'provide').callsFake(async function * () { // eslint-disable-line require-yield dhtDeferred.resolve() }) @@ -417,11 +416,11 @@ describe('content-routing', () => { protocols: [] }] - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT was not configured') } - sinon.stub(node.dht, 'findProviders').callsFake(async function * () { + sinon.stub(node.services.dht, 'findProviders').callsFake(async function * () { yield { from: providerPeerId, type: 0, @@ -452,11 +451,11 @@ describe('content-routing', () => { protocols: [] }] - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT was not configured') } - sinon.stub(node.dht, 'findProviders').callsFake(async function * () {}) + sinon.stub(node.services.dht, 'findProviders').callsFake(async function * () {}) delegate.findProviders.callsFake(async function * () { yield results[0] diff --git a/test/content-routing/dht/configuration.node.ts b/test/content-routing/dht/configuration.node.ts deleted file mode 100644 index 5e3ed5ebe1..0000000000 --- a/test/content-routing/dht/configuration.node.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* eslint-env mocha */ - -import { expect } from 'aegir/chai' -import { createLibp2p, Libp2p } from '../../../src/index.js' -import { createSubsystemOptions } from './utils.js' - -describe('DHT subsystem is configurable', () => { - let libp2p: Libp2p - - afterEach(async () => { - if (libp2p != null) { - await libp2p.stop() - } - }) - - it('should throw if no module is provided', async () => { - libp2p = await createLibp2p(createSubsystemOptions({ - dht: undefined - })) - await libp2p.start() - await expect(libp2p.dht.getMode()).to.eventually.be.rejected() - }) - - it('should not throw if the module is provided', async () => { - libp2p = await createLibp2p(createSubsystemOptions()) - await libp2p.start() - await expect(libp2p.dht.getMode()).to.eventually.equal('client') - }) -}) diff --git a/test/content-routing/dht/operation.node.ts b/test/content-routing/dht/operation.node.ts index cfff93707e..3c87b86d74 100644 --- a/test/content-routing/dht/operation.node.ts +++ b/test/content-routing/dht/operation.node.ts @@ -5,17 +5,22 @@ import type { Multiaddr } from '@multiformats/multiaddr' import { multiaddr } from '@multiformats/multiaddr' import pWaitFor from 'p-wait-for' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { subsystemMulticodecs, createSubsystemOptions } from './utils.js' +import { subsystemMulticodecs } from './utils.js' import { createPeerId } from '../../utils/creators/peer.js' import type { PeerId } from '@libp2p/interface-peer-id' -import { createLibp2pNode, Libp2pNode } from '../../../src/libp2p.js' -import { start } from '@libp2p/interfaces/startable' +import type { Libp2p } from '@libp2p/interface-libp2p' +import type { DualDHT } from '@libp2p/interface-dht' +import { createLibp2p } from '../../../src/index.js' +import { kadDHT } from '@libp2p/kad-dht' +import { tcp } from '@libp2p/tcp' +import { plaintext } from '../../../src/insecure/index.js' +import { mplex } from '@libp2p/mplex' const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/8000') const remoteListenAddr = multiaddr('/ip4/127.0.0.1/tcp/8001') -async function getRemoteAddr (remotePeerId: PeerId, libp2p: Libp2pNode): Promise { - const { addresses } = await libp2p.components.peerStore.get(remotePeerId) +async function getRemoteAddr (remotePeerId: PeerId, libp2p: Libp2p): Promise { + const { addresses } = await libp2p.peerStore.get(remotePeerId) if (addresses.length === 0) { throw new Error('No addrs found') @@ -29,8 +34,8 @@ async function getRemoteAddr (remotePeerId: PeerId, libp2p: Libp2pNode): Promise describe('DHT subsystem operates correctly', () => { let peerId: PeerId let remotePeerId: PeerId - let libp2p: Libp2pNode - let remoteLibp2p: Libp2pNode + let libp2p: Libp2p<{ dht: DualDHT }> + let remoteLibp2p: Libp2p<{ dht: DualDHT }> let remAddr: Multiaddr beforeEach(async () => { @@ -42,26 +47,50 @@ describe('DHT subsystem operates correctly', () => { describe('dht started before connect', () => { beforeEach(async () => { - libp2p = await createLibp2pNode(createSubsystemOptions({ + libp2p = await createLibp2p({ peerId, addresses: { listen: [listenAddr.toString()] + }, + transports: [ + tcp() + ], + connectionEncryption: [ + plaintext() + ], + streamMuxers: [ + mplex() + ], + services: { + dht: kadDHT() } - })) + }) - remoteLibp2p = await createLibp2pNode(createSubsystemOptions({ + remoteLibp2p = await createLibp2p({ peerId: remotePeerId, addresses: { listen: [remoteListenAddr.toString()] + }, + transports: [ + tcp() + ], + connectionEncryption: [ + plaintext() + ], + streamMuxers: [ + mplex() + ], + services: { + dht: kadDHT() } - })) + }) await Promise.all([ libp2p.start(), remoteLibp2p.start() ]) - await libp2p.components.peerStore.patch(remotePeerId, { + await libp2p.peerStore.patch(remotePeerId, { multiaddrs: [remoteListenAddr] }) remAddr = await getRemoteAddr(remotePeerId, libp2p) @@ -83,8 +112,8 @@ describe('DHT subsystem operates correctly', () => { expect(stream).to.exist() return await Promise.all([ - pWaitFor(() => libp2p.dht.lan.routingTable.size === 1), - pWaitFor(() => remoteLibp2p.dht.lan.routingTable.size === 1) + pWaitFor(() => libp2p.services.dht.lan.routingTable.size === 1), + pWaitFor(() => remoteLibp2p.services.dht.lan.routingTable.size === 1) ]) }) @@ -94,87 +123,13 @@ describe('DHT subsystem operates correctly', () => { await libp2p.dialProtocol(remotePeerId, subsystemMulticodecs) await Promise.all([ - pWaitFor(() => libp2p.dht.lan.routingTable.size === 1), - pWaitFor(() => remoteLibp2p.dht.lan.routingTable.size === 1) + pWaitFor(() => libp2p.services.dht.lan.routingTable.size === 1), + pWaitFor(() => remoteLibp2p.services.dht.lan.routingTable.size === 1) ]) - await libp2p.components.contentRouting.put(key, value) - - const fetchedValue = await remoteLibp2p.components.contentRouting.get(key) - expect(fetchedValue).to.equalBytes(value) - }) - }) - - describe('dht started after connect', () => { - beforeEach(async () => { - libp2p = await createLibp2pNode(createSubsystemOptions({ - peerId, - addresses: { - listen: [listenAddr.toString()] - } - })) - - remoteLibp2p = await createLibp2pNode(createSubsystemOptions({ - peerId: remotePeerId, - addresses: { - listen: [remoteListenAddr.toString()] - } - })) - - await libp2p.start() - await remoteLibp2p.start() - - await libp2p.components.peerStore.patch(remotePeerId, { - multiaddrs: [remoteListenAddr] - }) - remAddr = await getRemoteAddr(remotePeerId, libp2p) - }) - - afterEach(async () => { - if (libp2p != null) { - await libp2p.stop() - } - - if (remoteLibp2p != null) { - await remoteLibp2p.stop() - } - }) - - // TODO: we pre-fill the routing tables on dht startup with artificial peers so this test - // doesn't really work as intended. We should be testing that a connected peer can change - // it's supported protocols and we should notice that change so there may be something to - // salvage from here, though it could be better as identify protocol tests. - it.skip('should get notified of connected peers after starting', async () => { - const connection = await libp2p.dial(remAddr) - - expect(connection).to.exist() - expect(libp2p.dht.lan.routingTable).to.be.empty() - - const dht = remoteLibp2p.dht - - await start(dht) - - // should be 0 directly after start - TODO this may be susceptible to timing bugs, we should have - // the ability to report stats on the DHT routing table instead of reaching into it's heart like this - expect(remoteLibp2p.dht.lan.routingTable).to.be.empty() - - await pWaitFor(() => libp2p.dht.lan.routingTable.size === 1) - }) - - it('should put on a peer and get from the other', async () => { - await libp2p.dial(remAddr) - - const key = uint8ArrayFromString('hello') - const value = uint8ArrayFromString('world') - - const dht = remoteLibp2p.dht - - await start(dht) - - await pWaitFor(() => libp2p.dht.lan.routingTable.size === 1) - await libp2p.components.contentRouting.put(key, value) + await libp2p.contentRouting.put(key, value) - const fetchedValue = await remoteLibp2p.components.contentRouting.get(key) + const fetchedValue = await remoteLibp2p.contentRouting.get(key) expect(fetchedValue).to.equalBytes(value) }) }) diff --git a/test/content-routing/dht/utils.ts b/test/content-routing/dht/utils.ts index 702ee3c1d9..d3b85e5872 100644 --- a/test/content-routing/dht/utils.ts +++ b/test/content-routing/dht/utils.ts @@ -1,14 +1,3 @@ -import { kadDHT } from '@libp2p/kad-dht' -import type { Libp2pOptions } from '../../../src/index.js' -import { createBaseOptions } from '../../utils/base-options.js' - -export function createSubsystemOptions (...overrides: Libp2pOptions[]): Libp2pOptions { - return createBaseOptions({ - dht: kadDHT({ - kBucketSize: 20 - }) - }, ...overrides) -} export const subsystemMulticodecs = [ '/ipfs/lan/kad/1.0.0' diff --git a/test/content-routing/utils.ts b/test/content-routing/utils.ts index 67fb2f1a86..6195ba4e3d 100644 --- a/test/content-routing/utils.ts +++ b/test/content-routing/utils.ts @@ -1,11 +1,12 @@ +import type { DHT } from '@libp2p/interface-dht' import { kadDHT } from '@libp2p/kad-dht' import type { Libp2pOptions } from '../../src/index.js' import { createBaseOptions } from '../utils/base-options.js' -export function createRoutingOptions (...overrides: Libp2pOptions[]): Libp2pOptions { +export function createRoutingOptions (...overrides: Libp2pOptions[]): Libp2pOptions<{ dht: DHT }> { return createBaseOptions({ - dht: kadDHT({ - kBucketSize: 20 - }) + services: { + dht: kadDHT() + } }, ...overrides) } diff --git a/test/core/consume-peer-record.spec.ts b/test/core/consume-peer-record.spec.ts index 18e3525fbe..6f0eef0b8b 100644 --- a/test/core/consume-peer-record.spec.ts +++ b/test/core/consume-peer-record.spec.ts @@ -5,14 +5,13 @@ import { plaintext } from '../../src/insecure/index.js' import { createPeerId } from '../utils/creators/peer.js' import { multiaddr } from '@multiformats/multiaddr' import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' -import type { Libp2pOptions } from '../../src/index.js' describe('Consume peer record', () => { let libp2p: Libp2pNode beforeEach(async () => { const peerId = await createPeerId() - const config: Libp2pOptions = { + libp2p = await createLibp2pNode({ peerId, transports: [ webSockets() @@ -20,8 +19,7 @@ describe('Consume peer record', () => { connectionEncryption: [ plaintext() ] - } - libp2p = await createLibp2pNode(config) + }) }) afterEach(async () => { @@ -31,7 +29,7 @@ describe('Consume peer record', () => { it('should update addresses when observed addrs are confirmed', async () => { let done: () => void - libp2p.components.peerStore.patch = async () => { + libp2p.peerStore.patch = async () => { done() return {} as any } diff --git a/test/core/get-public-key.spec.ts b/test/core/get-public-key.spec.ts index af2fa1b589..3ba63cb823 100644 --- a/test/core/get-public-key.spec.ts +++ b/test/core/get-public-key.spec.ts @@ -4,17 +4,18 @@ import { expect } from 'aegir/chai' import { webSockets } from '@libp2p/websockets' import { plaintext } from '../../src/insecure/index.js' import { createPeerId } from '../utils/creators/peer.js' -import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' -import type { Libp2pOptions } from '../../src/index.js' +import { createLibp2pNode } from '../../src/libp2p.js' import sinon from 'sinon' import { kadDHT } from '@libp2p/kad-dht' +import type { DHT } from '@libp2p/interface-dht' +import type { Libp2p } from '@libp2p/interface-libp2p' describe('getPublicKey', () => { - let libp2p: Libp2pNode + let libp2p: Libp2p<{ dht: DHT }> beforeEach(async () => { const peerId = await createPeerId() - const config: Libp2pOptions = { + libp2p = await createLibp2pNode({ peerId, transports: [ webSockets() @@ -22,9 +23,10 @@ describe('getPublicKey', () => { connectionEncryption: [ plaintext() ], - dht: kadDHT() - } - libp2p = await createLibp2pNode(config) + services: { + dht: kadDHT() + } + }) await libp2p.start() }) @@ -64,11 +66,11 @@ describe('getPublicKey', () => { throw new Error('Public key was missing') } - if (libp2p.dht == null) { + if (libp2p.services.dht == null) { throw new Error('DHT was not configured') } - libp2p.dht.get = sinon.stub().returns([{ + libp2p.services.dht.get = sinon.stub().returns([{ name: 'VALUE', value: otherPeer.publicKey }]) diff --git a/test/fetch/fetch.node.ts b/test/fetch/fetch.node.ts index b9a797e99a..8173b0dac4 100644 --- a/test/fetch/fetch.node.ts +++ b/test/fetch/fetch.node.ts @@ -1,16 +1,18 @@ /* eslint-env mocha */ import { expect } from 'aegir/chai' -import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import { createLibp2p } from '../../src/index.js' import { tcp } from '@libp2p/tcp' import { mplex } from '@libp2p/mplex' import { plaintext } from '../../src/insecure/index.js' import { createPeerId } from '../utils/creators/peer.js' import { codes } from '../../src/errors.js' import type { PeerId } from '@libp2p/interface-peer-id' +import { FetchService, fetchService } from '../../src/fetch/index.js' +import type { Libp2p } from '@libp2p/interface-libp2p' -async function createNode (peerId: PeerId): Promise { - return await createLibp2pNode({ +async function createNode (peerId: PeerId): Promise> { + return await createLibp2p({ peerId, addresses: { listen: [ @@ -25,13 +27,16 @@ async function createNode (peerId: PeerId): Promise { ], connectionEncryption: [ plaintext() - ] + ], + services: { + fetch: fetchService() + } }) } describe('Fetch', () => { - let sender: Libp2pNode - let receiver: Libp2pNode + let sender: Libp2p<{ fetch: FetchService }> + let receiver: Libp2p<{ fetch: FetchService }> const PREFIX_A = '/moduleA/' const PREFIX_B = '/moduleB/' const DATA_A = { foobar: 'hello world' } @@ -66,9 +71,9 @@ describe('Fetch', () => { }) it('fetch key that exists in receivers datastore', async () => { - receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) + receiver.services.fetch.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) - const rawData = await sender.fetch(receiver.peerId, '/moduleA/foobar') + const rawData = await sender.services.fetch.fetch(receiver.peerId, '/moduleA/foobar') if (rawData == null) { throw new Error('Value was not found') @@ -79,10 +84,10 @@ describe('Fetch', () => { }) it('Different lookups for different prefixes', async () => { - receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) - receiver.fetchService.registerLookupFunction(PREFIX_B, generateLookupFunction(PREFIX_B, DATA_B)) + receiver.services.fetch.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) + receiver.services.fetch.registerLookupFunction(PREFIX_B, generateLookupFunction(PREFIX_B, DATA_B)) - const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') + const rawDataA = await sender.services.fetch.fetch(receiver.peerId, '/moduleA/foobar') if (rawDataA == null) { throw new Error('Value was not found') @@ -93,7 +98,7 @@ describe('Fetch', () => { // Different lookup functions can be registered on different prefixes, and have different // values for the same key underneath the different prefix. - const rawDataB = await sender.fetch(receiver.peerId, '/moduleB/foobar') + const rawDataB = await sender.services.fetch.fetch(receiver.peerId, '/moduleB/foobar') if (rawDataB == null) { throw new Error('Value was not found') @@ -104,30 +109,30 @@ describe('Fetch', () => { }) it('fetch key that does not exist in receivers datastore', async () => { - receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) - const result = await sender.fetch(receiver.peerId, '/moduleA/garbage') + receiver.services.fetch.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) + const result = await sender.services.fetch.fetch(receiver.peerId, '/moduleA/garbage') expect(result).to.equal(null) }) it('fetch key with unknown prefix throws error', async () => { - receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) + receiver.services.fetch.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) - await expect(sender.fetch(receiver.peerId, '/moduleUNKNOWN/foobar')) + await expect(sender.services.fetch.fetch(receiver.peerId, '/moduleUNKNOWN/foobar')) .to.eventually.be.rejected.with.property('code', codes.ERR_INVALID_PARAMETERS) }) it('registering multiple handlers for same prefix errors', async () => { - receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) + receiver.services.fetch.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) - expect(() => { receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_B)) }) + expect(() => { receiver.services.fetch.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_B)) }) .to.throw().with.property('code', codes.ERR_KEY_ALREADY_EXISTS) }) it('can unregister handler', async () => { const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A) - receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction) - const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') + receiver.services.fetch.registerLookupFunction(PREFIX_A, lookupFunction) + const rawDataA = await sender.services.fetch.fetch(receiver.peerId, '/moduleA/foobar') if (rawDataA == null) { throw new Error('Value was not found') @@ -136,16 +141,16 @@ describe('Fetch', () => { const valueA = (new TextDecoder()).decode(rawDataA) expect(valueA).to.equal('hello world') - receiver.fetchService.unregisterLookupFunction(PREFIX_A, lookupFunction) + receiver.services.fetch.unregisterLookupFunction(PREFIX_A, lookupFunction) - await expect(sender.fetch(receiver.peerId, '/moduleA/foobar')) + await expect(sender.services.fetch.fetch(receiver.peerId, '/moduleA/foobar')) .to.eventually.be.rejectedWith(/No lookup function registered for key/) }) it('can unregister all handlers', async () => { const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A) - receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction) - const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') + receiver.services.fetch.registerLookupFunction(PREFIX_A, lookupFunction) + const rawDataA = await sender.services.fetch.fetch(receiver.peerId, '/moduleA/foobar') if (rawDataA == null) { throw new Error('Value was not found') @@ -154,16 +159,16 @@ describe('Fetch', () => { const valueA = (new TextDecoder()).decode(rawDataA) expect(valueA).to.equal('hello world') - receiver.fetchService.unregisterLookupFunction(PREFIX_A) + receiver.services.fetch.unregisterLookupFunction(PREFIX_A) - await expect(sender.fetch(receiver.peerId, '/moduleA/foobar')) + await expect(sender.services.fetch.fetch(receiver.peerId, '/moduleA/foobar')) .to.eventually.be.rejectedWith(/No lookup function registered for key/) }) it('does not unregister wrong handlers', async () => { const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A) - receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction) - const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar') + receiver.services.fetch.registerLookupFunction(PREFIX_A, lookupFunction) + const rawDataA = await sender.services.fetch.fetch(receiver.peerId, '/moduleA/foobar') if (rawDataA == null) { throw new Error('Value was not found') @@ -172,9 +177,9 @@ describe('Fetch', () => { const valueA = (new TextDecoder()).decode(rawDataA) expect(valueA).to.equal('hello world') - receiver.fetchService.unregisterLookupFunction(PREFIX_A, async () => { return null }) + receiver.services.fetch.unregisterLookupFunction(PREFIX_A, async () => { return null }) - const rawDataB = await sender.fetch(receiver.peerId, '/moduleA/foobar') + const rawDataB = await sender.services.fetch.fetch(receiver.peerId, '/moduleA/foobar') if (rawDataB == null) { throw new Error('Value was not found') diff --git a/test/fetch/index.spec.ts b/test/fetch/index.spec.ts index 42f100398e..3eb9447d36 100644 --- a/test/fetch/index.spec.ts +++ b/test/fetch/index.spec.ts @@ -2,7 +2,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' -import { FetchService, FetchServiceInit } from '../../src/fetch/index.js' +import { fetchService, FetchServiceInit } from '../../src/fetch/index.js' import Peers from '../fixtures/peers.js' import { mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-mocks' import { createFromJSON } from '@libp2p/peer-id-factory' @@ -14,7 +14,7 @@ import delay from 'delay' import { pipe } from 'it-pipe' import { PersistentPeerStore } from '@libp2p/peer-store' import { MemoryDatastore } from 'datastore-core' -import { DefaultComponents } from '../../src/components.js' +import { defaultComponents, Components } from '../../src/components.js' import { stubInterface } from 'sinon-ts' import type { TransportManager } from '@libp2p/interface-transport' import type { ConnectionGater } from '@libp2p/interface-connection-gater' @@ -26,11 +26,11 @@ const defaultInit: FetchServiceInit = { timeout: 1000 } -async function createComponents (index: number): Promise { +async function createComponents (index: number): Promise { const peerId = await createFromJSON(Peers[index]) const events = new EventEmitter() - const components = new DefaultComponents({ + const components = defaultComponents({ peerId, registrar: mockRegistrar(), upgrader: mockUpgrader({ events }), @@ -50,8 +50,8 @@ async function createComponents (index: number): Promise { } describe('fetch', () => { - let localComponents: DefaultComponents - let remoteComponents: DefaultComponents + let localComponents: Components + let remoteComponents: Components beforeEach(async () => { localComponents = await createComponents(0) @@ -75,8 +75,8 @@ describe('fetch', () => { it('should be able to fetch from another peer', async () => { const key = 'key' const value = Uint8Array.from([0, 1, 2, 3, 4]) - const localFetch = new FetchService(localComponents, defaultInit) - const remoteFetch = new FetchService(remoteComponents, defaultInit) + const localFetch = fetchService(defaultInit)(localComponents) + const remoteFetch = fetchService(defaultInit)(remoteComponents) remoteFetch.registerLookupFunction(key, async (identifier) => { expect(identifier).to.equal(key) @@ -100,8 +100,8 @@ describe('fetch', () => { it('should time out fetching from another peer when waiting for the record', async () => { const key = 'key' - const localFetch = new FetchService(localComponents, defaultInit) - const remoteFetch = new FetchService(remoteComponents, defaultInit) + const localFetch = fetchService(defaultInit)(localComponents) + const remoteFetch = fetchService(defaultInit)(remoteComponents) await start(localFetch) await start(remoteFetch) diff --git a/test/identify/index.spec.ts b/test/identify/index.spec.ts index 70912a1ce6..3d9f2ab6df 100644 --- a/test/identify/index.spec.ts +++ b/test/identify/index.spec.ts @@ -6,7 +6,7 @@ import sinon from 'sinon' import { multiaddr } from '@multiformats/multiaddr' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { codes } from '../../src/errors.js' -import { IdentifyService, IdentifyServiceInit, Message } from '../../src/identify/index.js' +import { identifyService, IdentifyServiceInit, Message } from '../../src/identify/index.js' import Peers from '../fixtures/peers.js' import { PersistentPeerStore } from '@libp2p/peer-store' import { DefaultAddressManager } from '../../src/address-manager/index.js' @@ -27,7 +27,7 @@ import { start, stop } from '@libp2p/interfaces/startable' import { TimeoutController } from 'timeout-abort-controller' import { EventEmitter } from '@libp2p/interfaces/events' import pDefer from 'p-defer' -import { DefaultComponents } from '../../src/components.js' +import { defaultComponents, Components } from '../../src/components.js' import { stubInterface } from 'sinon-ts' import type { TransportManager } from '@libp2p/interface-transport' @@ -35,9 +35,7 @@ const listenMaddrs = [multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] const defaultInit: IdentifyServiceInit = { protocolPrefix: 'ipfs', - host: { - agentVersion: 'v1.0.0' - }, + agentVersion: 'v1.0.0', maxInboundStreams: 1, maxOutboundStreams: 1, maxPushIncomingStreams: 1, @@ -47,11 +45,11 @@ const defaultInit: IdentifyServiceInit = { const protocols = [MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_PUSH] -async function createComponents (index: number): Promise { +async function createComponents (index: number): Promise { const peerId = await createFromJSON(Peers[index]) const events = new EventEmitter() - const components = new DefaultComponents({ + const components = defaultComponents({ peerId, datastore: new MemoryDatastore(), registrar: mockRegistrar(), @@ -81,8 +79,8 @@ async function createComponents (index: number): Promise { } describe('identify', () => { - let localComponents: DefaultComponents - let remoteComponents: DefaultComponents + let localComponents: Components + let remoteComponents: Components beforeEach(async () => { localComponents = await createComponents(0) @@ -104,8 +102,8 @@ describe('identify', () => { }) it('should be able to identify another peer', async () => { - const localIdentify = new IdentifyService(localComponents, defaultInit) - const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) + const localIdentify = identifyService(defaultInit)(localComponents) + const remoteIdentify = identifyService(defaultInit)(remoteComponents) await start(localIdentify) await start(remoteIdentify) @@ -127,8 +125,8 @@ describe('identify', () => { }) it('should throw if identified peer is the wrong peer', async () => { - const localIdentify = new IdentifyService(localComponents, defaultInit) - const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) + const localIdentify = identifyService(defaultInit)(localComponents) + const remoteIdentify = identifyService(defaultInit)(remoteComponents) await start(localIdentify) await start(remoteIdentify) @@ -170,13 +168,11 @@ describe('identify', () => { it('should store own host data and protocol version into metadataBook on start', async () => { const agentVersion = 'js-project/1.0.0' - const localIdentify = new IdentifyService(localComponents, { + const localIdentify = identifyService({ ...defaultInit, protocolPrefix: 'ipfs', - host: { - agentVersion - } - }) + agentVersion + })(localComponents) const peer = await localComponents.peerStore.get(localComponents.peerId) expect(peer.metadata.get('AgentVersion')).to.be.undefined() @@ -192,8 +188,8 @@ describe('identify', () => { }) it('should time out during identify', async () => { - const localIdentify = new IdentifyService(localComponents, defaultInit) - const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) + const localIdentify = identifyService(defaultInit)(localComponents) + const remoteIdentify = identifyService(defaultInit)(remoteComponents) await start(localIdentify) await start(remoteIdentify) @@ -238,10 +234,10 @@ describe('identify', () => { it('should limit incoming identify message sizes', async () => { const deferred = pDefer() - const remoteIdentify = new IdentifyService(remoteComponents, { + const remoteIdentify = identifyService({ ...defaultInit, maxIdentifyMessageSize: 100 - }) + })(remoteComponents) await start(remoteIdentify) const identifySpy = sinon.spy(remoteIdentify, 'identify') @@ -284,10 +280,10 @@ describe('identify', () => { it('should time out incoming identify messages', async () => { const deferred = pDefer() - const remoteIdentify = new IdentifyService(remoteComponents, { + const remoteIdentify = identifyService({ ...defaultInit, timeout: 100 - }) + })(remoteComponents) await start(remoteIdentify) const identifySpy = sinon.spy(remoteIdentify, 'identify') diff --git a/test/identify/push.spec.ts b/test/identify/push.spec.ts index 43858ce783..117fb2d773 100644 --- a/test/identify/push.spec.ts +++ b/test/identify/push.spec.ts @@ -3,7 +3,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' import { multiaddr } from '@multiformats/multiaddr' -import { IdentifyService, IdentifyServiceInit } from '../../src/identify/index.js' +import { identifyService, IdentifyServiceInit } from '../../src/identify/index.js' import Peers from '../fixtures/peers.js' import { PersistentPeerStore } from '@libp2p/peer-store' import { DefaultAddressManager } from '../../src/address-manager/index.js' @@ -22,7 +22,7 @@ import { EventEmitter } from '@libp2p/interfaces/events' import delay from 'delay' import { pEvent } from 'p-event' import { start, stop } from '@libp2p/interfaces/startable' -import { DefaultComponents } from '../../src/components.js' +import { defaultComponents, Components } from '../../src/components.js' import type { TransportManager } from '@libp2p/interface-transport' import { stubInterface } from 'sinon-ts' @@ -30,9 +30,7 @@ const listenMaddrs = [multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] const defaultInit: IdentifyServiceInit = { protocolPrefix: 'ipfs', - host: { - agentVersion: 'v1.0.0' - }, + agentVersion: 'v1.0.0', maxInboundStreams: 1, maxOutboundStreams: 1, maxPushIncomingStreams: 1, @@ -42,11 +40,11 @@ const defaultInit: IdentifyServiceInit = { const protocols = [MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_PUSH] -async function createComponents (index: number): Promise { +async function createComponents (index: number): Promise { const peerId = await createFromJSON(Peers[index]) const events = new EventEmitter() - const components = new DefaultComponents({ + const components = defaultComponents({ peerId, datastore: new MemoryDatastore(), registrar: mockRegistrar(), @@ -76,8 +74,8 @@ async function createComponents (index: number): Promise { } describe('identify (push)', () => { - let localComponents: DefaultComponents - let remoteComponents: DefaultComponents + let localComponents: Components + let remoteComponents: Components beforeEach(async () => { localComponents = await createComponents(0) @@ -99,8 +97,8 @@ describe('identify (push)', () => { }) it('should be able to push identify updates to another peer', async () => { - const localIdentify = new IdentifyService(localComponents, defaultInit) - const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) + const localIdentify = identifyService(defaultInit)(localComponents) + const remoteIdentify = identifyService(defaultInit)(remoteComponents) await start(localIdentify) await start(remoteIdentify) @@ -173,11 +171,11 @@ describe('identify (push)', () => { it('should time out during push identify', async () => { let streamEnded = false - const localIdentify = new IdentifyService(localComponents, { + const localIdentify = identifyService({ ...defaultInit, timeout: 10 - }) - const remoteIdentify = new IdentifyService(remoteComponents, defaultInit) + })(localComponents) + const remoteIdentify = identifyService(defaultInit)(remoteComponents) await start(localIdentify) await start(remoteIdentify) @@ -207,10 +205,18 @@ describe('identify (push)', () => { ) }) + // make sure we'll return the connection + await localComponents.peerStore.patch(localToRemote.remotePeer, { + protocols: [ + MULTICODEC_IDENTIFY_PUSH + ] + }) + localComponents.connectionManager.getConnections = sinon.stub().returns([localToRemote]) + const newStreamSpy = sinon.spy(localToRemote, 'newStream') // push updated peer record to remote - await localIdentify.push([localToRemote]) + await localIdentify.push() // should have closed stream expect(newStreamSpy).to.have.property('callCount', 1) diff --git a/test/identify/service.spec.ts b/test/identify/service.spec.ts index b0e1a62a1d..df21fd532d 100644 --- a/test/identify/service.spec.ts +++ b/test/identify/service.spec.ts @@ -5,22 +5,23 @@ import sinon from 'sinon' import { multiaddr } from '@multiformats/multiaddr' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import Peers from '../fixtures/peers.js' -import { createLibp2pNode } from '../../src/libp2p.js' +import { createLibp2p } from '../../src/index.js' import { createBaseOptions } from '../utils/base-options.browser.js' import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' import { createFromJSON } from '@libp2p/peer-id-factory' import pWaitFor from 'p-wait-for' import { peerIdFromString } from '@libp2p/peer-id' import type { PeerId } from '@libp2p/interface-peer-id' -import type { Libp2pNode } from '../../src/libp2p.js' import { pEvent } from 'p-event' import { AGENT_VERSION } from '../../src/identify/consts.js' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import type { Libp2p } from '@libp2p/interface-libp2p' +import { identifyService, IdentifyService } from '../../src/identify/index.js' -describe('libp2p.dialer.identifyService', () => { +describe('identify', () => { let peerId: PeerId - let libp2p: Libp2pNode - let remoteLibp2p: Libp2pNode + let libp2p: Libp2p<{ identify: IdentifyService }> + let remoteLibp2p: Libp2p<{ identify: IdentifyService }> const remoteAddr = MULTIADDRS_WEBSOCKETS[0] before(async () => { @@ -42,17 +43,20 @@ describe('libp2p.dialer.identifyService', () => { }) it('should run identify automatically after connecting', async () => { - libp2p = await createLibp2pNode(createBaseOptions({ - peerId + libp2p = await createLibp2p(createBaseOptions({ + peerId, + services: { + identify: identifyService() + } })) await libp2p.start() - if (libp2p.identifyService == null) { + if (libp2p.services.identify == null) { throw new Error('Identity service was not configured') } - const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') + const identityServiceIdentifySpy = sinon.spy(libp2p.services.identify, 'identify') const connection = await libp2p.dial(remoteAddr) expect(connection).to.exist() @@ -66,17 +70,20 @@ describe('libp2p.dialer.identifyService', () => { }) it('should store remote agent and protocol versions in metadataBook after connecting', async () => { - libp2p = await createLibp2pNode(createBaseOptions({ - peerId + libp2p = await createLibp2p(createBaseOptions({ + peerId, + services: { + identify: identifyService() + } })) await libp2p.start() - if (libp2p.identifyService == null) { + if (libp2p.services.identify == null) { throw new Error('Identity service was not configured') } - const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') + const identityServiceIdentifySpy = sinon.spy(libp2p.services.identify, 'identify') const connection = await libp2p.dial(remoteAddr) expect(connection).to.exist() @@ -96,18 +103,21 @@ describe('libp2p.dialer.identifyService', () => { }) it('should push protocol updates to an already connected peer', async () => { - libp2p = await createLibp2pNode(createBaseOptions({ - peerId + libp2p = await createLibp2p(createBaseOptions({ + peerId, + services: { + identify: identifyService() + } })) await libp2p.start() - if (libp2p.identifyService == null) { + if (libp2p.services.identify == null) { throw new Error('Identity service was not configured') } - const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') - const identityServicePushSpy = sinon.spy(libp2p.identifyService, 'push') + const identityServiceIdentifySpy = sinon.spy(libp2p.services.identify, 'identify') + const identityServicePushSpy = sinon.spy(libp2p.services.identify, 'push') const connectionPromise = pEvent(libp2p, 'connection:open') const connection = await libp2p.dial(remoteAddr) @@ -136,13 +146,6 @@ describe('libp2p.dialer.identifyService', () => { // Verify the remote peer is notified of both changes expect(identityServicePushSpy.callCount).to.equal(2) - for (const call of identityServicePushSpy.getCalls()) { - const [connections] = call.args - expect(connections.length).to.equal(1) - expect(connections[0].remotePeer.toString()).to.equal(remoteAddr.getPeerId()) - await call.returnValue - } - // Verify the streams close await pWaitFor(() => connection.streams.length === 0) }) @@ -155,13 +158,16 @@ describe('libp2p.dialer.identifyService', () => { sinon.stub(navigator, 'userAgent').value('vTEST') } - libp2p = await createLibp2pNode(createBaseOptions({ - peerId + libp2p = await createLibp2p(createBaseOptions({ + peerId, + services: { + identify: identifyService() + } })) await libp2p.start() - if (libp2p.identifyService == null) { + if (libp2p.services.identify == null) { throw new Error('Identity service was not configured') } @@ -174,18 +180,18 @@ describe('libp2p.dialer.identifyService', () => { it('should store host data and protocol version into peer store', async () => { const agentVersion = 'js-project/1.0.0' - libp2p = await createLibp2pNode(createBaseOptions({ + libp2p = await createLibp2p(createBaseOptions({ peerId, - identify: { - host: { + services: { + identify: identifyService({ agentVersion - } + }) } })) await libp2p.start() - if (libp2p.identifyService == null) { + if (libp2p.services.identify == null) { throw new Error('Identity service was not configured') } @@ -195,18 +201,21 @@ describe('libp2p.dialer.identifyService', () => { }) it('should push multiaddr updates to an already connected peer', async () => { - libp2p = await createLibp2pNode(createBaseOptions({ - peerId + libp2p = await createLibp2p(createBaseOptions({ + peerId, + services: { + identify: identifyService() + } })) await libp2p.start() - if (libp2p.identifyService == null) { + if (libp2p.services.identify == null) { throw new Error('Identity service was not configured') } - const identityServiceIdentifySpy = sinon.spy(libp2p.identifyService, 'identify') - const identityServicePushSpy = sinon.spy(libp2p.identifyService, 'push') + const identityServiceIdentifySpy = sinon.spy(libp2p.services.identify, 'identify') + const identityServicePushSpy = sinon.spy(libp2p.services.identify, 'push') const connectionPromise = pEvent(libp2p, 'connection:open') const connection = await libp2p.dial(remoteAddr) @@ -227,12 +236,6 @@ describe('libp2p.dialer.identifyService', () => { // Verify the remote peer is notified of change expect(identityServicePushSpy.callCount).to.equal(1) - for (const call of identityServicePushSpy.getCalls()) { - const [connections] = call.args - expect(connections.length).to.equal(1) - expect(connections[0].remotePeer.toString()).to.equal(remoteAddr.getPeerId()) - await call.returnValue - } // Verify the streams close await pWaitFor(() => connection.streams.length === 0) diff --git a/test/interop.ts b/test/interop.ts index 1405356892..3468562b6d 100644 --- a/test/interop.ts +++ b/test/interop.ts @@ -2,7 +2,7 @@ import { interopTests } from '@libp2p/interop' import type { SpawnOptions, Daemon, DaemonFactory } from '@libp2p/interop' import { createServer } from '@libp2p/daemon-server' import { createClient } from '@libp2p/daemon-client' -import { createLibp2p, Libp2pOptions } from '../src/index.js' +import { createLibp2p, Libp2pOptions, ServiceFactoryMap } from '../src/index.js' import { noise } from '@chainsafe/libp2p-noise' import { tcp } from '@libp2p/tcp' import { multiaddr } from '@multiformats/multiaddr' @@ -20,6 +20,11 @@ import { peerIdFromKeys } from '@libp2p/peer-id' import { floodsub } from '@libp2p/floodsub' import { gossipsub } from '@chainsafe/libp2p-gossipsub' import { circuitRelayServer, circuitRelayTransport } from '../src/circuit-relay/index.js' +import type { ServiceMap } from '@libp2p/interface-libp2p' +import { identifyService } from '../src/identify/index.js' +import { contentRouting } from '@libp2p/interface-content-routing' +import { peerRouting } from '@libp2p/interface-peer-routing' +import { peerDiscovery } from '@libp2p/interface-peer-discovery' /** * @packageDocumentation @@ -117,7 +122,7 @@ async function createJsPeer (options: SpawnOptions): Promise { peerId = await peerIdFromKeys(privateKey.public.bytes, privateKey.bytes) } - const opts: Libp2pOptions = { + const opts: Libp2pOptions = { peerId, addresses: { listen: options.noListen === true ? [] : ['/ip4/127.0.0.1/tcp/0'] @@ -125,10 +130,11 @@ async function createJsPeer (options: SpawnOptions): Promise { transports: [tcp(), circuitRelayTransport()], streamMuxers: [], // @ts-expect-error remove after https://github.com/ChainSafe/js-libp2p-noise/pull/306 - connectionEncryption: [noise()], - nat: { - enabled: false - } + connectionEncryption: [noise()] + } + + const services: ServiceFactoryMap = { + identify: identifyService() } if (options.muxer === 'mplex') { @@ -139,39 +145,59 @@ async function createJsPeer (options: SpawnOptions): Promise { if (options.pubsub === true) { if (options.pubsubRouter === 'floodsub') { - opts.pubsub = floodsub() + services.pubsub = floodsub() } else { - opts.pubsub = gossipsub() + // @ts-expect-error remove after gossipsub is upgraded to @libp2p/interface-peer-store@2.x.x + services.pubsub = gossipsub() } } if (options.relay === true) { - opts.relay = circuitRelayServer() + services.relay = circuitRelayServer() } if (options.dht === true) { - opts.dht = (components: any) => { - const dht = kadDHT({ + services.dht = (components: any) => { + const dht: any = kadDHT({ clientMode: false })(components) // go-libp2p-daemon only has the older single-table DHT instead of the dual // lan/wan version found in recent go-ipfs versions. unfortunately it's been // abandoned so here we simulate the older config with the js implementation - const lan = dht.lan + const lan: any = dht.lan const protocol = '/ipfs/kad/1.0.0' lan.protocol = protocol - // @ts-expect-error lan.network.protocol = protocol - // @ts-expect-error lan.topologyListener.protocol = protocol + Object.defineProperties(lan, { + [contentRouting]: { + get () { + return dht[contentRouting] + } + }, + [peerRouting]: { + get () { + return dht[peerRouting] + } + }, + [peerDiscovery]: { + get () { + return dht[peerDiscovery] + } + } + }) + return lan } } - const node = await createLibp2p(opts) + const node: any = await createLibp2p({ + ...opts, + services + }) const server = createServer(multiaddr('/ip4/0.0.0.0/tcp/0'), node) await server.start() diff --git a/test/peer-discovery/index.node.ts b/test/peer-discovery/index.node.ts index 742a37bae4..ce6fbcc402 100644 --- a/test/peer-discovery/index.node.ts +++ b/test/peer-discovery/index.node.ts @@ -12,18 +12,31 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { createBaseOptions } from '../utils/base-options.js' import { createPeerId } from '../utils/creators/peer.js' import type { PeerId } from '@libp2p/interface-peer-id' -import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' -import { CustomEvent } from '@libp2p/interfaces/events' -import type { PeerInfo } from '@libp2p/interface-peer-info' +import { createLibp2p } from '../../src/index.js' +import { EventEmitter } from '@libp2p/interfaces/events' import type { Libp2pOptions } from '../../src/index.js' +import type { Libp2p } from '@libp2p/interface-libp2p' +import type { DHT } from '@libp2p/interface-dht' +import type { PeerDiscovery, PeerDiscoveryEvents } from '@libp2p/interface-peer-discovery' +import { symbol } from '@libp2p/interface-peer-discovery' const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') +class TestPeerDiscovery extends EventEmitter implements PeerDiscovery { + get [symbol] (): true { + return true + } + + get [Symbol.toStringTag] (): '@libp2p/test-peer-discovery' { + return '@libp2p/test-peer-discovery' + } +} + describe('peer discovery scenarios', () => { let peerId: PeerId let remotePeerId1: PeerId let remotePeerId2: PeerId - let libp2p: Libp2pNode + let libp2p: Libp2p beforeEach(async () => { [peerId, remotePeerId1, remotePeerId2] = await Promise.all([ @@ -40,23 +53,25 @@ describe('peer discovery scenarios', () => { }) it('should ignore self on discovery', async () => { - libp2p = await createLibp2pNode(createBaseOptions({ + const discovery = new TestPeerDiscovery() + + libp2p = await createLibp2p(createBaseOptions({ peerId, peerDiscovery: [ - mdns() + () => discovery ] })) await libp2p.start() const discoverySpy = sinon.spy() libp2p.addEventListener('peer:discovery', discoverySpy) - libp2p.onDiscoveryPeer(new CustomEvent('peer', { + discovery.safeDispatchEvent('peer', { detail: { id: libp2p.peerId, multiaddrs: [], protocols: [] } - })) + }) expect(discoverySpy.called).to.eql(false) }) @@ -69,7 +84,7 @@ describe('peer discovery scenarios', () => { `${listenAddr.toString()}/p2p/${remotePeerId2.toString()}` ] - libp2p = await createLibp2pNode(createBaseOptions({ + libp2p = await createLibp2p(createBaseOptions({ peerId, addresses: { listen: [ @@ -124,9 +139,9 @@ describe('peer discovery scenarios', () => { ] }) - libp2p = await createLibp2pNode(getConfig(peerId)) - const remoteLibp2p1 = await createLibp2pNode(getConfig(remotePeerId1)) - const remoteLibp2p2 = await createLibp2pNode(getConfig(remotePeerId2)) + libp2p = await createLibp2p(getConfig(peerId)) + const remoteLibp2p1 = await createLibp2p(getConfig(remotePeerId1)) + const remoteLibp2p2 = await createLibp2p(getConfig(remotePeerId2)) const expectedPeers = new Set([ remotePeerId1.toString(), @@ -159,22 +174,24 @@ describe('peer discovery scenarios', () => { it('kad-dht should discover other peers', async () => { const deferred = defer() - const getConfig = (peerId: PeerId): Libp2pOptions => createBaseOptions({ + const getConfig = (peerId: PeerId): Libp2pOptions<{ dht: DHT }> => createBaseOptions({ peerId, addresses: { listen: [ listenAddr.toString() ] }, - dht: kadDHT() + services: { + dht: kadDHT() + } }) const localConfig = getConfig(peerId) - libp2p = await createLibp2pNode(localConfig) + libp2p = await createLibp2p(localConfig) - const remoteLibp2p1 = await createLibp2pNode(getConfig(remotePeerId1)) - const remoteLibp2p2 = await createLibp2pNode(getConfig(remotePeerId2)) + const remoteLibp2p1 = await createLibp2p(getConfig(remotePeerId1)) + const remoteLibp2p2 = await createLibp2p(getConfig(remotePeerId2)) libp2p.addEventListener('peer:discovery', (evt) => { const { id } = evt.detail diff --git a/test/peer-routing/peer-routing.node.ts b/test/peer-routing/peer-routing.node.ts index 49f113efda..11d707cda7 100644 --- a/test/peer-routing/peer-routing.node.ts +++ b/test/peer-routing/peer-routing.node.ts @@ -14,11 +14,11 @@ import { createBaseOptions } from '../utils/base-options.js' import { createRoutingOptions } from './utils.js' import type { PeerId } from '@libp2p/interface-peer-id' import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import { EventTypes, MessageType } from '@libp2p/interface-dht' +import { DHT, EventTypes, MessageType } from '@libp2p/interface-dht' import type { PeerInfo } from '@libp2p/interface-peer-info' -import { kadDHT } from '@libp2p/kad-dht' import type { PeerRouting } from '@libp2p/interface-peer-routing' import { StubbedInstance, stubInterface } from 'sinon-ts' +import type { Libp2p } from '@libp2p/interface-libp2p' describe('peer-routing', () => { let peerId: PeerId @@ -56,7 +56,7 @@ describe('peer-routing', () => { }) describe('via dht router', () => { - let nodes: Libp2pNode[] + let nodes: Array> before(async () => { nodes = await Promise.all([ @@ -81,11 +81,11 @@ describe('peer-routing', () => { after(async () => await Promise.all(nodes.map(async (n) => { await n.stop() }))) it('should use the nodes dht', async () => { - if (nodes[0].dht == null) { + if (nodes[0].services.dht == null) { throw new Error('DHT not configured') } - const dhtFindPeerStub = sinon.stub(nodes[0].dht, 'findPeer').callsFake(async function * () { + const dhtFindPeerStub = sinon.stub(nodes[0].services.dht, 'findPeer').callsFake(async function * () { yield { from: nodes[2].peerId, type: EventTypes.FINAL_PEER, @@ -105,11 +105,11 @@ describe('peer-routing', () => { }) it('should use the nodes dht to get the closest peers', async () => { - if (nodes[0].dht == null) { + if (nodes[0].services.dht == null) { throw new Error('DHT not configured') } - const dhtGetClosestPeersStub = sinon.stub(nodes[0].dht, 'getClosestPeers').callsFake(async function * () { + const dhtGetClosestPeersStub = sinon.stub(nodes[0].services.dht, 'getClosestPeers').callsFake(async function * () { yield { from: nodes[2].peerId, type: EventTypes.FINAL_PEER, @@ -312,7 +312,7 @@ describe('peer-routing', () => { }) describe('via dht and delegate routers', () => { - let node: Libp2pNode + let node: Libp2p<{ dht: DHT }> let delegate: StubbedInstance beforeEach(async () => { @@ -324,8 +324,7 @@ describe('peer-routing', () => { config: createRoutingOptions({ peerRouters: [ () => delegate - ], - dht: kadDHT() + ] }) }) }) @@ -344,11 +343,11 @@ describe('peer-routing', () => { protocols: [] } - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT not configured') } - sinon.stub(node.dht, 'findPeer').callsFake(async function * () {}) + sinon.stub(node.services.dht, 'findPeer').callsFake(async function * () {}) delegate.findPeer.reset() delegate.findPeer.callsFake(async () => { return results @@ -366,13 +365,13 @@ describe('peer-routing', () => { protocols: [] } - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT not configured') } const defer = pDefer() - sinon.stub(node.dht, 'findPeer').callsFake(async function * () { + sinon.stub(node.services.dht, 'findPeer').callsFake(async function * () { yield { name: 'SENDING_QUERY', type: EventTypes.SENDING_QUERY, @@ -401,13 +400,13 @@ describe('peer-routing', () => { protocols: [] } - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT not configured') } const defer = pDefer() - sinon.stub(node.dht, 'findPeer').callsFake(async function * () { + sinon.stub(node.services.dht, 'findPeer').callsFake(async function * () { yield { from: remotePeerId, name: 'FINAL_PEER', @@ -436,13 +435,13 @@ describe('peer-routing', () => { protocols: [] } - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT not configured') } const spy = sinon.spy(node.peerStore, 'merge') - sinon.stub(node.dht, 'findPeer').callsFake(async function * () { + sinon.stub(node.services.dht, 'findPeer').callsFake(async function * () { yield { from: remotePeerId, name: 'FINAL_PEER', @@ -472,11 +471,11 @@ describe('peer-routing', () => { protocols: [] }] - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT not configured') } - sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { }) + sinon.stub(node.services.dht, 'getClosestPeers').callsFake(async function * () { }) delegate.getClosestPeers.callsFake(async function * () { yield results[0] @@ -498,13 +497,13 @@ describe('peer-routing', () => { protocols: [] } - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT not configured') } const spy = sinon.spy(node.peerStore, 'merge') - sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { }) + sinon.stub(node.services.dht, 'getClosestPeers').callsFake(async function * () { }) delegate.getClosestPeers.callsFake(async function * () { yield result @@ -527,11 +526,11 @@ describe('peer-routing', () => { protocols: [] }] - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT not configured') } - sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { + sinon.stub(node.services.dht, 'getClosestPeers').callsFake(async function * () { for (const peer of results) { yield { from: remotePeerId, @@ -553,7 +552,7 @@ describe('peer-routing', () => { }) describe('peer routing refresh manager service', () => { - let node: Libp2pNode + let node: Libp2p<{ dht: DHT }> let peerIds: PeerId[] before(async () => { @@ -590,12 +589,12 @@ describe('peer-routing', () => { started: false }) - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT not configured') } const peerStoreMergeStub = sinon.spy(node.peerStore, 'merge') - const dhtGetClosestPeersStub = sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { + const dhtGetClosestPeersStub = sinon.stub(node.services.dht, 'getClosestPeers').callsFake(async function * () { yield { name: 'FINAL_PEER', type: EventTypes.FINAL_PEER, @@ -642,11 +641,11 @@ describe('peer-routing', () => { started: false }) - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT not configured') } - const dhtGetClosestPeersStub = sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { + const dhtGetClosestPeersStub = sinon.stub(node.services.dht, 'getClosestPeers').callsFake(async function * () { yield { name: 'SENDING_QUERY', type: EventTypes.SENDING_QUERY, @@ -677,11 +676,11 @@ describe('peer-routing', () => { started: false }) - if (node.dht == null) { + if (node.services.dht == null) { throw new Error('DHT not configured') } - const dhtGetClosestPeersStub = sinon.stub(node.dht, 'getClosestPeers').callsFake(async function * () { + const dhtGetClosestPeersStub = sinon.stub(node.services.dht, 'getClosestPeers').callsFake(async function * () { yield { name: 'PEER_RESPONSE', type: EventTypes.PEER_RESPONSE, diff --git a/test/peer-routing/utils.ts b/test/peer-routing/utils.ts index 67fb2f1a86..6195ba4e3d 100644 --- a/test/peer-routing/utils.ts +++ b/test/peer-routing/utils.ts @@ -1,11 +1,12 @@ +import type { DHT } from '@libp2p/interface-dht' import { kadDHT } from '@libp2p/kad-dht' import type { Libp2pOptions } from '../../src/index.js' import { createBaseOptions } from '../utils/base-options.js' -export function createRoutingOptions (...overrides: Libp2pOptions[]): Libp2pOptions { +export function createRoutingOptions (...overrides: Libp2pOptions[]): Libp2pOptions<{ dht: DHT }> { return createBaseOptions({ - dht: kadDHT({ - kBucketSize: 20 - }) + services: { + dht: kadDHT() + } }, ...overrides) } diff --git a/test/ping/index.spec.ts b/test/ping/index.spec.ts index 78bdee2f3e..8723936be0 100644 --- a/test/ping/index.spec.ts +++ b/test/ping/index.spec.ts @@ -2,7 +2,7 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' -import { PingService, PingServiceInit } from '../../src/ping/index.js' +import { pingService, PingServiceInit } from '../../src/ping/index.js' import Peers from '../fixtures/peers.js' import { mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-mocks' import { createFromJSON } from '@libp2p/peer-id-factory' @@ -14,10 +14,11 @@ import delay from 'delay' import { pipe } from 'it-pipe' import { PersistentPeerStore } from '@libp2p/peer-store' import { MemoryDatastore } from 'datastore-core' -import { DefaultComponents } from '../../src/components.js' +import { defaultComponents, Components } from '../../src/components.js' import { stubInterface } from 'sinon-ts' import type { TransportManager } from '@libp2p/interface-transport' import type { ConnectionGater } from '@libp2p/interface-connection-gater' +import { PROTOCOL } from '../../src/ping/constants.js' const defaultInit: PingServiceInit = { protocolPrefix: 'ipfs', @@ -26,11 +27,11 @@ const defaultInit: PingServiceInit = { timeout: 1000 } -async function createComponents (index: number): Promise { +async function createComponents (index: number): Promise { const peerId = await createFromJSON(Peers[index]) const events = new EventEmitter() - const components = new DefaultComponents({ + const components = defaultComponents({ peerId, registrar: mockRegistrar(), upgrader: mockUpgrader({ events }), @@ -50,8 +51,8 @@ async function createComponents (index: number): Promise { } describe('ping', () => { - let localComponents: DefaultComponents - let remoteComponents: DefaultComponents + let localComponents: Components + let remoteComponents: Components beforeEach(async () => { localComponents = await createComponents(0) @@ -73,8 +74,8 @@ describe('ping', () => { }) it('should be able to ping another peer', async () => { - const localPing = new PingService(localComponents, defaultInit) - const remotePing = new PingService(remoteComponents, defaultInit) + const localPing = pingService(defaultInit)(localComponents) + const remotePing = pingService(defaultInit)(remoteComponents) await start(localPing) await start(remotePing) @@ -89,8 +90,8 @@ describe('ping', () => { }) it('should time out pinging another peer when waiting for a pong', async () => { - const localPing = new PingService(localComponents, defaultInit) - const remotePing = new PingService(remoteComponents, defaultInit) + const localPing = pingService(defaultInit)(localComponents) + const remotePing = pingService(defaultInit)(remoteComponents) await start(localPing) await start(remotePing) @@ -101,8 +102,8 @@ describe('ping', () => { remoteComponents.events.safeDispatchEvent('connection:open', { detail: remoteToLocal }) // replace existing handler with a really slow one - await remoteComponents.registrar.unhandle(remotePing.protocol) - await remoteComponents.registrar.handle(remotePing.protocol, ({ stream }) => { + await remoteComponents.registrar.unhandle(PROTOCOL) + await remoteComponents.registrar.handle(PROTOCOL, ({ stream }) => { void pipe( stream, async function * (source) { diff --git a/test/ping/ping.node.ts b/test/ping/ping.node.ts index 196824da92..00dec19820 100644 --- a/test/ping/ping.node.ts +++ b/test/ping/ping.node.ts @@ -7,23 +7,42 @@ import { createBaseOptions } from '../utils/base-options.js' import { PROTOCOL } from '../../src/ping/constants.js' import { multiaddr } from '@multiformats/multiaddr' import pDefer from 'p-defer' -import type { Libp2pNode } from '../../src/libp2p.js' +import { pingService, PingService } from '../../src/ping/index.js' +import type { Libp2p } from '@libp2p/interface-libp2p' describe('ping', () => { - let nodes: Libp2pNode[] + let nodes: Array> beforeEach(async () => { nodes = await Promise.all([ - createNode({ config: createBaseOptions() }), - createNode({ config: createBaseOptions() }), - createNode({ config: createBaseOptions() }) + createNode({ + config: createBaseOptions({ + services: { + ping: pingService() + } + }) + }), + createNode({ + config: createBaseOptions({ + services: { + ping: pingService() + } + }) + }), + createNode({ + config: createBaseOptions({ + services: { + ping: pingService() + } + }) + }) ]) await populateAddressBooks(nodes) - await nodes[0].components.peerStore.patch(nodes[1].peerId, { + await nodes[0].peerStore.patch(nodes[1].peerId, { multiaddrs: nodes[1].getMultiaddrs() }) - await nodes[1].components.peerStore.patch(nodes[0].peerId, { + await nodes[1].peerStore.patch(nodes[0].peerId, { multiaddrs: nodes[0].getMultiaddrs() }) }) @@ -31,14 +50,14 @@ describe('ping', () => { afterEach(async () => await Promise.all(nodes.map(async n => { await n.stop() }))) it('ping once from peer0 to peer1 using a multiaddr', async () => { - const ma = multiaddr(`${nodes[2].getMultiaddrs()[0].toString()}/p2p/${nodes[2].peerId.toString()}`) - const latency = await nodes[0].ping(ma) + const ma = multiaddr(nodes[2].getMultiaddrs()[0]) + const latency = await nodes[0].services.ping.ping(ma) expect(latency).to.be.a('Number') }) it('ping once from peer0 to peer1 using a peerId', async () => { - const latency = await nodes[0].ping(nodes[1].peerId) + const latency = await nodes[0].services.ping.ping(nodes[1].peerId) expect(latency).to.be.a('Number') }) @@ -47,7 +66,7 @@ describe('ping', () => { const latencies = [] for (let i = 0; i < 5; i++) { - latencies.push(await nodes[1].ping(nodes[0].peerId)) + latencies.push(await nodes[1].services.ping.ping(nodes[0].peerId)) } const averageLatency = latencies.reduce((p, c) => p + c, 0) / latencies.length @@ -73,7 +92,7 @@ describe('ping', () => { ) }) - const latency = await nodes[0].ping(nodes[1].peerId) + const latency = await nodes[0].services.ping.ping(nodes[1].peerId) expect(latency).to.be.a('Number') @@ -84,10 +103,12 @@ describe('ping', () => { const remote = nodes[0] const client = await createNode({ config: createBaseOptions({ - ping: { - // Allow two outbound ping streams. - // It is not allowed by the spec, but this test needs to open two concurrent streams. - maxOutboundStreams: 2 + services: { + ping: pingService({ + // Allow two outbound ping streams. + // It is not allowed by the spec, but this test needs to open two concurrent streams. + maxOutboundStreams: 2 + }) } }) }) @@ -100,8 +121,8 @@ describe('ping', () => { // Send two ping requests in parallel, this should open two concurrent streams const results = await Promise.allSettled([ - client.ping(remote.peerId), - client.ping(remote.peerId) + client.services.ping.ping(remote.peerId), + client.services.ping.ping(remote.peerId) ]) // Verify that the remote peer accepted both inbound streams diff --git a/test/registrar/registrar.spec.ts b/test/registrar/registrar.spec.ts index 2bb532ed6a..f2f841e11f 100644 --- a/test/registrar/registrar.spec.ts +++ b/test/registrar/registrar.spec.ts @@ -7,28 +7,34 @@ import { createTopology } from '@libp2p/topology' import { PersistentPeerStore } from '@libp2p/peer-store' import { DefaultRegistrar } from '../../src/registrar.js' import { mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-mocks' -import { createPeerId, createNode } from '../utils/creators/peer.js' -import { createBaseOptions } from '../utils/base-options.browser.js' +import { createPeerId } from '../utils/creators/peer.js' import type { Registrar } from '@libp2p/interface-registrar' import type { PeerId } from '@libp2p/interface-peer-id' -import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { EventEmitter } from '@libp2p/interfaces/events' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { plaintext } from '../../src/insecure/index.js' import { webSockets } from '@libp2p/websockets' import { mplex } from '@libp2p/mplex' -import { DefaultComponents } from '../../src/components.js' -import { stubInterface } from 'sinon-ts' +import { Components, defaultComponents } from '../../src/components.js' +import { StubbedInstance, stubInterface } from 'sinon-ts' import type { TransportManager } from '@libp2p/interface-transport' import type { ConnectionGater } from '@libp2p/interface-connection-gater' +import type { ConnectionManager } from '@libp2p/interface-connection-manager' +import type { Libp2pEvents } from '@libp2p/interface-libp2p' +import type { PeerStore } from '@libp2p/interface-peer-store' +import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' +import { CodeError } from '@libp2p/interfaces/errors' +import { codes } from '../../src/errors.js' +import { matchPeerId } from '../fixtures/match-peer-id.js' const protocol = '/test/1.0.0' describe('registrar', () => { - let components: DefaultComponents + let components: Components let registrar: Registrar let peerId: PeerId + let libp2p: Libp2pNode before(async () => { peerId = await createPeerId() @@ -37,7 +43,7 @@ describe('registrar', () => { describe('errors', () => { beforeEach(() => { const events = new EventEmitter() - components = new DefaultComponents({ + components = defaultComponents({ peerId, events, datastore: new MemoryDatastore(), @@ -70,29 +76,38 @@ describe('registrar', () => { }) describe('registration', () => { - let libp2p: Libp2pNode + let registrar: Registrar + let peerId: PeerId + let connectionManager: StubbedInstance + let peerStore: StubbedInstance + let events: EventEmitter beforeEach(async () => { - libp2p = await createNode({ - config: createBaseOptions(), - started: false + peerId = await createEd25519PeerId() + connectionManager = stubInterface() + peerStore = stubInterface() + events = new EventEmitter() + + registrar = new DefaultRegistrar({ + peerId, + connectionManager, + peerStore, + events }) }) - afterEach(async () => { await libp2p.stop() }) - it('should be able to register a protocol', async () => { const topology = createTopology({ onConnect: () => { }, onDisconnect: () => { } }) - expect(libp2p.components.registrar.getTopologies(protocol)).to.have.lengthOf(0) + expect(registrar.getTopologies(protocol)).to.have.lengthOf(0) - const identifier = await libp2p.components.registrar.register(protocol, topology) + const identifier = await registrar.register(protocol, topology) expect(identifier).to.exist() - expect(libp2p.components.registrar.getTopologies(protocol)).to.have.lengthOf(1) + expect(registrar.getTopologies(protocol)).to.have.lengthOf(1) }) it('should be able to unregister a protocol', async () => { @@ -101,19 +116,19 @@ describe('registrar', () => { onDisconnect: () => { } }) - expect(libp2p.components.registrar.getTopologies(protocol)).to.have.lengthOf(0) + expect(registrar.getTopologies(protocol)).to.have.lengthOf(0) - const identifier = await libp2p.components.registrar.register(protocol, topology) + const identifier = await registrar.register(protocol, topology) - expect(libp2p.components.registrar.getTopologies(protocol)).to.have.lengthOf(1) + expect(registrar.getTopologies(protocol)).to.have.lengthOf(1) - libp2p.components.registrar.unregister(identifier) + registrar.unregister(identifier) - expect(libp2p.components.registrar.getTopologies(protocol)).to.have.lengthOf(0) + expect(registrar.getTopologies(protocol)).to.have.lengthOf(0) }) it('should not error if unregistering unregistered topology handler', () => { - libp2p.components.registrar.unregister('bad-identifier') + registrar.unregister('bad-identifier') }) it('should call onConnect handler for connected peers after register', async () => { @@ -138,24 +153,27 @@ describe('registrar', () => { } }) - await libp2p.start() - // Register protocol - await libp2p.components.registrar.register(protocol, topology) - - // Add connected peer with protocol to peerStore and registrar - await libp2p.peerStore.patch(remotePeerId, { - protocols: [protocol] + await registrar.register(protocol, topology) + + // Peer data is in the peer store + peerStore.get.withArgs(matchPeerId(remotePeerId)).resolves({ + id: remotePeerId, + addresses: [], + protocols: [protocol], + metadata: new Map(), + tags: new Map() }) // remote peer connects - libp2p.components.events.safeDispatchEvent('connection:open', { + events.safeDispatchEvent('connection:open', { detail: conn }) await onConnectDefer.promise + // remote peer disconnects await conn.close() - libp2p.components.events.safeDispatchEvent('connection:close', { + events.safeDispatchEvent('connection:close', { detail: conn }) await onDisconnectDefer.promise @@ -178,23 +196,31 @@ describe('registrar', () => { } }) - await libp2p.start() - // Register protocol - await libp2p.components.registrar.register(protocol, topology) + await registrar.register(protocol, topology) - // Add connected peer to peerStore and registrar - await libp2p.peerStore.patch(remotePeerId, { - protocols: [] - }) + // No details before identify + peerStore.get.withArgs(conn.remotePeer).rejects(new CodeError('Not found', codes.ERR_NOT_FOUND)) // remote peer connects - libp2p.components.events.safeDispatchEvent('connection:open', { + events.safeDispatchEvent('connection:open', { detail: conn }) + // Can get details after identify + peerStore.get.withArgs(conn.remotePeer).resolves({ + id: conn.remotePeer, + addresses: [], + protocols: [protocol], + metadata: new Map(), + tags: new Map() + }) + + // we have a connection to this peer + connectionManager.getConnections.withArgs(conn.remotePeer).returns([conn]) + // identify completes - libp2p.components.events.safeDispatchEvent('peer:update', { + events.safeDispatchEvent('peer:update', { detail: { peer: { id: conn.remotePeer, @@ -208,7 +234,7 @@ describe('registrar', () => { await onConnectDefer.promise // Peer no longer supports the protocol our topology is registered for - libp2p.components.events.safeDispatchEvent('peer:update', { + events.safeDispatchEvent('peer:update', { detail: { peer: { id: conn.remotePeer, @@ -229,6 +255,8 @@ describe('registrar', () => { }) it('should be able to register and unregister a handler', async () => { + const deferred = pDefer() + libp2p = await createLibp2pNode({ peerId: await createEd25519PeerId(), transports: [ @@ -239,10 +267,17 @@ describe('registrar', () => { ], connectionEncryption: [ plaintext() - ] + ], + services: { + test: (components: any) => { + deferred.resolve(components) + } + } }) - const registrar = libp2p.components.registrar + const components = await deferred.promise + + const registrar = components.registrar expect(registrar.getProtocols()).to.not.have.any.keys(['/echo/1.0.0', '/echo/1.0.1']) diff --git a/test/transports/transport-manager.node.ts b/test/transports/transport-manager.node.ts index 0447c66e3c..7c31a78f18 100644 --- a/test/transports/transport-manager.node.ts +++ b/test/transports/transport-manager.node.ts @@ -12,7 +12,7 @@ import sinon from 'sinon' import Peers from '../fixtures/peers.js' import type { PeerId } from '@libp2p/interface-peer-id' import { createFromJSON } from '@libp2p/peer-id-factory' -import { DefaultComponents } from '../../src/components.js' +import { defaultComponents, Components } from '../../src/components.js' import { EventEmitter } from '@libp2p/interfaces/events' const addrs = [ @@ -23,7 +23,7 @@ const addrs = [ describe('Transport Manager (TCP)', () => { let tm: DefaultTransportManager let localPeer: PeerId - let components: DefaultComponents + let components: Components before(async () => { localPeer = await createFromJSON(Peers[0]) @@ -31,7 +31,7 @@ describe('Transport Manager (TCP)', () => { beforeEach(() => { const events = new EventEmitter() - components = new DefaultComponents({ + components = defaultComponents({ peerId: localPeer, events, datastore: new MemoryDatastore(), diff --git a/test/transports/transport-manager.spec.ts b/test/transports/transport-manager.spec.ts index 287306106a..7f25397a2e 100644 --- a/test/transports/transport-manager.spec.ts +++ b/test/transports/transport-manager.spec.ts @@ -15,15 +15,16 @@ import { codes as ErrorCodes } from '../../src/errors.js' import Peers from '../fixtures/peers.js' import { createEd25519PeerId, createFromJSON } from '@libp2p/peer-id-factory' import type { PeerId } from '@libp2p/interface-peer-id' -import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' -import type { DefaultComponents } from '../../src/components.js' +import type { Components } from '../../src/components.js' import { EventEmitter } from '@libp2p/interfaces/events' +import type { Libp2p } from '@libp2p/interface-libp2p' +import { createLibp2p } from '../../src/index.js' const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') describe('Transport Manager (WebSockets)', () => { let tm: DefaultTransportManager - let components: DefaultComponents + let components: Components before(async () => { const events = new EventEmitter() @@ -92,7 +93,7 @@ describe('Transport Manager (WebSockets)', () => { describe('libp2p.transportManager (dial only)', () => { let peerId: PeerId - let libp2p: Libp2pNode + let libp2p: Libp2p before(async () => { peerId = await createFromJSON(Peers[0]) @@ -107,21 +108,23 @@ describe('libp2p.transportManager (dial only)', () => { }) it('fails to start if multiaddr fails to listen', async () => { - libp2p = await createLibp2pNode({ + libp2p = await createLibp2p({ peerId, addresses: { listen: ['/ip4/127.0.0.1/tcp/0'] }, transports: [webSockets()], - connectionEncryption: [plaintext()] + connectionEncryption: [plaintext()], + start: false }) + expect(libp2p.isStarted()).to.be.false() await expect(libp2p.start()).to.eventually.be.rejected .with.property('code', ErrorCodes.ERR_NO_VALID_ADDRESSES) }) it('does not fail to start if provided listen multiaddr are not compatible to configured transports (when supporting dial only mode)', async () => { - libp2p = await createLibp2pNode({ + libp2p = await createLibp2p({ peerId, addresses: { listen: ['/ip4/127.0.0.1/tcp/0'] @@ -134,14 +137,16 @@ describe('libp2p.transportManager (dial only)', () => { ], connectionEncryption: [ plaintext() - ] + ], + start: false }) - await libp2p.start() + expect(libp2p.isStarted()).to.be.false() + await expect(libp2p.start()).to.eventually.be.undefined() }) it('does not fail to start if provided listen multiaddr fail to listen on configured transports (when supporting dial only mode)', async () => { - libp2p = await createLibp2pNode({ + libp2p = await createLibp2p({ peerId, addresses: { listen: ['/ip4/127.0.0.1/tcp/12345/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3/p2p-circuit'] @@ -154,9 +159,11 @@ describe('libp2p.transportManager (dial only)', () => { ], connectionEncryption: [ plaintext() - ] + ], + start: false }) - await libp2p.start() + expect(libp2p.isStarted()).to.be.false() + await expect(libp2p.start()).to.eventually.be.undefined() }) }) diff --git a/test/upgrading/upgrader.spec.ts b/test/upgrading/upgrader.spec.ts index 65ff6abbc8..ceb6442807 100644 --- a/test/upgrading/upgrader.spec.ts +++ b/test/upgrading/upgrader.spec.ts @@ -7,6 +7,7 @@ import { multiaddr } from '@multiformats/multiaddr' import { pipe } from 'it-pipe' import all from 'it-all' import { webSockets } from '@libp2p/websockets' +import * as filters from '@libp2p/websockets/filters' import { preSharedKey } from '../../src/pnet/index.js' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import swarmKey from '../fixtures/swarm.key.js' @@ -22,7 +23,6 @@ import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interface-c import type { StreamMuxer, StreamMuxerFactory, StreamMuxerInit } from '@libp2p/interface-stream-muxer' import type { Connection, ConnectionProtector, Stream } from '@libp2p/interface-connection' import pDefer from 'p-defer' -import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js' import { pEvent } from 'p-event' import { TimeoutController } from 'timeout-abort-controller' import delay from 'delay' @@ -30,9 +30,13 @@ import drain from 'it-drain' import { Uint8ArrayList } from 'uint8arraylist' import { PersistentPeerStore } from '@libp2p/peer-store' import { MemoryDatastore } from 'datastore-core' -import { DefaultComponents } from '../../src/components.js' +import { Components, defaultComponents } from '../../src/components.js' import { StubbedInstance, stubInterface } from 'sinon-ts' import { EventEmitter } from '@libp2p/interfaces/events' +import { createLibp2p } from '../../src/index.js' +import type { Libp2p } from '@libp2p/interface-libp2p' +import { circuitRelayTransport } from '../../src/circuit-relay/index.js' +import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' const addrs = [ multiaddr('/ip4/127.0.0.1/tcp/0'), @@ -50,8 +54,8 @@ describe('Upgrader', () => { let remoteConnectionProtector: StubbedInstance let localPeer: PeerId let remotePeer: PeerId - let localComponents: DefaultComponents - let remoteComponents: DefaultComponents + let localComponents: Components + let remoteComponents: Components beforeEach(async () => { ([ @@ -65,7 +69,7 @@ describe('Upgrader', () => { localConnectionProtector = stubInterface() localConnectionProtector.protect.resolvesArg(0) - localComponents = new DefaultComponents({ + localComponents = defaultComponents({ peerId: localPeer, connectionGater: mockConnectionGater(), registrar: mockRegistrar(), @@ -90,7 +94,7 @@ describe('Upgrader', () => { remoteConnectionProtector = stubInterface() remoteConnectionProtector.protect.resolvesArg(0) - remoteComponents = new DefaultComponents({ + remoteComponents = defaultComponents({ peerId: remotePeer, connectionGater: mockConnectionGater(), registrar: mockRegistrar(), @@ -541,8 +545,8 @@ describe('Upgrader', () => { describe('libp2p.upgrader', () => { let peers: PeerId[] - let libp2p: Libp2pNode - let remoteLibp2p: Libp2pNode + let libp2p: Libp2p + let remoteLibp2p: Libp2p before(async () => { peers = await Promise.all([ @@ -564,7 +568,9 @@ describe('libp2p.upgrader', () => { }) it('should create an Upgrader', async () => { - libp2p = await createLibp2pNode({ + const deferred = pDefer() + + libp2p = await createLibp2p({ peerId: peers[0], transports: [ webSockets() @@ -577,16 +583,26 @@ describe('libp2p.upgrader', () => { ], connectionProtector: preSharedKey({ psk: uint8ArrayFromString(swarmKey) - }) + }), + services: { + test: (components: any) => { + deferred.resolve(components) + } + } }) - expect(libp2p.components.upgrader).to.exist() - expect(libp2p.components.connectionProtector).to.exist() + const components = await deferred.promise + + expect(components.upgrader).to.exist() + expect(components.connectionProtector).to.exist() }) it('should return muxed streams', async () => { + const localDeferred = pDefer() + const remoteDeferred = pDefer() + const remotePeer = peers[1] - libp2p = await createLibp2pNode({ + libp2p = await createLibp2p({ peerId: peers[0], transports: [ webSockets() @@ -596,13 +612,17 @@ describe('libp2p.upgrader', () => { ], connectionEncryption: [ plaintext() - ] + ], + services: { + test: (components: any) => { + localDeferred.resolve(components) + } + } }) - await libp2p.start() const echoHandler = (): void => {} await libp2p.handle(['/echo/1.0.0'], echoHandler) - remoteLibp2p = await createLibp2pNode({ + remoteLibp2p = await createLibp2p({ peerId: remotePeer, transports: [ webSockets() @@ -612,17 +632,24 @@ describe('libp2p.upgrader', () => { ], connectionEncryption: [ plaintext() - ] + ], + services: { + test: (components: any) => { + remoteDeferred.resolve(components) + } + } }) - await remoteLibp2p.start() await remoteLibp2p.handle('/echo/1.0.0', echoHandler) + const localComponents = await localDeferred.promise + const remoteComponents = await remoteDeferred.promise + const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) const [localConnection] = await Promise.all([ - libp2p.components.upgrader.upgradeOutbound(outbound), - remoteLibp2p.components.upgrader.upgradeInbound(inbound) + localComponents.upgrader.upgradeOutbound(outbound), + remoteComponents.upgrader.upgradeInbound(inbound) ]) - const remoteLibp2pUpgraderOnStreamSpy = sinon.spy(remoteLibp2p.components.upgrader as DefaultUpgrader, '_onStream') + const remoteLibp2pUpgraderOnStreamSpy = sinon.spy(remoteComponents.upgrader as DefaultUpgrader, '_onStream') const stream = await localConnection.newStream(['/echo/1.0.0']) expect(stream).to.include.keys(['id', 'close', 'reset', 'stat']) @@ -633,10 +660,18 @@ describe('libp2p.upgrader', () => { it('should emit connect and disconnect events', async () => { const remotePeer = peers[1] - libp2p = await createLibp2pNode({ + libp2p = await createLibp2p({ peerId: peers[0], + addresses: { + listen: [ + `${MULTIADDRS_WEBSOCKETS}/p2p-circuit` + ] + }, transports: [ - webSockets() + webSockets({ + filter: filters.all + }), + circuitRelayTransport() ], streamMuxers: [ mplex() @@ -647,10 +682,13 @@ describe('libp2p.upgrader', () => { }) await libp2p.start() - remoteLibp2p = await createLibp2pNode({ + remoteLibp2p = await createLibp2p({ peerId: remotePeer, transports: [ - webSockets() + webSockets({ + filter: filters.all + }), + circuitRelayTransport() ], streamMuxers: [ mplex() @@ -661,15 +699,12 @@ describe('libp2p.upgrader', () => { }) await remoteLibp2p.start() - const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) - // Upgrade and check the connect event - const connectionPromise = pEvent(libp2p, 'connection:open') - const connections = await Promise.all([ - libp2p.components.upgrader.upgradeOutbound(outbound), - remoteLibp2p.components.upgrader.upgradeInbound(inbound) - ]) - const connectEvent = await connectionPromise as CustomEvent + const connectionPromise = pEvent<'connection:open', CustomEvent>(libp2p, 'connection:open') + + const connection = await remoteLibp2p.dial(libp2p.getMultiaddrs()) + + const connectEvent = await connectionPromise if (connectEvent.type !== 'connection:open') { throw new Error(`Incorrect event type, expected: 'connection:open' actual: ${connectEvent.type}`) @@ -677,12 +712,12 @@ describe('libp2p.upgrader', () => { expect(remotePeer.equals(connectEvent.detail.remotePeer)).to.equal(true) - const disconnectionPromise = pEvent(libp2p, 'peer:disconnect') + const disconnectionPromise = pEvent<'peer:disconnect', CustomEvent>(libp2p, 'peer:disconnect') // Close and check the disconnect event - await Promise.all(connections.map(async conn => { await conn.close() })) + await connection.close() - const disconnectEvent = await disconnectionPromise as CustomEvent + const disconnectEvent = await disconnectionPromise if (disconnectEvent.type !== 'peer:disconnect') { throw new Error(`Incorrect event type, expected: 'peer:disconnect' actual: ${disconnectEvent.type}`) @@ -692,9 +727,11 @@ describe('libp2p.upgrader', () => { }) it('should limit the number of incoming streams that can be opened using a protocol', async () => { + const localDeferred = pDefer() + const remoteDeferred = pDefer() const protocol = '/a-test-protocol/1.0.0' const remotePeer = peers[1] - libp2p = await createLibp2pNode({ + libp2p = await createLibp2p({ peerId: peers[0], transports: [ webSockets() @@ -704,11 +741,15 @@ describe('libp2p.upgrader', () => { ], connectionEncryption: [ plaintext() - ] + ], + services: { + test: (components: any) => { + localDeferred.resolve(components) + } + } }) - await libp2p.start() - remoteLibp2p = await createLibp2pNode({ + remoteLibp2p = await createLibp2p({ peerId: remotePeer, transports: [ webSockets() @@ -718,15 +759,22 @@ describe('libp2p.upgrader', () => { ], connectionEncryption: [ plaintext() - ] + ], + services: { + test: (components: any) => { + remoteDeferred.resolve(components) + } + } }) - await remoteLibp2p.start() const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + const localComponents = await localDeferred.promise + const remoteComponents = await remoteDeferred.promise + const [localToRemote] = await Promise.all([ - libp2p.components.upgrader.upgradeOutbound(outbound), - remoteLibp2p.components.upgrader.upgradeInbound(inbound) + localComponents.upgrader.upgradeOutbound(outbound), + remoteComponents.upgrader.upgradeInbound(inbound) ]) let streamCount = 0 @@ -754,9 +802,11 @@ describe('libp2p.upgrader', () => { }) it('should limit the number of outgoing streams that can be opened using a protocol', async () => { + const localDeferred = pDefer() + const remoteDeferred = pDefer() const protocol = '/a-test-protocol/1.0.0' const remotePeer = peers[1] - libp2p = await createLibp2pNode({ + libp2p = await createLibp2p({ peerId: peers[0], transports: [ webSockets() @@ -766,11 +816,15 @@ describe('libp2p.upgrader', () => { ], connectionEncryption: [ plaintext() - ] + ], + services: { + test: (components: any) => { + localDeferred.resolve(components) + } + } }) - await libp2p.start() - remoteLibp2p = await createLibp2pNode({ + remoteLibp2p = await createLibp2p({ peerId: remotePeer, transports: [ webSockets() @@ -780,15 +834,22 @@ describe('libp2p.upgrader', () => { ], connectionEncryption: [ plaintext() - ] + ], + services: { + test: (components: any) => { + remoteDeferred.resolve(components) + } + } }) - await remoteLibp2p.start() const { inbound, outbound } = mockMultiaddrConnPair({ addrs, remotePeer }) + const localComponents = await localDeferred.promise + const remoteComponents = await remoteDeferred.promise + const [localToRemote] = await Promise.all([ - libp2p.components.upgrader.upgradeOutbound(outbound), - remoteLibp2p.components.upgrader.upgradeInbound(inbound) + localComponents.upgrader.upgradeOutbound(outbound), + remoteComponents.upgrader.upgradeInbound(inbound) ]) let streamCount = 0 diff --git a/test/nat-manager/nat-manager.node.ts b/test/upnp-nat/upnp-nat.node.ts similarity index 89% rename from test/nat-manager/nat-manager.node.ts rename to test/upnp-nat/upnp-nat.node.ts index 873a1912b5..f26b746d7a 100644 --- a/test/nat-manager/nat-manager.node.ts +++ b/test/upnp-nat/upnp-nat.node.ts @@ -6,8 +6,7 @@ import { DefaultTransportManager } from '../../src/transport-manager.js' import { FaultTolerance } from '@libp2p/interface-transport' import { tcp } from '@libp2p/tcp' import { mockUpgrader } from '@libp2p/interface-mocks' -import { NatManager } from '../../src/nat-manager.js' -import delay from 'delay' +import { uPnPNAT } from '../../src/upnp-nat/index.js' import Peers from '../fixtures/peers.js' import { codes } from '../../src/errors.js' import { createFromJSON } from '@libp2p/peer-id-factory' @@ -15,7 +14,7 @@ import type { NatAPI } from '@achingbrain/nat-port-mapper' import { StubbedInstance, stubInterface } from 'sinon-ts' import { start, stop } from '@libp2p/interfaces/startable' import { multiaddr } from '@multiformats/multiaddr' -import { DefaultComponents } from '../../src/components.js' +import { defaultComponents, Components } from '../../src/components.js' import { EventEmitter } from '@libp2p/interfaces/events' import type { PeerData, PeerStore } from '@libp2p/interface-peer-store' import type { PeerId } from '@libp2p/interface-peer-id' @@ -25,11 +24,11 @@ const DEFAULT_ADDRESSES = [ '/ip4/0.0.0.0/tcp/0' ] -describe('Nat Manager (TCP)', () => { +describe('UPnP NAT (TCP)', () => { const teardown: Array<() => Promise> = [] let client: StubbedInstance - async function createNatManager (addrs = DEFAULT_ADDRESSES, natManagerOptions = {}): Promise<{ natManager: NatManager, components: DefaultComponents }> { + async function createNatManager (addrs = DEFAULT_ADDRESSES, natManagerOptions = {}): Promise<{ natManager: any, components: Components }> { const events = new EventEmitter() const components: any = { peerId: await createFromJSON(Peers[0]), @@ -52,11 +51,10 @@ describe('Nat Manager (TCP)', () => { faultTolerance: FaultTolerance.NO_FATAL }) - const natManager = new NatManager(components, { - enabled: true, + const natManager: any = uPnPNAT({ keepAlive: true, ...natManagerOptions - }) + })(components) client = stubInterface() @@ -142,21 +140,6 @@ describe('Nat Manager (TCP)', () => { expect(client.map.called).to.be.false() }) - it('should do nothing when disabled', async () => { - const { - natManager - } = await createNatManager(DEFAULT_ADDRESSES, { - enabled: false - }) - - await start(natManager) - - await delay(100) - - expect(client.externalIp.called).to.be.false() - expect(client.map.called).to.be.false() - }) - it('should not map non-ipv4 connections to external ports', async () => { const { natManager, @@ -246,7 +229,7 @@ describe('Nat Manager (TCP)', () => { const peerId = await createFromJSON(Peers[0]) expect(() => { - new NatManager(new DefaultComponents({ peerId }), { ttl: 5, enabled: true, keepAlive: true }) // eslint-disable-line no-new + uPnPNAT({ ttl: 5, keepAlive: true })(defaultComponents({ peerId })) }).to.throw().with.property('code', codes.ERR_INVALID_PARAMETERS) }) }) diff --git a/test/utils/base-options.browser.ts b/test/utils/base-options.browser.ts index 6b3d7a442f..9c1a5e82c8 100644 --- a/test/utils/base-options.browser.ts +++ b/test/utils/base-options.browser.ts @@ -3,11 +3,12 @@ import { webSockets } from '@libp2p/websockets' import * as filters from '@libp2p/websockets/filters' import { mplex } from '@libp2p/mplex' import { plaintext } from '../../src/insecure/index.js' -import type { Libp2pOptions } from '../../src' +import type { Libp2pOptions } from '../../src/index.js' import mergeOptions from 'merge-options' import { circuitRelayTransport } from '../../src/circuit-relay/index.js' +import type { ServiceMap } from '@libp2p/interface-libp2p' -export function createBaseOptions (overrides?: Libp2pOptions): Libp2pOptions { +export function createBaseOptions (overrides?: Libp2pOptions): Libp2pOptions { const options: Libp2pOptions = { transports: [ webSockets({ @@ -20,10 +21,7 @@ export function createBaseOptions (overrides?: Libp2pOptions): Libp2pOptions { ], connectionEncryption: [ plaintext() - ], - nat: { - enabled: false - } + ] } return mergeOptions(options, overrides) diff --git a/test/utils/base-options.ts b/test/utils/base-options.ts index 64fa9b2021..9c3a147481 100644 --- a/test/utils/base-options.ts +++ b/test/utils/base-options.ts @@ -3,21 +3,30 @@ import { mplex } from '@libp2p/mplex' import { plaintext } from '../../src/insecure/index.js' import type { Libp2pOptions } from '../../src' import mergeOptions from 'merge-options' +import type { ServiceMap } from '@libp2p/interface-libp2p' +import { webSockets } from '@libp2p/websockets' +import * as filters from '@libp2p/websockets/filters' +import { circuitRelayTransport } from '../../src/circuit-relay/index.js' +import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js' -export function createBaseOptions (...overrides: Libp2pOptions[]): Libp2pOptions { +export function createBaseOptions (...overrides: Array>): Libp2pOptions { const options: Libp2pOptions = { + addresses: { + listen: [`${MULTIADDRS_WEBSOCKETS}/p2p-circuit`] + }, transports: [ - tcp() + tcp(), + webSockets({ + filter: filters.all + }), + circuitRelayTransport() ], streamMuxers: [ mplex() ], connectionEncryption: [ plaintext() - ], - nat: { - enabled: false - } + ] } return mergeOptions(options, ...overrides) diff --git a/test/utils/creators/peer.ts b/test/utils/creators/peer.ts index ca57e6b23f..dcf643ed54 100644 --- a/test/utils/creators/peer.ts +++ b/test/utils/creators/peer.ts @@ -7,10 +7,11 @@ import pTimes from 'p-times' import type { Libp2pOptions } from '../../../src/index.js' import type { PeerId } from '@libp2p/interface-peer-id' import type { AddressManagerInit } from '../../../src/address-manager/index.js' +import type { Libp2p, ServiceMap } from '@libp2p/interface-libp2p' const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') -export interface CreatePeerOptions { +export interface CreatePeerOptions { /** * number of peers (default: 1) */ @@ -26,13 +27,13 @@ export interface CreatePeerOptions { */ started?: boolean - config?: Libp2pOptions + config?: Libp2pOptions } /** * Create libp2p nodes. */ -export async function createNode (options: CreatePeerOptions = {}): Promise { +export async function createNode (options: CreatePeerOptions = {}): Promise> { const started = options.started ?? true const config = options.config ?? {} const peerId = await createPeerId({ fixture: options.fixture }) @@ -63,12 +64,12 @@ export async function createNode (options: CreatePeerOptions = {}): Promise { +export async function populateAddressBooks (peers: Libp2p[]): Promise { for (let i = 0; i < peers.length; i++) { for (let j = 0; j < peers.length; j++) { if (i !== j) { - await peers[i].components.peerStore.patch(peers[j].peerId, { - multiaddrs: peers[j].components.addressManager.getAddresses() + await peers[i].peerStore.patch(peers[j].peerId, { + multiaddrs: peers[j].getMultiaddrs() }) } }