@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,265 @@
1
+ // @ts-expect-error no types
2
+ import KBuck from 'k-bucket'
3
+ import * as utils from '../utils.js'
4
+ import Queue from 'p-queue'
5
+ import { PROTOCOL_DHT } from '../constants.js'
6
+ import { TimeoutController } from 'timeout-abort-controller'
7
+ import { logger } from '@libp2p/logger'
8
+ import type { PeerId } from '@libp2p/interfaces/peer-id'
9
+ import type { Dialer, Startable } from '@libp2p/interfaces'
10
+ import type { ComponentMetricsTracker } from '@libp2p/interfaces/metrics'
11
+ import type { Logger } from '@libp2p/logger'
12
+
13
+ export interface KBucketPeer {
14
+ id: Uint8Array
15
+ peer: PeerId
16
+ }
17
+
18
+ export interface KBucket {
19
+ id: Uint8Array
20
+ contacts: KBucketPeer[]
21
+ dontSplit: boolean
22
+ left: KBucket
23
+ right: KBucket
24
+ }
25
+
26
+ export interface KBucketTree {
27
+ root: KBucket
28
+ localNodeId: Uint8Array
29
+ on: (event: 'ping', callback: (oldContacts: KBucketPeer[], newContact: KBucketPeer) => void) => void
30
+ closest: (key: Uint8Array, count: number) => KBucketPeer[]
31
+ closestPeer: (key: Uint8Array) => KBucketPeer
32
+ remove: (key: Uint8Array) => void
33
+ add: (peer: KBucketPeer) => void
34
+ get: (key: Uint8Array) => Uint8Array
35
+ count: () => number
36
+ toIterable: () => Iterable<KBucket>
37
+ }
38
+
39
+ const METRIC_ROUTING_TABLE_SIZE = 'routing-table-size'
40
+
41
+ export interface RoutingTableOptions {
42
+ peerId: PeerId
43
+ dialer: Dialer
44
+ lan: boolean
45
+ metrics?: ComponentMetricsTracker
46
+ kBucketSize?: number
47
+ pingTimeout?: number
48
+ }
49
+
50
+ /**
51
+ * A wrapper around `k-bucket`, to provide easy store and
52
+ * retrieval for peers.
53
+ */
54
+ export class RoutingTable implements Startable {
55
+ private readonly log: Logger
56
+ private readonly peerId: PeerId
57
+ public dialer: Dialer
58
+ private readonly lan: boolean
59
+ private readonly metrics?: ComponentMetricsTracker
60
+ public kBucketSize: number
61
+ private readonly pingTimeout: number
62
+ public kb?: KBucketTree
63
+ private running: boolean
64
+ public pingQueue: Queue
65
+
66
+ constructor (options: RoutingTableOptions) {
67
+ const { peerId, dialer, kBucketSize, pingTimeout, lan, metrics } = options
68
+
69
+ this.log = logger(`libp2p:kad-dht:${lan ? 'lan' : 'wan'}:routing-table`)
70
+ this.peerId = peerId
71
+ this.dialer = dialer
72
+ this.kBucketSize = kBucketSize ?? 20
73
+ this.pingTimeout = pingTimeout ?? 10000
74
+ this.lan = lan
75
+ this.metrics = metrics
76
+ this.pingQueue = new Queue({ concurrency: 1 })
77
+ this.running = false
78
+
79
+ this._onPing = this._onPing.bind(this)
80
+ }
81
+
82
+ isStarted () {
83
+ return this.running
84
+ }
85
+
86
+ async start () {
87
+ this.running = true
88
+
89
+ const kBuck = new KBuck({
90
+ localNodeId: await utils.convertPeerId(this.peerId),
91
+ numberOfNodesPerKBucket: this.kBucketSize,
92
+ numberOfNodesToPing: 1
93
+ })
94
+ kBuck.on('ping', this._onPing)
95
+ this.kb = kBuck
96
+ }
97
+
98
+ async stop () {
99
+ this.running = false
100
+ this.pingQueue.clear()
101
+ this.kb = undefined
102
+ }
103
+
104
+ /**
105
+ * Called on the `ping` event from `k-bucket` when a bucket is full
106
+ * and cannot split.
107
+ *
108
+ * `oldContacts.length` is defined by the `numberOfNodesToPing` param
109
+ * passed to the `k-bucket` constructor.
110
+ *
111
+ * `oldContacts` will not be empty and is the list of contacts that
112
+ * have not been contacted for the longest.
113
+ */
114
+ _onPing (oldContacts: KBucketPeer[], newContact: KBucketPeer) {
115
+ // add to a queue so multiple ping requests do not overlap and we don't
116
+ // flood the network with ping requests if lots of newContact requests
117
+ // are received
118
+ this.pingQueue.add(async () => {
119
+ if (!this.running) {
120
+ return
121
+ }
122
+
123
+ let responded = 0
124
+
125
+ try {
126
+ await Promise.all(
127
+ oldContacts.map(async oldContact => {
128
+ let timeoutController
129
+
130
+ try {
131
+ timeoutController = new TimeoutController(this.pingTimeout)
132
+
133
+ this.log('pinging old contact %p', oldContact.peer)
134
+ const { stream } = await this.dialer.dialProtocol(oldContact.peer, PROTOCOL_DHT, {
135
+ signal: timeoutController.signal
136
+ })
137
+ await stream.close()
138
+ responded++
139
+ } catch (err: any) {
140
+ if (this.running && this.kb != null) {
141
+ // only evict peers if we are still running, otherwise we evict when dialing is
142
+ // cancelled due to shutdown in progress
143
+ this.log.error('could not ping peer %p', oldContact.peer, err)
144
+ this.log('evicting old contact after ping failed %p', oldContact)
145
+ this.kb.remove(oldContact.id)
146
+ }
147
+ } finally {
148
+ if (timeoutController != null) {
149
+ timeoutController.clear()
150
+ }
151
+
152
+ this.metrics?.updateComponentMetric({
153
+ system: 'libp2p',
154
+ component: `kad-dht-${this.lan ? 'lan' : 'wan'}`,
155
+ metric: METRIC_ROUTING_TABLE_SIZE,
156
+ value: this.size
157
+ })
158
+ }
159
+ })
160
+ )
161
+
162
+ if (this.running && responded < oldContacts.length && this.kb != null) {
163
+ this.log('adding new contact %p', newContact.peer)
164
+ this.kb.add(newContact)
165
+ }
166
+ } catch (err: any) {
167
+ this.log.error('could not process k-bucket ping event', err)
168
+ }
169
+ })
170
+ .catch(err => {
171
+ this.log.error('could not process k-bucket ping event', err)
172
+ })
173
+ }
174
+
175
+ // -- Public Interface
176
+
177
+ /**
178
+ * Amount of currently stored peers
179
+ */
180
+ get size () {
181
+ if (this.kb == null) {
182
+ return 0
183
+ }
184
+
185
+ return this.kb.count()
186
+ }
187
+
188
+ /**
189
+ * Find a specific peer by id
190
+ */
191
+ async find (peer: PeerId) {
192
+ const key = await utils.convertPeerId(peer)
193
+ const closest = this.closestPeer(key)
194
+
195
+ if (closest != null && peer.equals(closest)) {
196
+ return closest
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Retrieve the closest peers to the given key
202
+ */
203
+ closestPeer (key: Uint8Array) {
204
+ const res = this.closestPeers(key, 1)
205
+
206
+ if (res.length > 0) {
207
+ return res[0]
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Retrieve the `count`-closest peers to the given key
213
+ */
214
+ closestPeers (key: Uint8Array, count = this.kBucketSize) {
215
+ if (this.kb == null) {
216
+ return []
217
+ }
218
+
219
+ const closest = this.kb.closest(key, count)
220
+
221
+ return closest.map(p => p.peer)
222
+ }
223
+
224
+ /**
225
+ * Add or update the routing table with the given peer
226
+ */
227
+ async add (peer: PeerId) {
228
+ if (this.kb == null) {
229
+ throw new Error('RoutingTable is not started')
230
+ }
231
+
232
+ const id = await utils.convertPeerId(peer)
233
+
234
+ this.kb.add({ id: id, peer: peer })
235
+
236
+ this.log('added %p with kad id %b', peer, id)
237
+
238
+ this.metrics?.updateComponentMetric({
239
+ system: 'libp2p',
240
+ component: `kad-dht-${this.lan ? 'lan' : 'wan'}`,
241
+ metric: METRIC_ROUTING_TABLE_SIZE,
242
+ value: this.size
243
+ })
244
+ }
245
+
246
+ /**
247
+ * Remove a given peer from the table
248
+ */
249
+ async remove (peer: PeerId) {
250
+ if (this.kb == null) {
251
+ throw new Error('RoutingTable is not started')
252
+ }
253
+
254
+ const id = await utils.convertPeerId(peer)
255
+
256
+ this.kb.remove(id)
257
+
258
+ this.metrics?.updateComponentMetric({
259
+ system: 'libp2p',
260
+ component: `kad-dht-${this.lan ? 'lan' : 'wan'}`,
261
+ metric: METRIC_ROUTING_TABLE_SIZE,
262
+ value: this.size
263
+ })
264
+ }
265
+ }
@@ -0,0 +1,263 @@
1
+ import { xor as uint8ArrayXor } from 'uint8arrays/xor'
2
+ import GENERATED_PREFIXES from './generated-prefix-list.js'
3
+ import { sha256 } from 'multiformats/hashes/sha2'
4
+ import { randomBytes } from '@libp2p/crypto'
5
+ import { peerIdFromBytes } from '@libp2p/peer-id'
6
+ import { logger } from '@libp2p/logger'
7
+ import length from 'it-length'
8
+ import { TimeoutController } from 'timeout-abort-controller'
9
+ import { TABLE_REFRESH_INTERVAL, TABLE_REFRESH_QUERY_TIMEOUT } from '../constants.js'
10
+ import type { RoutingTable } from './index.js'
11
+ import type { Logger } from '@libp2p/logger'
12
+ import type { PeerRouting } from '../peer-routing/index.js'
13
+
14
+ /**
15
+ * Cannot generate random KadIds longer than this + 1
16
+ */
17
+ const MAX_COMMON_PREFIX_LENGTH = 15
18
+
19
+ export interface RoutingTableRefreshOptions {
20
+ peerRouting: PeerRouting
21
+ routingTable: RoutingTable
22
+ lan: boolean
23
+ refreshInterval?: number
24
+ refreshQueryTimeout?: number
25
+ }
26
+
27
+ /**
28
+ * A wrapper around `k-bucket`, to provide easy store and
29
+ * retrieval for peers.
30
+ */
31
+ export class RoutingTableRefresh {
32
+ private readonly log: Logger
33
+ private readonly peerRouting: PeerRouting
34
+ private readonly routingTable: RoutingTable
35
+ private readonly refreshInterval: number
36
+ private readonly refreshQueryTimeout: number
37
+ private readonly commonPrefixLengthRefreshedAt: Date[]
38
+ private refreshTimeoutId?: NodeJS.Timer
39
+
40
+ constructor (options: RoutingTableRefreshOptions) {
41
+ const { peerRouting, routingTable, refreshInterval, refreshQueryTimeout, lan } = options
42
+ this.log = logger(`libp2p:kad-dht:${lan ? 'lan' : 'wan'}:routing-table:refresh`)
43
+ this.peerRouting = peerRouting
44
+ this.routingTable = routingTable
45
+ this.refreshInterval = refreshInterval ?? TABLE_REFRESH_INTERVAL
46
+ this.refreshQueryTimeout = refreshQueryTimeout ?? TABLE_REFRESH_QUERY_TIMEOUT
47
+ this.commonPrefixLengthRefreshedAt = []
48
+
49
+ this.refreshTable = this.refreshTable.bind(this)
50
+ }
51
+
52
+ async start () {
53
+ this.log(`refreshing routing table every ${this.refreshInterval}ms`)
54
+ this.refreshTable(true)
55
+ }
56
+
57
+ async stop () {
58
+ if (this.refreshTimeoutId != null) {
59
+ clearTimeout(this.refreshTimeoutId)
60
+ }
61
+ }
62
+
63
+ /**
64
+ * To speed lookups, we seed the table with random PeerIds. This means
65
+ * when we are asked to locate a peer on the network, we can find a KadId
66
+ * that is close to the requested peer ID and query that, then network
67
+ * peers will tell us who they know who is close to the fake ID
68
+ */
69
+ refreshTable (force: boolean = false) {
70
+ this.log('refreshing routing table')
71
+
72
+ const prefixLength = this._maxCommonPrefix()
73
+ const refreshCpls = this._getTrackedCommonPrefixLengthsForRefresh(prefixLength)
74
+
75
+ this.log(`max common prefix length ${prefixLength}`)
76
+ this.log(`tracked CPLs [ ${refreshCpls.map(date => date.toISOString()).join(', ')} ]`)
77
+
78
+ /**
79
+ * If we see a gap at a common prefix length in the Routing table, we ONLY refresh up until
80
+ * the maximum cpl we have in the Routing Table OR (2 * (Cpl+ 1) with the gap), whichever
81
+ * is smaller.
82
+ *
83
+ * This is to prevent refreshes for Cpls that have no peers in the network but happen to be
84
+ * before a very high max Cpl for which we do have peers in the network.
85
+ *
86
+ * The number of 2 * (Cpl + 1) can be proved and a proof would have been written here if
87
+ * the programmer had paid more attention in the Math classes at university.
88
+ *
89
+ * So, please be patient and a doc explaining it will be published soon.
90
+ *
91
+ * https://github.com/libp2p/go-libp2p-kad-dht/commit/2851c88acb0a3f86bcfe3cfd0f4604a03db801d8#diff-ad45f4ba97ffbc4083c2eb87a4420c1157057b233f048030d67c6b551855ccf6R219
92
+ */
93
+ Promise.all(
94
+ refreshCpls.map(async (lastRefresh, index) => {
95
+ try {
96
+ await this._refreshCommonPrefixLength(index, lastRefresh, force)
97
+
98
+ if (this._numPeersForCpl(prefixLength) === 0) {
99
+ const lastCpl = Math.min(2 * (index + 1), refreshCpls.length - 1)
100
+
101
+ for (let n = index + 1; n < lastCpl + 1; n++) {
102
+ try {
103
+ await this._refreshCommonPrefixLength(n, lastRefresh, force)
104
+ } catch (err: any) {
105
+ this.log.error(err)
106
+ }
107
+ }
108
+ }
109
+ } catch (err: any) {
110
+ this.log.error(err)
111
+ }
112
+ })
113
+ ).catch(err => {
114
+ this.log.error(err)
115
+ }).then(() => {
116
+ this.refreshTimeoutId = setTimeout(this.refreshTable, this.refreshInterval)
117
+
118
+ if (this.refreshTimeoutId.unref != null) {
119
+ this.refreshTimeoutId.unref()
120
+ }
121
+ }).catch(err => {
122
+ this.log.error(err)
123
+ })
124
+ }
125
+
126
+ async _refreshCommonPrefixLength (cpl: number, lastRefresh: Date, force: boolean) {
127
+ if (!force && lastRefresh.getTime() > (Date.now() - this.refreshInterval)) {
128
+ this.log('not running refresh for cpl %s as time since last refresh not above interval', cpl)
129
+ return
130
+ }
131
+
132
+ // gen a key for the query to refresh the cpl
133
+ const peerId = await this._generateRandomPeerId(cpl)
134
+
135
+ this.log('starting refreshing cpl %s with key %p (routing table size was %s)', cpl, peerId, this.routingTable.size)
136
+
137
+ const controller = new TimeoutController(this.refreshQueryTimeout)
138
+
139
+ try {
140
+ const peers = await length(this.peerRouting.getClosestPeers(peerId.toBytes(), { signal: controller.signal }))
141
+
142
+ this.log(`found ${peers} peers that were close to imaginary peer %p`, peerId)
143
+ this.log('finished refreshing cpl %s with key %p (routing table size is now %s)', cpl, peerId, this.routingTable.size)
144
+ } finally {
145
+ controller.clear()
146
+ }
147
+ }
148
+
149
+ _getTrackedCommonPrefixLengthsForRefresh (maxCommonPrefix: number) {
150
+ if (maxCommonPrefix > MAX_COMMON_PREFIX_LENGTH) {
151
+ maxCommonPrefix = MAX_COMMON_PREFIX_LENGTH
152
+ }
153
+
154
+ const dates = []
155
+
156
+ for (let i = 0; i <= maxCommonPrefix; i++) {
157
+ // defaults to the zero value if we haven't refreshed it yet.
158
+ dates[i] = this.commonPrefixLengthRefreshedAt[i] ?? new Date()
159
+ }
160
+
161
+ return dates
162
+ }
163
+
164
+ async _generateRandomPeerId (targetCommonPrefixLength: number) {
165
+ if (this.routingTable.kb == null) {
166
+ throw new Error('Routing table not started')
167
+ }
168
+
169
+ const randomData = randomBytes(2)
170
+ const randomUint16 = (randomData[1] << 8) + randomData[0]
171
+
172
+ const key = await this._makePeerId(this.routingTable.kb.localNodeId, randomUint16, targetCommonPrefixLength)
173
+
174
+ return peerIdFromBytes(key)
175
+ }
176
+
177
+ async _makePeerId (localKadId: Uint8Array, randomPrefix: number, targetCommonPrefixLength: number) {
178
+ if (targetCommonPrefixLength > MAX_COMMON_PREFIX_LENGTH) {
179
+ throw new Error(`Cannot generate peer ID for common prefix length greater than ${MAX_COMMON_PREFIX_LENGTH}`)
180
+ }
181
+
182
+ const view = new DataView(localKadId.buffer, localKadId.byteOffset, localKadId.byteLength)
183
+ const localPrefix = view.getUint16(0, false)
184
+
185
+ // For host with ID `L`, an ID `K` belongs to a bucket with ID `B` ONLY IF CommonPrefixLen(L,K) is EXACTLY B.
186
+ // Hence, to achieve a targetPrefix `T`, we must toggle the (T+1)th bit in L & then copy (T+1) bits from L
187
+ // to our randomly generated prefix.
188
+ const toggledLocalPrefix = localPrefix ^ (0x8000 >> targetCommonPrefixLength)
189
+
190
+ // Combine the toggled local prefix and the random bits at the correct offset
191
+ // such that ONLY the first `targetCommonPrefixLength` bits match the local ID.
192
+ const mask = 65535 << (16 - (targetCommonPrefixLength + 1))
193
+ const targetPrefix = (toggledLocalPrefix & mask) | (randomPrefix & ~mask)
194
+
195
+ // Convert to a known peer ID.
196
+ const keyPrefix = GENERATED_PREFIXES[targetPrefix]
197
+
198
+ const keyBuffer = new ArrayBuffer(34)
199
+ const keyView = new DataView(keyBuffer, 0, keyBuffer.byteLength)
200
+ keyView.setUint8(0, sha256.code)
201
+ keyView.setUint8(1, 32)
202
+ keyView.setUint32(2, keyPrefix, false)
203
+
204
+ return new Uint8Array(keyView.buffer, keyView.byteOffset, keyView.byteLength)
205
+ }
206
+
207
+ /**
208
+ * returns the maximum common prefix length between any peer in the table
209
+ * and the current peer
210
+ */
211
+ _maxCommonPrefix () {
212
+ // xor our KadId with every KadId in the k-bucket tree,
213
+ // return the longest id prefix that is the same
214
+ let prefixLength = 0
215
+
216
+ for (const length of this._prefixLengths()) {
217
+ if (length > prefixLength) {
218
+ prefixLength = length
219
+ }
220
+ }
221
+
222
+ return prefixLength
223
+ }
224
+
225
+ /**
226
+ * Returns the number of peers in the table with a given prefix length
227
+ */
228
+ _numPeersForCpl (prefixLength: number) {
229
+ let count = 0
230
+
231
+ for (const length of this._prefixLengths()) {
232
+ if (length === prefixLength) {
233
+ count++
234
+ }
235
+ }
236
+
237
+ return count
238
+ }
239
+
240
+ /**
241
+ * Yields the common prefix length of every peer in the table
242
+ */
243
+ * _prefixLengths () {
244
+ if (this.routingTable.kb == null) {
245
+ return
246
+ }
247
+
248
+ for (const { id } of this.routingTable.kb.toIterable()) {
249
+ const distance = uint8ArrayXor(this.routingTable.kb.localNodeId, id)
250
+ let leadingZeros = 0
251
+
252
+ for (const byte of distance) {
253
+ if (byte === 0) {
254
+ leadingZeros++
255
+ } else {
256
+ break
257
+ }
258
+ }
259
+
260
+ yield leadingZeros
261
+ }
262
+ }
263
+ }
@@ -0,0 +1,63 @@
1
+ import { CID } from 'multiformats/cid'
2
+ import errcode from 'err-code'
3
+ import { logger } from '@libp2p/logger'
4
+ import type { Providers } from '../../providers'
5
+ import type { PeerId } from '@libp2p/interfaces/peer-id'
6
+ import type { DHTMessageHandler } from '../index.js'
7
+ import type { Message } from '../../message/index.js'
8
+
9
+ const log = logger('libp2p:kad-dht:rpc:handlers:add-provider')
10
+
11
+ export interface AddProviderHandlerOptions {
12
+ providers: Providers
13
+ }
14
+
15
+ export class AddProviderHandler implements DHTMessageHandler {
16
+ private readonly providers: Providers
17
+
18
+ constructor (options: AddProviderHandlerOptions) {
19
+ const { providers } = options
20
+ this.providers = providers
21
+ }
22
+
23
+ async handle (peerId: PeerId, msg: Message) {
24
+ log('start')
25
+
26
+ if (msg.key == null || msg.key.length === 0) {
27
+ throw errcode(new Error('Missing key'), 'ERR_MISSING_KEY')
28
+ }
29
+
30
+ let cid: CID
31
+ try {
32
+ // this is actually just the multihash, not the whole CID
33
+ cid = CID.decode(msg.key)
34
+ } catch (err: any) {
35
+ throw errcode(new Error('Invalid CID'), 'ERR_INVALID_CID')
36
+ }
37
+
38
+ if (msg.providerPeers == null || msg.providerPeers.length === 0) {
39
+ log.error('no providers found in message')
40
+ }
41
+
42
+ await Promise.all(
43
+ msg.providerPeers.map(async (pi) => {
44
+ // Ignore providers not from the originator
45
+ if (!pi.id.equals(peerId)) {
46
+ log('invalid provider peer %p from %p', pi.id, peerId)
47
+ return
48
+ }
49
+
50
+ if (pi.multiaddrs.length < 1) {
51
+ log('no valid addresses for provider %p. Ignore', peerId)
52
+ return
53
+ }
54
+
55
+ log('received provider %p for %s (addrs %s)', peerId, cid, pi.multiaddrs.map((m) => m.toString()))
56
+
57
+ await this.providers.addProvider(cid, pi.id)
58
+ })
59
+ )
60
+
61
+ return undefined
62
+ }
63
+ }
@@ -0,0 +1,57 @@
1
+ import { Message } from '../../message/index.js'
2
+ import { logger } from '@libp2p/logger'
3
+ import {
4
+ removePrivateAddresses,
5
+ removePublicAddresses
6
+ } from '../../utils.js'
7
+ import { pipe } from 'it-pipe'
8
+ import type { DHTMessageHandler } from '../index.js'
9
+ import type { PeerRouting } from '../../peer-routing/index.js'
10
+ import type { PeerId } from '@libp2p/interfaces/peer-id'
11
+ import map from 'it-map'
12
+ import filter from 'it-filter'
13
+ import all from 'it-all'
14
+
15
+ const log = logger('libp2p:kad-dht:rpc:handlers:find-node')
16
+
17
+ export interface FindNodeHandlerOptions {
18
+ peerRouting: PeerRouting
19
+ lan: boolean
20
+ }
21
+
22
+ export class FindNodeHandler implements DHTMessageHandler {
23
+ private readonly peerRouting: PeerRouting
24
+ private readonly lan: boolean
25
+
26
+ constructor (options: FindNodeHandlerOptions) {
27
+ const { peerRouting, lan } = options
28
+ this.peerRouting = peerRouting
29
+ this.lan = Boolean(lan)
30
+ }
31
+
32
+ /**
33
+ * Process `FindNode` DHT messages
34
+ */
35
+ async handle (peerId: PeerId, msg: Message) {
36
+ log('incoming request from %p for peers closer to %b', peerId, msg.key)
37
+
38
+ const mapper = this.lan ? removePublicAddresses : removePrivateAddresses
39
+
40
+ const closer = await pipe(
41
+ await this.peerRouting.getCloserPeersOffline(msg.key, peerId),
42
+ (source) => map(source, mapper),
43
+ (source) => filter(source, ({ multiaddrs }) => multiaddrs.length > 0),
44
+ async (source) => await all(source)
45
+ )
46
+
47
+ const response = new Message(msg.type, new Uint8Array(0), msg.clusterLevel)
48
+
49
+ if (closer.length > 0) {
50
+ response.closerPeers = closer
51
+ } else {
52
+ log('could not find any peers closer to %b than %p', msg.key, peerId)
53
+ }
54
+
55
+ return response
56
+ }
57
+ }