mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-06-15 18:21:22 +00:00
feat: discover and connect to closest peers (#798)
This commit is contained in:
committed by
Vasco Santos
parent
4ebcdb085c
commit
baedf3fe5a
@ -4,12 +4,17 @@
|
||||
const { expect } = require('aegir/utils/chai')
|
||||
const nock = require('nock')
|
||||
const sinon = require('sinon')
|
||||
const intoStream = require('into-stream')
|
||||
|
||||
const delay = require('delay')
|
||||
const pDefer = require('p-defer')
|
||||
const pWaitFor = require('p-wait-for')
|
||||
const mergeOptions = require('merge-options')
|
||||
|
||||
const ipfsHttpClient = require('ipfs-http-client')
|
||||
const DelegatedPeerRouter = require('libp2p-delegated-peer-routing')
|
||||
const multiaddr = require('multiaddr')
|
||||
const PeerId = require('peer-id')
|
||||
|
||||
const peerUtils = require('../utils/creators/peer')
|
||||
const { baseOptions, routingOptions } = require('./utils')
|
||||
@ -29,6 +34,16 @@ describe('peer-routing', () => {
|
||||
.to.eventually.be.rejected()
|
||||
.and.to.have.property('code', 'NO_ROUTERS_AVAILABLE')
|
||||
})
|
||||
|
||||
it('.getClosestPeers should return an error', async () => {
|
||||
try {
|
||||
for await (const _ of node.peerRouting.getClosestPeers('a cid')) { } // eslint-disable-line
|
||||
throw new Error('.getClosestPeers should return an error')
|
||||
} catch (err) {
|
||||
expect(err).to.exist()
|
||||
expect(err.code).to.equal('NO_ROUTERS_AVAILABLE')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('via dht router', () => {
|
||||
@ -64,6 +79,19 @@ describe('peer-routing', () => {
|
||||
nodes[0].peerRouting.findPeer()
|
||||
return deferred.promise
|
||||
})
|
||||
|
||||
it('should use the nodes dht to get the closest peers', async () => {
|
||||
const deferred = pDefer()
|
||||
|
||||
sinon.stub(nodes[0]._dht, 'getClosestPeers').callsFake(function * () {
|
||||
deferred.resolve()
|
||||
yield
|
||||
})
|
||||
|
||||
await nodes[0].peerRouting.getClosestPeers().next()
|
||||
|
||||
return deferred.promise
|
||||
})
|
||||
})
|
||||
|
||||
describe('via delegate router', () => {
|
||||
@ -110,6 +138,19 @@ describe('peer-routing', () => {
|
||||
return deferred.promise
|
||||
})
|
||||
|
||||
it('should use the delegate router to get the closest peers', async () => {
|
||||
const deferred = pDefer()
|
||||
|
||||
sinon.stub(delegate, 'getClosestPeers').callsFake(function * () {
|
||||
deferred.resolve()
|
||||
yield
|
||||
})
|
||||
|
||||
await node.peerRouting.getClosestPeers().next()
|
||||
|
||||
return deferred.promise
|
||||
})
|
||||
|
||||
it('should be able to find a peer', async () => {
|
||||
const peerKey = 'QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL'
|
||||
const mockApi = nock('http://0.0.0.0:60197')
|
||||
@ -154,6 +195,60 @@ describe('peer-routing', () => {
|
||||
|
||||
expect(mockApi.isDone()).to.equal(true)
|
||||
})
|
||||
|
||||
it('should be able to get the closest peers', async () => {
|
||||
const peerId = await PeerId.create({ keyType: 'ed25519' })
|
||||
|
||||
const closest1 = '12D3KooWLewYMMdGWAtuX852n4rgCWkK7EBn4CWbwwBzhsVoKxk3'
|
||||
const closest2 = '12D3KooWDtoQbpKhtnWddfj72QmpFvvLDTsBLTFkjvgQm6cde2AK'
|
||||
|
||||
const mockApi = nock('http://0.0.0.0:60197')
|
||||
.post('/api/v0/dht/query')
|
||||
.query(true)
|
||||
.reply(200,
|
||||
() => intoStream([
|
||||
`{"extra":"","id":"${closest1}","responses":[{"ID":"${closest1}","Addrs":["/ip4/127.0.0.1/tcp/63930","/ip4/127.0.0.1/tcp/63930"]}],"type":1}\n`,
|
||||
`{"extra":"","id":"${closest2}","responses":[{"ID":"${closest2}","Addrs":["/ip4/127.0.0.1/tcp/63506","/ip4/127.0.0.1/tcp/63506"]}],"type":1}\n`,
|
||||
`{"Extra":"","ID":"${closest2}","Responses":[],"Type":2}\n`,
|
||||
`{"Extra":"","ID":"${closest1}","Responses":[],"Type":2}\n`
|
||||
]),
|
||||
[
|
||||
'Content-Type', 'application/json',
|
||||
'X-Chunked-Output', '1'
|
||||
])
|
||||
|
||||
const closestPeers = []
|
||||
for await (const peer of node.peerRouting.getClosestPeers(peerId.id, { timeout: 1000 })) {
|
||||
closestPeers.push(peer)
|
||||
}
|
||||
|
||||
expect(closestPeers).to.have.length(2)
|
||||
expect(closestPeers[0].id.toB58String()).to.equal(closest2)
|
||||
expect(closestPeers[0].multiaddrs).to.have.lengthOf(2)
|
||||
expect(closestPeers[1].id.toB58String()).to.equal(closest1)
|
||||
expect(closestPeers[1].multiaddrs).to.have.lengthOf(2)
|
||||
expect(mockApi.isDone()).to.equal(true)
|
||||
})
|
||||
|
||||
it('should handle errors when getting the closest peers', async () => {
|
||||
const peerId = await PeerId.create({ keyType: 'ed25519' })
|
||||
|
||||
const mockApi = nock('http://0.0.0.0:60197')
|
||||
.post('/api/v0/dht/query')
|
||||
.query(true)
|
||||
.reply(502, 'Bad Gateway', [
|
||||
'X-Chunked-Output', '1'
|
||||
])
|
||||
|
||||
try {
|
||||
for await (const _ of node.peerRouting.getClosestPeers(peerId.id)) { } // eslint-disable-line
|
||||
throw new Error('should handle errors when getting the closest peers')
|
||||
} catch (err) {
|
||||
expect(err).to.exist()
|
||||
}
|
||||
|
||||
expect(mockApi.isDone()).to.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('via dht and delegate routers', () => {
|
||||
@ -208,5 +303,148 @@ describe('peer-routing', () => {
|
||||
const peer = await node.peerRouting.findPeer('a peer id')
|
||||
expect(peer).to.eql(results)
|
||||
})
|
||||
|
||||
it('should only use the dht if it gets the closest peers', async () => {
|
||||
const results = [true]
|
||||
|
||||
sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () {
|
||||
yield results[0]
|
||||
})
|
||||
|
||||
sinon.stub(delegate, 'getClosestPeers').callsFake(function * () { // eslint-disable-line require-yield
|
||||
throw new Error('the delegate should not have been called')
|
||||
})
|
||||
|
||||
const closest = []
|
||||
for await (const peer of node.peerRouting.getClosestPeers('a cid')) {
|
||||
closest.push(peer)
|
||||
}
|
||||
|
||||
expect(closest).to.have.length.above(0)
|
||||
expect(closest).to.eql(results)
|
||||
})
|
||||
|
||||
it('should use the delegate if the dht fails to get the closest peer', async () => {
|
||||
const results = [true]
|
||||
|
||||
sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () { })
|
||||
|
||||
sinon.stub(delegate, 'getClosestPeers').callsFake(function * () {
|
||||
yield results[0]
|
||||
})
|
||||
|
||||
const closest = []
|
||||
for await (const peer of node.peerRouting.getClosestPeers('a cid')) {
|
||||
closest.push(peer)
|
||||
}
|
||||
|
||||
expect(closest).to.have.length.above(0)
|
||||
expect(closest).to.eql(results)
|
||||
})
|
||||
})
|
||||
|
||||
describe('peer routing refresh manager service', () => {
|
||||
let node
|
||||
let peerIds
|
||||
|
||||
before(async () => {
|
||||
peerIds = await peerUtils.createPeerId({ number: 2 })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
|
||||
return node && node.stop()
|
||||
})
|
||||
|
||||
it('should be enabled and start by default', async () => {
|
||||
const results = [
|
||||
{ id: peerIds[0], multiaddrs: [multiaddr('/ip4/30.0.0.1/tcp/2000')] },
|
||||
{ id: peerIds[1], multiaddrs: [multiaddr('/ip4/32.0.0.1/tcp/2000')] }
|
||||
]
|
||||
|
||||
;[node] = await peerUtils.createPeer({
|
||||
config: mergeOptions(routingOptions, {
|
||||
peerRouting: {
|
||||
refreshManager: {
|
||||
bootDelay: 100
|
||||
}
|
||||
}
|
||||
}),
|
||||
started: false
|
||||
})
|
||||
|
||||
sinon.spy(node.peerStore.addressBook, 'add')
|
||||
sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () {
|
||||
yield results[0]
|
||||
yield results[1]
|
||||
})
|
||||
|
||||
await node.start()
|
||||
|
||||
await pWaitFor(() => node._dht.getClosestPeers.callCount === 1)
|
||||
await pWaitFor(() => node.peerStore.addressBook.add.callCount === results.length)
|
||||
|
||||
const call0 = node.peerStore.addressBook.add.getCall(0)
|
||||
expect(call0.args[0].equals(results[0].id))
|
||||
call0.args[1].forEach((m, index) => {
|
||||
expect(m.equals(results[0].multiaddrs[index]))
|
||||
})
|
||||
|
||||
const call1 = node.peerStore.addressBook.add.getCall(1)
|
||||
expect(call1.args[0].equals(results[1].id))
|
||||
call0.args[1].forEach((m, index) => {
|
||||
expect(m.equals(results[1].multiaddrs[index]))
|
||||
})
|
||||
})
|
||||
|
||||
it('should support being disabled', async () => {
|
||||
[node] = await peerUtils.createPeer({
|
||||
config: mergeOptions(routingOptions, {
|
||||
peerRouting: {
|
||||
refreshManager: {
|
||||
bootDelay: 100,
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}),
|
||||
started: false
|
||||
})
|
||||
|
||||
sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () {
|
||||
yield
|
||||
throw new Error('should not be called')
|
||||
})
|
||||
|
||||
await node.start()
|
||||
await delay(100)
|
||||
|
||||
expect(node._dht.getClosestPeers.callCount === 0)
|
||||
})
|
||||
|
||||
it('should start and run recurrently on interval', async () => {
|
||||
[node] = await peerUtils.createPeer({
|
||||
config: mergeOptions(routingOptions, {
|
||||
peerRouting: {
|
||||
refreshManager: {
|
||||
interval: 500,
|
||||
bootDelay: 200
|
||||
}
|
||||
}
|
||||
}),
|
||||
started: false
|
||||
})
|
||||
|
||||
sinon.stub(node._dht, 'getClosestPeers').callsFake(function * () {
|
||||
yield { id: peerIds[0], multiaddrs: [multiaddr('/ip4/30.0.0.1/tcp/2000')] }
|
||||
})
|
||||
|
||||
await node.start()
|
||||
|
||||
await delay(300)
|
||||
expect(node._dht.getClosestPeers.callCount).to.eql(1)
|
||||
await delay(500)
|
||||
expect(node._dht.getClosestPeers.callCount).to.eql(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user