feat: address manager

This commit is contained in:
Vasco Santos 2020-04-18 23:26:46 +02:00 committed by Jacob Heun
parent 9e9ec0b575
commit 2a7967c1cc
24 changed files with 598 additions and 126 deletions

View File

@ -11,6 +11,9 @@
* [`handle`](#handle) * [`handle`](#handle)
* [`unhandle`](#unhandle) * [`unhandle`](#unhandle)
* [`ping`](#ping) * [`ping`](#ping)
* [`addressManager.listen`](#addressManagerlisten)
* [`addressManager.announce`](#addressManagerannounce)
* [`addressManager.noAnnounce`](#addressManagernoannounce)
* [`contentRouting.findProviders`](#contentroutingfindproviders) * [`contentRouting.findProviders`](#contentroutingfindproviders)
* [`contentRouting.provide`](#contentroutingprovide) * [`contentRouting.provide`](#contentroutingprovide)
* [`contentRouting.put`](#contentroutingput) * [`contentRouting.put`](#contentroutingput)
@ -360,31 +363,42 @@ Pings a given peer and get the operation's latency.
const latency = await libp2p.ping(otherPeerId) 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)` `libp2p.addressManager.listen`
#### 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 #### Example
```js ```js
// ... // ...
const peer = await libp2p.peerRouting.findPeer(peerId, options) const listenAddresses = libp2p.addressManager.listen
// [ <Multiaddr 047f00000106f9ba - /ip4/127.0.0.1/tcp/63930> ]
```
### 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
// [ <Multiaddr 047f00000106f9ba - /dns4/peer.io/...> ]
```
### 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
// [ <Multiaddr 047f00000106f9ba - /ip4/127.0.0.1/tcp/63930> ]
``` ```
### contentRouting.findProviders ### contentRouting.findProviders
@ -533,6 +547,33 @@ const key = '/key'
const { from, val } = await libp2p.contentRouting.get(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 ### peerStore.addressBook.add
Adds known `multiaddrs` of a given peer. If the peer is not known, it will be set with the provided multiaddrs. Adds known `multiaddrs` of a given peer. If the peer is not known, it will be set with the provided multiaddrs.

View File

@ -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). - `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. - 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 ### Examples
#### Basic setup #### Basic setup

View File

@ -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.

View File

@ -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<string>} [options.listen = []] list of multiaddrs string representation to listen.
* @param {Array<string>} [options.announce = []] list of multiaddrs string representation to announce.
* @param {Array<string>} [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<Multiaddr>}
*/
getListenMultiaddrs () {
return Array.from(this.listen).map((a) => multiaddr(a))
}
/**
* Get peer announcing multiaddrs.
* @return {Array<Multiaddr>}
*/
getAnnounceMultiaddrs () {
return Array.from(this.announce).map((a) => multiaddr(a))
}
/**
* Get peer noAnnouncing multiaddrs.
* @return {Array<Multiaddr>}
*/
getNoAnnounceMultiaddrs () {
return Array.from(this.noAnnounce).map((a) => multiaddr(a))
}
}
module.exports = AddressManager

View File

@ -32,7 +32,7 @@ class Circuit {
this._connectionManager = libp2p.connectionManager this._connectionManager = libp2p.connectionManager
this._upgrader = upgrader this._upgrader = upgrader
this._options = libp2p._config.relay this._options = libp2p._config.relay
this.addresses = libp2p.addresses this.addressManager = libp2p.addressManager
this.peerId = libp2p.peerId this.peerId = libp2p.peerId
this._registrar.handle(multicodec, this._onProtocol.bind(this)) this._registrar.handle(multicodec, this._onProtocol.bind(this))
} }
@ -122,7 +122,7 @@ class Circuit {
type: CircuitPB.Type.HOP, type: CircuitPB.Type.HOP,
srcPeer: { srcPeer: {
id: this.peerId.toBytes(), id: this.peerId.toBytes(),
addrs: this.addresses.listen.map(addr => addr.buffer) addrs: this.addressManager.getListenMultiaddrs().map(addr => addr.buffer)
}, },
dstPeer: { dstPeer: {
id: destinationPeer.toBytes(), id: destinationPeer.toBytes(),

View File

@ -5,7 +5,9 @@ const Constants = require('./constants')
const DefaultConfig = { const DefaultConfig = {
addresses: { addresses: {
listen: [] listen: [],
announce: [],
noAnnounce: []
}, },
connectionManager: { connectionManager: {
minPeers: 25 minPeers: 25

View File

@ -46,22 +46,20 @@ class IdentifyService {
/** /**
* @constructor * @constructor
* @param {object} options * @param {object} options
* @param {PeerStore} options.peerStore * @param {Libp2p} options.libp2p
* @param {ConnectionManager} options.connectionManager
* @param {Map<string, handler>} options.protocols A reference to the protocols we support * @param {Map<string, handler>} options.protocols A reference to the protocols we support
* @param {PeerId} options.peerId The peer running the identify service
* @param {{ listen: Array<Multiaddr>}} options.addresses The peer addresses
*/ */
constructor (options) { constructor (options) {
/** /**
* @property {PeerStore} * @property {PeerStore}
*/ */
this.peerStore = options.peerStore this.peerStore = options.libp2p.peerStore
/** /**
* @property {ConnectionManager} * @property {ConnectionManager}
*/ */
this.connectionManager = options.connectionManager this.connectionManager = options.libp2p.connectionManager
this.connectionManager.on('peer:connect', (connection) => { this.connectionManager.on('peer:connect', (connection) => {
const peerId = connection.remotePeer const peerId = connection.remotePeer
@ -71,9 +69,12 @@ class IdentifyService {
/** /**
* @property {PeerId} * @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 this._protocols = options.protocols
@ -92,7 +93,7 @@ class IdentifyService {
await pipe( await pipe(
[{ [{
listenAddrs: this.addresses.listen.map((ma) => ma.buffer), listenAddrs: this._libp2p.getAdvertisingMultiaddrs().map((ma) => ma.buffer),
protocols: Array.from(this._protocols.keys()) protocols: Array.from(this._protocols.keys())
}], }],
pb.encode(Message), pb.encode(Message),
@ -217,7 +218,7 @@ class IdentifyService {
protocolVersion: PROTOCOL_VERSION, protocolVersion: PROTOCOL_VERSION,
agentVersion: AGENT_VERSION, agentVersion: AGENT_VERSION,
publicKey, publicKey,
listenAddrs: this.addresses.listen.map((ma) => ma.buffer), listenAddrs: this._libp2p.getAdvertisingMultiaddrs().map((ma) => ma.buffer),
observedAddr: connection.remoteAddr.buffer, observedAddr: connection.remoteAddr.buffer,
protocols: Array.from(this._protocols.keys()) protocols: Array.from(this._protocols.keys())
}) })

View File

@ -15,6 +15,7 @@ const getPeer = require('./get-peer')
const { validate: validateConfig } = require('./config') const { validate: validateConfig } = require('./config')
const { codes } = require('./errors') const { codes } = require('./errors')
const AddressManager = require('./address-manager')
const ConnectionManager = require('./connection-manager') const ConnectionManager = require('./connection-manager')
const Circuit = require('./circuit') const Circuit = require('./circuit')
const Dialer = require('./dialer') const Dialer = require('./dialer')
@ -48,6 +49,7 @@ class Libp2p extends EventEmitter {
// Addresses {listen, announce, noAnnounce} // Addresses {listen, announce, noAnnounce}
this.addresses = this._options.addresses this.addresses = this._options.addresses
this.addressManager = new AddressManager(this._options.addresses)
this._modules = this._options.modules this._modules = this._options.modules
this._config = this._options.config this._config = this._options.config
@ -123,10 +125,7 @@ class Libp2p extends EventEmitter {
// Add the identify service since we can multiplex // Add the identify service since we can multiplex
this.identifyService = new IdentifyService({ this.identifyService = new IdentifyService({
peerStore: this.peerStore, libp2p: this,
connectionManager: this.connectionManager,
peerId: this.peerId,
addresses: this.addresses,
protocols: this.upgrader.protocols protocols: this.upgrader.protocols
}) })
this.handle(Object.values(IDENTIFY_PROTOCOLS), this.identifyService.handleMessage) this.handle(Object.values(IDENTIFY_PROTOCOLS), this.identifyService.handleMessage)
@ -190,6 +189,8 @@ class Libp2p extends EventEmitter {
*/ */
async start () { async start () {
log('libp2p is starting') log('libp2p is starting')
// TODO: consider validate listen addresses on start?
// depend on transports?
try { try {
await this._onStarting() await this._onStarting()
await this._onDidStart() await this._onDidStart()
@ -295,6 +296,54 @@ class Libp2p extends EventEmitter {
return connection 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<Multiaddr>}
*/
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` * Disconnects all connections to the given `peer`
* @param {PeerId|multiaddr|string} peer the peer to close connections to * @param {PeerId|multiaddr|string} peer the peer to close connections to
@ -362,14 +411,8 @@ class Libp2p extends EventEmitter {
} }
async _onStarting () { async _onStarting () {
// Listen on the addresses provided // Listen on the provided transports
const multiaddrs = this.addresses.listen await this.transportManager.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()
if (this._config.pubsub.enabled) { if (this._config.pubsub.enabled) {
this.pubsub && this.pubsub.start() this.pubsub && this.pubsub.start()
@ -475,7 +518,6 @@ class Libp2p extends EventEmitter {
if (typeof DiscoveryService === 'function') { if (typeof DiscoveryService === 'function') {
discoveryService = new DiscoveryService(Object.assign({}, config, { discoveryService = new DiscoveryService(Object.assign({}, config, {
peerId: this.peerId, peerId: this.peerId,
multiaddrs: this.addresses.listen,
libp2p: this libp2p: this
})) }))
} else { } else {

View File

@ -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 * @async
* @param {Multiaddr[]} addrs
*/ */
async listen (addrs) { async listen () {
const addrs = this.libp2p.addressManager.getListenMultiaddrs()
if (addrs.length === 0) { if (addrs.length === 0) {
log('no addresses were provided for listening, this node is dial only') log('no addresses were provided for listening, this node is dial only')
return return

View File

@ -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)
})
})

View File

@ -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)
})
})
})

16
test/addresses/utils.js Normal file
View File

@ -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

View File

@ -38,7 +38,7 @@ describe('Listening', () => {
await libp2p.start() await libp2p.start()
const addrs = libp2p.addresses.listen const addrs = libp2p.transportManager.getAddrs()
// Should get something like: // Should get something like:
// /ip4/127.0.0.1/tcp/50866 // /ip4/127.0.0.1/tcp/50866

View File

@ -21,8 +21,8 @@ describe('ping', () => {
config: baseOptions config: baseOptions
}) })
nodes[0].peerStore.addressBook.set(nodes[1].peerId, nodes[1].addresses.listen) nodes[0].peerStore.addressBook.set(nodes[1].peerId, nodes[1].getAdvertisingMultiaddrs())
nodes[1].peerStore.addressBook.set(nodes[0].peerId, nodes[0].addresses.listen) nodes[1].peerStore.addressBook.set(nodes[0].peerId, nodes[0].getAdvertisingMultiaddrs())
}) })
it('ping once from peer0 to peer1', async () => { it('ping once from peer0 to peer1', async () => {

View File

@ -22,6 +22,7 @@ const { AbortError } = require('libp2p-interfaces/src/transport/errors')
const Libp2p = require('../../src') const Libp2p = require('../../src')
const Dialer = require('../../src/dialer') const Dialer = require('../../src/dialer')
const AddressManager = require('../../src/address-manager')
const PeerStore = require('../../src/peer-store') const PeerStore = require('../../src/peer-store')
const TransportManager = require('../../src/transport-manager') const TransportManager = require('../../src/transport-manager')
const { codes: ErrorCodes } = require('../../src/errors') const { codes: ErrorCodes } = require('../../src/errors')
@ -47,7 +48,9 @@ describe('Dialing (direct, TCP)', () => {
PeerId.createFromJSON(Peers[0]) PeerId.createFromJSON(Peers[0])
]) ])
remoteTM = new TransportManager({ remoteTM = new TransportManager({
libp2p: {}, libp2p: {
addressManager: new AddressManager({ listen: [listenAddr] })
},
upgrader: mockUpgrader upgrader: mockUpgrader
}) })
remoteTM.add(Transport.prototype[Symbol.toStringTag], Transport) remoteTM.add(Transport.prototype[Symbol.toStringTag], Transport)
@ -279,7 +282,7 @@ describe('Dialing (direct, TCP)', () => {
}) })
sinon.spy(libp2p.dialer, 'connectToPeer') 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) const connection = await libp2p.dial(remotePeerId)
expect(connection).to.exist() expect(connection).to.exist()
@ -361,7 +364,7 @@ describe('Dialing (direct, TCP)', () => {
const fullAddress = remoteAddr.encapsulate(`/p2p/${remoteLibp2p.peerId.toB58String()}`) 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) => { const dialResults = await Promise.all([...new Array(dials)].map((_, index) => {
if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerId) if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerId)
return libp2p.dial(fullAddress) return libp2p.dial(fullAddress)
@ -391,7 +394,7 @@ describe('Dialing (direct, TCP)', () => {
const error = new Error('Boom') const error = new Error('Boom')
sinon.stub(libp2p.transportManager, 'dial').callsFake(() => Promise.reject(error)) 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) => { const dialResults = await pSettle([...new Array(dials)].map((_, index) => {
if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerId) if (index % 2 === 0) return libp2p.dial(remoteLibp2p.peerId)
return libp2p.dial(remoteAddr) return libp2p.dial(remoteAddr)

View File

@ -395,7 +395,7 @@ describe('Dialing (direct, WebSockets)', () => {
it('should be able to use hangup when no connection exists', async () => { it('should be able to use hangup when no connection exists', async () => {
libp2p = new Libp2p({ libp2p = new Libp2p({
peerInfo, peerId,
modules: { modules: {
transport: [Transport], transport: [Transport],
streamMuxer: [Muxer], streamMuxer: [Muxer],

View File

@ -6,6 +6,7 @@ const chai = require('chai')
chai.use(require('dirty-chai')) chai.use(require('dirty-chai'))
chai.use(require('chai-as-promised')) chai.use(require('chai-as-promised'))
const { expect } = chai const { expect } = chai
const sinon = require('sinon')
const multiaddr = require('multiaddr') const multiaddr = require('multiaddr')
const { collect } = require('streaming-iterables') const { collect } = require('streaming-iterables')
@ -25,7 +26,7 @@ describe('Dialing (via relay, TCP)', () => {
let relayLibp2p let relayLibp2p
let dstLibp2p let dstLibp2p
before(async () => { beforeEach(async () => {
const peerIds = await createPeerId({ number: 3 }) const peerIds = await createPeerId({ number: 3 })
// Create 3 nodes, and turn HOP on for the relay // Create 3 nodes, and turn HOP on for the relay
;[srcLibp2p, relayLibp2p, dstLibp2p] = peerIds.map((peerId, index) => { ;[srcLibp2p, relayLibp2p, dstLibp2p] = peerIds.map((peerId, index) => {
@ -69,7 +70,9 @@ describe('Dialing (via relay, TCP)', () => {
.encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toB58String()}`) .encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerId.toB58String()}`)
const tcpAddrs = dstLibp2p.transportManager.getAddrs() 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')]) expect(dstLibp2p.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')])
const connection = await srcLibp2p.dial(dialAddr) const connection = await srcLibp2p.dial(dialAddr)
@ -152,13 +155,15 @@ describe('Dialing (via relay, TCP)', () => {
// Connect the destination peer and the relay // Connect the destination peer and the relay
const tcpAddrs = dstLibp2p.transportManager.getAddrs() 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')]) expect(dstLibp2p.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')])
// Tamper with the our multiaddrs for the circuit message // Tamper with the our multiaddrs for the circuit message
srcLibp2p.addresses.listen = [{ sinon.stub(srcLibp2p.addressManager, 'getListenMultiaddrs').returns([{
buffer: Buffer.from('an invalid multiaddr') buffer: Buffer.from('an invalid multiaddr')
}] }])
await expect(srcLibp2p.dial(dialAddr)) await expect(srcLibp2p.dial(dialAddr))
.to.eventually.be.rejectedWith(AggregateError) .to.eventually.be.rejectedWith(AggregateError)

View File

@ -44,11 +44,8 @@ describe('Identify', () => {
it('should be able to identify another peer', async () => { it('should be able to identify another peer', async () => {
const localIdentify = new IdentifyService({ const localIdentify = new IdentifyService({
libp2p: {
peerId: localPeer, peerId: localPeer,
addresses: {
listen: []
},
protocols,
connectionManager: new EventEmitter(), connectionManager: new EventEmitter(),
peerStore: { peerStore: {
addressBook: { addressBook: {
@ -57,15 +54,18 @@ describe('Identify', () => {
protoBook: { protoBook: {
set: () => { } set: () => { }
} }
} },
getAdvertisingMultiaddrs: () => []
},
protocols
}) })
const remoteIdentify = new IdentifyService({ const remoteIdentify = new IdentifyService({
libp2p: {
peerId: remotePeer, peerId: remotePeer,
addresses: { connectionManager: new EventEmitter(),
listen: [] getAdvertisingMultiaddrs: () => []
}, },
protocols, protocols
connectionManager: new EventEmitter()
}) })
const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234')
@ -97,11 +97,8 @@ describe('Identify', () => {
it('should throw if identified peer is the wrong peer', async () => { it('should throw if identified peer is the wrong peer', async () => {
const localIdentify = new IdentifyService({ const localIdentify = new IdentifyService({
libp2p: {
peerId: localPeer, peerId: localPeer,
addresses: {
listen: []
},
protocols,
connectionManager: new EventEmitter(), connectionManager: new EventEmitter(),
peerStore: { peerStore: {
addressBook: { addressBook: {
@ -110,15 +107,18 @@ describe('Identify', () => {
protoBook: { protoBook: {
set: () => { } set: () => { }
} }
} },
getAdvertisingMultiaddrs: () => []
},
protocols
}) })
const remoteIdentify = new IdentifyService({ const remoteIdentify = new IdentifyService({
libp2p: {
peerId: remotePeer, peerId: remotePeer,
addresses: { connectionManager: new EventEmitter(),
listen: [] getAdvertisingMultiaddrs: () => []
}, },
protocols, protocols
connectionManager: new EventEmitter()
}) })
const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234')
@ -150,11 +150,11 @@ describe('Identify', () => {
connectionManager.getConnection = () => {} connectionManager.getConnection = () => {}
const localIdentify = new IdentifyService({ const localIdentify = new IdentifyService({
libp2p: {
peerId: localPeer, peerId: localPeer,
addresses: { connectionManager: new EventEmitter(),
listen: [listeningAddr] getAdvertisingMultiaddrs: () => [listeningAddr]
}, },
connectionManager,
protocols: new Map([ protocols: new Map([
[multicodecs.IDENTIFY], [multicodecs.IDENTIFY],
[multicodecs.IDENTIFY_PUSH], [multicodecs.IDENTIFY_PUSH],
@ -162,10 +162,8 @@ describe('Identify', () => {
]) ])
}) })
const remoteIdentify = new IdentifyService({ const remoteIdentify = new IdentifyService({
libp2p: {
peerId: remotePeer, peerId: remotePeer,
addresses: {
listen: []
},
connectionManager, connectionManager,
peerStore: { peerStore: {
addressBook: { addressBook: {
@ -174,6 +172,8 @@ describe('Identify', () => {
protoBook: { protoBook: {
set: () => { } set: () => { }
} }
},
getAdvertisingMultiaddrs: () => []
} }
}) })

View File

@ -33,7 +33,7 @@ describe('peer discovery scenarios', () => {
}) })
it('should ignore self on discovery', async () => { it('should ignore self on discovery', async () => {
libp2p = new Libp2p(mergeOptions(baseOptions, { libp2p = new Libp2p(mergeOptions(baseOptions, {
peerInfo, peerId,
modules: { modules: {
peerDiscovery: [MulticastDNS] peerDiscovery: [MulticastDNS]
} }
@ -42,7 +42,7 @@ describe('peer discovery scenarios', () => {
await libp2p.start() await libp2p.start()
const discoverySpy = sinon.spy() const discoverySpy = sinon.spy()
libp2p.on('peer:discovery', discoverySpy) 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) expect(discoverySpy.called).to.eql(false)
}) })
@ -193,8 +193,8 @@ describe('peer discovery scenarios', () => {
remoteLibp2p2.start() remoteLibp2p2.start()
]) ])
libp2p.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.addresses.listen) libp2p.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.getAdvertisingMultiaddrs())
remoteLibp2p2.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.addresses.listen) remoteLibp2p2.peerStore.addressBook.set(remotePeerId1, remoteLibp2p1.getAdvertisingMultiaddrs())
// Topology: // Topology:
// A -> B // A -> B

View File

@ -75,7 +75,7 @@ describe('Pubsub subsystem is able to use different implementations', () => {
]) ])
const libp2pId = libp2p.peerId.toB58String() 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) const connection = await libp2p.dialProtocol(remotePeerId, multicodec)
expect(connection).to.exist() expect(connection).to.exist()

View File

@ -47,7 +47,7 @@ describe('Pubsub subsystem operates correctly', () => {
remoteLibp2p.start() remoteLibp2p.start()
]) ])
libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.addresses.listen) libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.getAdvertisingMultiaddrs())
}) })
afterEach(() => Promise.all([ afterEach(() => Promise.all([
@ -124,7 +124,7 @@ describe('Pubsub subsystem operates correctly', () => {
await libp2p.start() await libp2p.start()
await remoteLibp2p.start() await remoteLibp2p.start()
libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.addresses.listen) libp2p.peerStore.addressBook.set(remotePeerId, remoteLibp2p.getAdvertisingMultiaddrs())
}) })
afterEach(() => Promise.all([ afterEach(() => Promise.all([

View File

@ -4,6 +4,8 @@
const chai = require('chai') const chai = require('chai')
chai.use(require('dirty-chai')) chai.use(require('dirty-chai'))
const { expect } = chai const { expect } = chai
const AddressManager = require('../../src/address-manager')
const TransportManager = require('../../src/transport-manager') const TransportManager = require('../../src/transport-manager')
const Transport = require('libp2p-tcp') const Transport = require('libp2p-tcp')
const multiaddr = require('multiaddr') const multiaddr = require('multiaddr')
@ -18,7 +20,9 @@ describe('Transport Manager (TCP)', () => {
before(() => { before(() => {
tm = new TransportManager({ tm = new TransportManager({
libp2p: {}, libp2p: {
addressManager: new AddressManager({ listen: addrs })
},
upgrader: mockUpgrader, upgrader: mockUpgrader,
onConnection: () => {} onConnection: () => {}
}) })
@ -37,7 +41,7 @@ describe('Transport Manager (TCP)', () => {
it('should be able to listen', async () => { it('should be able to listen', async () => {
tm.add(Transport.prototype[Symbol.toStringTag], Transport) 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).to.have.key(Transport.prototype[Symbol.toStringTag])
expect(tm._listeners.get(Transport.prototype[Symbol.toStringTag])).to.have.length(addrs.length) expect(tm._listeners.get(Transport.prototype[Symbol.toStringTag])).to.have.length(addrs.length)
// Ephemeral ip addresses may result in multiple listeners // Ephemeral ip addresses may result in multiple listeners
@ -48,7 +52,7 @@ describe('Transport Manager (TCP)', () => {
it('should be able to dial', async () => { it('should be able to dial', async () => {
tm.add(Transport.prototype[Symbol.toStringTag], Transport) tm.add(Transport.prototype[Symbol.toStringTag], Transport)
await tm.listen(addrs) await tm.listen()
const addr = tm.getAddrs().shift() const addr = tm.getAddrs().shift()
const connection = await tm.dial(addr) const connection = await tm.dial(addr)
expect(connection).to.exist() expect(connection).to.exist()

View File

@ -9,6 +9,7 @@ const sinon = require('sinon')
const multiaddr = require('multiaddr') const multiaddr = require('multiaddr')
const Transport = require('libp2p-websockets') const Transport = require('libp2p-websockets')
const AddressManager = require('../../src/address-manager')
const TransportManager = require('../../src/transport-manager') const TransportManager = require('../../src/transport-manager')
const mockUpgrader = require('../utils/mockUpgrader') const mockUpgrader = require('../utils/mockUpgrader')
const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser') const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser')
@ -17,12 +18,16 @@ const Libp2p = require('../../src')
const Peers = require('../fixtures/peers') const Peers = require('../fixtures/peers')
const PeerId = require('peer-id') const PeerId = require('peer-id')
const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0')
describe('Transport Manager (WebSockets)', () => { describe('Transport Manager (WebSockets)', () => {
let tm let tm
before(() => { before(() => {
tm = new TransportManager({ tm = new TransportManager({
libp2p: {}, libp2p: {
addressManager: new AddressManager({ listen: [listenAddr] })
},
upgrader: mockUpgrader, upgrader: mockUpgrader,
onConnection: () => {} onConnection: () => {}
}) })
@ -78,9 +83,8 @@ describe('Transport Manager (WebSockets)', () => {
it('should fail to listen with no valid address', async () => { it('should fail to listen with no valid address', async () => {
tm.add(Transport.prototype[Symbol.toStringTag], Transport) 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() .to.eventually.be.rejected()
.and.to.have.property('code', ErrorCodes.ERR_NO_VALID_ADDRESSES) .and.to.have.property('code', ErrorCodes.ERR_NO_VALID_ADDRESSES)
}) })

View File

@ -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) * @param {boolean} [properties.populateAddressBooks] nodes addressBooks should be populated with other peers (default: true)
* @return {Promise<Array<Libp2p>>} * @return {Promise<Array<Libp2p>>}
*/ */
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 peerIds = await createPeerId({ number, fixture })
const addresses = started ? { listen: [listenAddr] } : {} const addresses = started ? { listen: [listenAddr] } : {}
const peers = await pTimes(number, (i) => Libp2p.create({ const peers = await pTimes(number, (i) => Libp2p.create({
peerId: peerIds[i], peerId: peerIds[i],
addresses, addresses,
...defaultOptions,
...config ...config
})) }))
@ -44,7 +45,7 @@ function _populateAddressBooks (peers) {
for (let i = 0; i < peers.length; i++) { for (let i = 0; i < peers.length; i++) {
for (let j = 0; j < peers.length; j++) { for (let j = 0; j < peers.length; j++) {
if (i !== 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())
} }
} }
} }