mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-06-16 10:41:23 +00:00
feat: add delegated peer and content routing support (#242)
* feat: allow for configuring content and peer routing * feat: support multiple peer and content routing modules * docs: add delegated routing example
This commit is contained in:
@ -8,91 +8,261 @@ chai.use(require('dirty-chai'))
|
||||
const expect = chai.expect
|
||||
const parallel = require('async/parallel')
|
||||
const _times = require('lodash.times')
|
||||
const DelegatedPeerRouter = require('libp2p-delegated-peer-routing')
|
||||
const sinon = require('sinon')
|
||||
const nock = require('nock')
|
||||
|
||||
const createNode = require('./utils/create-node')
|
||||
|
||||
describe('.peerRouting', () => {
|
||||
let nodeA
|
||||
let nodeB
|
||||
let nodeC
|
||||
let nodeD
|
||||
let nodeE
|
||||
describe('via the dht', () => {
|
||||
let nodeA
|
||||
let nodeB
|
||||
let nodeC
|
||||
let nodeD
|
||||
let nodeE
|
||||
|
||||
before(function (done) {
|
||||
this.timeout(5 * 1000)
|
||||
|
||||
const tasks = _times(5, () => (cb) => {
|
||||
createNode('/ip4/0.0.0.0/tcp/0', {
|
||||
config: {
|
||||
EXPERIMENTAL: {
|
||||
dht: true
|
||||
before('create the outer ring of connections', (done) => {
|
||||
const tasks = _times(5, () => (cb) => {
|
||||
createNode('/ip4/0.0.0.0/tcp/0', {
|
||||
config: {
|
||||
EXPERIMENTAL: {
|
||||
dht: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}, (err, node) => {
|
||||
}, (err, node) => {
|
||||
expect(err).to.not.exist()
|
||||
node.start((err) => cb(err, node))
|
||||
})
|
||||
})
|
||||
|
||||
parallel(tasks, (err, nodes) => {
|
||||
expect(err).to.not.exist()
|
||||
node.start((err) => cb(err, node))
|
||||
nodeA = nodes[0]
|
||||
nodeB = nodes[1]
|
||||
nodeC = nodes[2]
|
||||
nodeD = nodes[3]
|
||||
nodeE = nodes[4]
|
||||
|
||||
parallel([
|
||||
(cb) => nodeA.dial(nodeB.peerInfo, cb),
|
||||
(cb) => nodeB.dial(nodeC.peerInfo, cb),
|
||||
(cb) => nodeC.dial(nodeD.peerInfo, cb),
|
||||
(cb) => nodeD.dial(nodeE.peerInfo, cb),
|
||||
(cb) => nodeE.dial(nodeA.peerInfo, cb)
|
||||
], (err) => {
|
||||
expect(err).to.not.exist()
|
||||
// Give the kbucket time to fill in the dht
|
||||
setTimeout(done, 250)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
parallel(tasks, (err, nodes) => {
|
||||
expect(err).to.not.exist()
|
||||
nodeA = nodes[0]
|
||||
nodeB = nodes[1]
|
||||
nodeC = nodes[2]
|
||||
nodeD = nodes[3]
|
||||
nodeE = nodes[4]
|
||||
|
||||
after((done) => {
|
||||
parallel([
|
||||
(cb) => nodeA.dial(nodeB.peerInfo, cb),
|
||||
(cb) => nodeB.dial(nodeC.peerInfo, cb),
|
||||
(cb) => nodeC.dial(nodeD.peerInfo, cb),
|
||||
(cb) => nodeD.dial(nodeE.peerInfo, cb),
|
||||
(cb) => nodeE.dial(nodeA.peerInfo, cb)
|
||||
(cb) => nodeA.stop(cb),
|
||||
(cb) => nodeB.stop(cb),
|
||||
(cb) => nodeC.stop(cb),
|
||||
(cb) => nodeD.stop(cb),
|
||||
(cb) => nodeE.stop(cb)
|
||||
], done)
|
||||
})
|
||||
|
||||
it('should use the nodes dht', (done) => {
|
||||
const stub = sinon.stub(nodeA._dht, 'findPeer').callsFake(() => {
|
||||
stub.restore()
|
||||
done()
|
||||
})
|
||||
|
||||
nodeA.peerRouting.findPeer()
|
||||
})
|
||||
|
||||
describe('connected in an el ring', () => {
|
||||
it('should be able to find a peer we are not directly connected to', (done) => {
|
||||
parallel([
|
||||
(cb) => nodeA.dial(nodeC.peerInfo.id, cb),
|
||||
(cb) => nodeB.dial(nodeD.peerInfo.id, cb),
|
||||
(cb) => nodeC.dial(nodeE.peerInfo.id, cb)
|
||||
], (err) => {
|
||||
if (err) throw err
|
||||
expect(err).to.not.exist()
|
||||
nodeB.peerRouting.findPeer(nodeE.peerInfo.id, (err, peerInfo) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(nodeE.peerInfo.id.toB58String()).to.equal(peerInfo.id.toB58String())
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
after((done) => {
|
||||
parallel([
|
||||
(cb) => nodeA.stop(cb),
|
||||
(cb) => nodeB.stop(cb),
|
||||
(cb) => nodeC.stop(cb),
|
||||
(cb) => nodeD.stop(cb),
|
||||
(cb) => nodeE.stop(cb)
|
||||
], done)
|
||||
describe('via a delegate', () => {
|
||||
let nodeA
|
||||
let delegate
|
||||
|
||||
before((done) => {
|
||||
parallel([
|
||||
// Create the node using the delegate
|
||||
(cb) => {
|
||||
delegate = new DelegatedPeerRouter({
|
||||
host: 'ipfs.io',
|
||||
protocol: 'https',
|
||||
port: '443'
|
||||
})
|
||||
createNode('/ip4/0.0.0.0/tcp/0', {
|
||||
modules: {
|
||||
peerRouting: [ delegate ]
|
||||
}
|
||||
}, (err, node) => {
|
||||
expect(err).to.not.exist()
|
||||
nodeA = node
|
||||
nodeA.start(cb)
|
||||
})
|
||||
}
|
||||
], done)
|
||||
})
|
||||
|
||||
after((done) => nodeA.stop(done))
|
||||
afterEach(() => nock.cleanAll())
|
||||
|
||||
it('should use the delegate router to find peers', (done) => {
|
||||
const stub = sinon.stub(delegate, 'findPeer').callsFake(() => {
|
||||
stub.restore()
|
||||
done()
|
||||
})
|
||||
nodeA.peerRouting.findPeer()
|
||||
})
|
||||
|
||||
it('should be able to find a peer', (done) => {
|
||||
const peerKey = 'QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL'
|
||||
const mockApi = nock('https://ipfs.io')
|
||||
.post('/api/v0/dht/findpeer')
|
||||
.query({
|
||||
arg: peerKey,
|
||||
timeout: '30000ms',
|
||||
'stream-channels': true
|
||||
})
|
||||
.reply(200, `{"Extra":"","ID":"some other id","Responses":null,"Type":0}\n{"Extra":"","ID":"","Responses":[{"Addrs":["/ip4/127.0.0.1/tcp/4001"],"ID":"${peerKey}"}],"Type":2}\n`, [
|
||||
'Content-Type', 'application/json',
|
||||
'X-Chunked-Output', '1'
|
||||
])
|
||||
|
||||
nodeA.peerRouting.findPeer(peerKey, (err, peerInfo) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(peerInfo.id.toB58String()).to.equal(peerKey)
|
||||
expect(mockApi.isDone()).to.equal(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should error when a peer cannot be found', (done) => {
|
||||
const peerKey = 'key of a peer not on the network'
|
||||
const mockApi = nock('https://ipfs.io')
|
||||
.post('/api/v0/dht/findpeer')
|
||||
.query({
|
||||
arg: peerKey,
|
||||
timeout: '30000ms',
|
||||
'stream-channels': true
|
||||
})
|
||||
.reply(200, `{"Extra":"","ID":"some other id","Responses":null,"Type":6}\n{"Extra":"","ID":"yet another id","Responses":null,"Type":0}\n{"Extra":"routing:not found","ID":"","Responses":null,"Type":3}\n`, [
|
||||
'Content-Type', 'application/json',
|
||||
'X-Chunked-Output', '1'
|
||||
])
|
||||
|
||||
nodeA.peerRouting.findPeer(peerKey, (err, peerInfo) => {
|
||||
expect(err).to.exist()
|
||||
expect(peerInfo).to.not.exist()
|
||||
expect(mockApi.isDone()).to.equal(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle errors from the api', (done) => {
|
||||
const peerKey = 'key of a peer not on the network'
|
||||
const mockApi = nock('https://ipfs.io')
|
||||
.post('/api/v0/dht/findpeer')
|
||||
.query({
|
||||
arg: peerKey,
|
||||
timeout: '30000ms',
|
||||
'stream-channels': true
|
||||
})
|
||||
.reply(502)
|
||||
|
||||
nodeA.peerRouting.findPeer(peerKey, (err, peerInfo) => {
|
||||
expect(err).to.exist()
|
||||
expect(peerInfo).to.not.exist()
|
||||
expect(mockApi.isDone()).to.equal(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('el ring', () => {
|
||||
it('let kbucket get filled', (done) => {
|
||||
setTimeout(() => done(), 250)
|
||||
describe('via the dht and a delegate', () => {
|
||||
let nodeA
|
||||
let delegate
|
||||
|
||||
before((done) => {
|
||||
parallel([
|
||||
// Create the node using the delegate
|
||||
(cb) => {
|
||||
delegate = new DelegatedPeerRouter({
|
||||
host: 'ipfs.io',
|
||||
protocol: 'https',
|
||||
port: '443'
|
||||
})
|
||||
createNode('/ip4/0.0.0.0/tcp/0', {
|
||||
modules: {
|
||||
peerRouting: [ delegate ]
|
||||
},
|
||||
config: {
|
||||
EXPERIMENTAL: {
|
||||
dht: true
|
||||
}
|
||||
}
|
||||
}, (err, node) => {
|
||||
expect(err).to.not.exist()
|
||||
nodeA = node
|
||||
nodeA.start(cb)
|
||||
})
|
||||
}
|
||||
], done)
|
||||
})
|
||||
|
||||
it('nodeA.dial by Id to node C', (done) => {
|
||||
nodeA.dial(nodeC.peerInfo.id, (err) => {
|
||||
expect(err).to.not.exist()
|
||||
done()
|
||||
after((done) => nodeA.stop(done))
|
||||
|
||||
describe('findPeer', () => {
|
||||
it('should only use the dht if it finds the peer', (done) => {
|
||||
const results = [true]
|
||||
const dhtStub = sinon.stub(nodeA._dht, 'findPeer').callsArgWith(2, null, results)
|
||||
const delegateStub = sinon.stub(delegate, 'findPeer').throws(() => {
|
||||
return new Error('the delegate should not have been called')
|
||||
})
|
||||
|
||||
nodeA.peerRouting.findPeer('a peer id', (err, results) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(results).to.equal(results)
|
||||
expect(dhtStub.calledOnce).to.equal(true)
|
||||
expect(delegateStub.notCalled).to.equal(true)
|
||||
delegateStub.restore()
|
||||
dhtStub.restore()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('nodeB.dial by Id to node D', (done) => {
|
||||
nodeB.dial(nodeD.peerInfo.id, (err) => {
|
||||
expect(err).to.not.exist()
|
||||
done()
|
||||
})
|
||||
})
|
||||
it('should use the delegate if the dht fails to find the peer', (done) => {
|
||||
const results = [true]
|
||||
const dhtStub = sinon.stub(nodeA._dht, 'findPeer').callsArgWith(2, null, undefined)
|
||||
const delegateStub = sinon.stub(delegate, 'findPeer').callsArgWith(2, null, results)
|
||||
|
||||
it('nodeC.dial by Id to node E', (done) => {
|
||||
nodeC.dial(nodeE.peerInfo.id, (err) => {
|
||||
expect(err).to.not.exist()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('nodeB.peerRouting.findPeer(nodeE.peerInfo.id)', (done) => {
|
||||
nodeB.peerRouting.findPeer(nodeE.peerInfo.id, (err, peerInfo) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(nodeE.peerInfo.id.toB58String()).to.equal(peerInfo.id.toB58String())
|
||||
done()
|
||||
nodeA.peerRouting.findPeer('a peer id', (err, results) => {
|
||||
expect(err).to.not.exist()
|
||||
expect(results).to.deep.equal(results)
|
||||
expect(dhtStub.calledOnce).to.equal(true)
|
||||
expect(delegateStub.calledOnce).to.equal(true)
|
||||
delegateStub.restore()
|
||||
dhtStub.restore()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user