@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.
- package/LICENSE +4 -0
- package/README.md +105 -0
- package/dist/src/constants.d.ts +20 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +34 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/content-fetching/index.d.ts +55 -0
- package/dist/src/content-fetching/index.d.ts.map +1 -0
- package/dist/src/content-fetching/index.js +190 -0
- package/dist/src/content-fetching/index.js.map +1 -0
- package/dist/src/content-routing/index.d.ts +42 -0
- package/dist/src/content-routing/index.d.ts.map +1 -0
- package/dist/src/content-routing/index.js +129 -0
- package/dist/src/content-routing/index.js.map +1 -0
- package/dist/src/dual-kad-dht.d.ts +65 -0
- package/dist/src/dual-kad-dht.d.ts.map +1 -0
- package/dist/src/dual-kad-dht.js +191 -0
- package/dist/src/dual-kad-dht.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +15 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/kad-dht.d.ts +131 -0
- package/dist/src/kad-dht.d.ts.map +1 -0
- package/dist/src/kad-dht.js +268 -0
- package/dist/src/kad-dht.js.map +1 -0
- package/dist/src/message/dht.d.ts +297 -0
- package/dist/src/message/dht.js +921 -0
- package/dist/src/message/index.d.ts +32 -0
- package/dist/src/message/index.d.ts.map +1 -0
- package/dist/src/message/index.js +81 -0
- package/dist/src/message/index.js.map +1 -0
- package/dist/src/network.d.ts +60 -0
- package/dist/src/network.d.ts.map +1 -0
- package/dist/src/network.js +124 -0
- package/dist/src/network.js.map +1 -0
- package/dist/src/peer-list/index.d.ts +29 -0
- package/dist/src/peer-list/index.d.ts.map +1 -0
- package/dist/src/peer-list/index.js +44 -0
- package/dist/src/peer-list/index.js.map +1 -0
- package/dist/src/peer-list/peer-distance-list.d.ts +34 -0
- package/dist/src/peer-list/peer-distance-list.d.ts.map +1 -0
- package/dist/src/peer-list/peer-distance-list.js +64 -0
- package/dist/src/peer-list/peer-distance-list.js.map +1 -0
- package/dist/src/peer-routing/index.d.ts +71 -0
- package/dist/src/peer-routing/index.d.ts.map +1 -0
- package/dist/src/peer-routing/index.js +256 -0
- package/dist/src/peer-routing/index.js.map +1 -0
- package/dist/src/providers.d.ts +64 -0
- package/dist/src/providers.d.ts.map +1 -0
- package/dist/src/providers.js +208 -0
- package/dist/src/providers.js.map +1 -0
- package/dist/src/query/events.d.ts +46 -0
- package/dist/src/query/events.d.ts.map +1 -0
- package/dist/src/query/events.js +73 -0
- package/dist/src/query/events.js.map +1 -0
- package/dist/src/query/manager.d.ts +40 -0
- package/dist/src/query/manager.d.ts.map +1 -0
- package/dist/src/query/manager.js +140 -0
- package/dist/src/query/manager.js.map +1 -0
- package/dist/src/query/query-path.d.ts +58 -0
- package/dist/src/query/query-path.d.ts.map +1 -0
- package/dist/src/query/query-path.js +171 -0
- package/dist/src/query/query-path.js.map +1 -0
- package/dist/src/query/types.d.ts +16 -0
- package/dist/src/query/types.d.ts.map +1 -0
- package/dist/src/query/types.js +2 -0
- package/dist/src/query/types.js.map +1 -0
- package/dist/src/query-self.d.ts +31 -0
- package/dist/src/query-self.d.ts.map +1 -0
- package/dist/src/query-self.js +73 -0
- package/dist/src/query-self.js.map +1 -0
- package/dist/src/routing-table/generated-prefix-list-browser.d.ts +3 -0
- package/dist/src/routing-table/generated-prefix-list-browser.d.ts.map +1 -0
- package/dist/src/routing-table/generated-prefix-list-browser.js +1027 -0
- package/dist/src/routing-table/generated-prefix-list-browser.js.map +1 -0
- package/dist/src/routing-table/generated-prefix-list.d.ts +3 -0
- package/dist/src/routing-table/generated-prefix-list.d.ts.map +1 -0
- package/dist/src/routing-table/generated-prefix-list.js +4099 -0
- package/dist/src/routing-table/generated-prefix-list.js.map +1 -0
- package/dist/src/routing-table/index.d.ts +91 -0
- package/dist/src/routing-table/index.d.ts.map +1 -0
- package/dist/src/routing-table/index.js +183 -0
- package/dist/src/routing-table/index.js.map +1 -0
- package/dist/src/routing-table/refresh.d.ts +50 -0
- package/dist/src/routing-table/refresh.d.ts.map +1 -0
- package/dist/src/routing-table/refresh.js +204 -0
- package/dist/src/routing-table/refresh.js.map +1 -0
- package/dist/src/routing-table/types.d.ts +24 -0
- package/dist/src/routing-table/types.d.ts.map +1 -0
- package/dist/src/rpc/handlers/add-provider.d.ts +13 -0
- package/dist/src/rpc/handlers/add-provider.d.ts.map +1 -0
- package/dist/src/rpc/handlers/add-provider.js +42 -0
- package/dist/src/rpc/handlers/add-provider.js.map +1 -0
- package/dist/src/rpc/handlers/find-node.d.ts +18 -0
- package/dist/src/rpc/handlers/find-node.d.ts.map +1 -0
- package/dist/src/rpc/handlers/find-node.js +32 -0
- package/dist/src/rpc/handlers/find-node.js.map +1 -0
- package/dist/src/rpc/handlers/get-providers.d.ts +24 -0
- package/dist/src/rpc/handlers/get-providers.d.ts.map +1 -0
- package/dist/src/rpc/handlers/get-providers.js +60 -0
- package/dist/src/rpc/handlers/get-providers.js.map +1 -0
- package/dist/src/rpc/handlers/get-value.d.ts +27 -0
- package/dist/src/rpc/handlers/get-value.d.ts.map +1 -0
- package/dist/src/rpc/handlers/get-value.js +94 -0
- package/dist/src/rpc/handlers/get-value.js.map +1 -0
- package/dist/src/rpc/handlers/index.d.ts +13 -0
- package/dist/src/rpc/handlers/index.d.ts.map +1 -0
- package/dist/src/rpc/handlers/ping.d.ts +7 -0
- package/dist/src/rpc/handlers/ping.d.ts.map +1 -0
- package/dist/src/rpc/handlers/ping.js +9 -0
- package/dist/src/rpc/handlers/ping.js.map +1 -0
- package/dist/src/rpc/handlers/put-value.d.ts +18 -0
- package/dist/src/rpc/handlers/put-value.d.ts.map +1 -0
- package/dist/src/rpc/handlers/put-value.js +35 -0
- package/dist/src/rpc/handlers/put-value.js.map +1 -0
- package/dist/src/rpc/index.d.ts +38 -0
- package/dist/src/rpc/index.d.ts.map +1 -0
- package/dist/src/rpc/index.js +75 -0
- package/dist/src/rpc/index.js.map +1 -0
- package/dist/src/rpc/types.d.ts +6 -0
- package/dist/src/rpc/types.d.ts.map +1 -0
- package/dist/src/topology-listener.d.ts +33 -0
- package/dist/src/topology-listener.d.ts.map +1 -0
- package/dist/src/topology-listener.js +50 -0
- package/dist/src/topology-listener.js.map +1 -0
- package/dist/src/types.d.ts +143 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/utils.d.ts +33 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +89 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +200 -0
- package/src/constants.ts +50 -0
- package/src/content-fetching/index.ts +276 -0
- package/src/content-routing/index.ts +202 -0
- package/src/dual-kad-dht.ts +257 -0
- package/src/index.ts +21 -0
- package/src/kad-dht.ts +396 -0
- package/src/message/dht.d.ts +297 -0
- package/src/message/dht.js +921 -0
- package/src/message/dht.proto +75 -0
- package/src/message/index.ts +111 -0
- package/src/network.ts +185 -0
- package/src/peer-list/index.ts +54 -0
- package/src/peer-list/peer-distance-list.ts +93 -0
- package/src/peer-routing/index.ts +332 -0
- package/src/providers.ts +278 -0
- package/src/query/events.ts +126 -0
- package/src/query/manager.ts +188 -0
- package/src/query/query-path.ts +263 -0
- package/src/query/types.ts +22 -0
- package/src/query-self.ts +106 -0
- package/src/routing-table/generated-prefix-list-browser.ts +1026 -0
- package/src/routing-table/generated-prefix-list.ts +4098 -0
- package/src/routing-table/index.ts +265 -0
- package/src/routing-table/refresh.ts +263 -0
- package/src/rpc/handlers/add-provider.ts +63 -0
- package/src/rpc/handlers/find-node.ts +57 -0
- package/src/rpc/handlers/get-providers.ts +95 -0
- package/src/rpc/handlers/get-value.ts +130 -0
- package/src/rpc/handlers/ping.ts +13 -0
- package/src/rpc/handlers/put-value.ts +58 -0
- package/src/rpc/index.ts +118 -0
- package/src/topology-listener.ts +78 -0
- package/src/utils.ts +108 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { logger } from '@libp2p/logger'
|
|
2
|
+
import errCode from 'err-code'
|
|
3
|
+
import merge from 'it-merge'
|
|
4
|
+
import { queryErrorEvent } from './query/events.js'
|
|
5
|
+
import type { KadDHT } from './kad-dht.js'
|
|
6
|
+
import type { DHT, QueryOptions } from '@libp2p/interfaces/dht'
|
|
7
|
+
import { AbortOptions, EventEmitter, CustomEvent } from '@libp2p/interfaces'
|
|
8
|
+
import type { CID } from 'multiformats'
|
|
9
|
+
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
|
10
|
+
import type { PeerDiscoveryEvents } from '@libp2p/interfaces/peer-discovery'
|
|
11
|
+
import type { PeerStore } from '@libp2p/interfaces/peer-store'
|
|
12
|
+
|
|
13
|
+
const log = logger('libp2p:kad-dht')
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A DHT implementation modelled after Kademlia with S/Kademlia modifications.
|
|
17
|
+
* Original implementation in go: https://github.com/libp2p/go-libp2p-kad-dht.
|
|
18
|
+
*/
|
|
19
|
+
export class DualKadDHT extends EventEmitter<PeerDiscoveryEvents> implements DHT {
|
|
20
|
+
public wan: KadDHT
|
|
21
|
+
public lan: KadDHT
|
|
22
|
+
public peerId: PeerId
|
|
23
|
+
public peerStore: PeerStore
|
|
24
|
+
|
|
25
|
+
constructor (wan: KadDHT, lan: KadDHT, peerId: PeerId, peerStore: PeerStore) {
|
|
26
|
+
super()
|
|
27
|
+
|
|
28
|
+
this.wan = wan
|
|
29
|
+
this.lan = lan
|
|
30
|
+
this.peerId = peerId
|
|
31
|
+
this.peerStore = peerStore
|
|
32
|
+
|
|
33
|
+
// handle peers being discovered during processing of DHT messages
|
|
34
|
+
this.wan.addEventListener('peer', (evt) => {
|
|
35
|
+
this.dispatchEvent(new CustomEvent('peer', {
|
|
36
|
+
detail: evt.detail
|
|
37
|
+
}))
|
|
38
|
+
})
|
|
39
|
+
this.lan.addEventListener('peer', (evt) => {
|
|
40
|
+
this.dispatchEvent(new CustomEvent('peer', {
|
|
41
|
+
detail: evt.detail
|
|
42
|
+
}))
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Is this DHT running.
|
|
48
|
+
*/
|
|
49
|
+
isStarted () {
|
|
50
|
+
return this.wan.isStarted() && this.lan.isStarted()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* If 'server' this node will respond to DHT queries, if 'client' this node will not
|
|
55
|
+
*/
|
|
56
|
+
async getMode () {
|
|
57
|
+
return await this.wan.getMode()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* If 'server' this node will respond to DHT queries, if 'client' this node will not
|
|
62
|
+
*/
|
|
63
|
+
async setMode (mode: 'client' | 'server') {
|
|
64
|
+
await this.wan.setMode(mode)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Start listening to incoming connections.
|
|
69
|
+
*/
|
|
70
|
+
async start () {
|
|
71
|
+
await Promise.all([
|
|
72
|
+
this.lan.start(),
|
|
73
|
+
this.wan.start()
|
|
74
|
+
])
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Stop accepting incoming connections and sending outgoing
|
|
79
|
+
* messages.
|
|
80
|
+
*/
|
|
81
|
+
async stop () {
|
|
82
|
+
await Promise.all([
|
|
83
|
+
this.lan.stop(),
|
|
84
|
+
this.wan.stop()
|
|
85
|
+
])
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Store the given key/value pair in the DHT
|
|
90
|
+
*/
|
|
91
|
+
async * put (key: Uint8Array, value: Uint8Array, options: QueryOptions = {}) {
|
|
92
|
+
let counterAll = 0
|
|
93
|
+
let counterSuccess = 0
|
|
94
|
+
|
|
95
|
+
for await (const event of merge(
|
|
96
|
+
this.lan.put(key, value, options),
|
|
97
|
+
this.wan.put(key, value, options)
|
|
98
|
+
)) {
|
|
99
|
+
yield event
|
|
100
|
+
|
|
101
|
+
if (event.name === 'SENDING_QUERY' && event.messageName === 'PUT_VALUE') {
|
|
102
|
+
counterAll++
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (event.name === 'PEER_RESPONSE' && event.messageName === 'PUT_VALUE') {
|
|
106
|
+
counterSuccess++
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Ensure we have a default `minPeers`
|
|
111
|
+
const minPeers = options.minPeers == null ? counterAll ?? 1 : options.minPeers
|
|
112
|
+
|
|
113
|
+
// verify if we were able to put to enough peers
|
|
114
|
+
if (counterSuccess < minPeers) {
|
|
115
|
+
const error = errCode(new Error(`Failed to put value to enough peers: ${counterSuccess}/${minPeers}`), 'ERR_NOT_ENOUGH_PUT_PEERS')
|
|
116
|
+
log.error(error)
|
|
117
|
+
throw error
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get the value that corresponds to the passed key
|
|
123
|
+
*/
|
|
124
|
+
async * get (key: Uint8Array, options: QueryOptions = {}) { // eslint-disable-line require-await
|
|
125
|
+
let queriedPeers = false
|
|
126
|
+
let foundValue = false
|
|
127
|
+
|
|
128
|
+
for await (const event of merge(
|
|
129
|
+
this.lan.get(key, options),
|
|
130
|
+
this.wan.get(key, options)
|
|
131
|
+
)) {
|
|
132
|
+
yield event
|
|
133
|
+
|
|
134
|
+
if (event.name === 'DIALING_PEER') {
|
|
135
|
+
queriedPeers = true
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (event.name === 'VALUE') {
|
|
139
|
+
queriedPeers = true
|
|
140
|
+
|
|
141
|
+
if (event.value != null) {
|
|
142
|
+
foundValue = true
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (event.name === 'SENDING_QUERY') {
|
|
147
|
+
queriedPeers = true
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!queriedPeers) {
|
|
152
|
+
throw errCode(new Error('No peers found in routing table!'), 'ERR_NO_PEERS_IN_ROUTING_TABLE')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!foundValue) {
|
|
156
|
+
yield queryErrorEvent({
|
|
157
|
+
from: this.peerId,
|
|
158
|
+
error: errCode(new Error('Not found'), 'ERR_NOT_FOUND')
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ----------- Content Routing
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Announce to the network that we can provide given key's value
|
|
167
|
+
*/
|
|
168
|
+
async * provide (key: CID, options: AbortOptions = {}) { // eslint-disable-line require-await
|
|
169
|
+
let sent = 0
|
|
170
|
+
let success = 0
|
|
171
|
+
const errors = []
|
|
172
|
+
|
|
173
|
+
const dhts = [this.lan]
|
|
174
|
+
|
|
175
|
+
// only run provide on the wan if we are in server mode
|
|
176
|
+
if ((await this.wan.getMode()) === 'server') {
|
|
177
|
+
dhts.push(this.wan)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for await (const event of merge(...dhts.map(dht => dht.provide(key, options)))) {
|
|
181
|
+
yield event
|
|
182
|
+
|
|
183
|
+
if (event.name === 'SENDING_QUERY') {
|
|
184
|
+
sent++
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (event.name === 'QUERY_ERROR') {
|
|
188
|
+
errors.push(event.error)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (event.name === 'PEER_RESPONSE' && event.messageName === 'ADD_PROVIDER') {
|
|
192
|
+
log('sent provider record for %s to %p', key, event.from)
|
|
193
|
+
success++
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (success === 0) {
|
|
198
|
+
if (errors.length > 0) {
|
|
199
|
+
// if all sends failed, throw an error to inform the caller
|
|
200
|
+
throw errCode(new Error(`Failed to provide to ${errors.length} of ${sent} peers`), 'ERR_PROVIDES_FAILED', { errors })
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
throw errCode(new Error('Failed to provide - no peers found'), 'ERR_PROVIDES_FAILED')
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Search the dht for up to `K` providers of the given CID
|
|
209
|
+
*/
|
|
210
|
+
async * findProviders (key: CID, options: QueryOptions = {}) {
|
|
211
|
+
yield * merge(
|
|
212
|
+
this.lan.findProviders(key, options),
|
|
213
|
+
this.wan.findProviders(key, options)
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ----------- Peer Routing -----------
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Search for a peer with the given ID
|
|
221
|
+
*/
|
|
222
|
+
async * findPeer (id: PeerId, options: QueryOptions = {}) {
|
|
223
|
+
let queriedPeers = false
|
|
224
|
+
|
|
225
|
+
for await (const event of merge(
|
|
226
|
+
this.lan.findPeer(id, options),
|
|
227
|
+
this.wan.findPeer(id, options)
|
|
228
|
+
)) {
|
|
229
|
+
yield event
|
|
230
|
+
|
|
231
|
+
if (event.name === 'SENDING_QUERY' || event.name === 'FINAL_PEER') {
|
|
232
|
+
queriedPeers = true
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!queriedPeers) {
|
|
237
|
+
throw errCode(new Error('Peer lookup failed'), 'ERR_LOOKUP_FAILED')
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Kademlia 'node lookup' operation
|
|
243
|
+
*/
|
|
244
|
+
async * getClosestPeers (key: Uint8Array, options: QueryOptions = {}) {
|
|
245
|
+
yield * merge(
|
|
246
|
+
this.lan.getClosestPeers(key, options),
|
|
247
|
+
this.wan.getClosestPeers(key, options)
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async refreshRoutingTable () {
|
|
252
|
+
await Promise.all([
|
|
253
|
+
this.lan.refreshRoutingTable(),
|
|
254
|
+
this.wan.refreshRoutingTable()
|
|
255
|
+
])
|
|
256
|
+
}
|
|
257
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { KadDHT, KadDHTOptions } from './kad-dht.js'
|
|
2
|
+
import { DualKadDHT } from './dual-kad-dht.js'
|
|
3
|
+
import type { DHT } from '@libp2p/interfaces/dht'
|
|
4
|
+
|
|
5
|
+
export function createKadDHT (opts: KadDHTOptions): DHT {
|
|
6
|
+
return new DualKadDHT(
|
|
7
|
+
new KadDHT({
|
|
8
|
+
...opts,
|
|
9
|
+
protocol: '/ipfs/kad/1.0.0',
|
|
10
|
+
lan: false
|
|
11
|
+
}),
|
|
12
|
+
new KadDHT({
|
|
13
|
+
...opts,
|
|
14
|
+
protocol: '/ipfs/lan/kad/1.0.0',
|
|
15
|
+
clientMode: false,
|
|
16
|
+
lan: true
|
|
17
|
+
}),
|
|
18
|
+
opts.peerId,
|
|
19
|
+
opts.peerStore
|
|
20
|
+
)
|
|
21
|
+
}
|
package/src/kad-dht.ts
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { RoutingTable } from './routing-table/index.js'
|
|
2
|
+
import { RoutingTableRefresh } from './routing-table/refresh.js'
|
|
3
|
+
import { Network } from './network.js'
|
|
4
|
+
import { ContentFetching } from './content-fetching/index.js'
|
|
5
|
+
import { ContentRouting } from './content-routing/index.js'
|
|
6
|
+
import { PeerRouting } from './peer-routing/index.js'
|
|
7
|
+
import { Providers } from './providers.js'
|
|
8
|
+
import { QueryManager } from './query/manager.js'
|
|
9
|
+
import { RPC } from './rpc/index.js'
|
|
10
|
+
import { TopologyListener } from './topology-listener.js'
|
|
11
|
+
import { QuerySelf } from './query-self.js'
|
|
12
|
+
import {
|
|
13
|
+
removePrivateAddresses,
|
|
14
|
+
removePublicAddresses
|
|
15
|
+
} from './utils.js'
|
|
16
|
+
import { Logger, logger } from '@libp2p/logger'
|
|
17
|
+
import type { DHT, QueryOptions, Validators, Selectors } from '@libp2p/interfaces/dht'
|
|
18
|
+
import type { PeerData } from '@libp2p/interfaces/peer-data'
|
|
19
|
+
import { CustomEvent, EventEmitter } from '@libp2p/interfaces'
|
|
20
|
+
import type { Addressable, Dialer } from '@libp2p/interfaces'
|
|
21
|
+
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
|
22
|
+
import type { PeerStore } from '@libp2p/interfaces/peer-store'
|
|
23
|
+
import type { ComponentMetricsTracker } from '@libp2p/interfaces/metrics'
|
|
24
|
+
import type { Datastore } from 'interface-datastore'
|
|
25
|
+
import type { Registrar } from '@libp2p/interfaces/registrar'
|
|
26
|
+
import type { CID } from 'multiformats/cid'
|
|
27
|
+
import type { PeerDiscoveryEvents } from '@libp2p/interfaces/peer-discovery'
|
|
28
|
+
|
|
29
|
+
export interface KadDHTOptions {
|
|
30
|
+
/**
|
|
31
|
+
* libp2p registrar handle protocol
|
|
32
|
+
*/
|
|
33
|
+
protocol: string
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* k-bucket size (default 20)
|
|
37
|
+
*/
|
|
38
|
+
kBucketSize?: number
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* If true, the DHT will not respond to queries. This should be true if your node will not be dialable. (default: false)
|
|
42
|
+
*/
|
|
43
|
+
clientMode?: boolean
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* validators object with namespace as keys and function(key, record, callback)
|
|
47
|
+
*/
|
|
48
|
+
validators: Validators
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* selectors object with namespace as keys and function(key, records)
|
|
52
|
+
*/
|
|
53
|
+
selectors: Selectors
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* how often to search the network for peers close to ourselves
|
|
57
|
+
*/
|
|
58
|
+
querySelfInterval: number
|
|
59
|
+
lan: boolean
|
|
60
|
+
bootstrapPeers: PeerData[]
|
|
61
|
+
dialer: Dialer
|
|
62
|
+
addressable: Addressable
|
|
63
|
+
peerStore: PeerStore
|
|
64
|
+
peerId: PeerId
|
|
65
|
+
datastore: Datastore
|
|
66
|
+
registrar: Registrar
|
|
67
|
+
metrics?: ComponentMetricsTracker
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A DHT implementation modelled after Kademlia with S/Kademlia modifications.
|
|
72
|
+
* Original implementation in go: https://github.com/libp2p/go-libp2p-kad-dht.
|
|
73
|
+
*/
|
|
74
|
+
export class KadDHT extends EventEmitter<PeerDiscoveryEvents> implements DHT {
|
|
75
|
+
private readonly log: Logger
|
|
76
|
+
private running: boolean
|
|
77
|
+
public protocol: string
|
|
78
|
+
private readonly kBucketSize: number
|
|
79
|
+
private clientMode: boolean
|
|
80
|
+
private readonly bootstrapPeers: PeerData[]
|
|
81
|
+
public routingTable: RoutingTable
|
|
82
|
+
public providers: Providers
|
|
83
|
+
private readonly lan: boolean
|
|
84
|
+
private readonly validators: Validators
|
|
85
|
+
private readonly selectors: Selectors
|
|
86
|
+
public network: Network
|
|
87
|
+
private readonly queryManager: QueryManager
|
|
88
|
+
public peerRouting: PeerRouting
|
|
89
|
+
private readonly contentFetching: ContentFetching
|
|
90
|
+
private readonly contentRouting: ContentRouting
|
|
91
|
+
private readonly routingTableRefresh: RoutingTableRefresh
|
|
92
|
+
private readonly rpc: RPC
|
|
93
|
+
private readonly topologyListener: TopologyListener
|
|
94
|
+
private readonly querySelf: QuerySelf
|
|
95
|
+
public addressable: Addressable
|
|
96
|
+
public registrar: Registrar
|
|
97
|
+
private registrarHandleId?: string
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Create a new KadDHT
|
|
101
|
+
*/
|
|
102
|
+
constructor (options: KadDHTOptions) {
|
|
103
|
+
super()
|
|
104
|
+
|
|
105
|
+
const {
|
|
106
|
+
kBucketSize,
|
|
107
|
+
clientMode,
|
|
108
|
+
validators,
|
|
109
|
+
selectors,
|
|
110
|
+
querySelfInterval,
|
|
111
|
+
lan,
|
|
112
|
+
protocol,
|
|
113
|
+
bootstrapPeers,
|
|
114
|
+
dialer,
|
|
115
|
+
addressable,
|
|
116
|
+
peerId,
|
|
117
|
+
peerStore,
|
|
118
|
+
metrics,
|
|
119
|
+
datastore,
|
|
120
|
+
registrar
|
|
121
|
+
} = options
|
|
122
|
+
|
|
123
|
+
this.running = false
|
|
124
|
+
this.log = logger(`libp2p:kad-dht:${lan ? 'lan' : 'wan'}`)
|
|
125
|
+
this.protocol = protocol ?? '/ipfs/kad/1.0.0'
|
|
126
|
+
this.kBucketSize = kBucketSize ?? 20
|
|
127
|
+
this.clientMode = clientMode ?? true
|
|
128
|
+
this.bootstrapPeers = bootstrapPeers ?? []
|
|
129
|
+
this.addressable = addressable
|
|
130
|
+
this.registrar = registrar
|
|
131
|
+
this.routingTable = new RoutingTable({
|
|
132
|
+
peerId,
|
|
133
|
+
dialer,
|
|
134
|
+
kBucketSize,
|
|
135
|
+
metrics,
|
|
136
|
+
lan
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
this.providers = new Providers({
|
|
140
|
+
datastore
|
|
141
|
+
})
|
|
142
|
+
this.lan = lan
|
|
143
|
+
this.validators = validators ?? {}
|
|
144
|
+
this.selectors = selectors ?? {}
|
|
145
|
+
this.network = new Network({
|
|
146
|
+
dialer,
|
|
147
|
+
protocol: this.protocol,
|
|
148
|
+
lan,
|
|
149
|
+
peerId
|
|
150
|
+
})
|
|
151
|
+
this.queryManager = new QueryManager({
|
|
152
|
+
peerId: peerId,
|
|
153
|
+
// Number of disjoint query paths to use - This is set to `kBucketSize/2` per the S/Kademlia paper
|
|
154
|
+
disjointPaths: Math.ceil(this.kBucketSize / 2),
|
|
155
|
+
metrics,
|
|
156
|
+
lan
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// DHT components
|
|
160
|
+
this.peerRouting = new PeerRouting({
|
|
161
|
+
peerId,
|
|
162
|
+
routingTable: this.routingTable,
|
|
163
|
+
peerStore,
|
|
164
|
+
network: this.network,
|
|
165
|
+
validators: this.validators,
|
|
166
|
+
queryManager: this.queryManager,
|
|
167
|
+
lan
|
|
168
|
+
})
|
|
169
|
+
this.contentFetching = new ContentFetching({
|
|
170
|
+
peerId,
|
|
171
|
+
datastore,
|
|
172
|
+
validators: this.validators,
|
|
173
|
+
selectors: this.selectors,
|
|
174
|
+
peerRouting: this.peerRouting,
|
|
175
|
+
queryManager: this.queryManager,
|
|
176
|
+
routingTable: this.routingTable,
|
|
177
|
+
network: this.network,
|
|
178
|
+
lan
|
|
179
|
+
})
|
|
180
|
+
this.contentRouting = new ContentRouting({
|
|
181
|
+
peerId,
|
|
182
|
+
network: this.network,
|
|
183
|
+
peerRouting: this.peerRouting,
|
|
184
|
+
queryManager: this.queryManager,
|
|
185
|
+
routingTable: this.routingTable,
|
|
186
|
+
providers: this.providers,
|
|
187
|
+
peerStore,
|
|
188
|
+
lan
|
|
189
|
+
})
|
|
190
|
+
this.routingTableRefresh = new RoutingTableRefresh({
|
|
191
|
+
peerRouting: this.peerRouting,
|
|
192
|
+
routingTable: this.routingTable,
|
|
193
|
+
lan
|
|
194
|
+
})
|
|
195
|
+
this.rpc = new RPC({
|
|
196
|
+
peerId,
|
|
197
|
+
routingTable: this.routingTable,
|
|
198
|
+
providers: this.providers,
|
|
199
|
+
peerRouting: this.peerRouting,
|
|
200
|
+
datastore,
|
|
201
|
+
validators: this.validators,
|
|
202
|
+
keyBook: peerStore.keyBook,
|
|
203
|
+
addressBook: peerStore.addressBook,
|
|
204
|
+
lan
|
|
205
|
+
})
|
|
206
|
+
this.topologyListener = new TopologyListener({
|
|
207
|
+
registrar,
|
|
208
|
+
protocol: this.protocol,
|
|
209
|
+
lan
|
|
210
|
+
})
|
|
211
|
+
this.querySelf = new QuerySelf({
|
|
212
|
+
peerId,
|
|
213
|
+
peerRouting: this.peerRouting,
|
|
214
|
+
interval: querySelfInterval,
|
|
215
|
+
lan
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
// handle peers being discovered during processing of DHT messages
|
|
219
|
+
this.network.addEventListener('peer', (evt) => {
|
|
220
|
+
const peerData = evt.detail
|
|
221
|
+
|
|
222
|
+
this.onPeerConnect(peerData).catch(err => {
|
|
223
|
+
this.log.error('could not add %p to routing table', peerData.id, err)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
this.dispatchEvent(new CustomEvent('peer', {
|
|
227
|
+
detail: peerData
|
|
228
|
+
}))
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
// handle peers being discovered via other peer discovery mechanisms
|
|
232
|
+
this.topologyListener.addEventListener('peer', (evt) => {
|
|
233
|
+
const peerId = evt.detail
|
|
234
|
+
|
|
235
|
+
Promise.resolve().then(async () => {
|
|
236
|
+
const multiaddrs = await peerStore.addressBook.get(peerId)
|
|
237
|
+
|
|
238
|
+
const peerData = {
|
|
239
|
+
id: peerId,
|
|
240
|
+
multiaddrs: multiaddrs.map(addr => addr.multiaddr),
|
|
241
|
+
protocols: []
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
await this.onPeerConnect(peerData)
|
|
245
|
+
}).catch(err => {
|
|
246
|
+
this.log.error('could not add %p to routing table', peerId, err)
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async onPeerConnect (peerData: PeerData) {
|
|
252
|
+
this.log('peer %p connected', peerData.id)
|
|
253
|
+
|
|
254
|
+
if (this.lan) {
|
|
255
|
+
peerData = removePublicAddresses(peerData)
|
|
256
|
+
} else {
|
|
257
|
+
peerData = removePrivateAddresses(peerData)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (peerData.multiaddrs.length === 0) {
|
|
261
|
+
this.log('ignoring %p as they do not have any %s addresses in %s', peerData.id, this.lan ? 'private' : 'public', peerData.multiaddrs.map(addr => addr.toString()))
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
await this.routingTable.add(peerData.id)
|
|
267
|
+
} catch (err: any) {
|
|
268
|
+
this.log.error('could not add %p to routing table', peerData.id, err)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Is this DHT running.
|
|
274
|
+
*/
|
|
275
|
+
isStarted () {
|
|
276
|
+
return this.running
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* If 'server' this node will respond to DHT queries, if 'client' this node will not
|
|
281
|
+
*/
|
|
282
|
+
async getMode () {
|
|
283
|
+
return this.clientMode ? 'client' : 'server'
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* If 'server' this node will respond to DHT queries, if 'client' this node will not
|
|
288
|
+
*/
|
|
289
|
+
async setMode (mode: 'client' | 'server') {
|
|
290
|
+
if (this.registrarHandleId != null) {
|
|
291
|
+
await this.registrar.unhandle(this.registrarHandleId)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (mode === 'client') {
|
|
295
|
+
this.log('enabling client mode')
|
|
296
|
+
this.clientMode = true
|
|
297
|
+
} else {
|
|
298
|
+
this.log('enabling server mode')
|
|
299
|
+
this.clientMode = false
|
|
300
|
+
this.registrarHandleId = await this.registrar.handle(this.protocol, this.rpc.onIncomingStream.bind(this.rpc))
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Start listening to incoming connections.
|
|
306
|
+
*/
|
|
307
|
+
async start () {
|
|
308
|
+
this.running = true
|
|
309
|
+
|
|
310
|
+
// Only respond to queries when not in client mode
|
|
311
|
+
await this.setMode(this.clientMode ? 'client' : 'server')
|
|
312
|
+
|
|
313
|
+
await Promise.all([
|
|
314
|
+
this.providers.start(),
|
|
315
|
+
this.queryManager.start(),
|
|
316
|
+
this.network.start(),
|
|
317
|
+
this.routingTable.start(),
|
|
318
|
+
this.topologyListener.start(),
|
|
319
|
+
this.querySelf.start()
|
|
320
|
+
])
|
|
321
|
+
|
|
322
|
+
await Promise.all(
|
|
323
|
+
this.bootstrapPeers.map(async peerData => await this.routingTable.add(peerData.id))
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
await this.routingTableRefresh.start()
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Stop accepting incoming connections and sending outgoing
|
|
331
|
+
* messages.
|
|
332
|
+
*/
|
|
333
|
+
async stop () {
|
|
334
|
+
this.running = false
|
|
335
|
+
|
|
336
|
+
await Promise.all([
|
|
337
|
+
this.providers.stop(),
|
|
338
|
+
this.queryManager.stop(),
|
|
339
|
+
this.network.stop(),
|
|
340
|
+
this.routingTable.stop(),
|
|
341
|
+
this.routingTableRefresh.stop(),
|
|
342
|
+
this.topologyListener.stop(),
|
|
343
|
+
this.querySelf.stop()
|
|
344
|
+
])
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Store the given key/value pair in the DHT
|
|
349
|
+
*/
|
|
350
|
+
async * put (key: Uint8Array, value: Uint8Array, options: QueryOptions = {}) { // eslint-disable-line require-await
|
|
351
|
+
yield * this.contentFetching.put(key, value, options)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Get the value that corresponds to the passed key
|
|
356
|
+
*/
|
|
357
|
+
async * get (key: Uint8Array, options: QueryOptions = {}) { // eslint-disable-line require-await
|
|
358
|
+
yield * this.contentFetching.get(key, options)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ----------- Content Routing
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Announce to the network that we can provide given key's value
|
|
365
|
+
*/
|
|
366
|
+
async * provide (key: CID, options: QueryOptions = {}) { // eslint-disable-line require-await
|
|
367
|
+
yield * this.contentRouting.provide(key, this.addressable.multiaddrs, options)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Search the dht for providers of the given CID
|
|
372
|
+
*/
|
|
373
|
+
async * findProviders (key: CID, options: QueryOptions = {}) {
|
|
374
|
+
yield * this.contentRouting.findProviders(key, options)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ----------- Peer Routing -----------
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Search for a peer with the given ID
|
|
381
|
+
*/
|
|
382
|
+
async * findPeer (id: PeerId, options: QueryOptions = {}) { // eslint-disable-line require-await
|
|
383
|
+
yield * this.peerRouting.findPeer(id, options)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Kademlia 'node lookup' operation
|
|
388
|
+
*/
|
|
389
|
+
async * getClosestPeers (key: Uint8Array, options: QueryOptions = {}) {
|
|
390
|
+
yield * this.peerRouting.getClosestPeers(key, options)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async refreshRoutingTable () {
|
|
394
|
+
await this.routingTableRefresh.refreshTable(true)
|
|
395
|
+
}
|
|
396
|
+
}
|