/* eslint-env mocha */ import { expect } from 'aegir/chai' import sinon from 'sinon' import { FetchService, FetchServiceInit } from '../../src/fetch/index.js' import Peers from '../fixtures/peers.js' import { mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-mocks' import { createFromJSON } from '@libp2p/peer-id-factory' import { Components } from '@libp2p/components' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' import { start, stop } from '@libp2p/interfaces/startable' import { CustomEvent } from '@libp2p/interfaces/events' import { TimeoutController } from 'timeout-abort-controller' import delay from 'delay' import { pipe } from 'it-pipe' import { PersistentPeerStore } from '@libp2p/peer-store' import { MemoryDatastore } from 'datastore-core' const defaultInit: FetchServiceInit = { protocolPrefix: 'ipfs', maxInboundStreams: 1, maxOutboundStreams: 1, timeout: 1000 } async function createComponents (index: number) { const peerId = await createFromJSON(Peers[index]) const components = new Components({ peerId, registrar: mockRegistrar(), upgrader: mockUpgrader(), peerStore: new PersistentPeerStore(), datastore: new MemoryDatastore(), connectionManager: new DefaultConnectionManager({ minConnections: 50, maxConnections: 1000, autoDialInterval: 1000, inboundUpgradeTimeout: 1000 }) }) return components } describe('fetch', () => { let localComponents: Components let remoteComponents: Components beforeEach(async () => { localComponents = await createComponents(0) remoteComponents = await createComponents(1) await Promise.all([ start(localComponents), start(remoteComponents) ]) }) afterEach(async () => { sinon.restore() await Promise.all([ stop(localComponents), stop(remoteComponents) ]) }) it('should be able to fetch from another peer', async () => { const key = 'key' const value = Uint8Array.from([0, 1, 2, 3, 4]) const localFetch = new FetchService(localComponents, defaultInit) const remoteFetch = new FetchService(remoteComponents, defaultInit) remoteFetch.registerLookupFunction(key, async (identifier) => { expect(identifier).to.equal(key) return value }) await start(localFetch) await start(remoteFetch) // simulate connection between nodes const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) // Run fetch const result = await localFetch.fetch(remoteComponents.getPeerId(), key) expect(result).to.equalBytes(value) }) it('should time out fetching from another peer when waiting for the record', async () => { const key = 'key' const localFetch = new FetchService(localComponents, defaultInit) const remoteFetch = new FetchService(remoteComponents, defaultInit) await start(localFetch) await start(remoteFetch) // simulate connection between nodes const [localToRemote, remoteToLocal] = connectionPair(localComponents, remoteComponents) localComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: localToRemote })) remoteComponents.getUpgrader().dispatchEvent(new CustomEvent('connection', { detail: remoteToLocal })) // replace existing handler with a really slow one await remoteComponents.getRegistrar().unhandle(remoteFetch.protocol) await remoteComponents.getRegistrar().handle(remoteFetch.protocol, ({ stream }) => { void pipe( stream, async function * (source) { for await (const chunk of source) { // longer than the timeout await delay(1000) yield chunk } }, stream ) }) const newStreamSpy = sinon.spy(localToRemote, 'newStream') // 10 ms timeout const timeoutController = new TimeoutController(10) // Run fetch, should time out await expect(localFetch.fetch(remoteComponents.getPeerId(), key, { signal: timeoutController.signal })) .to.eventually.be.rejected.with.property('code', 'ABORT_ERR') // 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') }) })