mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-06-13 01:01:23 +00:00
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:
57
test/registrar/registrar.node.js
Normal file
57
test/registrar/registrar.node.js
Normal 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()
|
||||
})
|
||||
})
|
224
test/registrar/registrar.spec.js
Normal file
224
test/registrar/registrar.spec.js
Normal 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
50
test/registrar/utils.js
Normal 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
|
||||
})
|
||||
}
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user