Skip to content
This repository was archived by the owner on Jul 21, 2023. It is now read-only.

Commit 5ae4440

Browse files
zeroxbtachingbrain
andauthored
fix: re-enable ensuring queries run along disjoint paths (#371)
During the queries, the query paths might overlap and the same nodes will be queried multiple times. There is no need to query a node if it has been queried during the execution of another query path, so the peersSeen set should be shared among different query paths belonging to the same query. This also protects the network in that you have to control more nodes to affect query paths of other nodes. Co-authored-by: Alex Potsides <[email protected]>
1 parent e48754f commit 5ae4440

File tree

4 files changed

+48
-7
lines changed

4 files changed

+48
-7
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@
150150
"@libp2p/interface-registrar": "^2.0.3",
151151
"@libp2p/interfaces": "^3.0.3",
152152
"@libp2p/logger": "^2.0.1",
153+
"@libp2p/peer-collections": "^2.2.0",
153154
"@libp2p/peer-id": "^1.1.15",
154155
"@libp2p/record": "^2.0.2",
155156
"@libp2p/topology": "^3.0.0",

src/query/manager.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { Startable } from '@libp2p/interfaces/startable'
1414
import type { QueryFunc } from './types.js'
1515
import type { QueryOptions } from '@libp2p/interface-dht'
1616
import { Components, Initializable } from '@libp2p/components'
17+
import { PeerSet } from '@libp2p/peer-collections'
1718

1819
const METRIC_RUNNING_QUERIES = 'running-queries'
1920

@@ -139,6 +140,9 @@ export class QueryManager implements Startable, Initializable {
139140
return
140141
}
141142

143+
// make sure we don't get trapped in a loop
144+
const peersSeen = new PeerSet()
145+
142146
// Create query paths from the starting peers
143147
const paths = peersToQuery.map((peer, index) => {
144148
return queryPath({
@@ -152,7 +156,8 @@ export class QueryManager implements Startable, Initializable {
152156
alpha: this.alpha,
153157
cleanUp,
154158
queryFuncTimeout: options.queryFuncTimeout,
155-
log
159+
log,
160+
peersSeen
156161
})
157162
})
158163

src/query/query-path.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { CleanUpEvents } from './manager.js'
1313
import type { Logger } from '@libp2p/logger'
1414
import type { QueryFunc } from '../query/types.js'
1515
import type { QueryEvent } from '@libp2p/interface-dht'
16+
import type { PeerSet } from '@libp2p/peer-collections'
1617

1718
const MAX_XOR = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')
1819

@@ -71,14 +72,19 @@ export interface QueryPathOptions {
7172
* Query log
7273
*/
7374
log: Logger
75+
76+
/**
77+
* Set of peers seen by this and other paths
78+
*/
79+
peersSeen: PeerSet
7480
}
7581

7682
/**
7783
* Walks a path through the DHT, calling the passed query function for
7884
* every peer encountered that we have not seen before
7985
*/
8086
export async function * queryPath (options: QueryPathOptions) {
81-
const { key, startingPeer, ourPeerId, signal, query, alpha, pathIndex, numPaths, cleanUp, queryFuncTimeout, log } = options
87+
const { key, startingPeer, ourPeerId, signal, query, alpha, pathIndex, numPaths, cleanUp, queryFuncTimeout, log, peersSeen } = options
8288
// Only ALPHA node/value lookups are allowed at any given time for each process
8389
// https://github.com/libp2p/specs/tree/master/kad-dht#alpha-concurrency-parameter-%CE%B1
8490
const queue = new Queue({
@@ -88,9 +94,6 @@ export async function * queryPath (options: QueryPathOptions) {
8894
// perform lookups on kadId, not the actual value
8995
const kadId = await convertBuffer(key)
9096

91-
// make sure we don't get trapped in a loop
92-
const peersSeen = new Set()
93-
9497
/**
9598
* Adds the passed peer to the query queue if it's not us and no
9699
* other path has passed through this peer
@@ -100,7 +103,7 @@ export async function * queryPath (options: QueryPathOptions) {
100103
return
101104
}
102105

103-
peersSeen.add(peer.toString())
106+
peersSeen.add(peer)
104107

105108
const peerXor = BigInt('0x' + toString(xor(peerKadId, kadId), 'base16'))
106109

@@ -130,7 +133,7 @@ export async function * queryPath (options: QueryPathOptions) {
130133
// if there are closer peers and the query has not completed, continue the query
131134
if (event.name === 'PEER_RESPONSE') {
132135
for (const closerPeer of event.closer) {
133-
if (peersSeen.has(closerPeer.id.toString())) { // eslint-disable-line max-depth
136+
if (peersSeen.has(closerPeer.id)) { // eslint-disable-line max-depth
134137
log('already seen %p in query', closerPeer.id)
135138
continue
136139
}

test/query.spec.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,38 @@ describe('QueryManager', () => {
427427
await manager.stop()
428428
})
429429

430+
it('should stop when passing through the same node twice', async () => {
431+
const manager = new QueryManager({ disjointPaths: 20, alpha: 1 })
432+
manager.init(new Components({
433+
peerId: ourPeerId
434+
}))
435+
await manager.start()
436+
437+
const topology = createTopology({
438+
6: { closerPeers: [2] },
439+
5: { closerPeers: [4] },
440+
4: { closerPeers: [3] },
441+
3: { closerPeers: [2] },
442+
2: { closerPeers: [1] },
443+
1: { closerPeers: [0] },
444+
0: { value: uint8ArrayFromString('hello world') }
445+
})
446+
447+
const results = await all(manager.run(key, [peers[6], peers[5]], createQueryFunction(topology)))
448+
const traversedPeers = results
449+
.map(event => {
450+
if (event.type !== EventTypes.PEER_RESPONSE && event.type !== EventTypes.VALUE) {
451+
throw new Error(`Unexpected query event type ${event.type}`)
452+
}
453+
454+
return event.from
455+
})
456+
457+
expect(traversedPeers).lengthOf(7)
458+
459+
await manager.stop()
460+
})
461+
430462
it('only closerPeers', async () => {
431463
const manager = new QueryManager({ disjointPaths: 1, alpha: 1 })
432464
manager.init(new Components({

0 commit comments

Comments
 (0)