@libp2p/kad-dht 0.28.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +105 -0
  3. package/dist/src/constants.d.ts +20 -0
  4. package/dist/src/constants.d.ts.map +1 -0
  5. package/dist/src/constants.js +34 -0
  6. package/dist/src/constants.js.map +1 -0
  7. package/dist/src/content-fetching/index.d.ts +55 -0
  8. package/dist/src/content-fetching/index.d.ts.map +1 -0
  9. package/dist/src/content-fetching/index.js +190 -0
  10. package/dist/src/content-fetching/index.js.map +1 -0
  11. package/dist/src/content-routing/index.d.ts +42 -0
  12. package/dist/src/content-routing/index.d.ts.map +1 -0
  13. package/dist/src/content-routing/index.js +129 -0
  14. package/dist/src/content-routing/index.js.map +1 -0
  15. package/dist/src/dual-kad-dht.d.ts +65 -0
  16. package/dist/src/dual-kad-dht.d.ts.map +1 -0
  17. package/dist/src/dual-kad-dht.js +191 -0
  18. package/dist/src/dual-kad-dht.js.map +1 -0
  19. package/dist/src/index.d.ts +4 -0
  20. package/dist/src/index.d.ts.map +1 -0
  21. package/dist/src/index.js +15 -0
  22. package/dist/src/index.js.map +1 -0
  23. package/dist/src/kad-dht.d.ts +131 -0
  24. package/dist/src/kad-dht.d.ts.map +1 -0
  25. package/dist/src/kad-dht.js +268 -0
  26. package/dist/src/kad-dht.js.map +1 -0
  27. package/dist/src/message/dht.d.ts +297 -0
  28. package/dist/src/message/dht.js +921 -0
  29. package/dist/src/message/index.d.ts +32 -0
  30. package/dist/src/message/index.d.ts.map +1 -0
  31. package/dist/src/message/index.js +81 -0
  32. package/dist/src/message/index.js.map +1 -0
  33. package/dist/src/network.d.ts +60 -0
  34. package/dist/src/network.d.ts.map +1 -0
  35. package/dist/src/network.js +124 -0
  36. package/dist/src/network.js.map +1 -0
  37. package/dist/src/peer-list/index.d.ts +29 -0
  38. package/dist/src/peer-list/index.d.ts.map +1 -0
  39. package/dist/src/peer-list/index.js +44 -0
  40. package/dist/src/peer-list/index.js.map +1 -0
  41. package/dist/src/peer-list/peer-distance-list.d.ts +34 -0
  42. package/dist/src/peer-list/peer-distance-list.d.ts.map +1 -0
  43. package/dist/src/peer-list/peer-distance-list.js +64 -0
  44. package/dist/src/peer-list/peer-distance-list.js.map +1 -0
  45. package/dist/src/peer-routing/index.d.ts +71 -0
  46. package/dist/src/peer-routing/index.d.ts.map +1 -0
  47. package/dist/src/peer-routing/index.js +256 -0
  48. package/dist/src/peer-routing/index.js.map +1 -0
  49. package/dist/src/providers.d.ts +64 -0
  50. package/dist/src/providers.d.ts.map +1 -0
  51. package/dist/src/providers.js +208 -0
  52. package/dist/src/providers.js.map +1 -0
  53. package/dist/src/query/events.d.ts +46 -0
  54. package/dist/src/query/events.d.ts.map +1 -0
  55. package/dist/src/query/events.js +73 -0
  56. package/dist/src/query/events.js.map +1 -0
  57. package/dist/src/query/manager.d.ts +40 -0
  58. package/dist/src/query/manager.d.ts.map +1 -0
  59. package/dist/src/query/manager.js +140 -0
  60. package/dist/src/query/manager.js.map +1 -0
  61. package/dist/src/query/query-path.d.ts +58 -0
  62. package/dist/src/query/query-path.d.ts.map +1 -0
  63. package/dist/src/query/query-path.js +171 -0
  64. package/dist/src/query/query-path.js.map +1 -0
  65. package/dist/src/query/types.d.ts +16 -0
  66. package/dist/src/query/types.d.ts.map +1 -0
  67. package/dist/src/query/types.js +2 -0
  68. package/dist/src/query/types.js.map +1 -0
  69. package/dist/src/query-self.d.ts +31 -0
  70. package/dist/src/query-self.d.ts.map +1 -0
  71. package/dist/src/query-self.js +73 -0
  72. package/dist/src/query-self.js.map +1 -0
  73. package/dist/src/routing-table/generated-prefix-list-browser.d.ts +3 -0
  74. package/dist/src/routing-table/generated-prefix-list-browser.d.ts.map +1 -0
  75. package/dist/src/routing-table/generated-prefix-list-browser.js +1027 -0
  76. package/dist/src/routing-table/generated-prefix-list-browser.js.map +1 -0
  77. package/dist/src/routing-table/generated-prefix-list.d.ts +3 -0
  78. package/dist/src/routing-table/generated-prefix-list.d.ts.map +1 -0
  79. package/dist/src/routing-table/generated-prefix-list.js +4099 -0
  80. package/dist/src/routing-table/generated-prefix-list.js.map +1 -0
  81. package/dist/src/routing-table/index.d.ts +91 -0
  82. package/dist/src/routing-table/index.d.ts.map +1 -0
  83. package/dist/src/routing-table/index.js +183 -0
  84. package/dist/src/routing-table/index.js.map +1 -0
  85. package/dist/src/routing-table/refresh.d.ts +50 -0
  86. package/dist/src/routing-table/refresh.d.ts.map +1 -0
  87. package/dist/src/routing-table/refresh.js +204 -0
  88. package/dist/src/routing-table/refresh.js.map +1 -0
  89. package/dist/src/routing-table/types.d.ts +24 -0
  90. package/dist/src/routing-table/types.d.ts.map +1 -0
  91. package/dist/src/rpc/handlers/add-provider.d.ts +13 -0
  92. package/dist/src/rpc/handlers/add-provider.d.ts.map +1 -0
  93. package/dist/src/rpc/handlers/add-provider.js +42 -0
  94. package/dist/src/rpc/handlers/add-provider.js.map +1 -0
  95. package/dist/src/rpc/handlers/find-node.d.ts +18 -0
  96. package/dist/src/rpc/handlers/find-node.d.ts.map +1 -0
  97. package/dist/src/rpc/handlers/find-node.js +32 -0
  98. package/dist/src/rpc/handlers/find-node.js.map +1 -0
  99. package/dist/src/rpc/handlers/get-providers.d.ts +24 -0
  100. package/dist/src/rpc/handlers/get-providers.d.ts.map +1 -0
  101. package/dist/src/rpc/handlers/get-providers.js +60 -0
  102. package/dist/src/rpc/handlers/get-providers.js.map +1 -0
  103. package/dist/src/rpc/handlers/get-value.d.ts +27 -0
  104. package/dist/src/rpc/handlers/get-value.d.ts.map +1 -0
  105. package/dist/src/rpc/handlers/get-value.js +94 -0
  106. package/dist/src/rpc/handlers/get-value.js.map +1 -0
  107. package/dist/src/rpc/handlers/index.d.ts +13 -0
  108. package/dist/src/rpc/handlers/index.d.ts.map +1 -0
  109. package/dist/src/rpc/handlers/ping.d.ts +7 -0
  110. package/dist/src/rpc/handlers/ping.d.ts.map +1 -0
  111. package/dist/src/rpc/handlers/ping.js +9 -0
  112. package/dist/src/rpc/handlers/ping.js.map +1 -0
  113. package/dist/src/rpc/handlers/put-value.d.ts +18 -0
  114. package/dist/src/rpc/handlers/put-value.d.ts.map +1 -0
  115. package/dist/src/rpc/handlers/put-value.js +35 -0
  116. package/dist/src/rpc/handlers/put-value.js.map +1 -0
  117. package/dist/src/rpc/index.d.ts +38 -0
  118. package/dist/src/rpc/index.d.ts.map +1 -0
  119. package/dist/src/rpc/index.js +75 -0
  120. package/dist/src/rpc/index.js.map +1 -0
  121. package/dist/src/rpc/types.d.ts +6 -0
  122. package/dist/src/rpc/types.d.ts.map +1 -0
  123. package/dist/src/topology-listener.d.ts +33 -0
  124. package/dist/src/topology-listener.d.ts.map +1 -0
  125. package/dist/src/topology-listener.js +50 -0
  126. package/dist/src/topology-listener.js.map +1 -0
  127. package/dist/src/types.d.ts +143 -0
  128. package/dist/src/types.d.ts.map +1 -0
  129. package/dist/src/utils.d.ts +33 -0
  130. package/dist/src/utils.d.ts.map +1 -0
  131. package/dist/src/utils.js +89 -0
  132. package/dist/src/utils.js.map +1 -0
  133. package/package.json +200 -0
  134. package/src/constants.ts +50 -0
  135. package/src/content-fetching/index.ts +276 -0
  136. package/src/content-routing/index.ts +202 -0
  137. package/src/dual-kad-dht.ts +257 -0
  138. package/src/index.ts +21 -0
  139. package/src/kad-dht.ts +396 -0
  140. package/src/message/dht.d.ts +297 -0
  141. package/src/message/dht.js +921 -0
  142. package/src/message/dht.proto +75 -0
  143. package/src/message/index.ts +111 -0
  144. package/src/network.ts +185 -0
  145. package/src/peer-list/index.ts +54 -0
  146. package/src/peer-list/peer-distance-list.ts +93 -0
  147. package/src/peer-routing/index.ts +332 -0
  148. package/src/providers.ts +278 -0
  149. package/src/query/events.ts +126 -0
  150. package/src/query/manager.ts +188 -0
  151. package/src/query/query-path.ts +263 -0
  152. package/src/query/types.ts +22 -0
  153. package/src/query-self.ts +106 -0
  154. package/src/routing-table/generated-prefix-list-browser.ts +1026 -0
  155. package/src/routing-table/generated-prefix-list.ts +4098 -0
  156. package/src/routing-table/index.ts +265 -0
  157. package/src/routing-table/refresh.ts +263 -0
  158. package/src/rpc/handlers/add-provider.ts +63 -0
  159. package/src/rpc/handlers/find-node.ts +57 -0
  160. package/src/rpc/handlers/get-providers.ts +95 -0
  161. package/src/rpc/handlers/get-value.ts +130 -0
  162. package/src/rpc/handlers/ping.ts +13 -0
  163. package/src/rpc/handlers/put-value.ts +58 -0
  164. package/src/rpc/index.ts +118 -0
  165. package/src/topology-listener.ts +78 -0
  166. package/src/utils.ts +108 -0
@@ -0,0 +1,126 @@
1
+ import { MESSAGE_TYPE_LOOKUP } from '../message/index.js'
2
+ import type { SendingQueryEvent, PeerResponseEvent, MessageType, DialingPeerEvent, AddingPeerEvent, ValueEvent, ProviderEvent, QueryErrorEvent, FinalPeerEvent } from '@libp2p/interfaces/dht'
3
+ import type { PeerData } from '@libp2p/interfaces/peer-data'
4
+ import type { PeerId } from '@libp2p/interfaces/peer-id'
5
+ import type { Libp2pRecord } from '@libp2p/record'
6
+
7
+ const MESSAGE_NAMES = [
8
+ 'PUT_VALUE',
9
+ 'GET_VALUE',
10
+ 'ADD_PROVIDER',
11
+ 'GET_PROVIDERS',
12
+ 'FIND_NODE',
13
+ 'PING'
14
+ ]
15
+
16
+ export interface QueryEventFields {
17
+ to: PeerId
18
+ type: MessageType
19
+ }
20
+
21
+ export function sendingQueryEvent (fields: QueryEventFields): SendingQueryEvent {
22
+ return {
23
+ ...fields,
24
+ name: 'SENDING_QUERY',
25
+ type: 0,
26
+ // @ts-expect-error cannot look up values like this
27
+ messageName: MESSAGE_TYPE_LOOKUP[fields.type],
28
+ messageType: fields.type
29
+ }
30
+ }
31
+
32
+ export interface PeerResponseEventField {
33
+ from: PeerId
34
+ messageType: MessageType
35
+ closer?: PeerData[]
36
+ providers?: PeerData[]
37
+ record?: Libp2pRecord
38
+ }
39
+
40
+ export function peerResponseEvent (fields: PeerResponseEventField): PeerResponseEvent {
41
+ return {
42
+ ...fields,
43
+ name: 'PEER_RESPONSE',
44
+ type: 1,
45
+ // @ts-expect-error cannot look up values like this
46
+ messageName: MESSAGE_NAMES[fields.messageType],
47
+ closer: (fields.closer != null) ? fields.closer : [],
48
+ providers: (fields.providers != null) ? fields.providers : []
49
+ }
50
+ }
51
+
52
+ export interface FinalPeerEventFields {
53
+ from: PeerId
54
+ peer: PeerData
55
+ }
56
+
57
+ export function finalPeerEvent (fields: FinalPeerEventFields): FinalPeerEvent {
58
+ return {
59
+ ...fields,
60
+ name: 'FINAL_PEER',
61
+ type: 2
62
+ }
63
+ }
64
+
65
+ export interface ErrorEventFields {
66
+ from: PeerId
67
+ error: Error
68
+ }
69
+
70
+ export function queryErrorEvent (fields: ErrorEventFields): QueryErrorEvent {
71
+ return {
72
+ ...fields,
73
+ name: 'QUERY_ERROR',
74
+ type: 3
75
+ }
76
+ }
77
+
78
+ export interface ProviderEventFields {
79
+ from: PeerId
80
+ providers: PeerData[]
81
+ }
82
+
83
+ export function providerEvent (fields: ProviderEventFields): ProviderEvent {
84
+ return {
85
+ ...fields,
86
+ name: 'PROVIDER',
87
+ type: 4
88
+ }
89
+ }
90
+
91
+ export interface ValueEventFields {
92
+ from: PeerId
93
+ value: Uint8Array
94
+ }
95
+
96
+ export function valueEvent (fields: ValueEventFields): ValueEvent {
97
+ return {
98
+ ...fields,
99
+ name: 'VALUE',
100
+ type: 5
101
+ }
102
+ }
103
+
104
+ export interface PeerEventFields {
105
+ peer: PeerId
106
+ }
107
+
108
+ export function addingPeerEvent (fields: PeerEventFields): AddingPeerEvent {
109
+ return {
110
+ ...fields,
111
+ name: 'ADDING_PEER',
112
+ type: 6
113
+ }
114
+ }
115
+
116
+ export interface DialingPeerEventFields {
117
+ peer: PeerId
118
+ }
119
+
120
+ export function dialingPeerEvent (fields: DialingPeerEventFields): DialingPeerEvent {
121
+ return {
122
+ ...fields,
123
+ name: 'DIALING_PEER',
124
+ type: 7
125
+ }
126
+ }
@@ -0,0 +1,188 @@
1
+ import { TimeoutController } from 'timeout-abort-controller'
2
+ import { anySignal } from 'any-signal'
3
+ import {
4
+ ALPHA, K, DEFAULT_QUERY_TIMEOUT
5
+ } from '../constants.js'
6
+ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
7
+ import { queryPath } from './query-path.js'
8
+ import merge from 'it-merge'
9
+ import {
10
+ // @ts-expect-error only defined in node 15+
11
+ setMaxListeners
12
+ } from 'events'
13
+ import { EventEmitter, CustomEvent } from '@libp2p/interfaces'
14
+ import { logger } from '@libp2p/logger'
15
+ import type { PeerId } from '@libp2p/interfaces/peer-id'
16
+ import type { ComponentMetricsTracker } from '@libp2p/interfaces/metrics'
17
+ import type { Startable } from '@libp2p/interfaces'
18
+ import type { QueryFunc } from './types.js'
19
+ import type { QueryOptions } from '@libp2p/interfaces/dht'
20
+
21
+ const METRIC_RUNNING_QUERIES = 'running-queries'
22
+
23
+ export interface CleanUpEvents {
24
+ 'cleanup': CustomEvent
25
+ }
26
+
27
+ export interface QueryManagerOptions {
28
+ peerId: PeerId
29
+ lan?: boolean
30
+ metrics?: ComponentMetricsTracker
31
+ disjointPaths?: number
32
+ alpha?: number
33
+ }
34
+
35
+ /**
36
+ * Keeps track of all running queries
37
+ */
38
+ export class QueryManager implements Startable {
39
+ private readonly peerId: PeerId
40
+ private readonly lan: boolean
41
+ private readonly metrics?: ComponentMetricsTracker
42
+ public disjointPaths: number
43
+ private readonly alpha: number
44
+ private readonly controllers: Set<AbortController>
45
+ private running: boolean
46
+ private queries: number
47
+
48
+ constructor (options: QueryManagerOptions) {
49
+ const { peerId, lan = false, metrics, disjointPaths = K, alpha = ALPHA } = options
50
+ this.peerId = peerId
51
+ this.disjointPaths = disjointPaths ?? K
52
+ this.controllers = new Set()
53
+ this.running = false
54
+ this.alpha = alpha ?? ALPHA
55
+ this.lan = lan
56
+ this.metrics = metrics
57
+ this.queries = 0
58
+ }
59
+
60
+ isStarted () {
61
+ return this.running
62
+ }
63
+
64
+ /**
65
+ * Starts the query manager
66
+ */
67
+ async start () {
68
+ this.running = true
69
+ }
70
+
71
+ /**
72
+ * Stops all queries
73
+ */
74
+ async stop () {
75
+ this.running = false
76
+
77
+ for (const controller of this.controllers) {
78
+ controller.abort()
79
+ }
80
+
81
+ this.controllers.clear()
82
+ }
83
+
84
+ async * run (key: Uint8Array, peers: PeerId[], queryFunc: QueryFunc, options: QueryOptions = {}) {
85
+ if (!this.running) {
86
+ throw new Error('QueryManager not started')
87
+ }
88
+
89
+ let timeoutController
90
+
91
+ if (options.signal == null) {
92
+ // don't let queries run forever
93
+ timeoutController = new TimeoutController(DEFAULT_QUERY_TIMEOUT)
94
+ options.signal = timeoutController.signal
95
+ }
96
+
97
+ // allow us to stop queries on shut down
98
+ const abortController = new AbortController()
99
+ this.controllers.add(abortController)
100
+ const signals = [abortController.signal]
101
+
102
+ if (options.signal != null) {
103
+ signals.push(options.signal)
104
+ }
105
+
106
+ const signal = anySignal(signals)
107
+
108
+ // this signal will get listened to for every invocation of queryFunc
109
+ // so make sure we don't make a lot of noise in the logs
110
+ try {
111
+ if (setMaxListeners != null) {
112
+ setMaxListeners(0, signal)
113
+ }
114
+ } catch {} // fails on node < 15.4
115
+
116
+ const log = logger(`libp2p:kad-dht:${this.lan ? 'lan' : 'wan'}:query:` + uint8ArrayToString(key, 'base58btc'))
117
+
118
+ // query a subset of peers up to `kBucketSize / 2` in length
119
+ const peersToQuery = peers.slice(0, Math.min(this.disjointPaths, peers.length))
120
+ const startTime = Date.now()
121
+ const cleanUp = new EventEmitter<CleanUpEvents>()
122
+
123
+ try {
124
+ log('query:start')
125
+ this.queries++
126
+ this.metrics?.updateComponentMetric({
127
+ system: 'libp2p',
128
+ component: `kad-dht-${this.lan ? 'lan' : 'wan'}`,
129
+ metric: METRIC_RUNNING_QUERIES,
130
+ value: this.queries
131
+ })
132
+
133
+ if (peers.length === 0) {
134
+ log.error('Running query with no peers')
135
+ return
136
+ }
137
+
138
+ // Create query paths from the starting peers
139
+ const paths = peersToQuery.map((peer, index) => {
140
+ return queryPath({
141
+ key,
142
+ startingPeer: peer,
143
+ ourPeerId: this.peerId,
144
+ signal,
145
+ query: queryFunc,
146
+ pathIndex: index,
147
+ numPaths: peersToQuery.length,
148
+ alpha: this.alpha,
149
+ cleanUp,
150
+ queryFuncTimeout: options.queryFuncTimeout,
151
+ log
152
+ })
153
+ })
154
+
155
+ // Execute the query along each disjoint path and yield their results as they become available
156
+ for await (const event of merge(...paths)) {
157
+ yield event
158
+
159
+ if (event.name === 'QUERY_ERROR') {
160
+ log('error', event.error)
161
+ }
162
+ }
163
+ } catch (err: any) {
164
+ if (!this.running && err.code === 'ERR_QUERY_ABORTED') {
165
+ // ignore query aborted errors that were thrown during query manager shutdown
166
+ } else {
167
+ throw err
168
+ }
169
+ } finally {
170
+ this.controllers.delete(abortController)
171
+
172
+ if (timeoutController != null) {
173
+ timeoutController.clear()
174
+ }
175
+
176
+ this.queries--
177
+ this.metrics?.updateComponentMetric({
178
+ system: 'libp2p',
179
+ component: `kad-dht-${this.lan ? 'lan' : 'wan'}`,
180
+ metric: METRIC_RUNNING_QUERIES,
181
+ value: this.queries
182
+ })
183
+
184
+ cleanUp.dispatchEvent(new CustomEvent('cleanup'))
185
+ log('query:done in %dms', Date.now() - startTime)
186
+ }
187
+ }
188
+ }
@@ -0,0 +1,263 @@
1
+ import Queue from 'p-queue'
2
+ import { xor } from 'uint8arrays/xor'
3
+ import { toString } from 'uint8arrays/to-string'
4
+ import defer from 'p-defer'
5
+ import errCode from 'err-code'
6
+ import { convertPeerId, convertBuffer } from '../utils.js'
7
+ import { TimeoutController } from 'timeout-abort-controller'
8
+ import { anySignal } from 'any-signal'
9
+ import { queryErrorEvent } from './events.js'
10
+ import type { PeerId } from '@libp2p/interfaces/peer-id'
11
+ import type { EventEmitter } from '@libp2p/interfaces'
12
+ import type { CleanUpEvents } from './manager.js'
13
+ import type { Logger } from '@libp2p/logger'
14
+ import type { QueryFunc } from '../query/types.js'
15
+ import { base58btc } from 'multiformats/bases/base58'
16
+ import type { QueryEvent } from '@libp2p/interfaces/dht'
17
+
18
+ const MAX_XOR = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')
19
+
20
+ export interface QueryPathOptions {
21
+ /**
22
+ * What are we trying to find
23
+ */
24
+ key: Uint8Array
25
+
26
+ /**
27
+ * Where we start our query
28
+ */
29
+ startingPeer: PeerId
30
+
31
+ /**
32
+ * Who we are
33
+ */
34
+ ourPeerId: PeerId
35
+
36
+ /**
37
+ * When to stop querying
38
+ */
39
+ signal: AbortSignal
40
+
41
+ /**
42
+ * The query function to run with each peer
43
+ */
44
+ query: QueryFunc
45
+
46
+ /**
47
+ * How many concurrent node/value lookups to run
48
+ */
49
+ alpha: number
50
+
51
+ /**
52
+ * How many concurrent node/value lookups to run
53
+ */
54
+ pathIndex: number
55
+
56
+ /**
57
+ * How many concurrent node/value lookups to run
58
+ */
59
+ numPaths: number
60
+
61
+ /**
62
+ * will emit a 'cleanup' event if the caller exits the for..await of early
63
+ */
64
+ cleanUp: EventEmitter<CleanUpEvents>
65
+
66
+ /**
67
+ * A timeout for queryFunc in ms
68
+ */
69
+ queryFuncTimeout?: number
70
+
71
+ /**
72
+ * Query log
73
+ */
74
+ log: Logger
75
+ }
76
+
77
+ /**
78
+ * Walks a path through the DHT, calling the passed query function for
79
+ * every peer encountered that we have not seen before
80
+ */
81
+ export async function * queryPath (options: QueryPathOptions) {
82
+ const { key, startingPeer, ourPeerId, signal, query, alpha, pathIndex, numPaths, cleanUp, queryFuncTimeout, log } = options
83
+ // Only ALPHA node/value lookups are allowed at any given time for each process
84
+ // https://github.com/libp2p/specs/tree/master/kad-dht#alpha-concurrency-parameter-%CE%B1
85
+ const queue = new Queue({
86
+ concurrency: alpha
87
+ })
88
+
89
+ // perform lookups on kadId, not the actual value
90
+ const kadId = await convertBuffer(key)
91
+
92
+ // make sure we don't get trapped in a loop
93
+ const peersSeen = new Set()
94
+
95
+ /**
96
+ * Adds the passed peer to the query queue if it's not us and no
97
+ * other path has passed through this peer
98
+ */
99
+ function queryPeer (peer: PeerId, peerKadId: Uint8Array) {
100
+ if (peer == null) {
101
+ return
102
+ }
103
+
104
+ peersSeen.add(peer.toString(base58btc))
105
+
106
+ const peerXor = BigInt('0x' + toString(xor(peerKadId, kadId), 'base16'))
107
+
108
+ queue.add(async () => {
109
+ let timeout
110
+ const signals = [signal]
111
+
112
+ if (queryFuncTimeout != null) {
113
+ timeout = new TimeoutController(queryFuncTimeout)
114
+ signals.push(timeout.signal)
115
+ }
116
+
117
+ const compoundSignal = anySignal(signals)
118
+
119
+ try {
120
+ for await (const event of query({
121
+ key,
122
+ peer,
123
+ signal: compoundSignal,
124
+ pathIndex,
125
+ numPaths
126
+ })) {
127
+ if (compoundSignal.aborted) {
128
+ return
129
+ }
130
+
131
+ // if there are closer peers and the query has not completed, continue the query
132
+ if (event.name === 'PEER_RESPONSE') {
133
+ for (const closerPeer of event.closer) {
134
+ if (peersSeen.has(closerPeer.id.toString(base58btc))) { // eslint-disable-line max-depth
135
+ log('already seen %p in query', closerPeer.id)
136
+ continue
137
+ }
138
+
139
+ if (ourPeerId.equals(closerPeer.id)) { // eslint-disable-line max-depth
140
+ log('not querying ourselves')
141
+ continue
142
+ }
143
+
144
+ const closerPeerKadId = await convertPeerId(closerPeer.id)
145
+ const closerPeerXor = BigInt('0x' + toString(xor(closerPeerKadId, kadId), 'base16'))
146
+
147
+ // only continue query if closer peer is actually closer
148
+ if (closerPeerXor > peerXor) { // eslint-disable-line max-depth
149
+ log('skipping %p as they are not closer to %b than %p', closerPeer.id, key, peer)
150
+ continue
151
+ }
152
+
153
+ log('querying closer peer %p', closerPeer.id)
154
+ queryPeer(closerPeer.id, closerPeerKadId)
155
+ }
156
+ }
157
+
158
+ // TODO: we have upgraded to p-queue@7, this should no longer be necessary
159
+ queue.emit('completed', event)
160
+ }
161
+
162
+ timeout?.clear()
163
+ } catch (err: any) {
164
+ if (signal.aborted) {
165
+ // TODO: we have upgraded to p-queue@7, this should no longer be necessary
166
+ queue.emit('error', err)
167
+ } else {
168
+ // TODO: we have upgraded to p-queue@7, this should no longer be necessary
169
+ queue.emit('completed', queryErrorEvent({
170
+ from: peer,
171
+ error: err
172
+ }))
173
+ }
174
+ } finally {
175
+ timeout?.clear()
176
+ }
177
+ }, {
178
+ // use xor value as the queue priority - closer peers should execute first
179
+ // subtract it from MAX_XOR because higher priority values execute sooner
180
+
181
+ // @ts-expect-error this is supposed to be a Number but it's ok to use BigInts
182
+ // as long as all priorities are BigInts since we won't mix BigInts and Number
183
+ // values in arithmetic operations
184
+ priority: MAX_XOR - peerXor
185
+ }).catch(err => {
186
+ log.error(err)
187
+ })
188
+ }
189
+
190
+ // begin the query with the starting peer
191
+ queryPeer(startingPeer, await convertPeerId(startingPeer))
192
+
193
+ // yield results as they come in
194
+ yield * toGenerator(queue, signal, cleanUp, log)
195
+ }
196
+
197
+ async function * toGenerator (queue: Queue, signal: AbortSignal, cleanUp: EventEmitter<CleanUpEvents>, log: Logger) {
198
+ let deferred = defer()
199
+ let running = true
200
+ const results: QueryEvent[] = []
201
+
202
+ const cleanup = () => {
203
+ if (!running) {
204
+ return
205
+ }
206
+
207
+ log('clean up queue, results %d, queue size %d, pending tasks %d', results.length, queue.size, queue.pending)
208
+
209
+ running = false
210
+ queue.clear()
211
+ results.splice(0, results.length)
212
+ }
213
+
214
+ queue.on('completed', result => {
215
+ results.push(result)
216
+ deferred.resolve()
217
+ })
218
+ queue.on('error', err => {
219
+ log('queue error', err)
220
+ cleanup()
221
+ deferred.reject(err)
222
+ })
223
+ queue.on('idle', () => {
224
+ log('queue idle')
225
+ running = false
226
+ deferred.resolve()
227
+ })
228
+
229
+ // clear the queue and throw if the query is aborted
230
+ signal.addEventListener('abort', () => {
231
+ log('abort queue')
232
+ const wasRunning = running
233
+ cleanup()
234
+
235
+ if (wasRunning) {
236
+ deferred.reject(errCode(new Error('Query aborted'), 'ERR_QUERY_ABORTED'))
237
+ }
238
+ })
239
+
240
+ // the user broke out of the loop early, ensure we resolve the deferred result
241
+ // promise and clear the queue of any remaining jobs
242
+ cleanUp.addEventListener('cleanup', () => {
243
+ cleanup()
244
+ deferred.resolve()
245
+ })
246
+
247
+ while (running) { // eslint-disable-line no-unmodified-loop-condition
248
+ await deferred.promise
249
+ deferred = defer()
250
+
251
+ // yield all available results
252
+ while (results.length > 0) {
253
+ const result = results.shift()
254
+
255
+ if (result != null) {
256
+ yield result
257
+ }
258
+ }
259
+ }
260
+
261
+ // yield any remaining results
262
+ yield * results
263
+ }
@@ -0,0 +1,22 @@
1
+ import type { PeerId } from '@libp2p/interfaces/peer-id'
2
+ import type { QueryEvent } from '@libp2p/interfaces/dht'
3
+
4
+ export interface QueryContext {
5
+ // the key we are looking up
6
+ key: Uint8Array
7
+ // the current peer being queried
8
+ peer: PeerId
9
+ // if this signal emits an 'abort' event, any long-lived processes or requests started as part of this query should be terminated
10
+ signal: AbortSignal
11
+ // which disjoint path we are following
12
+ pathIndex: number
13
+ // the total number of disjoint paths being executed
14
+ numPaths: number
15
+ }
16
+
17
+ /**
18
+ * Query function
19
+ */
20
+ export interface QueryFunc {
21
+ (context: QueryContext): AsyncIterable<QueryEvent>
22
+ }
@@ -0,0 +1,106 @@
1
+ // @ts-expect-error setMaxListeners is missing from the types
2
+ import { setMaxListeners } from 'events'
3
+ import take from 'it-take'
4
+ import length from 'it-length'
5
+ import { QUERY_SELF_INTERVAL, QUERY_SELF_TIMEOUT, K } from './constants.js'
6
+ import { TimeoutController } from 'timeout-abort-controller'
7
+ import { anySignal } from 'any-signal'
8
+ import { logger, Logger } from '@libp2p/logger'
9
+ import type { PeerRouting } from './peer-routing/index.js'
10
+ import type { PeerId } from '@libp2p/interfaces/peer-id'
11
+ import type { Startable } from '@libp2p/interfaces'
12
+ import { pipe } from 'it-pipe'
13
+
14
+ export interface QuerySelfOptions {
15
+ peerId: PeerId
16
+ lan: boolean
17
+ peerRouting: PeerRouting
18
+ count?: number
19
+ interval?: number
20
+ queryTimeout?: number
21
+ }
22
+
23
+ /**
24
+ * Receives notifications of new peers joining the network that support the DHT protocol
25
+ */
26
+ export class QuerySelf implements Startable {
27
+ private readonly log: Logger
28
+ private readonly peerId: PeerId
29
+ private readonly peerRouting: PeerRouting
30
+ private readonly count: number
31
+ private readonly interval: number
32
+ private readonly queryTimeout: number
33
+ private running: boolean
34
+ private timeoutId?: NodeJS.Timer
35
+ private controller?: AbortController
36
+
37
+ constructor (options: QuerySelfOptions) {
38
+ const { peerId, peerRouting, lan, count, interval, queryTimeout } = options
39
+
40
+ this.log = logger(`libp2p:kad-dht:${lan ? 'lan' : 'wan'}:query-self`)
41
+ this.running = false
42
+ this.peerId = peerId
43
+ this.peerRouting = peerRouting
44
+ this.count = count ?? K
45
+ this.interval = interval ?? QUERY_SELF_INTERVAL
46
+ this.queryTimeout = queryTimeout ?? QUERY_SELF_TIMEOUT
47
+ }
48
+
49
+ isStarted () {
50
+ return this.running
51
+ }
52
+
53
+ async start () {
54
+ if (this.running) {
55
+ return
56
+ }
57
+
58
+ this.running = true
59
+ this._querySelf()
60
+ }
61
+
62
+ async stop () {
63
+ this.running = false
64
+
65
+ if (this.timeoutId != null) {
66
+ clearTimeout(this.timeoutId)
67
+ }
68
+
69
+ if (this.controller != null) {
70
+ this.controller.abort()
71
+ }
72
+ }
73
+
74
+ _querySelf () {
75
+ Promise.resolve().then(async () => {
76
+ const timeoutController = new TimeoutController(this.queryTimeout)
77
+
78
+ try {
79
+ this.controller = new AbortController()
80
+ const signal = anySignal([this.controller.signal, timeoutController.signal])
81
+ // this controller will get used for lots of dial attempts so make sure we don't cause warnings to be logged
82
+ try {
83
+ if (setMaxListeners != null) {
84
+ setMaxListeners(Infinity, signal)
85
+ }
86
+ } catch {} // fails on node < 15.4
87
+ const found = await pipe(
88
+ this.peerRouting.getClosestPeers(this.peerId.toBytes(), {
89
+ signal
90
+ }),
91
+ (source) => take(source, this.count),
92
+ async (source) => await length(source)
93
+ )
94
+
95
+ this.log('query ran successfully - found %d peers', found)
96
+ } catch (err: any) {
97
+ this.log('query error', err)
98
+ } finally {
99
+ this.timeoutId = setTimeout(this._querySelf.bind(this), this.interval)
100
+ timeoutController.clear()
101
+ }
102
+ }).catch(err => {
103
+ this.log('query error', err)
104
+ })
105
+ }
106
+ }