feat: registrar (#471)

* feat: peer-store v0

* feat: registrar

* chore: apply suggestions from code review

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

* chore: address review

* chore: support multiple conns

* chore: address review

* fix: no remote peer from topology on disconnect
This commit is contained in:
Vasco Santos
2019-11-06 15:47:11 +01:00
committed by Jacob Heun
parent 582094a834
commit 9d52b80c45
8 changed files with 594 additions and 8 deletions

View File

@ -0,0 +1,57 @@
'use strict'
/* eslint-env mocha */
const chai = require('chai')
chai.use(require('dirty-chai'))
const { expect } = chai
const sinon = require('sinon')
const mergeOptions = require('merge-options')
const multiaddr = require('multiaddr')
const Libp2p = require('../../src')
const baseOptions = require('../utils/base-options')
const peerUtils = require('../utils/creators/peer')
const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0')
describe('registrar on dial', () => {
let peerInfo
let remotePeerInfo
let libp2p
let remoteLibp2p
let remoteAddr
before(async () => {
[peerInfo, remotePeerInfo] = await peerUtils.createPeerInfoFromFixture(2)
remoteLibp2p = new Libp2p(mergeOptions(baseOptions, {
peerInfo: remotePeerInfo
}))
await remoteLibp2p.transportManager.listen([listenAddr])
remoteAddr = remoteLibp2p.transportManager.getAddrs()[0]
})
after(async () => {
sinon.restore()
await remoteLibp2p.stop()
libp2p && await libp2p.stop()
})
it('should inform registrar of a new connection', async () => {
libp2p = new Libp2p(mergeOptions(baseOptions, {
peerInfo
}))
sinon.spy(remoteLibp2p.registrar, 'onConnect')
await libp2p.dial(remoteAddr)
expect(remoteLibp2p.registrar.onConnect.callCount).to.equal(1)
const libp2pConn = libp2p.registrar.getConnection(remotePeerInfo)
expect(libp2pConn).to.exist()
const remoteConn = remoteLibp2p.registrar.getConnection(peerInfo)
expect(remoteConn).to.exist()
})
})

View File

@ -0,0 +1,224 @@
'use strict'
/* eslint-env mocha */
const chai = require('chai')
chai.use(require('dirty-chai'))
const { expect } = chai
const pDefer = require('p-defer')
const PeerInfo = require('peer-info')
const PeerStore = require('../../src/peer-store')
const Registrar = require('../../src/registrar')
const { createMockConnection } = require('./utils')
const multicodec = '/test/1.0.0'
describe('registrar', () => {
let peerStore, registrar
describe('errors', () => {
beforeEach(() => {
peerStore = new PeerStore()
registrar = new Registrar({ peerStore })
})
it('should fail to register a protocol if no multicodec is provided', () => {
try {
registrar.register()
} catch (err) {
expect(err).to.exist()
return
}
throw new Error('should fail to register a protocol if no multicodec is provided')
})
it('should fail to register a protocol if no handlers are provided', () => {
const topologyProps = {
multicodecs: multicodec
}
try {
registrar.register(topologyProps)
} catch (err) {
expect(err).to.exist()
return
}
throw new Error('should fail to register a protocol if no handlers are provided')
})
it('should fail to register a protocol if the onConnect handler is not provided', () => {
const topologyProps = {
multicodecs: multicodec,
handlers: {
onDisconnect: () => { }
}
}
try {
registrar.register(topologyProps)
} catch (err) {
expect(err).to.exist()
return
}
throw new Error('should fail to register a protocol if the onConnect handler is not provided')
})
it('should fail to register a protocol if the onDisconnect handler is not provided', () => {
const topologyProps = {
multicodecs: multicodec,
handlers: {
onConnect: () => { }
}
}
try {
registrar.register(topologyProps)
} catch (err) {
expect(err).to.exist()
return
}
throw new Error('should fail to register a protocol if the onDisconnect handler is not provided')
})
})
describe('registration', () => {
beforeEach(() => {
peerStore = new PeerStore()
registrar = new Registrar({ peerStore })
})
it('should be able to register a protocol', () => {
const topologyProps = {
handlers: {
onConnect: () => { },
onDisconnect: () => { }
},
multicodecs: multicodec
}
const identifier = registrar.register(topologyProps)
expect(identifier).to.exist()
})
it('should be able to unregister a protocol', () => {
const topologyProps = {
handlers: {
onConnect: () => { },
onDisconnect: () => { }
},
multicodecs: multicodec
}
const identifier = registrar.register(topologyProps)
const success = registrar.unregister(identifier)
expect(success).to.eql(true)
})
it('should fail to unregister if no register was made', () => {
const success = registrar.unregister('bad-identifier')
expect(success).to.eql(false)
})
it('should call onConnect handler for connected peers after register', async () => {
const onConnectDefer = pDefer()
const onDisconnectDefer = pDefer()
// Setup connections before registrar
const conn = await createMockConnection()
const remotePeerInfo = await PeerInfo.create(conn.remotePeer)
// Add protocol to peer
remotePeerInfo.protocols.add(multicodec)
// Add connected peer to peerStore and registrar
peerStore.put(remotePeerInfo)
registrar.onConnect(remotePeerInfo, conn)
expect(registrar.connections.size).to.eql(1)
const topologyProps = {
multicodecs: multicodec,
handlers: {
onConnect: (peerInfo, connection) => {
expect(peerInfo.id.toB58String()).to.eql(remotePeerInfo.id.toB58String())
expect(connection.id).to.eql(conn.id)
onConnectDefer.resolve()
},
onDisconnect: (peerInfo) => {
expect(peerInfo.id.toB58String()).to.eql(remotePeerInfo.id.toB58String())
onDisconnectDefer.resolve()
}
}
}
// Register protocol
const identifier = registrar.register(topologyProps)
const topology = registrar.topologies.get(identifier)
// Topology created
expect(topology).to.exist()
expect(topology.peers.size).to.eql(1)
registrar.onDisconnect(remotePeerInfo)
expect(registrar.connections.size).to.eql(0)
expect(topology.peers.size).to.eql(1) // topology should keep the peer
// Wait for handlers to be called
return Promise.all([
onConnectDefer.promise,
onDisconnectDefer.promise
])
})
it('should call onConnect handler after register, once a peer is connected and protocols are updated', async () => {
const onConnectDefer = pDefer()
const onDisconnectDefer = pDefer()
const topologyProps = {
multicodecs: multicodec,
handlers: {
onConnect: () => {
onConnectDefer.resolve()
},
onDisconnect: () => {
onDisconnectDefer.resolve()
}
}
}
// Register protocol
const identifier = registrar.register(topologyProps)
const topology = registrar.topologies.get(identifier)
// Topology created
expect(topology).to.exist()
expect(topology.peers.size).to.eql(0)
expect(registrar.connections.size).to.eql(0)
// Setup connections before registrar
const conn = await createMockConnection()
const peerInfo = await PeerInfo.create(conn.remotePeer)
// Add connected peer to peerStore and registrar
peerStore.put(peerInfo)
registrar.onConnect(peerInfo, conn)
// Add protocol to peer and update it
peerInfo.protocols.add(multicodec)
peerStore.put(peerInfo)
await onConnectDefer.promise
expect(topology.peers.size).to.eql(1)
// Remove protocol to peer and update it
peerInfo.protocols.delete(multicodec)
peerStore.put(peerInfo)
await onDisconnectDefer.promise
})
})
})

50
test/registrar/utils.js Normal file
View File

@ -0,0 +1,50 @@
'use strict'
const { Connection } = require('libp2p-interfaces/src/connection')
const multiaddr = require('multiaddr')
const pair = require('it-pair')
const peerUtils = require('../utils/creators/peer')
module.exports.createMockConnection = async (properties = {}) => {
const localAddr = multiaddr('/ip4/127.0.0.1/tcp/8080')
const remoteAddr = multiaddr('/ip4/127.0.0.1/tcp/8081')
const [localPeer, remotePeer] = await peerUtils.createPeerInfoFromFixture(2)
const openStreams = []
let streamId = 0
return new Connection({
localPeer: localPeer.id,
remotePeer: remotePeer.id,
localAddr,
remoteAddr,
stat: {
timeline: {
open: Date.now() - 10,
upgraded: Date.now()
},
direction: 'outbound',
encryption: '/secio/1.0.0',
multiplexer: '/mplex/6.7.0'
},
newStream: (protocols) => {
const id = streamId++
const stream = pair()
stream.close = () => stream.sink([])
stream.id = id
openStreams.push(stream)
return {
stream,
protocol: protocols[0]
}
},
close: () => { },
getStreams: () => openStreams,
...properties
})
}

View File

@ -2,12 +2,12 @@
const Transport = require('libp2p-tcp')
const Muxer = require('libp2p-mplex')
const mockCrypto = require('../utils/mockCrypto')
const Crypto = require('../../src/insecure/plaintext')
module.exports = {
modules: {
transport: [Transport],
streamMuxer: [Muxer],
connEncryption: [mockCrypto]
connEncryption: [Crypto]
}
}