js-libp2p/test/identify/push.spec.ts
Alex Potsides 633d4a9740
fix: remove ipfs dependency and upgrade multiaddr (#1387)
- Upgrades @multiformats/multiaddr to 11.0.0
- Removes ipfs-http-client and delegate router dependencies
- Test delegation using interface stubs instead of implementations
2022-09-21 14:41:20 +01:00

303 lines
11 KiB
TypeScript

/* eslint-env mocha */
import { expect } from 'aegir/chai'
import sinon from 'sinon'
import { multiaddr } from '@multiformats/multiaddr'
import { IdentifyService, IdentifyServiceInit } from '../../src/identify/index.js'
import Peers from '../fixtures/peers.js'
import { PersistentPeerStore } from '@libp2p/peer-store'
import { DefaultAddressManager } from '../../src/address-manager/index.js'
import { MemoryDatastore } from 'datastore-core/memory'
import drain from 'it-drain'
import { pipe } from 'it-pipe'
import { mockConnectionGater, mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-mocks'
import { createFromJSON } from '@libp2p/peer-id-factory'
import { Components } from '@libp2p/components'
import { PeerRecordUpdater } from '../../src/peer-record-updater.js'
import {
MULTICODEC_IDENTIFY,
MULTICODEC_IDENTIFY_PUSH
} from '../../src/identify/consts.js'
import { DefaultConnectionManager } from '../../src/connection-manager/index.js'
import { DefaultTransportManager } from '../../src/transport-manager.js'
import { CustomEvent } from '@libp2p/interfaces/events'
import delay from 'delay'
import { pEvent } from 'p-event'
import { start, stop } from '@libp2p/interfaces/startable'
const listenMaddrs = [multiaddr('/ip4/127.0.0.1/tcp/15002/ws')]
const defaultInit: IdentifyServiceInit = {
protocolPrefix: 'ipfs',
host: {
agentVersion: 'v1.0.0'
},
maxInboundStreams: 1,
maxOutboundStreams: 1,
maxPushIncomingStreams: 1,
maxPushOutgoingStreams: 1,
timeout: 1000
}
const protocols = [MULTICODEC_IDENTIFY, MULTICODEC_IDENTIFY_PUSH]
async function createComponents (index: number) {
const peerId = await createFromJSON(Peers[index])
const components = new Components({
peerId,
datastore: new MemoryDatastore(),
registrar: mockRegistrar(),
upgrader: mockUpgrader(),
connectionGater: mockConnectionGater(),
peerStore: new PersistentPeerStore(),
connectionManager: new DefaultConnectionManager({
minConnections: 50,
maxConnections: 1000,
autoDialInterval: 1000,
inboundUpgradeTimeout: 1000
})
})
components.setAddressManager(new DefaultAddressManager(components, {
announce: listenMaddrs.map(ma => ma.toString())
}))
const transportManager = new DefaultTransportManager(components)
components.setTransportManager(transportManager)
await components.getPeerStore().protoBook.set(peerId, protocols)
return components
}
describe('identify (push)', () => {
let localComponents: Components
let remoteComponents: Components
let localPeerRecordUpdater: PeerRecordUpdater
beforeEach(async () => {
localComponents = await createComponents(0)
remoteComponents = await createComponents(1)
localPeerRecordUpdater = new PeerRecordUpdater(localComponents)
await Promise.all([
start(localComponents),
start(remoteComponents)
])
})
afterEach(async () => {
sinon.restore()
await Promise.all([
stop(localComponents),
stop(remoteComponents)
])
})
it('should be able to push identify updates to another peer', async () => {
const localIdentify = new IdentifyService(localComponents, defaultInit)
const remoteIdentify = new IdentifyService(remoteComponents, defaultInit)
await start(localIdentify)
await start(remoteIdentify)
const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents)
// ensure connections are registered by connection manager
localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', {
detail: localToRemote
}))
remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', {
detail: remoteToLocal
}))
// identify both ways
await localIdentify.identify(localToRemote)
await remoteIdentify.identify(remoteToLocal)
const updatedProtocol = '/special-new-protocol/1.0.0'
const updatedAddress = multiaddr('/ip4/127.0.0.1/tcp/48322')
// should have protocols but not our new one
const identifiedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId())
expect(identifiedProtocols).to.not.be.empty()
expect(identifiedProtocols).to.not.include(updatedProtocol)
// should have addresses but not our new one
const identifiedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId())
expect(identifiedAddresses).to.not.be.empty()
expect(identifiedAddresses.map(a => a.multiaddr.toString())).to.not.include(updatedAddress.toString())
// update local data - change event will trigger push
await localComponents.getPeerStore().protoBook.add(localComponents.getPeerId(), [updatedProtocol])
await localComponents.getPeerStore().addressBook.add(localComponents.getPeerId(), [updatedAddress])
// needed to update the peer record and send our supported addresses
const addressManager = localComponents.getAddressManager()
addressManager.getAddresses = () => {
return [updatedAddress]
}
// ensure sequence number of peer record we are about to create is different
await delay(1000)
// make sure we have a peer record to send
await localPeerRecordUpdater.update()
// wait for the remote peer store to notice the changes
const eventPromise = pEvent(remoteComponents.getPeerStore(), 'change:multiaddrs')
// push updated peer record to connections
await localIdentify.pushToPeerStore()
await eventPromise
// should have new protocol
const updatedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId())
expect(updatedProtocols).to.not.be.empty()
expect(updatedProtocols).to.include(updatedProtocol)
// should have new address
const updatedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId())
expect(updatedAddresses.map(a => {
return {
multiaddr: a.multiaddr.toString(),
isCertified: a.isCertified
}
})).to.deep.equal([{
multiaddr: updatedAddress.toString(),
isCertified: true
}])
await stop(localIdentify)
await stop(remoteIdentify)
})
it('should time out during push identify', async () => {
let streamEnded = false
const localIdentify = new IdentifyService(localComponents, {
...defaultInit,
timeout: 10
})
const remoteIdentify = new IdentifyService(remoteComponents, defaultInit)
await start(localIdentify)
await start(remoteIdentify)
// simulate connection between nodes
const [localToRemote] = connectionPair(localComponents, remoteComponents)
// replace existing handler with a really slow one
await remoteComponents.getRegistrar().unhandle(MULTICODEC_IDENTIFY_PUSH)
await remoteComponents.getRegistrar().handle(MULTICODEC_IDENTIFY_PUSH, ({ stream }) => {
void pipe(
stream,
async function * (source) {
// ignore the sent data
await drain(source)
// longer than the timeout
await delay(1000)
// the delay should have caused the local push to time out so this should
// occur after the local push method invocation has completed
streamEnded = true
yield new Uint8Array()
},
stream
)
})
const newStreamSpy = sinon.spy(localToRemote, 'newStream')
// push updated peer record to remote
await localIdentify.push([localToRemote])
// should have closed stream
expect(newStreamSpy).to.have.property('callCount', 1)
const stream = await newStreamSpy.getCall(0).returnValue
expect(stream).to.have.nested.property('stat.timeline.close')
// method should have returned before the remote handler completes as we timed
// out so we ignore the return value
expect(streamEnded).to.be.false()
})
// LEGACY
it('should be able to push identify updates to another peer with no certified peer records support', async () => {
const localIdentify = new IdentifyService(localComponents, defaultInit)
const remoteIdentify = new IdentifyService(remoteComponents, defaultInit)
await start(localIdentify)
await start(remoteIdentify)
const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents)
// ensure connections are registered by connection manager
localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', {
detail: localToRemote
}))
remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', {
detail: remoteToLocal
}))
// identify both ways
await localIdentify.identify(localToRemote)
await remoteIdentify.identify(remoteToLocal)
const updatedProtocol = '/special-new-protocol/1.0.0'
const updatedAddress = multiaddr('/ip4/127.0.0.1/tcp/48322')
// should have protocols but not our new one
const identifiedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId())
expect(identifiedProtocols).to.not.be.empty()
expect(identifiedProtocols).to.not.include(updatedProtocol)
// should have addresses but not our new one
const identifiedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId())
expect(identifiedAddresses).to.not.be.empty()
expect(identifiedAddresses.map(a => a.multiaddr.toString())).to.not.include(updatedAddress.toString())
// update local data - change event will trigger push
await localComponents.getPeerStore().protoBook.add(localComponents.getPeerId(), [updatedProtocol])
await localComponents.getPeerStore().addressBook.add(localComponents.getPeerId(), [updatedAddress])
// needed to send our supported addresses
const addressManager = localComponents.getAddressManager()
addressManager.getAddresses = () => {
return [updatedAddress]
}
// wait until remote peer store notices protocol list update
const waitForUpdate = pEvent(remoteComponents.getPeerStore(), 'change:protocols')
await localIdentify.pushToPeerStore()
await waitForUpdate
// should have new protocol
const updatedProtocols = await remoteComponents.getPeerStore().protoBook.get(localComponents.getPeerId())
expect(updatedProtocols).to.not.be.empty()
expect(updatedProtocols).to.include(updatedProtocol)
// should have new address
const updatedAddresses = await remoteComponents.getPeerStore().addressBook.get(localComponents.getPeerId())
expect(updatedAddresses.map(a => {
return {
multiaddr: a.multiaddr.toString(),
isCertified: a.isCertified
}
})).to.deep.equal([{
multiaddr: updatedAddress.toString(),
isCertified: false
}])
await stop(localIdentify)
await stop(remoteIdentify)
})
})