Skip to content

Commit 772b401

Browse files
authored
fix: allow partial routing implementations (#3093)
So routing implementations don't have to have empty methods for features they don't support, guard on the routing method being present before invoking it.
1 parent 1b62bc3 commit 772b401

File tree

4 files changed

+128
-21
lines changed

4 files changed

+128
-21
lines changed

packages/libp2p/src/content-routing.ts

+30-13
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ export class CompoundContentRouting implements ContentRouting, Startable {
104104
const seen = new PeerSet()
105105

106106
for await (const peer of merge(
107-
...self.routers.map(router => router.findProviders(key, options))
107+
...self.routers
108+
.filter(router => router.findProviders instanceof Function)
109+
.map(router => router.findProviders(key, options))
108110
)) {
109111
// the peer was yielded by a content router without multiaddrs and we
110112
// failed to load them
@@ -139,19 +141,26 @@ export class CompoundContentRouting implements ContentRouting, Startable {
139141
throw new NoContentRoutersError('No content routers available')
140142
}
141143

142-
await Promise.all(this.routers.map(async (router) => {
143-
await router.provide(key, options)
144-
}))
144+
await Promise.all(
145+
this.routers
146+
.filter(router => router.provide instanceof Function)
147+
.map(async (router) => {
148+
await router.provide(key, options)
149+
}))
145150
}
146151

147152
async cancelReprovide (key: CID, options: AbortOptions = {}): Promise<void> {
148153
if (this.routers.length === 0) {
149154
throw new NoContentRoutersError('No content routers available')
150155
}
151156

152-
await Promise.all(this.routers.map(async (router) => {
153-
await router.cancelReprovide(key, options)
154-
}))
157+
await Promise.all(
158+
this.routers
159+
.filter(router => router.cancelReprovide instanceof Function)
160+
.map(async (router) => {
161+
await router.cancelReprovide(key, options)
162+
})
163+
)
155164
}
156165

157166
/**
@@ -162,9 +171,13 @@ export class CompoundContentRouting implements ContentRouting, Startable {
162171
throw new NotStartedError()
163172
}
164173

165-
await Promise.all(this.routers.map(async (router) => {
166-
await router.put(key, value, options)
167-
}))
174+
await Promise.all(
175+
this.routers
176+
.filter(router => router.put instanceof Function)
177+
.map(async (router) => {
178+
await router.put(key, value, options)
179+
})
180+
)
168181
}
169182

170183
/**
@@ -176,8 +189,12 @@ export class CompoundContentRouting implements ContentRouting, Startable {
176189
throw new NotStartedError()
177190
}
178191

179-
return Promise.any(this.routers.map(async (router) => {
180-
return router.get(key, options)
181-
}))
192+
return Promise.any(
193+
this.routers
194+
.filter(router => router.get instanceof Function)
195+
.map(async (router) => {
196+
return router.get(key, options)
197+
})
198+
)
182199
}
183200
}

packages/libp2p/src/peer-routing.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,15 @@ export class DefaultPeerRouting implements PeerRouting {
7272

7373
const self = this
7474
const source = merge(
75-
...this.routers.map(router => (async function * () {
76-
try {
77-
yield await router.findPeer(id, options)
78-
} catch (err) {
79-
self.log.error(err)
80-
}
81-
})())
75+
...this.routers
76+
.filter(router => router.findPeer instanceof Function)
77+
.map(router => (async function * () {
78+
try {
79+
yield await router.findPeer(id, options)
80+
} catch (err) {
81+
self.log.error(err)
82+
}
83+
})())
8284
)
8385

8486
for await (const peer of source) {
@@ -113,7 +115,9 @@ export class DefaultPeerRouting implements PeerRouting {
113115
for await (const peer of parallel(
114116
async function * () {
115117
const source = merge(
116-
...self.routers.map(router => router.getClosestPeers(key, options))
118+
...self.routers
119+
.filter(router => router.getClosestPeers instanceof Function)
120+
.map(router => router.getClosestPeers(key, options))
117121
)
118122

119123
for await (let peer of source) {

packages/libp2p/test/content-routing/content-routing.spec.ts

+42
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import all from 'it-all'
99
import drain from 'it-drain'
1010
import { CID } from 'multiformats/cid'
1111
import pDefer from 'p-defer'
12+
import sinon from 'sinon'
1213
import { type StubbedInstance, stubInterface } from 'sinon-ts'
1314
import { createLibp2p, type Libp2p } from '../../src/index.js'
1415
import type { ContentRouting, PeerInfo } from '@libp2p/interface'
@@ -392,4 +393,45 @@ describe('content-routing', () => {
392393
expect(providers).to.eql(results)
393394
})
394395
})
396+
397+
describe('partial implementation', () => {
398+
let node: Libp2p
399+
let router: StubbedInstance<Partial<ContentRouting>>
400+
401+
beforeEach(async () => {
402+
router = {
403+
provide: sinon.stub()
404+
}
405+
406+
node = await createLibp2p({
407+
services: {
408+
router: () => ({
409+
[contentRoutingSymbol]: router
410+
})
411+
}
412+
})
413+
})
414+
415+
afterEach(async () => {
416+
await node?.stop()
417+
})
418+
419+
it('should invoke a method defined on the service', async () => {
420+
const deferred = pDefer()
421+
422+
router.provide?.callsFake(async function () {
423+
deferred.resolve()
424+
})
425+
426+
void node.contentRouting.provide(CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB'))
427+
428+
await deferred.promise
429+
})
430+
431+
it('should not invoke a method not defined on the service', async () => {
432+
const result = await all(node.contentRouting.findProviders(CID.parse('QmU621oD8AhHw6t25vVyfYKmL9VV3PTgc52FngEhTGACFB')))
433+
434+
expect(result).to.be.empty()
435+
})
436+
})
395437
})

packages/libp2p/test/peer-routing/peer-routing.spec.ts

+44
Original file line numberDiff line numberDiff line change
@@ -459,4 +459,48 @@ describe('peer-routing', () => {
459459
expect(peers).to.be.an('array').with.a.lengthOf(1).that.deep.equals(results)
460460
})
461461
})
462+
463+
describe('partial implementation', () => {
464+
let node: Libp2p
465+
let router: StubbedInstance<Partial<PeerRouting>>
466+
467+
beforeEach(async () => {
468+
router = {
469+
findPeer: sinon.stub()
470+
}
471+
472+
node = await createLibp2p({
473+
services: {
474+
router: () => ({
475+
[peerRoutingSymbol]: router
476+
})
477+
}
478+
})
479+
})
480+
481+
afterEach(async () => {
482+
await node?.stop()
483+
})
484+
485+
it('should invoke a method defined on the service', async () => {
486+
const peerInfo = {
487+
id: peerIdFromPrivateKey(await generateKeyPair('Ed25519')),
488+
multiaddrs: [
489+
multiaddr('/ip4/123.123.123.123/tcp/4001')
490+
]
491+
}
492+
493+
router.findPeer?.callsFake(async function () {
494+
return peerInfo
495+
})
496+
497+
await expect(node.peerRouting.findPeer(peerInfo.id)).to.eventually.deep.equal(peerInfo)
498+
})
499+
500+
it('should not invoke a method not defined on the service', async () => {
501+
const result = await all(node.peerRouting.getClosestPeers(Uint8Array.from([0, 1, 2, 3, 4])))
502+
503+
expect(result).to.be.empty()
504+
})
505+
})
462506
})

0 commit comments

Comments
 (0)