From 2a7967c1cc0f4535b805cc4502370da2fbecb717 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Sat, 18 Apr 2020 23:26:46 +0200 Subject: [PATCH] feat: address manager --- doc/API.md | 77 ++++++++--- doc/CONFIGURATION.md | 2 + src/address-manager/README.md | 49 +++++++ src/address-manager/index.js | 55 ++++++++ src/circuit/index.js | 4 +- src/config.js | 4 +- src/identify/index.js | 21 +-- src/index.js | 68 ++++++++-- src/transport-manager.js | 8 +- test/addresses/address-manager.spec.js | 93 +++++++++++++ test/addresses/addresses.node.js | 152 ++++++++++++++++++++++ test/addresses/utils.js | 16 +++ test/core/listening.node.js | 2 +- test/core/ping.node.js | 4 +- test/dialing/direct.node.js | 11 +- test/dialing/direct.spec.js | 2 +- test/dialing/relay.node.js | 15 ++- test/identify/index.spec.js | 102 +++++++-------- test/peer-discovery/index.node.js | 8 +- test/pubsub/implementations.node.js | 2 +- test/pubsub/operation.node.js | 4 +- test/transports/transport-manager.node.js | 10 +- test/transports/transport-manager.spec.js | 10 +- test/utils/creators/peer.js | 5 +- 24 files changed, 598 insertions(+), 126 deletions(-) create mode 100644 src/address-manager/README.md create mode 100644 src/address-manager/index.js create mode 100644 test/addresses/address-manager.spec.js create mode 100644 test/addresses/addresses.node.js create mode 100644 test/addresses/utils.js diff --git a/doc/API.md b/doc/API.md index 80080704..07e5817e 100644 --- a/doc/API.md +++ b/doc/API.md @@ -11,6 +11,9 @@ * [`handle`](#handle) * [`unhandle`](#unhandle) * [`ping`](#ping) + * [`addressManager.listen`](#addressManagerlisten) + * [`addressManager.announce`](#addressManagerannounce) + * [`addressManager.noAnnounce`](#addressManagernoannounce) * [`contentRouting.findProviders`](#contentroutingfindproviders) * [`contentRouting.provide`](#contentroutingprovide) * [`contentRouting.put`](#contentroutingput) @@ -360,31 +363,42 @@ Pings a given peer and get the operation's latency. const latency = await libp2p.ping(otherPeerId) ``` -### peerRouting.findPeer +### addressManager.listen -Iterates over all peer routers in series to find the given peer. If the DHT is enabled, it will be tried first. +Getter for getting the addresses that the peer is using for listening on libp2p transports. -`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 | +`libp2p.addressManager.listen` #### Example ```js // ... -const peer = await libp2p.peerRouting.findPeer(peerId, options) +const listenAddresses = libp2p.addressManager.listen +// [ ] +``` + +### addressManager.announce + +Getter for getting the addresses that the peer is announcing to other peers in the network. + +`libp2p.addressManager.announce` + +```js +// ... +const announceAddresses = libp2p.addressManager.announce +// [ ] +``` + +### addressManager.noAnnounce + +Getter for getting the addresses that the peer is not announcing in the network. + +`libp2p.addressManager.noAnnounce` + +```js +// ... +const noAnnounceAddresses = libp2p.addressManager.noAnnounce +// [ ] ``` ### contentRouting.findProviders @@ -533,6 +547,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 33802c7c..b70f6933 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -207,6 +207,8 @@ Besides the `modules` and `config`, libp2p allows other internal options and con - `peerInfo`: a previously created instance of [libp2p/js-peer-info](https://github.com/libp2p/js-peer-info). - 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. +TODO: Add listen/announce addresses and remove peerInfo!! + ### Examples #### Basic setup diff --git a/src/address-manager/README.md b/src/address-manager/README.md new file mode 100644 index 00000000..6cceff6a --- /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, a set of listen addresses should be provided to the AddressManager, so that when the node is started, 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 to listen on addresses that intend to rely on any available local port. 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/0.0.0.0/tcp/8989`. As a consequence, libp2p should take into account this when advertising its 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 can also not announce addresses that will not be reachable. This way, No Announce Addresses should be specified so that they are not announced by the peer as addresses that other peers can use to dial it. + +As stated into the Listen Addresses section, Listen Addresses might get modified after libp2p transports get in action and use them to listen for new connections. This way, libp2p should also take into account these changes so that they can be matched when No Announce Addresses are being filtered out for advertising addresses. + +## 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 use them to listen on. + +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 is a feasible operation, removing one listen address might have bad implications for the node, since all the connections using that listen address will be closed. With this in mind and taking also into consideration the lack of good use cases for removing listen addresses, the Address Manager API only allows libp2p users to add new Listen Addresses on runtime. + +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 00000000..972f6fb2 --- /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 the peer 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} + */ + getListenMultiaddrs () { + return Array.from(this.listen).map((a) => multiaddr(a)) + } + + /** + * Get peer announcing multiaddrs. + * @return {Array} + */ + getAnnounceMultiaddrs () { + return Array.from(this.announce).map((a) => multiaddr(a)) + } + + /** + * Get peer noAnnouncing multiaddrs. + * @return {Array} + */ + getNoAnnounceMultiaddrs () { + 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 c833f124..637f5f86 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.addressManager = libp2p.addressManager 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.addressManager.getListenMultiaddrs().map(addr => addr.buffer) }, dstPeer: { id: destinationPeer.toBytes(), diff --git a/src/config.js b/src/config.js index 618c35d9..cb6aa6f5 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 3a5a4f5c..701587a0 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -46,22 +46,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) { /** * @property {PeerStore} */ - this.peerStore = options.peerStore + this.peerStore = options.libp2p.peerStore /** * @property {ConnectionManager} */ - this.connectionManager = options.connectionManager + this.connectionManager = options.libp2p.connectionManager + this.connectionManager.on('peer:connect', (connection) => { const peerId = connection.remotePeer @@ -71,9 +69,12 @@ class IdentifyService { /** * @property {PeerId} */ - this.peerId = options.peerId + this.peerId = options.libp2p.peerId - this.addresses = options.addresses || {} + /** + * @property {AddressManager} + */ + this._libp2p = options.libp2p this._protocols = options.protocols @@ -92,7 +93,7 @@ class IdentifyService { await pipe( [{ - listenAddrs: this.addresses.listen.map((ma) => ma.buffer), + listenAddrs: this._libp2p.getAdvertisingMultiaddrs().map((ma) => ma.buffer), protocols: Array.from(this._protocols.keys()) }], pb.encode(Message), @@ -217,7 +218,7 @@ class IdentifyService { protocolVersion: PROTOCOL_VERSION, agentVersion: AGENT_VERSION, publicKey, - listenAddrs: this.addresses.listen.map((ma) => ma.buffer), + listenAddrs: this._libp2p.getAdvertisingMultiaddrs().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 21efcc3e..d02e0d5b 100644 --- a/src/index.js +++ b/src/index.js @@ -15,6 +15,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') @@ -48,6 +49,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 @@ -123,10 +125,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) @@ -190,6 +189,8 @@ class Libp2p extends EventEmitter { */ async start () { log('libp2p is starting') + // TODO: consider validate listen addresses on start? + // depend on transports? try { await this._onStarting() await this._onDidStart() @@ -295,6 +296,54 @@ 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. + * This takes into account random ports on matching noAnnounce addresses. + * @return {Array} + */ + getAdvertisingMultiaddrs () { + // Filter noAnnounce multiaddrs + const filterMa = this.addressManager.getNoAnnounceMultiaddrs() + + // Special filter for noAnnounce addresses using a random port + // eg /ip4/0.0.0.0/tcp/0 => /ip4/192.168.1.0/tcp/58751 + const filterSpecial = filterMa + .map((ma) => ({ + protos: ma.protos(), + ...ma.toOptions() + })) + .filter((op) => op.port === 0) + + // Create advertising list + return this.transportManager.getAddrs() + .concat(this.addressManager.getAnnounceMultiaddrs()) + .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 + } + + // Filter out if in the special filter + const options = ma.toOptions() + if (filterSpecial.find((op) => + op.family === options.family && + op.host === options.host && + op.transport === options.transport && + op.protos.length === ma.protos().length + )) { + return false + } + return true + }) + } + /** * Disconnects all connections to the given `peer` * @param {PeerId|multiaddr|string} peer the peer to close connections to @@ -362,14 +411,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() @@ -475,7 +518,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 bcbaa45e..d0aae705 100644 --- a/src/transport-manager.js +++ b/src/transport-manager.js @@ -127,11 +127,13 @@ class TransportManager { } /** - * Starts listeners for each given Multiaddr. + * Starts listeners for each listen Multiaddr. + * Update listen multiaddrs of the Address Manager after the operation. * @async - * @param {Multiaddr[]} addrs */ - async listen (addrs) { + async listen () { + const addrs = this.libp2p.addressManager.getListenMultiaddrs() + 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 00000000..4ac35f79 --- /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.getListenMultiaddrs() + 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.getAnnounceMultiaddrs() + 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.getNoAnnounceMultiaddrs() + 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 00000000..0526d388 --- /dev/null +++ b/test/addresses/addresses.node.js @@ -0,0 +1,152 @@ +'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.getAdvertisingMultiaddrs', () => { + 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, 'getAnnounceMultiaddrs') + const spyNoAnnounce = sinon.spy(libp2p.addressManager, 'getNoAnnounceMultiaddrs') + const spyListen = sinon.spy(libp2p.addressManager, 'getListenMultiaddrs') + const spyTranspMgr = sinon.spy(libp2p.transportManager, 'getAddrs') + + const advertiseMultiaddrs = libp2p.getAdvertisingMultiaddrs().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.getAdvertisingMultiaddrs().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.getAdvertisingMultiaddrs().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) + }) + }) + + it('should not advertise noAnnounce addresses with random port switch', async () => { + const noAnnounce = [listenAddresses[0]] + ;[libp2p] = await peerUtils.createPeer({ + config: { + ...AddressesOptions, + addresses: { + listen: listenAddresses, + announce: announceAddreses, + noAnnounce + } + } + }) + + const advertiseMultiaddrs = libp2p.getAdvertisingMultiaddrs().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 00000000..08295c7b --- /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 b54f4814..de4bf47b 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 e5546a0a..99e57d60 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].getAdvertisingMultiaddrs()) + nodes[1].peerStore.addressBook.set(nodes[0].peerId, nodes[0].getAdvertisingMultiaddrs()) }) it('ping once from peer0 to peer1', async () => { diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index 57d02c59..7b6cd9f6 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -22,6 +22,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') @@ -47,7 +48,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) @@ -279,7 +282,7 @@ describe('Dialing (direct, TCP)', () => { }) sinon.spy(libp2p.dialer, 'connectToPeer') - libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.addresses.listen) + libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.getAdvertisingMultiaddrs()) const connection = await libp2p.dial(remotePeerId) expect(connection).to.exist() @@ -361,7 +364,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.getAdvertisingMultiaddrs()) const dialResults = await Promise.all([...new Array(dials)].map((_, index) => { if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerId) return libp2p.dial(fullAddress) @@ -391,7 +394,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.getAdvertisingMultiaddrs()) 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/direct.spec.js b/test/dialing/direct.spec.js index 32d2a244..9d9f1f4a 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -395,7 +395,7 @@ describe('Dialing (direct, WebSockets)', () => { it('should be able to use hangup when no connection exists', async () => { libp2p = new Libp2p({ - peerInfo, + peerId, modules: { transport: [Transport], streamMuxer: [Muxer], diff --git a/test/dialing/relay.node.js b/test/dialing/relay.node.js index 659b5f72..74e12c00 100644 --- a/test/dialing/relay.node.js +++ b/test/dialing/relay.node.js @@ -6,6 +6,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') @@ -25,7 +26,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) => { @@ -69,7 +70,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) @@ -152,13 +155,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, 'getListenMultiaddrs').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.addressManager, 'getListenMultiaddrs').returns([{ 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 c10bda7b..b0db6675 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: () => { } - } - } + getAdvertisingMultiaddrs: () => [] + }, + protocols }) const remoteIdentify = new IdentifyService({ - peerId: remotePeer, - addresses: { - listen: [] + libp2p: { + peerId: remotePeer, + connectionManager: new EventEmitter(), + getAdvertisingMultiaddrs: () => [] }, - 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: () => { } - } - } + getAdvertisingMultiaddrs: () => [] + }, + protocols }) const remoteIdentify = new IdentifyService({ - peerId: remotePeer, - addresses: { - listen: [] + libp2p: { + peerId: remotePeer, + connectionManager: new EventEmitter(), + getAdvertisingMultiaddrs: () => [] }, - 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(), + getAdvertisingMultiaddrs: () => [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: () => { } - } + getAdvertisingMultiaddrs: () => [] } }) diff --git a/test/peer-discovery/index.node.js b/test/peer-discovery/index.node.js index 19dbf9c5..81d7309b 100644 --- a/test/peer-discovery/index.node.js +++ b/test/peer-discovery/index.node.js @@ -33,7 +33,7 @@ describe('peer discovery scenarios', () => { }) it('should ignore self on discovery', async () => { libp2p = new Libp2p(mergeOptions(baseOptions, { - peerInfo, + peerId, modules: { peerDiscovery: [MulticastDNS] } @@ -42,7 +42,7 @@ describe('peer discovery scenarios', () => { await libp2p.start() const discoverySpy = sinon.spy() libp2p.on('peer:discovery', discoverySpy) - libp2p._discovery.get('mdns').emit('peer', libp2p.peerInfo) + libp2p._discovery.get('mdns').emit('peer', { id: libp2p.peerId }) expect(discoverySpy.called).to.eql(false) }) @@ -193,8 +193,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.getAdvertisingMultiaddrs()) + remoteLibp2p2.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.getAdvertisingMultiaddrs()) // Topology: // A -> B diff --git a/test/pubsub/implementations.node.js b/test/pubsub/implementations.node.js index e5ee043e..49310358 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.getAdvertisingMultiaddrs()) 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 f92c1915..02cf24ea 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.getAdvertisingMultiaddrs()) }) 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.getAdvertisingMultiaddrs()) }) afterEach(() => Promise.all([ diff --git a/test/transports/transport-manager.node.js b/test/transports/transport-manager.node.js index 4fd57e9c..1036230a 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 d165923f..4211bd06 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 d85c4e8d..db0026f4 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].getAdvertisingMultiaddrs()) } } }