mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-07-02 18:31:35 +00:00
refactor: circuit relay to async (#477)
* refactor: add dialing over relay support * chore: fix lint * fix: dont clear listeners on close * fix: if dial errors already have codes, just rethrow them * fix: clear the registrar when libp2p stops * fix: improve connection maintenance with circuit * chore: correct feedback * test: use chai as promised * test(fix): reset multiaddrs on dial test
This commit is contained in:
@ -3,6 +3,7 @@
|
||||
|
||||
const chai = require('chai')
|
||||
chai.use(require('dirty-chai'))
|
||||
chai.use(require('chai-as-promised'))
|
||||
const { expect } = chai
|
||||
const sinon = require('sinon')
|
||||
const Transport = require('libp2p-tcp')
|
||||
@ -77,14 +78,9 @@ describe('Dialing (direct, TCP)', () => {
|
||||
it('should fail to connect to an unsupported multiaddr', async () => {
|
||||
const dialer = new Dialer({ transportManager: localTM })
|
||||
|
||||
try {
|
||||
await dialer.connectToMultiaddr(unsupportedAddr)
|
||||
} catch (err) {
|
||||
expect(err).to.satisfy((err) => err.code === ErrorCodes.ERR_TRANSPORT_UNAVAILABLE)
|
||||
return
|
||||
}
|
||||
|
||||
expect.fail('Dial should have failed')
|
||||
await expect(dialer.connectToMultiaddr(unsupportedAddr))
|
||||
.to.eventually.be.rejected()
|
||||
.and.to.have.property('code', ErrorCodes.ERR_TRANSPORT_UNAVAILABLE)
|
||||
})
|
||||
|
||||
it('should be able to connect to a given peer info', async () => {
|
||||
@ -121,14 +117,9 @@ describe('Dialing (direct, TCP)', () => {
|
||||
const peerInfo = new PeerInfo(peerId)
|
||||
peerInfo.multiaddrs.add(unsupportedAddr)
|
||||
|
||||
try {
|
||||
await dialer.connectToPeer(peerInfo)
|
||||
} catch (err) {
|
||||
expect(err).to.satisfy((err) => err.code === ErrorCodes.ERR_CONNECTION_FAILED)
|
||||
return
|
||||
}
|
||||
|
||||
expect.fail('Dial should have failed')
|
||||
await expect(dialer.connectToPeer(peerInfo))
|
||||
.to.eventually.be.rejected()
|
||||
.and.to.have.property('code', ErrorCodes.ERR_CONNECTION_FAILED)
|
||||
})
|
||||
|
||||
it('should abort dials on queue task timeout', async () => {
|
||||
@ -144,14 +135,9 @@ describe('Dialing (direct, TCP)', () => {
|
||||
expect(options.signal.aborted).to.equal(true)
|
||||
})
|
||||
|
||||
try {
|
||||
await dialer.connectToMultiaddr(remoteAddr)
|
||||
} catch (err) {
|
||||
expect(err).to.satisfy((err) => err.code === ErrorCodes.ERR_TIMEOUT)
|
||||
return
|
||||
}
|
||||
|
||||
expect.fail('Dial should have failed')
|
||||
await expect(dialer.connectToMultiaddr(remoteAddr))
|
||||
.to.eventually.be.rejected()
|
||||
.and.to.have.property('code', ErrorCodes.ERR_TIMEOUT)
|
||||
})
|
||||
|
||||
it('should dial to the max concurrency', async () => {
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
const chai = require('chai')
|
||||
chai.use(require('dirty-chai'))
|
||||
chai.use(require('chai-as-promised'))
|
||||
const { expect } = chai
|
||||
const sinon = require('sinon')
|
||||
const pDefer = require('p-defer')
|
||||
@ -67,14 +68,9 @@ describe('Dialing (direct, WebSockets)', () => {
|
||||
it('should fail to connect to an unsupported multiaddr', async () => {
|
||||
const dialer = new Dialer({ transportManager: localTM })
|
||||
|
||||
try {
|
||||
await dialer.connectToMultiaddr(unsupportedAddr)
|
||||
} catch (err) {
|
||||
expect(err).to.satisfy((err) => err.code === ErrorCodes.ERR_TRANSPORT_DIAL_FAILED)
|
||||
return
|
||||
}
|
||||
|
||||
expect.fail('Dial should have failed')
|
||||
await expect(dialer.connectToMultiaddr(unsupportedAddr))
|
||||
.to.eventually.be.rejected()
|
||||
.and.to.have.property('code', ErrorCodes.ERR_TRANSPORT_DIAL_FAILED)
|
||||
})
|
||||
|
||||
it('should be able to connect to a given peer', async () => {
|
||||
@ -94,14 +90,9 @@ describe('Dialing (direct, WebSockets)', () => {
|
||||
const peerInfo = new PeerInfo(peerId)
|
||||
peerInfo.multiaddrs.add(unsupportedAddr)
|
||||
|
||||
try {
|
||||
await dialer.connectToPeer(peerInfo)
|
||||
} catch (err) {
|
||||
expect(err).to.satisfy((err) => err.code === ErrorCodes.ERR_CONNECTION_FAILED)
|
||||
return
|
||||
}
|
||||
|
||||
expect.fail('Dial should have failed')
|
||||
await expect(dialer.connectToPeer(peerInfo))
|
||||
.to.eventually.be.rejected()
|
||||
.and.to.have.property('code', ErrorCodes.ERR_CONNECTION_FAILED)
|
||||
})
|
||||
|
||||
it('should abort dials on queue task timeout', async () => {
|
||||
@ -117,14 +108,9 @@ describe('Dialing (direct, WebSockets)', () => {
|
||||
expect(options.signal.aborted).to.equal(true)
|
||||
})
|
||||
|
||||
try {
|
||||
await dialer.connectToMultiaddr(remoteAddr)
|
||||
} catch (err) {
|
||||
expect(err).to.satisfy((err) => err.code === ErrorCodes.ERR_TIMEOUT)
|
||||
return
|
||||
}
|
||||
|
||||
expect.fail('Dial should have failed')
|
||||
await expect(dialer.connectToMultiaddr(remoteAddr))
|
||||
.to.eventually.be.rejected()
|
||||
.and.to.have.property('code', ErrorCodes.ERR_TIMEOUT)
|
||||
})
|
||||
|
||||
it('should dial to the max concurrency', async () => {
|
||||
|
162
test/dialing/relay.node.js
Normal file
162
test/dialing/relay.node.js
Normal file
@ -0,0 +1,162 @@
|
||||
'use strict'
|
||||
/* eslint-env mocha */
|
||||
|
||||
const chai = require('chai')
|
||||
chai.use(require('dirty-chai'))
|
||||
chai.use(require('chai-as-promised'))
|
||||
const { expect } = chai
|
||||
const sinon = require('sinon')
|
||||
|
||||
const multiaddr = require('multiaddr')
|
||||
const { collect } = require('streaming-iterables')
|
||||
const pipe = require('it-pipe')
|
||||
const { createPeerInfoFromFixture } = require('../utils/creators/peer')
|
||||
const baseOptions = require('../utils/base-options')
|
||||
const Libp2p = require('../../src')
|
||||
const { codes: Errors } = require('../../src/errors')
|
||||
|
||||
describe('Dialing (via relay, TCP)', () => {
|
||||
let srcLibp2p
|
||||
let relayLibp2p
|
||||
let dstLibp2p
|
||||
|
||||
before(async () => {
|
||||
const peerInfos = await createPeerInfoFromFixture(3)
|
||||
// Create 3 nodes, and turn HOP on for the relay
|
||||
;[srcLibp2p, relayLibp2p, dstLibp2p] = peerInfos.map((peerInfo, index) => {
|
||||
const opts = baseOptions
|
||||
index === 1 && (opts.config.relay.hop.enabled = true)
|
||||
return new Libp2p({
|
||||
...opts,
|
||||
peerInfo
|
||||
})
|
||||
})
|
||||
|
||||
dstLibp2p.handle('/echo/1.0.0', ({ stream }) => pipe(stream, stream))
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
// Start each node
|
||||
return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(libp2p => {
|
||||
// Reset multiaddrs and start
|
||||
libp2p.peerInfo.multiaddrs.clear()
|
||||
libp2p.peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
|
||||
libp2p.start()
|
||||
}))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
// Stop each node
|
||||
return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(libp2p => libp2p.stop()))
|
||||
})
|
||||
|
||||
it('should be able to connect to a peer over a relay with active connections', async () => {
|
||||
const relayAddr = relayLibp2p.transportManager.getAddrs()[0]
|
||||
const relayIdString = relayLibp2p.peerInfo.id.toString()
|
||||
|
||||
const dialAddr = relayAddr
|
||||
.encapsulate(`/p2p/${relayIdString}`)
|
||||
.encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerInfo.id.toString()}`)
|
||||
|
||||
const tcpAddrs = dstLibp2p.transportManager.getAddrs()
|
||||
await dstLibp2p.transportManager.listen([multiaddr(`/p2p-circuit${relayAddr}/p2p/${relayIdString}`)])
|
||||
expect(dstLibp2p.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')])
|
||||
|
||||
const connection = await srcLibp2p.dial(dialAddr)
|
||||
expect(connection).to.exist()
|
||||
expect(connection.remotePeer.toBytes()).to.eql(dstLibp2p.peerInfo.id.toBytes())
|
||||
expect(connection.localPeer.toBytes()).to.eql(srcLibp2p.peerInfo.id.toBytes())
|
||||
expect(connection.remoteAddr).to.eql(dialAddr)
|
||||
expect(connection.localAddr).to.eql(
|
||||
relayAddr // the relay address
|
||||
.encapsulate(`/p2p/${relayIdString}`) // with its peer id
|
||||
.encapsulate('/p2p-circuit') // the local peer is connected over the relay
|
||||
.encapsulate(`/p2p/${srcLibp2p.peerInfo.id.toB58String()}`) // and the local peer id
|
||||
)
|
||||
|
||||
const { stream: echoStream } = await connection.newStream('/echo/1.0.0')
|
||||
const input = Buffer.from('hello')
|
||||
const [output] = await pipe(
|
||||
[input],
|
||||
echoStream,
|
||||
collect
|
||||
)
|
||||
expect(output.slice()).to.eql(input)
|
||||
})
|
||||
|
||||
it('should fail to connect to a peer over a relay with inactive connections', async () => {
|
||||
const relayAddr = relayLibp2p.transportManager.getAddrs()[0]
|
||||
const relayIdString = relayLibp2p.peerInfo.id.toString()
|
||||
|
||||
const dialAddr = relayAddr
|
||||
.encapsulate(`/p2p/${relayIdString}`)
|
||||
.encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerInfo.id.toString()}`)
|
||||
|
||||
await expect(srcLibp2p.dial(dialAddr))
|
||||
.to.eventually.be.rejected()
|
||||
.and.to.have.property('code', Errors.ERR_HOP_REQUEST_FAILED)
|
||||
})
|
||||
|
||||
it('should not stay connected to a relay when not already connected and HOP fails', async () => {
|
||||
const relayAddr = relayLibp2p.transportManager.getAddrs()[0]
|
||||
const relayIdString = relayLibp2p.peerInfo.id.toString()
|
||||
|
||||
const dialAddr = relayAddr
|
||||
.encapsulate(`/p2p/${relayIdString}`)
|
||||
.encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerInfo.id.toString()}`)
|
||||
|
||||
await expect(srcLibp2p.dial(dialAddr))
|
||||
.to.eventually.be.rejected()
|
||||
.and.to.have.property('code', Errors.ERR_HOP_REQUEST_FAILED)
|
||||
|
||||
// We should not be connected to the relay, because we weren't before the dial
|
||||
const srcToRelayConn = srcLibp2p.registrar.getConnection(relayLibp2p.peerInfo)
|
||||
expect(srcToRelayConn).to.not.exist()
|
||||
})
|
||||
|
||||
it('dialer should stay connected to an already connected relay on hop failure', async () => {
|
||||
const relayAddr = relayLibp2p.transportManager.getAddrs()[0]
|
||||
const relayIdString = relayLibp2p.peerInfo.id.toString()
|
||||
|
||||
const dialAddr = relayAddr
|
||||
.encapsulate(`/p2p/${relayIdString}`)
|
||||
.encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerInfo.id.toString()}`)
|
||||
|
||||
await srcLibp2p.dial(relayAddr)
|
||||
|
||||
await expect(srcLibp2p.dial(dialAddr))
|
||||
.to.eventually.be.rejected()
|
||||
.and.to.have.property('code', Errors.ERR_HOP_REQUEST_FAILED)
|
||||
|
||||
const srcToRelayConn = srcLibp2p.registrar.getConnection(relayLibp2p.peerInfo)
|
||||
expect(srcToRelayConn).to.exist()
|
||||
expect(srcToRelayConn.stat.status).to.equal('open')
|
||||
})
|
||||
|
||||
it('destination peer should stay connected to an already connected relay on hop failure', async () => {
|
||||
const relayAddr = relayLibp2p.transportManager.getAddrs()[0]
|
||||
const relayIdString = relayLibp2p.peerInfo.id.toString()
|
||||
|
||||
const dialAddr = relayAddr
|
||||
.encapsulate(`/p2p/${relayIdString}`)
|
||||
.encapsulate(`/p2p-circuit/p2p/${dstLibp2p.peerInfo.id.toString()}`)
|
||||
|
||||
// Connect the destination peer and the relay
|
||||
const tcpAddrs = dstLibp2p.transportManager.getAddrs()
|
||||
await dstLibp2p.transportManager.listen([multiaddr(`/p2p-circuit${relayAddr}/p2p/${relayIdString}`)])
|
||||
expect(dstLibp2p.transportManager.getAddrs()).to.have.deep.members([...tcpAddrs, dialAddr.decapsulate('p2p')])
|
||||
|
||||
// Tamper with the our multiaddrs for the circuit message
|
||||
sinon.stub(srcLibp2p.peerInfo.multiaddrs, 'toArray').returns([{
|
||||
buffer: Buffer.from('an invalid multiaddr')
|
||||
}])
|
||||
|
||||
await expect(srcLibp2p.dial(dialAddr))
|
||||
.to.eventually.be.rejected()
|
||||
.and.to.have.property('code', Errors.ERR_HOP_REQUEST_FAILED)
|
||||
|
||||
const dstToRelayConn = dstLibp2p.registrar.getConnection(relayLibp2p.peerInfo)
|
||||
expect(dstToRelayConn).to.exist()
|
||||
expect(dstToRelayConn.stat.status).to.equal('open')
|
||||
})
|
||||
})
|
Reference in New Issue
Block a user