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

@ -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()
const addrs = libp2p.addresses.listen
const addrs = libp2p.transportManager.getAddrs()
// Should get something like:
// /ip4/127.0.0.1/tcp/50866

View File

@ -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 () => {

View File

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

View File

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

View File

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

View File

@ -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: () => []
}
})

View File

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

View File

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

View File

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

View File

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

View File

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

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