feat: address and proto books (#590)

* feat: address and proto books

* chore: apply suggestions from code review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>

* chore: minor fixes and initial tests added

* chore: integrate new peer-store with code using adapters for other modules

* chore: do not use peerstore.put on get-peer-info

* chore: apply suggestions from code review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>

* chore: add new peer store tests

* chore: apply suggestions from code review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
This commit is contained in:
Vasco Santos
2020-04-09 16:07:18 +02:00
parent 97455957ac
commit a08a725123
23 changed files with 2019 additions and 480 deletions

View File

@ -44,7 +44,8 @@ describe('DHT subsystem operates correctly', () => {
remoteLibp2p.start()
])
remAddr = libp2p.peerStore.multiaddrsForPeer(remotePeerInfo)[0]
libp2p.peerStore.addressBook.set(remotePeerInfo.id, [remoteListenAddr])
remAddr = libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerInfo.id)[0]
})
afterEach(() => Promise.all([
@ -68,7 +69,6 @@ describe('DHT subsystem operates correctly', () => {
const value = Buffer.from('world')
await libp2p.dialProtocol(remAddr, subsystemMulticodecs)
await Promise.all([
pWaitFor(() => libp2p._dht.routingTable.size === 1),
pWaitFor(() => remoteLibp2p._dht.routingTable.size === 1)
@ -99,7 +99,8 @@ describe('DHT subsystem operates correctly', () => {
await libp2p.start()
await remoteLibp2p.start()
remAddr = libp2p.peerStore.multiaddrsForPeer(remotePeerInfo)[0]
libp2p.peerStore.addressBook.set(remotePeerInfo.id, [remoteListenAddr])
remAddr = libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerInfo.id)[0]
})
afterEach(() => Promise.all([

View File

@ -100,7 +100,10 @@ describe('Dialing (direct, TCP)', () => {
const dialer = new Dialer({
transportManager: localTM,
peerStore: {
multiaddrsForPeer: () => [remoteAddr]
addressBook: {
add: () => {},
getMultiaddrsForPeer: () => [remoteAddr]
}
}
})
const peerId = await PeerId.createFromJSON(Peers[0])
@ -121,7 +124,7 @@ describe('Dialing (direct, TCP)', () => {
const peerId = await PeerId.createFromJSON(Peers[0])
const peerInfo = new PeerInfo(peerId)
peerInfo.multiaddrs.add(remoteAddr)
peerStore.put(peerInfo)
peerStore.addressBook.set(peerInfo.id, peerInfo.multiaddrs.toArray())
const connection = await dialer.connectToPeer(peerInfo)
expect(connection).to.exist()
@ -132,7 +135,10 @@ describe('Dialing (direct, TCP)', () => {
const dialer = new Dialer({
transportManager: localTM,
peerStore: {
multiaddrsForPeer: () => [unsupportedAddr]
addressBook: {
add: () => {},
getMultiaddrsForPeer: () => [unsupportedAddr]
}
}
})
const peerId = await PeerId.createFromJSON(Peers[0])
@ -173,7 +179,10 @@ describe('Dialing (direct, TCP)', () => {
transportManager: localTM,
concurrency: 2,
peerStore: {
multiaddrsForPeer: () => addrs
addressBook: {
add: () => {},
getMultiaddrsForPeer: () => addrs
}
}
})

View File

@ -87,7 +87,10 @@ describe('Dialing (direct, WebSockets)', () => {
const dialer = new Dialer({
transportManager: localTM,
peerStore: {
multiaddrsForPeer: () => [remoteAddr]
addressBook: {
add: () => {},
getMultiaddrsForPeer: () => [remoteAddr]
}
}
})
@ -100,7 +103,10 @@ describe('Dialing (direct, WebSockets)', () => {
const dialer = new Dialer({
transportManager: localTM,
peerStore: {
multiaddrsForPeer: () => [remoteAddr]
addressBook: {
add: () => {},
getMultiaddrsForPeer: () => [remoteAddr]
}
}
})
@ -121,7 +127,10 @@ describe('Dialing (direct, WebSockets)', () => {
const dialer = new Dialer({
transportManager: localTM,
peerStore: {
multiaddrsForPeer: () => [remoteAddr]
addressBook: {
add: () => {},
getMultiaddrsForPeer: () => [remoteAddr]
}
}
})
const peerId = await PeerId.createFromJSON(Peers[0])
@ -135,7 +144,10 @@ describe('Dialing (direct, WebSockets)', () => {
const dialer = new Dialer({
transportManager: localTM,
peerStore: {
multiaddrsForPeer: () => [unsupportedAddr]
addressBook: {
set: () => {},
getMultiaddrsForPeer: () => [unsupportedAddr]
}
}
})
const peerId = await PeerId.createFromJSON(Peers[0])
@ -150,7 +162,10 @@ describe('Dialing (direct, WebSockets)', () => {
transportManager: localTM,
timeout: 50,
peerStore: {
multiaddrsForPeer: () => [remoteAddr]
addressBook: {
add: () => {},
getMultiaddrsForPeer: () => [remoteAddr]
}
}
})
sinon.stub(localTM, 'dial').callsFake(async (addr, options) => {
@ -172,7 +187,10 @@ describe('Dialing (direct, WebSockets)', () => {
transportManager: localTM,
concurrency: 2,
peerStore: {
multiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr]
addressBook: {
set: () => {},
getMultiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr]
}
}
})
@ -208,7 +226,10 @@ describe('Dialing (direct, WebSockets)', () => {
transportManager: localTM,
concurrency: 2,
peerStore: {
multiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr]
addressBook: {
set: () => {},
getMultiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr]
}
}
})
@ -316,7 +337,7 @@ describe('Dialing (direct, WebSockets)', () => {
})
sinon.spy(libp2p.dialer, 'connectToPeer')
sinon.spy(libp2p.peerStore, 'put')
sinon.spy(libp2p.peerStore.addressBook, 'add')
const connection = await libp2p.dial(remoteAddr)
expect(connection).to.exist()
@ -325,7 +346,7 @@ describe('Dialing (direct, WebSockets)', () => {
expect(protocol).to.equal('/echo/1.0.0')
await connection.close()
expect(libp2p.dialer.connectToPeer.callCount).to.equal(1)
expect(libp2p.peerStore.put.callCount).to.be.at.least(1)
expect(libp2p.peerStore.addressBook.add.callCount).to.be.at.least(1)
})
it('should run identify automatically after connecting', async () => {
@ -339,19 +360,22 @@ describe('Dialing (direct, WebSockets)', () => {
})
sinon.spy(libp2p.identifyService, 'identify')
sinon.spy(libp2p.peerStore, 'replace')
sinon.spy(libp2p.upgrader, 'onConnection')
const connection = await libp2p.dial(remoteAddr)
expect(connection).to.exist()
sinon.spy(libp2p.peerStore.addressBook, 'set')
sinon.spy(libp2p.peerStore.protoBook, 'set')
// Wait for onConnection to be called
await pWaitFor(() => libp2p.upgrader.onConnection.callCount === 1)
expect(libp2p.identifyService.identify.callCount).to.equal(1)
await libp2p.identifyService.identify.firstCall.returnValue
expect(libp2p.peerStore.replace.callCount).to.equal(1)
expect(libp2p.peerStore.addressBook.set.callCount).to.equal(1)
expect(libp2p.peerStore.protoBook.set.callCount).to.equal(1)
})
it('should be able to use hangup to close connections', async () => {

View File

@ -12,6 +12,8 @@ const multiaddr = require('multiaddr')
const { collect } = require('streaming-iterables')
const pipe = require('it-pipe')
const AggregateError = require('aggregate-error')
const PeerId = require('peer-id')
const { createPeerInfo } = require('../utils/creators/peer')
const baseOptions = require('../utils/base-options')
const Libp2p = require('../../src')
@ -52,8 +54,9 @@ describe('Dialing (via relay, TCP)', () => {
return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(async libp2p => {
await libp2p.stop()
// Clear the peer stores
for (const peerId of libp2p.peerStore.peers.keys()) {
libp2p.peerStore.remove(peerId)
for (const peerIdStr of libp2p.peerStore.peers.keys()) {
const peerId = PeerId.createFromCID(peerIdStr)
libp2p.peerStore.delete(peerId)
}
}))
})

View File

@ -48,7 +48,12 @@ describe('Identify', () => {
protocols,
registrar: {
peerStore: {
replace: () => {}
addressBook: {
set: () => { }
},
protoBook: {
set: () => { }
}
}
}
})
@ -64,7 +69,8 @@ describe('Identify', () => {
const [local, remote] = duplexPair()
sinon.stub(localConnectionMock, 'newStream').returns({ stream: local, protocol: multicodecs.IDENTIFY })
sinon.spy(localIdentify.registrar.peerStore, 'replace')
sinon.spy(localIdentify.registrar.peerStore.addressBook, 'set')
sinon.spy(localIdentify.registrar.peerStore.protoBook, 'set')
// Run identify
await Promise.all([
@ -76,9 +82,10 @@ describe('Identify', () => {
})
])
expect(localIdentify.registrar.peerStore.replace.callCount).to.equal(1)
expect(localIdentify.registrar.peerStore.addressBook.set.callCount).to.equal(1)
expect(localIdentify.registrar.peerStore.protoBook.set.callCount).to.equal(1)
// Validate the remote peer gets updated in the peer store
const call = localIdentify.registrar.peerStore.replace.firstCall
const call = localIdentify.registrar.peerStore.addressBook.set.firstCall
expect(call.args[0].id.bytes).to.equal(remotePeer.id.bytes)
})
@ -88,7 +95,12 @@ describe('Identify', () => {
protocols,
registrar: {
peerStore: {
replace: () => {}
addressBook: {
set: () => { }
},
protoBook: {
set: () => { }
}
}
}
})
@ -134,7 +146,12 @@ describe('Identify', () => {
peerInfo: remotePeer,
registrar: {
peerStore: {
replace: () => {}
addressBook: {
set: () => {}
},
protoBook: {
set: () => { }
}
}
}
})
@ -152,9 +169,8 @@ describe('Identify', () => {
const [local, remote] = duplexPair()
sinon.stub(localConnectionMock, 'newStream').returns({ stream: local, protocol: multicodecs.IDENTIFY_PUSH })
sinon.spy(IdentifyService, 'updatePeerAddresses')
sinon.spy(IdentifyService, 'updatePeerProtocols')
sinon.spy(remoteIdentify.registrar.peerStore, 'replace')
sinon.spy(remoteIdentify.registrar.peerStore.addressBook, 'set')
sinon.spy(remoteIdentify.registrar.peerStore.protoBook, 'set')
// Run identify
await Promise.all([
@ -166,14 +182,14 @@ describe('Identify', () => {
})
])
expect(IdentifyService.updatePeerAddresses.callCount).to.equal(1)
expect(IdentifyService.updatePeerProtocols.callCount).to.equal(1)
expect(remoteIdentify.registrar.peerStore.replace.callCount).to.equal(1)
const [peerInfo] = remoteIdentify.registrar.peerStore.replace.firstCall.args
expect(peerInfo.id.bytes).to.eql(localPeer.id.bytes)
expect(peerInfo.multiaddrs.toArray()).to.eql([listeningAddr])
expect(peerInfo.protocols).to.eql(localProtocols)
expect(remoteIdentify.registrar.peerStore.addressBook.set.callCount).to.equal(1)
expect(remoteIdentify.registrar.peerStore.protoBook.set.callCount).to.equal(1)
const [peerId, multiaddrs] = remoteIdentify.registrar.peerStore.addressBook.set.firstCall.args
expect(peerId.bytes).to.eql(localPeer.id.bytes)
expect(multiaddrs).to.eql([listeningAddr])
const [peerId2, protocols] = remoteIdentify.registrar.peerStore.protoBook.set.firstCall.args
expect(peerId2.bytes).to.eql(localPeer.id.bytes)
expect(protocols).to.eql(Array.from(localProtocols))
})
})
@ -204,13 +220,15 @@ describe('Identify', () => {
})
sinon.spy(libp2p.identifyService, 'identify')
const peerStoreSpy = sinon.spy(libp2p.peerStore, 'replace')
const peerStoreSpySet = sinon.spy(libp2p.peerStore.addressBook, 'set')
const peerStoreSpyAdd = sinon.spy(libp2p.peerStore.addressBook, 'add')
const connection = await libp2p.dialer.connectToPeer(remoteAddr)
expect(connection).to.exist()
// Wait for peer store to be updated
await pWaitFor(() => peerStoreSpy.callCount === 1)
// Dialer._createDialTarget (add), Identify (replace)
await pWaitFor(() => peerStoreSpySet.callCount === 1 && peerStoreSpyAdd.callCount === 1)
expect(libp2p.identifyService.identify.callCount).to.equal(1)
// The connection should have no open streams
@ -226,7 +244,6 @@ describe('Identify', () => {
sinon.spy(libp2p.identifyService, 'identify')
sinon.spy(libp2p.identifyService, 'push')
sinon.spy(libp2p.peerStore, 'update')
const connection = await libp2p.dialer.connectToPeer(remoteAddr)
expect(connection).to.exist()

View File

@ -35,7 +35,9 @@ describe('peer discovery', () => {
...baseOptions,
peerInfo
})
libp2p.peerStore.add(remotePeerInfo)
libp2p.peerStore.addressBook.set(remotePeerInfo.id, remotePeerInfo.multiaddrs.toArray())
libp2p.peerStore.protoBook.set(remotePeerInfo.id, Array.from(remotePeerInfo.protocols))
const deferred = defer()
sinon.stub(libp2p.dialer, 'connectToPeer').callsFake((remotePeerInfo) => {
expect(remotePeerInfo).to.equal(remotePeerInfo)
@ -46,7 +48,9 @@ describe('peer discovery', () => {
libp2p.start()
await deferred.promise
expect(spy.getCall(0).args).to.eql([remotePeerInfo])
expect(spy.calledOnce).to.eql(true)
expect(spy.getCall(0).args[0].id.toString()).to.eql(remotePeerInfo.id.toString())
})
it('should stop discovery on libp2p start/stop', async () => {

View File

@ -0,0 +1,365 @@
'use strict'
/* eslint-env mocha */
const chai = require('chai')
chai.use(require('dirty-chai'))
const { expect } = chai
const { EventEmitter } = require('events')
const pDefer = require('p-defer')
const multiaddr = require('multiaddr')
const AddressBook = require('../../src/peer-store/address-book')
const peerUtils = require('../utils/creators/peer')
const {
ERR_INVALID_PARAMETERS
} = require('../../src/errors')
const addr1 = multiaddr('/ip4/127.0.0.1/tcp/8000')
const addr2 = multiaddr('/ip4/127.0.0.1/tcp/8001')
const addr3 = multiaddr('/ip4/127.0.0.1/tcp/8002')
const arraysAreEqual = (a, b) => a.length === b.length && a.sort().every((item, index) => b[index] === item)
describe('addressBook', () => {
let peerId
before(async () => {
[peerId] = await peerUtils.createPeerId()
})
describe('addressBook.set', () => {
let ee, ab
beforeEach(() => {
ee = new EventEmitter()
ab = new AddressBook(ee)
})
afterEach(() => {
ee.removeAllListeners()
})
it('throwns invalid parameters error if invalid PeerId is provided', () => {
expect(() => {
ab.set('invalid peerId')
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('throwns invalid parameters error if no addresses provided', () => {
expect(() => {
ab.set(peerId)
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('throwns invalid parameters error if invalid multiaddrs are provided', () => {
expect(() => {
ab.set(peerId, 'invalid multiaddr')
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('replaces the stored content by default and emit change event', () => {
const defer = pDefer()
const supportedMultiaddrs = [addr1, addr2]
ee.once('change:multiaddrs', ({ peerId, multiaddrs }) => {
expect(peerId).to.exist()
expect(multiaddrs).to.eql(supportedMultiaddrs)
defer.resolve()
})
ab.set(peerId, supportedMultiaddrs)
const multiaddrInfos = ab.get(peerId)
const multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr)
expect(multiaddrs).to.have.deep.members(supportedMultiaddrs)
return defer.promise
})
it('emits on set if not storing the exact same content', async () => {
const defer = pDefer()
const supportedMultiaddrsA = [addr1, addr2]
const supportedMultiaddrsB = [addr2]
let changeCounter = 0
ee.on('change:multiaddrs', () => {
changeCounter++
if (changeCounter > 1) {
defer.resolve()
}
})
// set 1
ab.set(peerId, supportedMultiaddrsA)
// set 2 (same content)
ab.set(peerId, supportedMultiaddrsB)
const multiaddrInfos = ab.get(peerId)
const multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr)
expect(multiaddrs).to.have.deep.members(supportedMultiaddrsB)
await defer.promise
})
it('does not emit on set if it is storing the exact same content', async () => {
const defer = pDefer()
const supportedMultiaddrs = [addr1, addr2]
let changeCounter = 0
ee.on('change:multiaddrs', () => {
changeCounter++
if (changeCounter > 1) {
defer.reject()
}
})
// set 1
ab.set(peerId, supportedMultiaddrs)
// set 2 (same content)
ab.set(peerId, supportedMultiaddrs)
// Wait 50ms for incorrect second event
setTimeout(() => {
defer.resolve()
}, 50)
await defer.promise
})
})
describe('addressBook.add', () => {
let ee, ab
beforeEach(() => {
ee = new EventEmitter()
ab = new AddressBook(ee)
})
afterEach(() => {
ee.removeAllListeners()
})
it('throwns invalid parameters error if invalid PeerId is provided', () => {
expect(() => {
ab.add('invalid peerId')
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('throwns invalid parameters error if no addresses provided', () => {
expect(() => {
ab.add(peerId)
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('throwns invalid parameters error if invalid multiaddrs are provided', () => {
expect(() => {
ab.add(peerId, 'invalid multiaddr')
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('adds the new content and emits change event', () => {
const defer = pDefer()
const supportedMultiaddrsA = [addr1, addr2]
const supportedMultiaddrsB = [addr3]
const finalMultiaddrs = supportedMultiaddrsA.concat(supportedMultiaddrsB)
let changeTrigger = 2
ee.on('change:multiaddrs', ({ multiaddrs }) => {
changeTrigger--
if (changeTrigger === 0 && arraysAreEqual(multiaddrs, finalMultiaddrs)) {
defer.resolve()
}
})
// Replace
ab.set(peerId, supportedMultiaddrsA)
let multiaddrInfos = ab.get(peerId)
let multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr)
expect(multiaddrs).to.have.deep.members(supportedMultiaddrsA)
// Add
ab.add(peerId, supportedMultiaddrsB)
multiaddrInfos = ab.get(peerId)
multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr)
expect(multiaddrs).to.have.deep.members(finalMultiaddrs)
return defer.promise
})
it('emits on add if the content to add not exists', async () => {
const defer = pDefer()
const supportedMultiaddrsA = [addr1]
const supportedMultiaddrsB = [addr2]
const finalMultiaddrs = supportedMultiaddrsA.concat(supportedMultiaddrsB)
let changeCounter = 0
ee.on('change:multiaddrs', () => {
changeCounter++
if (changeCounter > 1) {
defer.resolve()
}
})
// set 1
ab.set(peerId, supportedMultiaddrsA)
// set 2 (content already existing)
ab.add(peerId, supportedMultiaddrsB)
const multiaddrInfos = ab.get(peerId)
const multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr)
expect(multiaddrs).to.have.deep.members(finalMultiaddrs)
await defer.promise
})
it('does not emit on add if the content to add already exists', async () => {
const defer = pDefer()
const supportedMultiaddrsA = [addr1, addr2]
const supportedMultiaddrsB = [addr2]
let changeCounter = 0
ee.on('change:multiaddrs', () => {
changeCounter++
if (changeCounter > 1) {
defer.reject()
}
})
// set 1
ab.set(peerId, supportedMultiaddrsA)
// set 2 (content already existing)
ab.add(peerId, supportedMultiaddrsB)
// Wait 50ms for incorrect second event
setTimeout(() => {
defer.resolve()
}, 50)
await defer.promise
})
})
describe('addressBook.get', () => {
let ee, ab
beforeEach(() => {
ee = new EventEmitter()
ab = new AddressBook(ee)
})
it('throwns invalid parameters error if invalid PeerId is provided', () => {
expect(() => {
ab.get('invalid peerId')
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('returns undefined if no multiaddrs are known for the provided peer', () => {
const multiaddrInfos = ab.get(peerId)
expect(multiaddrInfos).to.not.exist()
})
it('returns the multiaddrs stored', () => {
const supportedMultiaddrs = [addr1, addr2]
ab.set(peerId, supportedMultiaddrs)
const multiaddrInfos = ab.get(peerId)
const multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr)
expect(multiaddrs).to.have.deep.members(supportedMultiaddrs)
})
})
describe('addressBook.getMultiaddrsForPeer', () => {
let ee, ab
beforeEach(() => {
ee = new EventEmitter()
ab = new AddressBook(ee)
})
it('throwns invalid parameters error if invalid PeerId is provided', () => {
expect(() => {
ab.getMultiaddrsForPeer('invalid peerId')
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('returns undefined if no multiaddrs are known for the provided peer', () => {
const multiaddrInfos = ab.getMultiaddrsForPeer(peerId)
expect(multiaddrInfos).to.not.exist()
})
it('returns the multiaddrs stored', () => {
const supportedMultiaddrs = [addr1, addr2]
ab.set(peerId, supportedMultiaddrs)
const multiaddrs = ab.getMultiaddrsForPeer(peerId)
multiaddrs.forEach((m) => {
expect(m.getPeerId()).to.equal(peerId.toB58String())
})
})
})
describe('addressBook.delete', () => {
let ee, ab
beforeEach(() => {
ee = new EventEmitter()
ab = new AddressBook(ee)
})
it('throwns invalid parameters error if invalid PeerId is provided', () => {
expect(() => {
ab.delete('invalid peerId')
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('returns false if no records exist for the peer and no event is emitted', () => {
const defer = pDefer()
ee.on('change:multiaddrs', () => {
defer.reject()
})
const deleted = ab.delete(peerId)
expect(deleted).to.equal(false)
// Wait 50ms for incorrect invalid event
setTimeout(() => {
defer.resolve()
}, 50)
return defer.promise
})
it('returns true if the record exists and an event is emitted', () => {
const defer = pDefer()
const supportedMultiaddrs = [addr1, addr2]
ab.set(peerId, supportedMultiaddrs)
// Listen after set
ee.on('change:multiaddrs', ({ multiaddrs }) => {
expect(multiaddrs.length).to.eql(0)
defer.resolve()
})
const deleted = ab.delete(peerId)
expect(deleted).to.equal(true)
return defer.promise
})
})
})

View File

@ -4,185 +4,147 @@
const chai = require('chai')
chai.use(require('dirty-chai'))
const { expect } = chai
const sinon = require('sinon')
const pDefer = require('p-defer')
const PeerStore = require('../../src/peer-store')
const multiaddr = require('multiaddr')
const peerUtils = require('../utils/creators/peer')
const addr = multiaddr('/ip4/127.0.0.1/tcp/8000')
const addr1 = multiaddr('/ip4/127.0.0.1/tcp/8000')
const addr2 = multiaddr('/ip4/127.0.0.1/tcp/8001')
const addr3 = multiaddr('/ip4/127.0.0.1/tcp/8002')
const addr4 = multiaddr('/ip4/127.0.0.1/tcp/8003')
const proto1 = '/protocol1'
const proto2 = '/protocol2'
const proto3 = '/protocol3'
describe('peer-store', () => {
let peerStore
beforeEach(() => {
peerStore = new PeerStore()
let peerIds
before(async () => {
peerIds = await peerUtils.createPeerId({
number: 4
})
})
it('should add a new peer and emit it when it does not exist', async () => {
const defer = pDefer()
describe('empty books', () => {
let peerStore
sinon.spy(peerStore, 'put')
sinon.spy(peerStore, 'add')
sinon.spy(peerStore, 'update')
const [peerInfo] = await peerUtils.createPeerInfo()
peerStore.on('peer', (peer) => {
expect(peer).to.exist()
defer.resolve()
beforeEach(() => {
peerStore = new PeerStore()
})
peerStore.put(peerInfo)
// Wait for peerStore to emit the peer
await defer.promise
it('has an empty map of peers', () => {
const peers = peerStore.peers
expect(peers.size).to.equal(0)
})
expect(peerStore.put.callCount).to.equal(1)
expect(peerStore.add.callCount).to.equal(1)
expect(peerStore.update.callCount).to.equal(0)
it('returns false on trying to delete a non existant peerId', () => {
const deleted = peerStore.delete(peerIds[0])
expect(deleted).to.equal(false)
})
it('returns undefined on trying to find a non existant peerId', () => {
const peerInfo = peerStore.find(peerIds[0])
expect(peerInfo).to.not.exist()
})
})
it('should update peer when it is already in the store', async () => {
const [peerInfo] = await peerUtils.createPeerInfo()
describe('previously populated books', () => {
let peerStore
// Put the peer in the store
peerStore.put(peerInfo)
beforeEach(() => {
peerStore = new PeerStore()
sinon.spy(peerStore, 'add')
sinon.spy(peerStore, 'update')
// Add peer0 with { addr1, addr2 } and { proto1 }
peerStore.addressBook.set(peerIds[0], [addr1, addr2])
peerStore.protoBook.set(peerIds[0], [proto1])
// When updating, peer event must not be emitted
peerStore.on('peer', () => {
throw new Error('should not emit twice')
})
// If no multiaddrs change, the event should not be emitted
peerStore.on('change:multiaddrs', () => {
throw new Error('should not emit change:multiaddrs')
})
// If no protocols change, the event should not be emitted
peerStore.on('change:protocols', () => {
throw new Error('should not emit change:protocols')
// Add peer1 with { addr3 } and { proto2, proto3 }
peerStore.addressBook.set(peerIds[1], [addr3])
peerStore.protoBook.set(peerIds[1], [proto2, proto3])
// Add peer2 with { addr4 }
peerStore.addressBook.set(peerIds[2], [addr4])
// Add peer3 with { addr4 } and { proto2 }
peerStore.addressBook.set(peerIds[3], [addr4])
peerStore.protoBook.set(peerIds[3], [proto2])
})
peerStore.put(peerInfo)
it('has peers', () => {
const peers = peerStore.peers
expect(peerStore.add.callCount).to.equal(0)
expect(peerStore.update.callCount).to.equal(1)
})
expect(peers.size).to.equal(4)
expect(Array.from(peers.keys())).to.have.members([
peerIds[0].toB58String(),
peerIds[1].toB58String(),
peerIds[2].toB58String(),
peerIds[3].toB58String()
])
})
it('should emit the "change:multiaddrs" event when a peer has new multiaddrs', async () => {
const defer = pDefer()
const [createdPeerInfo] = await peerUtils.createPeerInfo()
it('returns true on deleting a stored peer', () => {
const deleted = peerStore.delete(peerIds[0])
expect(deleted).to.equal(true)
// Put the peer in the store
peerStore.put(createdPeerInfo)
const peers = peerStore.peers
expect(peers.size).to.equal(3)
expect(Array.from(peers.keys())).to.not.have.members([peerIds[0].toB58String()])
})
// When updating, "change:multiaddrs" event must not be emitted
peerStore.on('change:multiaddrs', ({ peerInfo, multiaddrs }) => {
it('returns true on deleting a stored peer which is only on one book', () => {
const deleted = peerStore.delete(peerIds[2])
expect(deleted).to.equal(true)
const peers = peerStore.peers
expect(peers.size).to.equal(3)
})
it('finds the stored information of a peer in all its books', () => {
const peerInfo = peerStore.find(peerIds[0])
expect(peerInfo).to.exist()
expect(peerInfo.id).to.eql(createdPeerInfo.id)
expect(peerInfo.protocols).to.eql(createdPeerInfo.protocols)
expect(multiaddrs).to.exist()
expect(multiaddrs).to.eql(createdPeerInfo.multiaddrs.toArray())
defer.resolve()
})
// If no protocols change, the event should not be emitted
peerStore.on('change:protocols', () => {
throw new Error('should not emit change:protocols')
expect(peerInfo.protocols).to.have.members([proto1])
const peerMultiaddrs = peerInfo.multiaddrInfos.map((mi) => mi.multiaddr)
expect(peerMultiaddrs).to.have.members([addr1, addr2])
})
createdPeerInfo.multiaddrs.add(addr)
peerStore.put(createdPeerInfo)
// Wait for peerStore to emit the event
await defer.promise
})
it('should emit the "change:protocols" event when a peer has new protocols', async () => {
const defer = pDefer()
const [createdPeerInfo] = await peerUtils.createPeerInfo()
// Put the peer in the store
peerStore.put(createdPeerInfo)
// If no multiaddrs change, the event should not be emitted
peerStore.on('change:multiaddrs', () => {
throw new Error('should not emit change:multiaddrs')
})
// When updating, "change:protocols" event must be emitted
peerStore.on('change:protocols', ({ peerInfo, protocols }) => {
it('finds the stored information of a peer that is not present in all its books', () => {
const peerInfo = peerStore.find(peerIds[2])
expect(peerInfo).to.exist()
expect(peerInfo.id).to.eql(createdPeerInfo.id)
expect(peerInfo.multiaddrs).to.eql(createdPeerInfo.multiaddrs)
expect(protocols).to.exist()
expect(protocols).to.eql(Array.from(createdPeerInfo.protocols))
defer.resolve()
expect(peerInfo.protocols.length).to.eql(0)
const peerMultiaddrs = peerInfo.multiaddrInfos.map((mi) => mi.multiaddr)
expect(peerMultiaddrs).to.have.members([addr4])
})
createdPeerInfo.protocols.add('/new-protocol/1.0.0')
peerStore.put(createdPeerInfo)
it('can find all the peers supporting a protocol', () => {
const peerSupporting2 = []
// Wait for peerStore to emit the event
await defer.promise
})
for (const [, peerInfo] of peerStore.peers.entries()) {
if (peerInfo.protocols.has(proto2)) {
peerSupporting2.push(peerInfo)
}
}
it('should be able to retrieve a peer from store through its b58str id', async () => {
const [peerInfo] = await peerUtils.createPeerInfo()
const id = peerInfo.id
expect(peerSupporting2.length).to.eql(2)
expect(peerSupporting2[0].id.toB58String()).to.eql(peerIds[1].toB58String())
expect(peerSupporting2[1].id.toB58String()).to.eql(peerIds[3].toB58String())
})
let retrievedPeer = peerStore.get(id)
expect(retrievedPeer).to.not.exist()
it('can find all the peers listening on a given address', () => {
const peerListenint4 = []
// Put the peer in the store
peerStore.put(peerInfo)
for (const [, peerInfo] of peerStore.peers.entries()) {
if (peerInfo.multiaddrs.has(addr4)) {
peerListenint4.push(peerInfo)
}
}
retrievedPeer = peerStore.get(id)
expect(retrievedPeer).to.exist()
expect(retrievedPeer.id).to.equal(peerInfo.id)
expect(retrievedPeer.multiaddrs).to.eql(peerInfo.multiaddrs)
expect(retrievedPeer.protocols).to.eql(peerInfo.protocols)
})
it('should be able to remove a peer from store through its b58str id', async () => {
const [peerInfo] = await peerUtils.createPeerInfo()
const id = peerInfo.id
let removed = peerStore.remove(id)
expect(removed).to.eql(false)
// Put the peer in the store
peerStore.put(peerInfo)
expect(peerStore.peers.size).to.equal(1)
removed = peerStore.remove(id)
expect(removed).to.eql(true)
expect(peerStore.peers.size).to.equal(0)
})
it('should be able to get the multiaddrs for a peer', async () => {
const [peerInfo, relayInfo] = await peerUtils.createPeerInfo({ number: 2 })
const id = peerInfo.id
const ma1 = multiaddr('/ip4/127.0.0.1/tcp/4001')
const ma2 = multiaddr('/ip4/127.0.0.1/tcp/4002/ws')
const ma3 = multiaddr(`/ip4/127.0.0.1/tcp/4003/ws/p2p/${relayInfo.id.toB58String()}/p2p-circuit`)
peerInfo.multiaddrs.add(ma1)
peerInfo.multiaddrs.add(ma2)
peerInfo.multiaddrs.add(ma3)
const multiaddrs = peerStore.multiaddrsForPeer(peerInfo)
const expectedAddrs = [
ma1.encapsulate(`/p2p/${id.toB58String()}`),
ma2.encapsulate(`/p2p/${id.toB58String()}`),
ma3.encapsulate(`/p2p/${id.toB58String()}`)
]
expect(multiaddrs).to.eql(expectedAddrs)
expect(peerListenint4.length).to.eql(2)
expect(peerListenint4[0].id.toB58String()).to.eql(peerIds[2].toB58String())
expect(peerListenint4[1].id.toB58String()).to.eql(peerIds[3].toB58String())
})
})
})
describe('peer-store on discovery', () => {
// TODO: implement with discovery
})

View File

@ -0,0 +1,310 @@
'use strict'
/* eslint-env mocha */
const chai = require('chai')
chai.use(require('dirty-chai'))
const { expect } = chai
const { EventEmitter } = require('events')
const pDefer = require('p-defer')
const ProtoBook = require('../../src/peer-store/proto-book')
const peerUtils = require('../utils/creators/peer')
const {
ERR_INVALID_PARAMETERS
} = require('../../src/errors')
const arraysAreEqual = (a, b) => a.length === b.length && a.sort().every((item, index) => b[index] === item)
describe('protoBook', () => {
let peerId
before(async () => {
[peerId] = await peerUtils.createPeerId()
})
describe('protoBook.set', () => {
let ee, pb
beforeEach(() => {
ee = new EventEmitter()
pb = new ProtoBook(ee)
})
afterEach(() => {
ee.removeAllListeners()
})
it('throwns invalid parameters error if invalid PeerId is provided', () => {
expect(() => {
pb.set('invalid peerId')
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('throwns invalid parameters error if no protocols provided', () => {
expect(() => {
pb.set(peerId)
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('replaces the stored content by default and emit change event', () => {
const defer = pDefer()
const supportedProtocols = ['protocol1', 'protocol2']
ee.once('change:protocols', ({ peerId, protocols }) => {
expect(peerId).to.exist()
expect(protocols).to.have.deep.members(supportedProtocols)
defer.resolve()
})
pb.set(peerId, supportedProtocols)
const protocols = pb.get(peerId)
expect(protocols).to.have.deep.members(supportedProtocols)
return defer.promise
})
it('emits on set if not storing the exact same content', () => {
const defer = pDefer()
const supportedProtocolsA = ['protocol1', 'protocol2']
const supportedProtocolsB = ['protocol2']
let changeCounter = 0
ee.on('change:protocols', () => {
changeCounter++
if (changeCounter > 1) {
defer.resolve()
}
})
// set 1
pb.set(peerId, supportedProtocolsA)
// set 2 (same content)
pb.set(peerId, supportedProtocolsB)
const protocols = pb.get(peerId)
expect(protocols).to.have.deep.members(supportedProtocolsB)
return defer.promise
})
it('does not emit on set if it is storing the exact same content', () => {
const defer = pDefer()
const supportedProtocols = ['protocol1', 'protocol2']
let changeCounter = 0
ee.on('change:protocols', () => {
changeCounter++
if (changeCounter > 1) {
defer.reject()
}
})
// set 1
pb.set(peerId, supportedProtocols)
// set 2 (same content)
pb.set(peerId, supportedProtocols)
// Wait 50ms for incorrect second event
setTimeout(() => {
defer.resolve()
}, 50)
return defer.promise
})
})
describe('protoBook.add', () => {
let ee, pb
beforeEach(() => {
ee = new EventEmitter()
pb = new ProtoBook(ee)
})
afterEach(() => {
ee.removeAllListeners()
})
it('throwns invalid parameters error if invalid PeerId is provided', () => {
expect(() => {
pb.add('invalid peerId')
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('throwns invalid parameters error if no protocols provided', () => {
expect(() => {
pb.add(peerId)
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('adds the new content and emits change event', () => {
const defer = pDefer()
const supportedProtocolsA = ['protocol1', 'protocol2']
const supportedProtocolsB = ['protocol3']
const finalProtocols = supportedProtocolsA.concat(supportedProtocolsB)
let changeTrigger = 2
ee.on('change:protocols', ({ protocols }) => {
changeTrigger--
if (changeTrigger === 0 && arraysAreEqual(protocols, finalProtocols)) {
defer.resolve()
}
})
// Replace
pb.set(peerId, supportedProtocolsA)
let protocols = pb.get(peerId)
expect(protocols).to.have.deep.members(supportedProtocolsA)
// Add
pb.add(peerId, supportedProtocolsB)
protocols = pb.get(peerId)
expect(protocols).to.have.deep.members(finalProtocols)
return defer.promise
})
it('emits on add if the content to add not exists', () => {
const defer = pDefer()
const supportedProtocolsA = ['protocol1']
const supportedProtocolsB = ['protocol2']
const finalProtocols = supportedProtocolsA.concat(supportedProtocolsB)
let changeCounter = 0
ee.on('change:protocols', () => {
changeCounter++
if (changeCounter > 1) {
defer.resolve()
}
})
// set 1
pb.set(peerId, supportedProtocolsA)
// set 2 (content already existing)
pb.add(peerId, supportedProtocolsB)
const protocols = pb.get(peerId)
expect(protocols).to.have.deep.members(finalProtocols)
return defer.promise
})
it('does not emit on add if the content to add already exists', () => {
const defer = pDefer()
const supportedProtocolsA = ['protocol1', 'protocol2']
const supportedProtocolsB = ['protocol2']
let changeCounter = 0
ee.on('change:protocols', () => {
changeCounter++
if (changeCounter > 1) {
defer.reject()
}
})
// set 1
pb.set(peerId, supportedProtocolsA)
// set 2 (content already existing)
pb.add(peerId, supportedProtocolsB)
// Wait 50ms for incorrect second event
setTimeout(() => {
defer.resolve()
}, 50)
return defer.promise
})
})
describe('protoBook.get', () => {
let ee, pb
beforeEach(() => {
ee = new EventEmitter()
pb = new ProtoBook(ee)
})
it('throwns invalid parameters error if invalid PeerId is provided', () => {
expect(() => {
pb.get('invalid peerId')
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('returns undefined if no protocols are known for the provided peer', () => {
const protocols = pb.get(peerId)
expect(protocols).to.not.exist()
})
it('returns the protocols stored', () => {
const supportedProtocols = ['protocol1', 'protocol2']
pb.set(peerId, supportedProtocols)
const protocols = pb.get(peerId)
expect(protocols).to.have.deep.members(supportedProtocols)
})
})
describe('protoBook.delete', () => {
let ee, pb
beforeEach(() => {
ee = new EventEmitter()
pb = new ProtoBook(ee)
})
it('throwns invalid parameters error if invalid PeerId is provided', () => {
expect(() => {
pb.delete('invalid peerId')
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('returns false if no records exist for the peer and no event is emitted', () => {
const defer = pDefer()
ee.on('change:protocols', () => {
defer.reject()
})
const deleted = pb.delete(peerId)
expect(deleted).to.equal(false)
// Wait 50ms for incorrect invalid event
setTimeout(() => {
defer.resolve()
}, 50)
return defer.promise
})
it('returns true if the record exists and an event is emitted', () => {
const defer = pDefer()
const supportedProtocols = ['protocol1', 'protocol2']
pb.set(peerId, supportedProtocols)
// Listen after set
ee.on('change:protocols', ({ protocols }) => {
expect(protocols.length).to.eql(0)
defer.resolve()
})
const deleted = pb.delete(peerId)
expect(deleted).to.equal(true)
return defer.promise
})
})
})

View File

@ -89,7 +89,9 @@ describe('registrar', () => {
remotePeerInfo.protocols.add(multicodec)
// Add connected peer to peerStore and registrar
peerStore.put(remotePeerInfo)
peerStore.addressBook.set(remotePeerInfo.id, remotePeerInfo.multiaddrs.toArray())
peerStore.protoBook.set(remotePeerInfo.id, Array.from(remotePeerInfo.protocols))
registrar.onConnect(remotePeerInfo, conn)
expect(registrar.connections.size).to.eql(1)
@ -156,18 +158,23 @@ describe('registrar', () => {
const peerInfo = await PeerInfo.create(conn.remotePeer)
// Add connected peer to peerStore and registrar
peerStore.put(peerInfo)
peerStore.addressBook.set(peerInfo.id, peerInfo.multiaddrs.toArray())
peerStore.protoBook.set(peerInfo.id, Array.from(peerInfo.protocols))
registrar.onConnect(peerInfo, conn)
// Add protocol to peer and update it
peerInfo.protocols.add(multicodec)
peerStore.put(peerInfo)
peerStore.addressBook.add(peerInfo.id, peerInfo.multiaddrs.toArray())
peerStore.protoBook.add(peerInfo.id, Array.from(peerInfo.protocols))
await onConnectDefer.promise
// Remove protocol to peer and update it
peerInfo.protocols.delete(multicodec)
peerStore.replace(peerInfo)
peerStore.addressBook.set(peerInfo.id, peerInfo.multiaddrs.toArray())
peerStore.protoBook.set(peerInfo.id, Array.from(peerInfo.protocols))
await onDisconnectDefer.promise
})
@ -197,7 +204,8 @@ describe('registrar', () => {
const id = peerInfo.id.toB58String()
// Add connection to registrar
peerStore.put(peerInfo)
peerStore.addressBook.set(peerInfo.id, peerInfo.multiaddrs.toArray())
peerStore.protoBook.set(peerInfo.id, Array.from(peerInfo.protocols))
registrar.onConnect(peerInfo, conn1)
registrar.onConnect(peerInfo, conn2)