diff --git a/doc/API.md b/doc/API.md index 800807047b..8278e10064 100644 --- a/doc/API.md +++ b/doc/API.md @@ -11,6 +11,10 @@ * [`handle`](#handle) * [`unhandle`](#unhandle) * [`ping`](#ping) + * [`multiaddrs`](#multiaddrs) + * [`addressManager.getListenAddrs`](#addressmanagergetlistenaddrs) + * [`addressmger.getAnnounceAddrs`](#addressmanagergetannounceaddrs) + * [`addressManager.getNoAnnounceAddrs`](#addressmanagergetnoannounceaddrs) * [`contentRouting.findProviders`](#contentroutingfindproviders) * [`contentRouting.provide`](#contentroutingprovide) * [`contentRouting.put`](#contentroutingput) @@ -63,7 +67,7 @@ Creates an instance of Libp2p. |------|------|-------------| | options | `object` | libp2p options | | options.modules | `Array` | libp2p modules to use | -| [options.addresses] | `{ listen: Array }` | Addresses to use for transport listening and to announce to the network | +| [options.addresses] | `{ listen: Array, announce: Array, noAnnounce: Array }` | Addresses for transport listening and to advertise to the network | | [options.config] | `object` | libp2p modules configuration and core configuration | | [options.connectionManager] | `object` | libp2p Connection Manager configuration | | [options.datastore] | `object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) (in memory datastore will be used if not provided) | @@ -360,31 +364,106 @@ Pings a given peer and get the operation's latency. const latency = await libp2p.ping(otherPeerId) ``` -### peerRouting.findPeer +## multiaddrs -Iterates over all peer routers in series to find the given peer. If the DHT is enabled, it will be tried first. +Gets the multiaddrs the libp2p node announces to the network. This computes the advertising multiaddrs +of the peer by joining the multiaddrs that libp2p transports are listening on with the announce multiaddrs +provided in the libp2p config. Configured no announce multiaddrs will be filtered out of the advertised addresses. -`libp2p.peerRouting.findPeer(peerId, options)` +`libp2p.multiaddrs` -#### Parameters +#### Returns -| Name | Type | Description | -|------|------|-------------| -| peerId | [`PeerId`][peer-id] | ID of the peer to find | -| options | `object` | operation options | -| options.timeout | `number` | maximum time the query should run | +| Type | Description | +|------|-------------| +| `Array` | Advertising multiaddrs | + +#### Example + +```js +// ... +const listenMa = libp2p.multiaddrs +// [ ] +``` + +### addressManager.getListenAddrs + +Get the multiaddrs that were provided for listening on libp2p transports. + +`libp2p.addressManager.getListenAddrs()` #### Returns | Type | Description | |------|-------------| -| `Promise<{ id: PeerId, multiaddrs: Multiaddr[] }>` | Peer data of a known peer | +| `Array` | Provided listening multiaddrs | #### Example ```js // ... -const peer = await libp2p.peerRouting.findPeer(peerId, options) +const listenMa = libp2p.addressManager.getListenAddrs() +// [ ] +``` + +### addressManager.getAnnounceAddrs + +Get the multiaddrs that were provided to announce to the network. + +`libp2p.addressManager.getAnnounceAddrs()` + +#### Returns + +| Type | Description | +|------|-------------| +| `Array` | Provided announce multiaddrs | + +#### Example + +```js +// ... +const announceMa = libp2p.addressManager.getAnnounceAddrs() +// [ ] +``` + +### addressManager.getNoAnnounceAddrs + +Get the multiaddrs that were provided to not announce to the network. + +`libp2p.addressManager.getNoAnnounceAddrs()` + +#### Returns + +| Type | Description | +|------|-------------| +| `Array` | Provided noAnnounce multiaddrs | + +#### Example + +```js +// ... +const noAnnounceMa = libp2p.addressManager.getNoAnnounceAddrs() +// [ ] +``` + +### transportManager.getAddrs + +Get the multiaddrs that libp2p transports are using to listen on. + +`libp2p.transportManager.getAddrs()` + +#### Returns + +| Type | Description | +|------|-------------| +| `Array` | listening multiaddrs | + +#### Example + +```js +// ... +const listenMa = libp2p.transportManager.getAddrs() +// [ ] ``` ### contentRouting.findProviders @@ -533,6 +612,33 @@ const key = '/key' const { from, val } = await libp2p.contentRouting.get(key) ``` +### peerRouting.findPeer + +Iterates over all peer routers in series to find the given peer. If the DHT is enabled, it will be tried first. + +`libp2p.peerRouting.findPeer(peerId, options)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | [`PeerId`][peer-id] | ID of the peer to find | +| options | `object` | operation options | +| options.timeout | `number` | maximum time the query should run | + +#### Returns + +| Type | Description | +|------|-------------| +| `Promise<{ id: PeerId, multiaddrs: Multiaddr[] }>` | Peer data of a known peer | + +#### Example + +```js +// ... +const peer = await libp2p.peerRouting.findPeer(peerId, options) +``` + ### peerStore.addressBook.add Adds known `multiaddrs` of a given peer. If the peer is not known, it will be set with the provided multiaddrs. diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index c74a59a6c9..fe1c10f256 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -204,8 +204,12 @@ Moreover, the majority of the modules can be customized via option parameters. T Besides the `modules` and `config`, libp2p allows other internal options and configurations: - `datastore`: an instance of [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore/) modules. - This is used in modules such as the DHT. If it is not provided, `js-libp2p` will use an in memory datastore. -- `peerInfo`: a previously created instance of [libp2p/js-peer-info](https://github.com/libp2p/js-peer-info). +- `peerId`: the identity of the node, an instance of [libp2p/js-peer-id](https://github.com/libp2p/js-peer-id). - This is particularly useful if you want to reuse the same `peer-id`, as well as for modules like `libp2p-delegated-content-routing`, which need a `peer-id` in their instantiation. +- `addresses`: an object containing `listen`, `announce` and `noAnnounce` properties with `Array`: + - `listen` addresses will be provided to the libp2p underlying transports for listening on them. + - `announce` addresses will be used to compute the advertises that the node should advertise to the network. + - `noAnnounce` addresses will be used as a filter to compute the advertises that the node should advertise to the network. ### Examples @@ -533,7 +537,6 @@ const node = await Libp2p.create({ As libp2p is designed to be a modular networking library, its usage will vary based on individual project needs. We've included links to some existing project configurations for your reference, in case you wish to replicate their configuration: - - [libp2p-ipfs-nodejs](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/libp2p-nodejs.js) - libp2p configuration used by js-ipfs when running in Node.js - [libp2p-ipfs-browser](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/libp2p-browser.js) - libp2p configuration used by js-ipfs when running in a Browser (that supports WebRTC) diff --git a/doc/GETTING_STARTED.md b/doc/GETTING_STARTED.md index b05cc4f6e7..c9a772de00 100644 --- a/doc/GETTING_STARTED.md +++ b/doc/GETTING_STARTED.md @@ -143,6 +143,9 @@ const SECIO = require('libp2p-secio') const MPLEX = require('libp2p-mplex') const node = await Libp2p.create({ + addresses: { + listen: ['/ip4/127.0.0.1/tcp/8000/ws'] + }, modules: { transport: [WebSockets], connEncryption: [SECIO], @@ -154,6 +157,12 @@ const node = await Libp2p.create({ await node.start() console.log('libp2p has started') +const listenAddrs = node.transportManager.getAddrs() +console.log('libp2p is listening on the following addresses: ', listenAddrs) + +const advertiseAddrs = node.multiaddrs +console.log('libp2p is advertising the following addresses: ', advertiseAddrs) + // stop libp2p await node.stop() console.log('libp2p has stopped') diff --git a/package.json b/package.json index 5b5d3bf8a6..4a1bcf5e44 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "libp2p-floodsub": "^0.21.0", "libp2p-gossipsub": "^0.4.0", "libp2p-kad-dht": "^0.19.1", - "libp2p-mdns": "^0.14.0", + "libp2p-mdns": "^0.14.1", "libp2p-mplex": "^0.9.1", "libp2p-secio": "^0.12.1", "libp2p-tcp": "^0.14.1", diff --git a/src/address-manager/README.md b/src/address-manager/README.md new file mode 100644 index 0000000000..53f9324802 --- /dev/null +++ b/src/address-manager/README.md @@ -0,0 +1,49 @@ +# Address Manager + +The Address manager is responsible for keeping an updated register of the peer's addresses. It includes 3 different types of Addresses: `Listen Addresses`, `Announce Addresses` and `No Announce Addresses`. + +These Addresses should be specified in your libp2p [configuration](../../doc/CONFIGURATION.md) when you create your node. + +## Listen Addresses + +A libp2p node should have a set of listen addresses, which will be used by libp2p underlying transports to listen for dials from other nodes in the network. + +Before a libp2p node starts, its configured listen addresses will be passed to the AddressManager, so that during startup the libp2p transports can use them to listen for connections. Accordingly, listen addresses should be specified through the libp2p configuration, in order to have the `AddressManager` created with them. + +It is important pointing out that libp2p accepts ephemeral listening addresses. In this context, the provided listen addresses might not be exactly the same as the ones used by the transports. For example TCP may replace `/ip4/0.0.0.0/tcp/0` with something like `/ip4/127.0.0.1/tcp/8989`. As a consequence, libp2p should take into account this when determining its advertised addresses. + +## Announce Addresses + +In some scenarios, a libp2p node will need to announce addresses that it is not listening on. In other words, Announce Addresses are an amendment to the Listen Addresses that aim to enable other nodes to achieve connectivity to this node. + +Scenarios for Announce Addresses include: +- when you setup a libp2p node in your private network at home, but you need to announce your public IP Address to the outside world; +- when you want to announce a DNS address, which maps to your public IP Address. + +## No Announce Addresses + +While we need to add Announce Addresses to enable peers' connectivity, we should also avoid announcing addresses that will not be reachable. No Announce Addresses should be specified so that they are filtered from the advertised multiaddrs. + +As stated in the Listen Addresses section, Listen Addresses might be modified by libp2p transports after the successfully bind to those addresses. Libp2p should also take these changes into account so that they can be matched when No Announce Addresses are being filtered out of the advertised multiaddrs. + +## Implementation + +When a libp2p node is created, the Address Manager will be populated from the provided addresses through the libp2p configuration. Once the node is started, the Transport Manager component will gather the listen addresses from the Address Manager, so that the libp2p transports can attempt to bind to them. + +Libp2p will use the the Address Manager as the source of truth when advertising the peers addresses. After all transports are ready, other libp2p components/subsystems will kickoff, namely the Identify Service and the DHT. Both of them will announce the node addresses to the other peers in the network. The announce and noAnnounce addresses will have an important role here and will be gathered by libp2p to compute its current addresses to advertise everytime it is needed. + +## Future Considerations + +### Dynamic address modifications + +In a future iteration, we can enable these addresses to be modified in runtime. For this, the Address Manager should be responsible for notifying interested subsystems of these changes, through an Event Emitter. + +#### Modify Listen Addresses + +While adding new addresses to listen on runtime should be trivial, removing a listen address might have bad implications for the node, since all the connections using that listen address will be closed. However, libp2p should provide a mechanism for both adding and removing listen addresses in the future. + +Every time a new listen address is added, the Address Manager should emit an event with the new multiaddrs to listen. The Transport Manager should listen to this events and act accordingly. + +#### Modify Announce Addresses + +When the announce addresses are modified, the Address Manager should emit an event so that other subsystems can act accordingly. For example, libp2p identify service should use the libp2p push protocol to inform other peers about these changes. diff --git a/src/address-manager/index.js b/src/address-manager/index.js new file mode 100644 index 0000000000..e15279367b --- /dev/null +++ b/src/address-manager/index.js @@ -0,0 +1,55 @@ +'use strict' + +const debug = require('debug') +const log = debug('libp2p:addresses') +log.error = debug('libp2p:addresses:error') + +const multiaddr = require('multiaddr') + +/** + * Responsible for managing this peers addresses. + * Peers can specify their listen, announce and noAnnounce addresses. + * The listen addresses will be used by the libp2p transports to listen for new connections, + * while the announce an noAnnounce addresses will be combined with the listen addresses for + * address adverstising to other peers in the network. + */ +class AddressManager { + /** + * @constructor + * @param {object} [options] + * @param {Array} [options.listen = []] list of multiaddrs string representation to listen. + * @param {Array} [options.announce = []] list of multiaddrs string representation to announce. + * @param {Array} [options.noAnnounce = []] list of multiaddrs string representation to not announce. + */ + constructor ({ listen = [], announce = [], noAnnounce = [] } = {}) { + this.listen = new Set(listen) + this.announce = new Set(announce) + this.noAnnounce = new Set(noAnnounce) + } + + /** + * Get peer listen multiaddrs. + * @return {Array} + */ + getListenAddrs () { + return Array.from(this.listen).map((a) => multiaddr(a)) + } + + /** + * Get peer announcing multiaddrs. + * @return {Array} + */ + getAnnounceAddrs () { + return Array.from(this.announce).map((a) => multiaddr(a)) + } + + /** + * Get peer noAnnouncing multiaddrs. + * @return {Array} + */ + getNoAnnounceAddrs () { + return Array.from(this.noAnnounce).map((a) => multiaddr(a)) + } +} + +module.exports = AddressManager diff --git a/src/circuit/index.js b/src/circuit/index.js index c833f124ea..904e9ad2d9 100644 --- a/src/circuit/index.js +++ b/src/circuit/index.js @@ -32,7 +32,7 @@ class Circuit { this._connectionManager = libp2p.connectionManager this._upgrader = upgrader this._options = libp2p._config.relay - this.addresses = libp2p.addresses + this._libp2p = libp2p this.peerId = libp2p.peerId this._registrar.handle(multicodec, this._onProtocol.bind(this)) } @@ -122,7 +122,7 @@ class Circuit { type: CircuitPB.Type.HOP, srcPeer: { id: this.peerId.toBytes(), - addrs: this.addresses.listen.map(addr => addr.buffer) + addrs: this._libp2p.multiaddrs.map(addr => addr.buffer) }, dstPeer: { id: destinationPeer.toBytes(), diff --git a/src/config.js b/src/config.js index 618c35d9e2..cb6aa6f551 100644 --- a/src/config.js +++ b/src/config.js @@ -5,7 +5,9 @@ const Constants = require('./constants') const DefaultConfig = { addresses: { - listen: [] + listen: [], + announce: [], + noAnnounce: [] }, connectionManager: { minPeers: 25 diff --git a/src/identify/index.js b/src/identify/index.js index d8ea46e857..a89e84bdfa 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -45,22 +45,20 @@ class IdentifyService { /** * @constructor * @param {object} options - * @param {PeerStore} options.peerStore - * @param {ConnectionManager} options.connectionManager + * @param {Libp2p} options.libp2p * @param {Map} options.protocols A reference to the protocols we support - * @param {PeerId} options.peerId The peer running the identify service - * @param {{ listen: Array}} options.addresses The peer addresses */ - constructor (options) { + constructor ({ libp2p, protocols }) { /** * @property {PeerStore} */ - this.peerStore = options.peerStore + this.peerStore = libp2p.peerStore /** * @property {ConnectionManager} */ - this.connectionManager = options.connectionManager + this.connectionManager = libp2p.connectionManager + this.connectionManager.on('peer:connect', (connection) => { const peerId = connection.remotePeer @@ -70,11 +68,14 @@ class IdentifyService { /** * @property {PeerId} */ - this.peerId = options.peerId + this.peerId = libp2p.peerId - this.addresses = options.addresses || {} + /** + * @property {AddressManager} + */ + this._libp2p = libp2p - this._protocols = options.protocols + this._protocols = protocols this.handleMessage = this.handleMessage.bind(this) } @@ -91,7 +92,7 @@ class IdentifyService { await pipe( [{ - listenAddrs: this.addresses.listen.map((ma) => ma.buffer), + listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.buffer), protocols: Array.from(this._protocols.keys()) }], pb.encode(Message), @@ -216,7 +217,7 @@ class IdentifyService { protocolVersion: PROTOCOL_VERSION, agentVersion: AGENT_VERSION, publicKey, - listenAddrs: this.addresses.listen.map((ma) => ma.buffer), + listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.buffer), observedAddr: connection.remoteAddr.buffer, protocols: Array.from(this._protocols.keys()) }) diff --git a/src/index.js b/src/index.js index b32c2f7d0c..229b04010c 100644 --- a/src/index.js +++ b/src/index.js @@ -14,6 +14,7 @@ const getPeer = require('./get-peer') const { validate: validateConfig } = require('./config') const { codes } = require('./errors') +const AddressManager = require('./address-manager') const ConnectionManager = require('./connection-manager') const Circuit = require('./circuit') const Dialer = require('./dialer') @@ -47,6 +48,7 @@ class Libp2p extends EventEmitter { // Addresses {listen, announce, noAnnounce} this.addresses = this._options.addresses + this.addressManager = new AddressManager(this._options.addresses) this._modules = this._options.modules this._config = this._options.config @@ -122,10 +124,7 @@ class Libp2p extends EventEmitter { // Add the identify service since we can multiplex this.identifyService = new IdentifyService({ - peerStore: this.peerStore, - connectionManager: this.connectionManager, - peerId: this.peerId, - addresses: this.addresses, + libp2p: this, protocols: this.upgrader.protocols }) this.handle(Object.values(IDENTIFY_PROTOCOLS), this.identifyService.handleMessage) @@ -189,6 +188,7 @@ class Libp2p extends EventEmitter { */ async start () { log('libp2p is starting') + try { await this._onStarting() await this._onDidStart() @@ -292,6 +292,34 @@ class Libp2p extends EventEmitter { return connection } + /** + * Get peer advertising multiaddrs by concating the addresses used + * by transports to listen with the announce addresses. + * Duplicated addresses and noAnnounce addresses are filtered out. + * @return {Array} + */ + get multiaddrs () { + // Filter noAnnounce multiaddrs + const filterMa = this.addressManager.getNoAnnounceAddrs() + + // Create advertising list + return this.transportManager.getAddrs() + .concat(this.addressManager.getAnnounceAddrs()) + .filter((ma, index, array) => { + // Filter out if repeated + if (array.findIndex((otherMa) => otherMa.equals(ma)) !== index) { + return false + } + + // Filter out if in noAnnounceMultiaddrs + if (filterMa.find((fm) => fm.equals(ma))) { + return false + } + + return true + }) + } + /** * Disconnects all connections to the given `peer` * @param {PeerId|multiaddr|string} peer the peer to close connections to @@ -353,14 +381,8 @@ class Libp2p extends EventEmitter { } async _onStarting () { - // Listen on the addresses provided - const multiaddrs = this.addresses.listen - - await this.transportManager.listen(multiaddrs) - - // The addresses may change once the listener starts - // eg /ip4/0.0.0.0/tcp/0 => /ip4/192.168.1.0/tcp/58751 - this.addresses.listen = this.transportManager.getAddrs() + // Listen on the provided transports + await this.transportManager.listen() if (this._config.pubsub.enabled) { this.pubsub && this.pubsub.start() @@ -466,7 +488,6 @@ class Libp2p extends EventEmitter { if (typeof DiscoveryService === 'function') { discoveryService = new DiscoveryService(Object.assign({}, config, { peerId: this.peerId, - multiaddrs: this.addresses.listen, libp2p: this })) } else { diff --git a/src/transport-manager.js b/src/transport-manager.js index bcbaa45e1f..b1994938fb 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -127,11 +127,12 @@ class TransportManager { } /** - * Starts listeners for each given Multiaddr. + * Starts listeners for each listen Multiaddr. * @async - * @param {Multiaddr[]} addrs */ - async listen (addrs) { + async listen () { + const addrs = this.libp2p.addressManager.getListenAddrs() + if (addrs.length === 0) { log('no addresses were provided for listening, this node is dial only') return diff --git a/test/addresses/address-manager.spec.js b/test/addresses/address-manager.spec.js new file mode 100644 index 0000000000..3e0d0efdc3 --- /dev/null +++ b/test/addresses/address-manager.spec.js @@ -0,0 +1,93 @@ +'use strict' +/* eslint-env mocha */ + +const chai = require('chai') +chai.use(require('dirty-chai')) +chai.use(require('chai-as-promised')) +const { expect } = chai + +const multiaddr = require('multiaddr') + +const AddressManager = require('../../src/address-manager') +const peerUtils = require('../utils/creators/peer') + +const listenAddresses = ['/ip4/127.0.0.1/tcp/15006/ws', '/ip4/127.0.0.1/tcp/15008/ws'] +const announceAddreses = ['/dns4/peer.io'] + +describe('Address Manager', () => { + it('should not need any addresses', () => { + const am = new AddressManager() + + expect(am.listen.size).to.equal(0) + expect(am.announce.size).to.equal(0) + expect(am.noAnnounce.size).to.equal(0) + }) + + it('should return listen multiaddrs on get', () => { + const am = new AddressManager({ + listen: listenAddresses + }) + + expect(am.listen.size).to.equal(listenAddresses.length) + expect(am.announce.size).to.equal(0) + expect(am.noAnnounce.size).to.equal(0) + + const listenMultiaddrs = am.getListenAddrs() + expect(listenMultiaddrs.length).to.equal(2) + expect(listenMultiaddrs[0].equals(multiaddr(listenAddresses[0]))).to.equal(true) + expect(listenMultiaddrs[1].equals(multiaddr(listenAddresses[1]))).to.equal(true) + }) + + it('should return announce multiaddrs on get', () => { + const am = new AddressManager({ + listen: listenAddresses, + announce: announceAddreses + }) + + expect(am.listen.size).to.equal(listenAddresses.length) + expect(am.announce.size).to.equal(announceAddreses.length) + expect(am.noAnnounce.size).to.equal(0) + + const announceMultiaddrs = am.getAnnounceAddrs() + expect(announceMultiaddrs.length).to.equal(1) + expect(announceMultiaddrs[0].equals(multiaddr(announceAddreses[0]))).to.equal(true) + }) + + it('should return noAnnounce multiaddrs on get', () => { + const am = new AddressManager({ + listen: listenAddresses, + noAnnounce: listenAddresses + }) + + expect(am.listen.size).to.equal(listenAddresses.length) + expect(am.announce.size).to.equal(0) + expect(am.noAnnounce.size).to.equal(listenAddresses.length) + + const noAnnounceMultiaddrs = am.getNoAnnounceAddrs() + expect(noAnnounceMultiaddrs.length).to.equal(2) + expect(noAnnounceMultiaddrs[0].equals(multiaddr(listenAddresses[0]))).to.equal(true) + expect(noAnnounceMultiaddrs[1].equals(multiaddr(listenAddresses[1]))).to.equal(true) + }) +}) + +describe('libp2p.addressManager', () => { + let libp2p + afterEach(() => libp2p && libp2p.stop()) + + it('should populate the AddressManager from the config', async () => { + [libp2p] = await peerUtils.createPeer({ + started: false, + config: { + addresses: { + listen: listenAddresses, + announce: announceAddreses, + noAnnounce: listenAddresses + } + } + }) + + expect(libp2p.addressManager.listen.size).to.equal(listenAddresses.length) + expect(libp2p.addressManager.announce.size).to.equal(announceAddreses.length) + expect(libp2p.addressManager.noAnnounce.size).to.equal(listenAddresses.length) + }) +}) diff --git a/test/addresses/addresses.node.js b/test/addresses/addresses.node.js new file mode 100644 index 0000000000..8993fe1c54 --- /dev/null +++ b/test/addresses/addresses.node.js @@ -0,0 +1,126 @@ +'use strict' +/* eslint-env mocha */ + +const chai = require('chai') +chai.use(require('dirty-chai')) +chai.use(require('chai-as-promised')) +const { expect } = chai +const sinon = require('sinon') + +const { AddressesOptions } = require('./utils') +const peerUtils = require('../utils/creators/peer') + +const listenAddresses = ['/ip4/127.0.0.1/tcp/0', '/ip4/127.0.0.1/tcp/8000/ws'] +const announceAddreses = ['/dns4/peer.io'] + +describe('libp2p.multiaddrs', () => { + let libp2p + + afterEach(() => libp2p && libp2p.stop()) + + it('should keep listen addresses after start, even if changed', async () => { + [libp2p] = await peerUtils.createPeer({ + started: false, + config: { + ...AddressesOptions, + addresses: { + listen: listenAddresses, + announce: announceAddreses + } + } + }) + + let listenAddrs = libp2p.addressManager.listen + expect(listenAddrs.size).to.equal(listenAddresses.length) + expect(listenAddrs.has(listenAddresses[0])).to.equal(true) + expect(listenAddrs.has(listenAddresses[1])).to.equal(true) + + // Should not replace listen addresses after transport listen + // Only transportManager has visibility of the port used + await libp2p.start() + + listenAddrs = libp2p.addressManager.listen + expect(listenAddrs.size).to.equal(listenAddresses.length) + expect(listenAddrs.has(listenAddresses[0])).to.equal(true) + expect(listenAddrs.has(listenAddresses[1])).to.equal(true) + }) + + it('should advertise all addresses if noAnnounce addresses are not provided, but with correct ports', async () => { + [libp2p] = await peerUtils.createPeer({ + config: { + ...AddressesOptions, + addresses: { + listen: listenAddresses, + announce: announceAddreses + } + } + }) + + const tmListen = libp2p.transportManager.getAddrs().map((ma) => ma.toString()) + + const spyAnnounce = sinon.spy(libp2p.addressManager, 'getAnnounceAddrs') + const spyNoAnnounce = sinon.spy(libp2p.addressManager, 'getNoAnnounceAddrs') + const spyListen = sinon.spy(libp2p.addressManager, 'getListenAddrs') + const spyTranspMgr = sinon.spy(libp2p.transportManager, 'getAddrs') + + const advertiseMultiaddrs = libp2p.multiaddrs.map((ma) => ma.toString()) + + expect(spyAnnounce).to.have.property('callCount', 1) + expect(spyNoAnnounce).to.have.property('callCount', 1) + expect(spyListen).to.have.property('callCount', 0) // Listen addr should not be used + expect(spyTranspMgr).to.have.property('callCount', 1) + + // Announce 2 listen (transport) + 1 announce + expect(advertiseMultiaddrs.length).to.equal(3) + tmListen.forEach((m) => { + expect(advertiseMultiaddrs).to.include(m) + }) + announceAddreses.forEach((m) => { + expect(advertiseMultiaddrs).to.include(m) + }) + expect(advertiseMultiaddrs).to.not.include(listenAddresses[0]) // Random Port switch + }) + + it('should remove replicated addresses', async () => { + [libp2p] = await peerUtils.createPeer({ + config: { + ...AddressesOptions, + addresses: { + listen: listenAddresses, + announce: [listenAddresses[1]] + } + } + }) + + const advertiseMultiaddrs = libp2p.multiaddrs.map((ma) => ma.toString()) + + // Announce 2 listen (transport), ignoring duplicated in announce + expect(advertiseMultiaddrs.length).to.equal(2) + }) + + it('should not advertise noAnnounce addresses', async () => { + const noAnnounce = [listenAddresses[1]] + ;[libp2p] = await peerUtils.createPeer({ + config: { + ...AddressesOptions, + addresses: { + listen: listenAddresses, + announce: announceAddreses, + noAnnounce + } + } + }) + + const advertiseMultiaddrs = libp2p.multiaddrs.map((ma) => ma.toString()) + + // Announce 1 listen (transport) not in the noAnnounce and the announce + expect(advertiseMultiaddrs.length).to.equal(2) + + announceAddreses.forEach((m) => { + expect(advertiseMultiaddrs).to.include(m) + }) + noAnnounce.forEach((m) => { + expect(advertiseMultiaddrs).to.not.include(m) + }) + }) +}) diff --git a/test/addresses/utils.js b/test/addresses/utils.js new file mode 100644 index 0000000000..08295c7bb8 --- /dev/null +++ b/test/addresses/utils.js @@ -0,0 +1,16 @@ +'use strict' + +const Transport1 = require('libp2p-tcp') +const Transport2 = require('libp2p-websockets') +const mergeOptions = require('merge-options') +const baseOptions = require('../utils/base-options') + +module.exports.baseOptions = baseOptions + +const AddressesOptions = mergeOptions(baseOptions, { + modules: { + transport: [Transport1, Transport2] + } +}) + +module.exports.AddressesOptions = AddressesOptions diff --git a/test/core/listening.node.js b/test/core/listening.node.js index b54f481403..de4bf47bdc 100644 --- a/test/core/listening.node.js +++ b/test/core/listening.node.js @@ -38,7 +38,7 @@ describe('Listening', () => { await libp2p.start() - const addrs = libp2p.addresses.listen + const addrs = libp2p.transportManager.getAddrs() // Should get something like: // /ip4/127.0.0.1/tcp/50866 diff --git a/test/core/ping.node.js b/test/core/ping.node.js index e5546a0a52..439fac7427 100644 --- a/test/core/ping.node.js +++ b/test/core/ping.node.js @@ -21,8 +21,8 @@ describe('ping', () => { config: baseOptions }) - nodes[0].peerStore.addressBook.set(nodes[1].peerId, nodes[1].addresses.listen) - nodes[1].peerStore.addressBook.set(nodes[0].peerId, nodes[0].addresses.listen) + nodes[0].peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs) + nodes[1].peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs) }) it('ping once from peer0 to peer1', async () => { diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index e3bd7d7be5..ba47d86e83 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -21,6 +21,7 @@ const { AbortError } = require('libp2p-interfaces/src/transport/errors') const Libp2p = require('../../src') const Dialer = require('../../src/dialer') +const AddressManager = require('../../src/address-manager') const PeerStore = require('../../src/peer-store') const TransportManager = require('../../src/transport-manager') const { codes: ErrorCodes } = require('../../src/errors') @@ -46,7 +47,9 @@ describe('Dialing (direct, TCP)', () => { PeerId.createFromJSON(Peers[0]) ]) remoteTM = new TransportManager({ - libp2p: {}, + libp2p: { + addressManager: new AddressManager({ listen: [listenAddr] }) + }, upgrader: mockUpgrader }) remoteTM.add(Transport.prototype[Symbol.toStringTag], Transport) @@ -278,7 +281,7 @@ describe('Dialing (direct, TCP)', () => { }) sinon.spy(libp2p.dialer, 'connectToPeer') - libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.addresses.listen) + libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) const connection = await libp2p.dial(remotePeerId) expect(connection).to.exist() @@ -360,7 +363,7 @@ describe('Dialing (direct, TCP)', () => { const fullAddress = remoteAddr.encapsulate(`/p2p/${remoteLibp2p.peerId.toB58String()}`) - libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.addresses.listen) + libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) const dialResults = await Promise.all([...new Array(dials)].map((_, index) => { if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerId) return libp2p.dial(fullAddress) @@ -390,7 +393,7 @@ describe('Dialing (direct, TCP)', () => { const error = new Error('Boom') sinon.stub(libp2p.transportManager, 'dial').callsFake(() => Promise.reject(error)) - libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.addresses.listen) + libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) const dialResults = await pSettle([...new Array(dials)].map((_, index) => { if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerId) return libp2p.dial(remoteAddr) diff --git a/test/dialing/relay.node.js b/test/dialing/relay.node.js index 4b706225ec..12c01c04c2 100644 --- a/test/dialing/relay.node.js +++ b/test/dialing/relay.node.js @@ -5,6 +5,7 @@ const chai = require('chai') chai.use(require('dirty-chai')) chai.use(require('chai-as-promised')) const { expect } = chai +const sinon = require('sinon') const multiaddr = require('multiaddr') const { collect } = require('streaming-iterables') @@ -24,7 +25,7 @@ describe('Dialing (via relay, TCP)', () => { let relayLibp2p let dstLibp2p - before(async () => { + beforeEach(async () => { const peerIds = await createPeerId({ number: 3 }) // Create 3 nodes, and turn HOP on for the relay ;[srcLibp2p, relayLibp2p, dstLibp2p] = peerIds.map((peerId, index) => { @@ -68,7 +69,9 @@ describe('Dialing (via relay, TCP)', () => { .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toB58String()}`) const tcpAddrs = dstLibp2p.transportManager.getAddrs() - await dstLibp2p.transportManager.listen([multiaddr(`/p2p-circuit${relayAddr}/p2p/${relayIdString}`)]) + sinon.stub(dstLibp2p.addressManager, 'listen').value([multiaddr(`/p2p-circuit${relayAddr}/p2p/${relayIdString}`)]) + + await dstLibp2p.transportManager.listen() expect(dstLibp2p.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')]) const connection = await srcLibp2p.dial(dialAddr) @@ -151,13 +154,15 @@ describe('Dialing (via relay, TCP)', () => { // Connect the destination peer and the relay const tcpAddrs = dstLibp2p.transportManager.getAddrs() - await dstLibp2p.transportManager.listen([multiaddr(`${relayAddr}/p2p-circuit`)]) + sinon.stub(dstLibp2p.addressManager, 'getListenAddrs').returns([multiaddr(`${relayAddr}/p2p-circuit`)]) + + await dstLibp2p.transportManager.listen() expect(dstLibp2p.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')]) // Tamper with the our multiaddrs for the circuit message - srcLibp2p.addresses.listen = [{ + sinon.stub(srcLibp2p, 'multiaddrs').value([{ buffer: Buffer.from('an invalid multiaddr') - }] + }]) await expect(srcLibp2p.dial(dialAddr)) .to.eventually.be.rejectedWith(AggregateError) diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index c10bda7b87..9ca892ff05 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -44,28 +44,28 @@ describe('Identify', () => { it('should be able to identify another peer', async () => { const localIdentify = new IdentifyService({ - peerId: localPeer, - addresses: { - listen: [] - }, - protocols, - connectionManager: new EventEmitter(), - peerStore: { - addressBook: { - set: () => { } + libp2p: { + peerId: localPeer, + connectionManager: new EventEmitter(), + peerStore: { + addressBook: { + set: () => { } + }, + protoBook: { + set: () => { } + } }, - protoBook: { - set: () => { } - } - } + multiaddrs: [] + }, + protocols }) const remoteIdentify = new IdentifyService({ - peerId: remotePeer, - addresses: { - listen: [] + libp2p: { + peerId: remotePeer, + connectionManager: new EventEmitter(), + multiaddrs: [] }, - protocols, - connectionManager: new EventEmitter() + protocols }) const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') @@ -97,28 +97,28 @@ describe('Identify', () => { it('should throw if identified peer is the wrong peer', async () => { const localIdentify = new IdentifyService({ - peerId: localPeer, - addresses: { - listen: [] - }, - protocols, - connectionManager: new EventEmitter(), - peerStore: { - addressBook: { - set: () => { } + libp2p: { + peerId: localPeer, + connectionManager: new EventEmitter(), + peerStore: { + addressBook: { + set: () => { } + }, + protoBook: { + set: () => { } + } }, - protoBook: { - set: () => { } - } - } + multiaddrs: [] + }, + protocols }) const remoteIdentify = new IdentifyService({ - peerId: remotePeer, - addresses: { - listen: [] + libp2p: { + peerId: remotePeer, + connectionManager: new EventEmitter(), + multiaddrs: [] }, - protocols, - connectionManager: new EventEmitter() + protocols }) const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') @@ -150,11 +150,11 @@ describe('Identify', () => { connectionManager.getConnection = () => {} const localIdentify = new IdentifyService({ - peerId: localPeer, - addresses: { - listen: [listeningAddr] + libp2p: { + peerId: localPeer, + connectionManager: new EventEmitter(), + multiaddrs: [listeningAddr] }, - connectionManager, protocols: new Map([ [multicodecs.IDENTIFY], [multicodecs.IDENTIFY_PUSH], @@ -162,18 +162,18 @@ describe('Identify', () => { ]) }) const remoteIdentify = new IdentifyService({ - peerId: remotePeer, - addresses: { - listen: [] - }, - connectionManager, - peerStore: { - addressBook: { - set: () => { } + libp2p: { + peerId: remotePeer, + connectionManager, + peerStore: { + addressBook: { + set: () => { } + }, + protoBook: { + set: () => { } + } }, - protoBook: { - set: () => { } - } + multiaddrs: [] } }) diff --git a/test/peer-discovery/index.node.js b/test/peer-discovery/index.node.js index 1784c63d32..12dd4b67d1 100644 --- a/test/peer-discovery/index.node.js +++ b/test/peer-discovery/index.node.js @@ -177,8 +177,8 @@ describe('peer discovery scenarios', () => { remoteLibp2p2.start() ]) - libp2p.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.addresses.listen) - remoteLibp2p2.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.addresses.listen) + libp2p.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.multiaddrs) + remoteLibp2p2.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.multiaddrs) // Topology: // A -> B diff --git a/test/pubsub/implementations.node.js b/test/pubsub/implementations.node.js index e5ee043e58..7ce32c0a68 100644 --- a/test/pubsub/implementations.node.js +++ b/test/pubsub/implementations.node.js @@ -75,7 +75,7 @@ describe('Pubsub subsystem is able to use different implementations', () => { ]) const libp2pId = libp2p.peerId.toB58String() - libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.addresses.listen) + libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) const connection = await libp2p.dialProtocol(remotePeerId, multicodec) expect(connection).to.exist() diff --git a/test/pubsub/operation.node.js b/test/pubsub/operation.node.js index f92c191536..a29ac953da 100644 --- a/test/pubsub/operation.node.js +++ b/test/pubsub/operation.node.js @@ -47,7 +47,7 @@ describe('Pubsub subsystem operates correctly', () => { remoteLibp2p.start() ]) - libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.addresses.listen) + libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) }) afterEach(() => Promise.all([ @@ -124,7 +124,7 @@ describe('Pubsub subsystem operates correctly', () => { await libp2p.start() await remoteLibp2p.start() - libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.addresses.listen) + libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.multiaddrs) }) afterEach(() => Promise.all([ diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js index 4fd57e9cd1..1036230acb 100644 --- a/test/transports/transport-manager.node.js +++ b/test/transports/transport-manager.node.js @@ -4,6 +4,8 @@ const chai = require('chai') chai.use(require('dirty-chai')) const { expect } = chai + +const AddressManager = require('../../src/address-manager') const TransportManager = require('../../src/transport-manager') const Transport = require('libp2p-tcp') const multiaddr = require('multiaddr') @@ -18,7 +20,9 @@ describe('Transport Manager (TCP)', () => { before(() => { tm = new TransportManager({ - libp2p: {}, + libp2p: { + addressManager: new AddressManager({ listen: addrs }) + }, upgrader: mockUpgrader, onConnection: () => {} }) @@ -37,7 +41,7 @@ describe('Transport Manager (TCP)', () => { it('should be able to listen', async () => { tm.add(Transport.prototype[Symbol.toStringTag], Transport) - await tm.listen(addrs) + await tm.listen() expect(tm._listeners).to.have.key(Transport.prototype[Symbol.toStringTag]) expect(tm._listeners.get(Transport.prototype[Symbol.toStringTag])).to.have.length(addrs.length) // Ephemeral ip addresses may result in multiple listeners @@ -48,7 +52,7 @@ describe('Transport Manager (TCP)', () => { it('should be able to dial', async () => { tm.add(Transport.prototype[Symbol.toStringTag], Transport) - await tm.listen(addrs) + await tm.listen() const addr = tm.getAddrs().shift() const connection = await tm.dial(addr) expect(connection).to.exist() diff --git a/test/transports/transport-manager.spec.js b/test/transports/transport-manager.spec.js index d165923f49..4211bd06d3 100644 --- a/test/transports/transport-manager.spec.js +++ b/test/transports/transport-manager.spec.js @@ -9,6 +9,7 @@ const sinon = require('sinon') const multiaddr = require('multiaddr') const Transport = require('libp2p-websockets') +const AddressManager = require('../../src/address-manager') const TransportManager = require('../../src/transport-manager') const mockUpgrader = require('../utils/mockUpgrader') const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') @@ -17,12 +18,16 @@ const Libp2p = require('../../src') const Peers = require('../fixtures/peers') const PeerId = require('peer-id') +const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') + describe('Transport Manager (WebSockets)', () => { let tm before(() => { tm = new TransportManager({ - libp2p: {}, + libp2p: { + addressManager: new AddressManager({ listen: [listenAddr] }) + }, upgrader: mockUpgrader, onConnection: () => {} }) @@ -78,9 +83,8 @@ describe('Transport Manager (WebSockets)', () => { it('should fail to listen with no valid address', async () => { tm.add(Transport.prototype[Symbol.toStringTag], Transport) - const addrs = [multiaddr('/ip4/127.0.0.1/tcp/0')] - await expect(tm.listen(addrs)) + await expect(tm.listen()) .to.eventually.be.rejected() .and.to.have.property('code', ErrorCodes.ERR_NO_VALID_ADDRESSES) }) diff --git a/test/utils/creators/peer.js b/test/utils/creators/peer.js index d85c4e8d38..6e4a586597 100644 --- a/test/utils/creators/peer.js +++ b/test/utils/creators/peer.js @@ -21,13 +21,14 @@ const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0') * @param {boolean} [properties.populateAddressBooks] nodes addressBooks should be populated with other peers (default: true) * @return {Promise>} */ -async function createPeer ({ number = 1, fixture = true, started = true, populateAddressBooks = true, config = defaultOptions } = {}) { +async function createPeer ({ number = 1, fixture = true, started = true, populateAddressBooks = true, config = {} } = {}) { const peerIds = await createPeerId({ number, fixture }) const addresses = started ? { listen: [listenAddr] } : {} const peers = await pTimes(number, (i) => Libp2p.create({ peerId: peerIds[i], addresses, + ...defaultOptions, ...config })) @@ -44,7 +45,7 @@ function _populateAddressBooks (peers) { for (let i = 0; i < peers.length; i++) { for (let j = 0; j < peers.length; j++) { if (i !== j) { - peers[i].peerStore.addressBook.set(peers[j].peerId, peers[j].addresses.listen) + peers[i].peerStore.addressBook.set(peers[j].peerId, peers[j].multiaddrs) } } }