libp2p 0.35.8 → 0.36.0-d44bd90
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/dist/index.min.js +55 -0
- package/dist/src/circuit/auto-relay.d.ts +8 -10
- package/dist/src/circuit/auto-relay.d.ts.map +1 -1
- package/dist/src/config.d.ts +2 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/connection-manager/auto-dialler.d.ts +1 -1
- package/dist/src/connection-manager/auto-dialler.d.ts.map +1 -1
- package/dist/src/connection-manager/index.d.ts +1 -2
- package/dist/src/connection-manager/index.d.ts.map +1 -1
- package/dist/src/content-routing/utils.d.ts +3 -3
- package/dist/src/content-routing/utils.d.ts.map +1 -1
- package/dist/src/dialer/dial-request.d.ts.map +1 -1
- package/dist/src/dialer/index.d.ts +12 -7
- package/dist/src/dialer/index.d.ts.map +1 -1
- package/dist/src/errors.d.ts +2 -0
- package/dist/src/fetch/constants.d.ts +2 -0
- package/dist/src/fetch/constants.d.ts.map +1 -0
- package/dist/src/fetch/index.d.ts +76 -0
- package/dist/src/fetch/index.d.ts.map +1 -0
- package/dist/src/fetch/proto.d.ts +134 -0
- package/dist/src/identify/index.d.ts +4 -4
- package/dist/src/identify/index.d.ts.map +1 -1
- package/dist/src/index.d.ts +26 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/metrics/index.d.ts +9 -4
- package/dist/src/metrics/index.d.ts.map +1 -1
- package/dist/src/metrics/tracked-map.d.ts +6 -1
- package/dist/src/metrics/tracked-map.d.ts.map +1 -1
- package/dist/src/peer-routing.d.ts +1 -1
- package/dist/src/peer-routing.d.ts.map +1 -1
- package/dist/src/peer-store/address-book.d.ts +30 -84
- package/dist/src/peer-store/address-book.d.ts.map +1 -1
- package/dist/src/peer-store/index.d.ts +28 -94
- package/dist/src/peer-store/index.d.ts.map +1 -1
- package/dist/src/peer-store/key-book.d.ts +31 -15
- package/dist/src/peer-store/key-book.d.ts.map +1 -1
- package/dist/src/peer-store/metadata-book.d.ts +33 -29
- package/dist/src/peer-store/metadata-book.d.ts.map +1 -1
- package/dist/src/peer-store/pb/peer.d.ts +222 -0
- package/dist/src/peer-store/proto-book.d.ts +27 -28
- package/dist/src/peer-store/proto-book.d.ts.map +1 -1
- package/dist/src/peer-store/store.d.ts +73 -0
- package/dist/src/peer-store/store.d.ts.map +1 -0
- package/dist/src/peer-store/types.d.ts +202 -0
- package/dist/src/peer-store/types.d.ts.map +1 -0
- package/dist/src/ping/index.d.ts +32 -25
- package/dist/src/ping/index.d.ts.map +1 -1
- package/dist/src/record/peer-record/index.d.ts +2 -2
- package/dist/src/record/peer-record/index.d.ts.map +1 -1
- package/dist/src/registrar.d.ts +5 -5
- package/dist/src/registrar.d.ts.map +1 -1
- package/dist/src/types.d.ts +90 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/upgrader.d.ts +8 -2
- package/dist/src/upgrader.d.ts.map +1 -1
- package/package.json +23 -26
- package/src/circuit/auto-relay.js +52 -38
- package/src/config.js +2 -0
- package/src/connection-manager/auto-dialler.js +20 -8
- package/src/connection-manager/index.js +42 -22
- package/src/content-routing/utils.js +4 -4
- package/src/dialer/dial-request.js +4 -1
- package/src/dialer/index.js +38 -8
- package/src/errors.js +2 -0
- package/src/fetch/README.md +36 -0
- package/src/fetch/constants.js +6 -0
- package/src/fetch/index.js +159 -0
- package/src/fetch/proto.d.ts +134 -0
- package/src/fetch/proto.js +333 -0
- package/src/fetch/proto.proto +15 -0
- package/src/identify/index.js +47 -38
- package/src/index.js +81 -34
- package/src/metrics/index.js +15 -8
- package/src/metrics/tracked-map.js +40 -14
- package/src/peer-routing.js +4 -1
- package/src/peer-store/address-book.js +254 -226
- package/src/peer-store/index.js +79 -110
- package/src/peer-store/key-book.js +99 -54
- package/src/peer-store/metadata-book.js +171 -101
- package/src/peer-store/pb/peer.d.ts +222 -0
- package/src/peer-store/pb/peer.js +643 -0
- package/src/peer-store/pb/peer.proto +31 -0
- package/src/peer-store/proto-book.js +171 -105
- package/src/peer-store/store.js +263 -0
- package/src/peer-store/types.ts +245 -0
- package/src/ping/index.js +51 -46
- package/src/record/peer-record/index.js +1 -1
- package/src/record/utils.js +1 -1
- package/src/registrar.js +4 -4
- package/src/types.ts +98 -0
- package/src/upgrader.js +32 -2
- package/dist/src/peer-store/book.d.ts +0 -81
- package/dist/src/peer-store/book.d.ts.map +0 -1
- package/dist/src/peer-store/persistent/consts.d.ts +0 -6
- package/dist/src/peer-store/persistent/consts.d.ts.map +0 -1
- package/dist/src/peer-store/persistent/index.d.ts +0 -129
- package/dist/src/peer-store/persistent/index.d.ts.map +0 -1
- package/dist/src/peer-store/persistent/pb/address-book.d.ts +0 -198
- package/dist/src/peer-store/persistent/pb/proto-book.d.ts +0 -59
- package/src/peer-store/book.js +0 -124
- package/src/peer-store/persistent/consts.js +0 -15
- package/src/peer-store/persistent/index.js +0 -408
- package/src/peer-store/persistent/pb/address-book.d.ts +0 -198
- package/src/peer-store/persistent/pb/address-book.js +0 -522
- package/src/peer-store/persistent/pb/address-book.proto +0 -27
- package/src/peer-store/persistent/pb/proto-book.d.ts +0 -59
- package/src/peer-store/persistent/pb/proto-book.js +0 -157
- package/src/peer-store/persistent/pb/proto-book.proto +0 -5
|
@@ -87,14 +87,22 @@ class ConnectionManager extends EventEmitter {
|
|
|
87
87
|
*
|
|
88
88
|
* @type {Map<string, number>}
|
|
89
89
|
*/
|
|
90
|
-
this._peerValues = trackedMap(
|
|
90
|
+
this._peerValues = trackedMap({
|
|
91
|
+
component: METRICS_COMPONENT,
|
|
92
|
+
metric: METRICS_PEER_VALUES,
|
|
93
|
+
metrics: this._libp2p.metrics
|
|
94
|
+
})
|
|
91
95
|
|
|
92
96
|
/**
|
|
93
97
|
* Map of connections per peer
|
|
94
98
|
*
|
|
95
99
|
* @type {Map<string, Connection[]>}
|
|
96
100
|
*/
|
|
97
|
-
this.connections = trackedMap(
|
|
101
|
+
this.connections = trackedMap({
|
|
102
|
+
component: METRICS_COMPONENT,
|
|
103
|
+
metric: METRICS_PEER_CONNECTIONS,
|
|
104
|
+
metrics: this._libp2p.metrics
|
|
105
|
+
})
|
|
98
106
|
|
|
99
107
|
this._started = false
|
|
100
108
|
this._timer = null
|
|
@@ -104,6 +112,9 @@ class ConnectionManager extends EventEmitter {
|
|
|
104
112
|
latencyCheckIntervalMs: this._options.pollInterval,
|
|
105
113
|
dataEmitIntervalMs: this._options.pollInterval
|
|
106
114
|
})
|
|
115
|
+
|
|
116
|
+
// This emitter gets listened to a lot
|
|
117
|
+
this.setMaxListeners(Infinity)
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
/**
|
|
@@ -187,19 +198,22 @@ class ConnectionManager extends EventEmitter {
|
|
|
187
198
|
*
|
|
188
199
|
* @private
|
|
189
200
|
*/
|
|
190
|
-
_checkMetrics () {
|
|
201
|
+
async _checkMetrics () {
|
|
191
202
|
if (this._libp2p.metrics) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
+
try {
|
|
204
|
+
const movingAverages = this._libp2p.metrics.global.movingAverages
|
|
205
|
+
// @ts-ignore moving averages object types
|
|
206
|
+
const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage()
|
|
207
|
+
await this._checkMaxLimit('maxReceivedData', received)
|
|
208
|
+
// @ts-ignore moving averages object types
|
|
209
|
+
const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage()
|
|
210
|
+
await this._checkMaxLimit('maxSentData', sent)
|
|
211
|
+
const total = received + sent
|
|
212
|
+
await this._checkMaxLimit('maxData', total)
|
|
213
|
+
log('metrics update', total)
|
|
214
|
+
} finally {
|
|
215
|
+
this._timer = retimer(this._checkMetrics, this._options.pollInterval)
|
|
216
|
+
}
|
|
203
217
|
}
|
|
204
218
|
}
|
|
205
219
|
|
|
@@ -207,9 +221,8 @@ class ConnectionManager extends EventEmitter {
|
|
|
207
221
|
* Tracks the incoming connection and check the connection limit
|
|
208
222
|
*
|
|
209
223
|
* @param {Connection} connection
|
|
210
|
-
* @returns {void}
|
|
211
224
|
*/
|
|
212
|
-
onConnect (connection) {
|
|
225
|
+
async onConnect (connection) {
|
|
213
226
|
const peerId = connection.remotePeer
|
|
214
227
|
const peerIdStr = peerId.toB58String()
|
|
215
228
|
const storedConn = this.connections.get(peerIdStr)
|
|
@@ -222,13 +235,13 @@ class ConnectionManager extends EventEmitter {
|
|
|
222
235
|
this.connections.set(peerIdStr, [connection])
|
|
223
236
|
}
|
|
224
237
|
|
|
225
|
-
this._libp2p.peerStore.keyBook.set(peerId, peerId.pubKey)
|
|
238
|
+
await this._libp2p.peerStore.keyBook.set(peerId, peerId.pubKey)
|
|
226
239
|
|
|
227
240
|
if (!this._peerValues.has(peerIdStr)) {
|
|
228
241
|
this._peerValues.set(peerIdStr, this._options.defaultPeerValue)
|
|
229
242
|
}
|
|
230
243
|
|
|
231
|
-
this._checkMaxLimit('maxConnections', this.size)
|
|
244
|
+
await this._checkMaxLimit('maxConnections', this.size)
|
|
232
245
|
}
|
|
233
246
|
|
|
234
247
|
/**
|
|
@@ -296,6 +309,9 @@ class ConnectionManager extends EventEmitter {
|
|
|
296
309
|
*/
|
|
297
310
|
_onLatencyMeasure (summary) {
|
|
298
311
|
this._checkMaxLimit('maxEventLoopDelay', summary.avgMs)
|
|
312
|
+
.catch(err => {
|
|
313
|
+
log.error(err)
|
|
314
|
+
})
|
|
299
315
|
}
|
|
300
316
|
|
|
301
317
|
/**
|
|
@@ -305,12 +321,12 @@ class ConnectionManager extends EventEmitter {
|
|
|
305
321
|
* @param {string} name - The name of the field to check limits for
|
|
306
322
|
* @param {number} value - The current value of the field
|
|
307
323
|
*/
|
|
308
|
-
_checkMaxLimit (name, value) {
|
|
324
|
+
async _checkMaxLimit (name, value) {
|
|
309
325
|
const limit = this._options[name]
|
|
310
326
|
log('checking limit of %s. current value: %d of %d', name, value, limit)
|
|
311
327
|
if (value > limit) {
|
|
312
328
|
log('%s: limit exceeded: %s, %d', this._peerId, name, value)
|
|
313
|
-
this._maybeDisconnectOne()
|
|
329
|
+
await this._maybeDisconnectOne()
|
|
314
330
|
}
|
|
315
331
|
}
|
|
316
332
|
|
|
@@ -320,7 +336,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
320
336
|
*
|
|
321
337
|
* @private
|
|
322
338
|
*/
|
|
323
|
-
_maybeDisconnectOne () {
|
|
339
|
+
async _maybeDisconnectOne () {
|
|
324
340
|
if (this._options.minConnections < this.connections.size) {
|
|
325
341
|
const peerValues = Array.from(new Map([...this._peerValues.entries()].sort((a, b) => a[1] - b[1])))
|
|
326
342
|
log('%s: sorted peer values: %j', this._peerId, peerValues)
|
|
@@ -331,7 +347,11 @@ class ConnectionManager extends EventEmitter {
|
|
|
331
347
|
log('%s: closing a connection to %j', this._peerId, peerId)
|
|
332
348
|
for (const connections of this.connections.values()) {
|
|
333
349
|
if (connections[0].remotePeer.toB58String() === peerId) {
|
|
334
|
-
connections[0].close()
|
|
350
|
+
connections[0].close().catch(err => {
|
|
351
|
+
log.error(err)
|
|
352
|
+
})
|
|
353
|
+
// TODO: should not need to invoke this manually
|
|
354
|
+
this.onDisconnect(connections[0])
|
|
335
355
|
break
|
|
336
356
|
}
|
|
337
357
|
}
|
|
@@ -14,12 +14,12 @@ const take = require('it-take')
|
|
|
14
14
|
* Store the multiaddrs from every peer in the passed peer store
|
|
15
15
|
*
|
|
16
16
|
* @param {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>} source
|
|
17
|
-
* @param {import('../peer-store')} peerStore
|
|
17
|
+
* @param {import('../peer-store/types').PeerStore} peerStore
|
|
18
18
|
*/
|
|
19
|
-
function storeAddresses (source, peerStore) {
|
|
20
|
-
|
|
19
|
+
async function * storeAddresses (source, peerStore) {
|
|
20
|
+
yield * map(source, async (peer) => {
|
|
21
21
|
// ensure we have the addresses for a given peer
|
|
22
|
-
peerStore.addressBook.add(peer.id, peer.multiaddrs)
|
|
22
|
+
await peerStore.addressBook.add(peer.id, peer.multiaddrs)
|
|
23
23
|
|
|
24
24
|
return peer
|
|
25
25
|
})
|
|
@@ -63,7 +63,10 @@ class DialRequest {
|
|
|
63
63
|
tokens.forEach(token => tokenHolder.push(token))
|
|
64
64
|
const dialAbortControllers = this.addrs.map(() => {
|
|
65
65
|
const controller = new AbortController()
|
|
66
|
-
|
|
66
|
+
try {
|
|
67
|
+
// fails on node < 15.4
|
|
68
|
+
setMaxListeners && setMaxListeners(Infinity, controller.signal)
|
|
69
|
+
} catch {}
|
|
67
70
|
|
|
68
71
|
return controller
|
|
69
72
|
})
|
package/src/dialer/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const debug = require('debug')
|
|
4
|
+
const all = require('it-all')
|
|
5
|
+
const filter = require('it-filter')
|
|
6
|
+
const { pipe } = require('it-pipe')
|
|
4
7
|
const log = Object.assign(debug('libp2p:dialer'), {
|
|
5
8
|
error: debug('libp2p:dialer:err')
|
|
6
9
|
})
|
|
@@ -30,15 +33,17 @@ const METRICS_PENDING_DIAL_TARGETS = 'pending-dial-targets'
|
|
|
30
33
|
/**
|
|
31
34
|
* @typedef {import('libp2p-interfaces/src/connection').Connection} Connection
|
|
32
35
|
* @typedef {import('peer-id')} PeerId
|
|
33
|
-
* @typedef {import('../peer-store')} PeerStore
|
|
34
|
-
* @typedef {import('../peer-store/
|
|
36
|
+
* @typedef {import('../peer-store/types').PeerStore} PeerStore
|
|
37
|
+
* @typedef {import('../peer-store/types').Address} Address
|
|
35
38
|
* @typedef {import('../transport-manager')} TransportManager
|
|
39
|
+
* @typedef {import('../types').ConnectionGater} ConnectionGater
|
|
36
40
|
*/
|
|
37
41
|
|
|
38
42
|
/**
|
|
39
43
|
* @typedef {Object} DialerProperties
|
|
40
44
|
* @property {PeerStore} peerStore
|
|
41
45
|
* @property {TransportManager} transportManager
|
|
46
|
+
* @property {ConnectionGater} connectionGater
|
|
42
47
|
*
|
|
43
48
|
* @typedef {(addr:Multiaddr) => Promise<string[]>} Resolver
|
|
44
49
|
*
|
|
@@ -70,6 +75,7 @@ class Dialer {
|
|
|
70
75
|
constructor ({
|
|
71
76
|
transportManager,
|
|
72
77
|
peerStore,
|
|
78
|
+
connectionGater,
|
|
73
79
|
addressSorter = publicAddressesFirst,
|
|
74
80
|
maxParallelDials = MAX_PARALLEL_DIALS,
|
|
75
81
|
maxAddrsToDial = MAX_ADDRS_TO_DIAL,
|
|
@@ -78,6 +84,7 @@ class Dialer {
|
|
|
78
84
|
resolvers = {},
|
|
79
85
|
metrics
|
|
80
86
|
}) {
|
|
87
|
+
this.connectionGater = connectionGater
|
|
81
88
|
this.transportManager = transportManager
|
|
82
89
|
this.peerStore = peerStore
|
|
83
90
|
this.addressSorter = addressSorter
|
|
@@ -88,10 +95,18 @@ class Dialer {
|
|
|
88
95
|
this.tokens = [...new Array(maxParallelDials)].map((_, index) => index)
|
|
89
96
|
|
|
90
97
|
/** @type {Map<string, PendingDial>} */
|
|
91
|
-
this._pendingDials = trackedMap(
|
|
98
|
+
this._pendingDials = trackedMap({
|
|
99
|
+
component: METRICS_COMPONENT,
|
|
100
|
+
metric: METRICS_PENDING_DIALS,
|
|
101
|
+
metrics
|
|
102
|
+
})
|
|
92
103
|
|
|
93
104
|
/** @type {Map<string, { resolve: (value: any) => void, reject: (err: Error) => void}>} */
|
|
94
|
-
this._pendingDialTargets = trackedMap(
|
|
105
|
+
this._pendingDialTargets = trackedMap({
|
|
106
|
+
component: METRICS_COMPONENT,
|
|
107
|
+
metric: METRICS_PENDING_DIAL_TARGETS,
|
|
108
|
+
metrics
|
|
109
|
+
})
|
|
95
110
|
|
|
96
111
|
for (const [key, value] of Object.entries(resolvers)) {
|
|
97
112
|
Multiaddr.resolvers.set(key, value)
|
|
@@ -128,6 +143,12 @@ class Dialer {
|
|
|
128
143
|
* @returns {Promise<Connection>}
|
|
129
144
|
*/
|
|
130
145
|
async connectToPeer (peer, options = {}) {
|
|
146
|
+
const { id } = getPeer(peer)
|
|
147
|
+
|
|
148
|
+
if (await this.connectionGater.denyDialPeer(id)) {
|
|
149
|
+
throw errCode(new Error('The dial request is blocked by gater.allowDialPeer'), codes.ERR_PEER_DIAL_INTERCEPTED)
|
|
150
|
+
}
|
|
151
|
+
|
|
131
152
|
const dialTarget = await this._createCancellableDialTarget(peer)
|
|
132
153
|
|
|
133
154
|
if (!dialTarget.addrs.length) {
|
|
@@ -192,10 +213,16 @@ class Dialer {
|
|
|
192
213
|
const { id, multiaddrs } = getPeer(peer)
|
|
193
214
|
|
|
194
215
|
if (multiaddrs) {
|
|
195
|
-
this.peerStore.addressBook.add(id, multiaddrs)
|
|
216
|
+
await this.peerStore.addressBook.add(id, multiaddrs)
|
|
196
217
|
}
|
|
197
218
|
|
|
198
|
-
let knownAddrs =
|
|
219
|
+
let knownAddrs = await pipe(
|
|
220
|
+
await this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter),
|
|
221
|
+
(source) => filter(source, async (multiaddr) => {
|
|
222
|
+
return !(await this.connectionGater.denyDialMultiaddr(id, multiaddr))
|
|
223
|
+
}),
|
|
224
|
+
(source) => all(source)
|
|
225
|
+
)
|
|
199
226
|
|
|
200
227
|
// If received a multiaddr to dial, it should be the first to use
|
|
201
228
|
// But, if we know other multiaddrs for the peer, we should try them too.
|
|
@@ -215,7 +242,7 @@ class Dialer {
|
|
|
215
242
|
const supportedAddrs = addrs.filter(a => this.transportManager.transportForMultiaddr(a))
|
|
216
243
|
|
|
217
244
|
if (supportedAddrs.length > this.maxAddrsToDial) {
|
|
218
|
-
this.peerStore.delete(id)
|
|
245
|
+
await this.peerStore.delete(id)
|
|
219
246
|
throw errCode(new Error('dial with more addresses than allowed'), codes.ERR_TOO_MANY_ADDRESSES)
|
|
220
247
|
}
|
|
221
248
|
|
|
@@ -259,7 +286,10 @@ class Dialer {
|
|
|
259
286
|
|
|
260
287
|
// this signal will potentially be used while dialing lots of
|
|
261
288
|
// peers so prevent MaxListenersExceededWarning appearing in the console
|
|
262
|
-
|
|
289
|
+
try {
|
|
290
|
+
// fails on node < 15.4
|
|
291
|
+
setMaxListeners && setMaxListeners(Infinity, signal)
|
|
292
|
+
} catch {}
|
|
263
293
|
|
|
264
294
|
const pendingDial = {
|
|
265
295
|
dialRequest,
|
package/src/errors.js
CHANGED
|
@@ -12,6 +12,8 @@ exports.codes = {
|
|
|
12
12
|
PUBSUB_NOT_STARTED: 'ERR_PUBSUB_NOT_STARTED',
|
|
13
13
|
DHT_NOT_STARTED: 'ERR_DHT_NOT_STARTED',
|
|
14
14
|
CONN_ENCRYPTION_REQUIRED: 'ERR_CONN_ENCRYPTION_REQUIRED',
|
|
15
|
+
ERR_PEER_DIAL_INTERCEPTED: 'ERR_PEER_DIAL_INTERCEPTED',
|
|
16
|
+
ERR_CONNECTION_INTERCEPTED: 'ERR_CONNECTION_INTERCEPTED',
|
|
15
17
|
ERR_INVALID_PROTOCOLS_FOR_STREAM: 'ERR_INVALID_PROTOCOLS_FOR_STREAM',
|
|
16
18
|
ERR_CONNECTION_ENDED: 'ERR_CONNECTION_ENDED',
|
|
17
19
|
ERR_CONNECTION_FAILED: 'ERR_CONNECTION_FAILED',
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
libp2p-fetch JavaScript Implementation
|
|
2
|
+
=====================================
|
|
3
|
+
|
|
4
|
+
> Libp2p fetch protocol JavaScript implementation
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
An implementation of the Fetch protocol as described here: https://github.com/libp2p/specs/tree/master/fetch
|
|
9
|
+
|
|
10
|
+
The fetch protocol is a simple protocol for requesting a value corresponding to a key from a peer.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
const Libp2p = require('libp2p')
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Given a key (as a string) returns a value (as a Uint8Array), or null if the key isn't found.
|
|
19
|
+
* All keys must be prefixed my the same prefix, which will be used to find the appropriate key
|
|
20
|
+
* lookup function.
|
|
21
|
+
* @param key - a string
|
|
22
|
+
* @returns value - a Uint8Array value that corresponds to the given key, or null if the key doesn't
|
|
23
|
+
* have a corresponding value.
|
|
24
|
+
*/
|
|
25
|
+
async function my_subsystem_key_lookup(key) {
|
|
26
|
+
// app specific callback to lookup key-value pairs.
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Enable this peer to respond to fetch requests for keys that begin with '/my_subsystem_key_prefix/'
|
|
30
|
+
const libp2p = Libp2p.create(...)
|
|
31
|
+
libp2p.fetchService.registerLookupFunction('/my_subsystem_key_prefix/', my_subsystem_key_lookup)
|
|
32
|
+
|
|
33
|
+
const key = '/my_subsystem_key_prefix/{...}'
|
|
34
|
+
const peerDst = PeerId.parse('Qmfoo...') // or Multiaddr instance
|
|
35
|
+
const value = await libp2p.fetch(peerDst, key)
|
|
36
|
+
```
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const debug = require('debug')
|
|
4
|
+
const log = Object.assign(debug('libp2p:fetch'), {
|
|
5
|
+
error: debug('libp2p:fetch:err')
|
|
6
|
+
})
|
|
7
|
+
const errCode = require('err-code')
|
|
8
|
+
const { codes } = require('../errors')
|
|
9
|
+
const lp = require('it-length-prefixed')
|
|
10
|
+
const { FetchRequest, FetchResponse } = require('./proto')
|
|
11
|
+
// @ts-ignore it-handshake does not export types
|
|
12
|
+
const handshake = require('it-handshake')
|
|
13
|
+
const { PROTOCOL } = require('./constants')
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {import('../')} Libp2p
|
|
17
|
+
* @typedef {import('multiaddr').Multiaddr} Multiaddr
|
|
18
|
+
* @typedef {import('peer-id')} PeerId
|
|
19
|
+
* @typedef {import('libp2p-interfaces/src/stream-muxer/types').MuxedStream} MuxedStream
|
|
20
|
+
* @typedef {(key: string) => Promise<Uint8Array | null>} LookupFunction
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A simple libp2p protocol for requesting a value corresponding to a key from a peer.
|
|
25
|
+
* Developers can register one or more lookup function for retrieving the value corresponding to
|
|
26
|
+
* a given key. Each lookup function must act on a distinct part of the overall key space, defined
|
|
27
|
+
* by a fixed prefix that all keys that should be routed to that lookup function will start with.
|
|
28
|
+
*/
|
|
29
|
+
class FetchProtocol {
|
|
30
|
+
/**
|
|
31
|
+
* @param {Libp2p} libp2p
|
|
32
|
+
*/
|
|
33
|
+
constructor (libp2p) {
|
|
34
|
+
this._lookupFunctions = new Map() // Maps key prefix to value lookup function
|
|
35
|
+
this._libp2p = libp2p
|
|
36
|
+
this.handleMessage = this.handleMessage.bind(this)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sends a request to fetch the value associated with the given key from the given peer.
|
|
41
|
+
*
|
|
42
|
+
* @param {PeerId|Multiaddr} peer
|
|
43
|
+
* @param {string} key
|
|
44
|
+
* @returns {Promise<Uint8Array | null>}
|
|
45
|
+
*/
|
|
46
|
+
async fetch (peer, key) {
|
|
47
|
+
// @ts-ignore multiaddr might not have toB58String
|
|
48
|
+
log('dialing %s to %s', this._protocol, peer.toB58String ? peer.toB58String() : peer)
|
|
49
|
+
|
|
50
|
+
const connection = await this._libp2p.dial(peer)
|
|
51
|
+
const { stream } = await connection.newStream(FetchProtocol.PROTOCOL)
|
|
52
|
+
const shake = handshake(stream)
|
|
53
|
+
|
|
54
|
+
// send message
|
|
55
|
+
const request = new FetchRequest({ identifier: key })
|
|
56
|
+
shake.write(lp.encode.single(FetchRequest.encode(request).finish()))
|
|
57
|
+
|
|
58
|
+
// read response
|
|
59
|
+
const response = FetchResponse.decode((await lp.decode.fromReader(shake.reader).next()).value.slice())
|
|
60
|
+
switch (response.status) {
|
|
61
|
+
case (FetchResponse.StatusCode.OK): {
|
|
62
|
+
return response.data
|
|
63
|
+
}
|
|
64
|
+
case (FetchResponse.StatusCode.NOT_FOUND): {
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
case (FetchResponse.StatusCode.ERROR): {
|
|
68
|
+
const errmsg = (new TextDecoder()).decode(response.data)
|
|
69
|
+
throw errCode(new Error('Error in fetch protocol response: ' + errmsg), codes.ERR_INVALID_PARAMETERS)
|
|
70
|
+
}
|
|
71
|
+
default: {
|
|
72
|
+
throw errCode(new Error('Unknown response status'), codes.ERR_INVALID_MESSAGE)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Invoked when a fetch request is received. Reads the request message off the given stream and
|
|
79
|
+
* responds based on looking up the key in the request via the lookup callback that corresponds
|
|
80
|
+
* to the key's prefix.
|
|
81
|
+
*
|
|
82
|
+
* @param {object} options
|
|
83
|
+
* @param {MuxedStream} options.stream
|
|
84
|
+
* @param {string} options.protocol
|
|
85
|
+
*/
|
|
86
|
+
async handleMessage (options) {
|
|
87
|
+
const { stream } = options
|
|
88
|
+
const shake = handshake(stream)
|
|
89
|
+
const request = FetchRequest.decode((await lp.decode.fromReader(shake.reader).next()).value.slice())
|
|
90
|
+
|
|
91
|
+
let response
|
|
92
|
+
const lookup = this._getLookupFunction(request.identifier)
|
|
93
|
+
if (lookup) {
|
|
94
|
+
const data = await lookup(request.identifier)
|
|
95
|
+
if (data) {
|
|
96
|
+
response = new FetchResponse({ status: FetchResponse.StatusCode.OK, data })
|
|
97
|
+
} else {
|
|
98
|
+
response = new FetchResponse({ status: FetchResponse.StatusCode.NOT_FOUND })
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
const errmsg = (new TextEncoder()).encode('No lookup function registered for key: ' + request.identifier)
|
|
102
|
+
response = new FetchResponse({ status: FetchResponse.StatusCode.ERROR, data: errmsg })
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
shake.write(lp.encode.single(FetchResponse.encode(response).finish()))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Given a key, finds the appropriate function for looking up its corresponding value, based on
|
|
110
|
+
* the key's prefix.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} key
|
|
113
|
+
*/
|
|
114
|
+
_getLookupFunction (key) {
|
|
115
|
+
for (const prefix of this._lookupFunctions.keys()) {
|
|
116
|
+
if (key.startsWith(prefix)) {
|
|
117
|
+
return this._lookupFunctions.get(prefix)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Registers a new lookup callback that can map keys to values, for a given set of keys that
|
|
125
|
+
* share the same prefix.
|
|
126
|
+
*
|
|
127
|
+
* @param {string} prefix
|
|
128
|
+
* @param {LookupFunction} lookup
|
|
129
|
+
*/
|
|
130
|
+
registerLookupFunction (prefix, lookup) {
|
|
131
|
+
if (this._lookupFunctions.has(prefix)) {
|
|
132
|
+
throw errCode(new Error("Fetch protocol handler for key prefix '" + prefix + "' already registered"), codes.ERR_KEY_ALREADY_EXISTS)
|
|
133
|
+
}
|
|
134
|
+
this._lookupFunctions.set(prefix, lookup)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Registers a new lookup callback that can map keys to values, for a given set of keys that
|
|
139
|
+
* share the same prefix.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} prefix
|
|
142
|
+
* @param {LookupFunction} [lookup]
|
|
143
|
+
*/
|
|
144
|
+
unregisterLookupFunction (prefix, lookup) {
|
|
145
|
+
if (lookup != null) {
|
|
146
|
+
const existingLookup = this._lookupFunctions.get(prefix)
|
|
147
|
+
|
|
148
|
+
if (existingLookup !== lookup) {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this._lookupFunctions.delete(prefix)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
FetchProtocol.PROTOCOL = PROTOCOL
|
|
158
|
+
|
|
159
|
+
exports = module.exports = FetchProtocol
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import * as $protobuf from "protobufjs";
|
|
2
|
+
/** Properties of a FetchRequest. */
|
|
3
|
+
export interface IFetchRequest {
|
|
4
|
+
|
|
5
|
+
/** FetchRequest identifier */
|
|
6
|
+
identifier?: (string|null);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Represents a FetchRequest. */
|
|
10
|
+
export class FetchRequest implements IFetchRequest {
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Constructs a new FetchRequest.
|
|
14
|
+
* @param [p] Properties to set
|
|
15
|
+
*/
|
|
16
|
+
constructor(p?: IFetchRequest);
|
|
17
|
+
|
|
18
|
+
/** FetchRequest identifier. */
|
|
19
|
+
public identifier: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Encodes the specified FetchRequest message. Does not implicitly {@link FetchRequest.verify|verify} messages.
|
|
23
|
+
* @param m FetchRequest message or plain object to encode
|
|
24
|
+
* @param [w] Writer to encode to
|
|
25
|
+
* @returns Writer
|
|
26
|
+
*/
|
|
27
|
+
public static encode(m: IFetchRequest, w?: $protobuf.Writer): $protobuf.Writer;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Decodes a FetchRequest message from the specified reader or buffer.
|
|
31
|
+
* @param r Reader or buffer to decode from
|
|
32
|
+
* @param [l] Message length if known beforehand
|
|
33
|
+
* @returns FetchRequest
|
|
34
|
+
* @throws {Error} If the payload is not a reader or valid buffer
|
|
35
|
+
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
|
36
|
+
*/
|
|
37
|
+
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): FetchRequest;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a FetchRequest message from a plain object. Also converts values to their respective internal types.
|
|
41
|
+
* @param d Plain object
|
|
42
|
+
* @returns FetchRequest
|
|
43
|
+
*/
|
|
44
|
+
public static fromObject(d: { [k: string]: any }): FetchRequest;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Creates a plain object from a FetchRequest message. Also converts values to other types if specified.
|
|
48
|
+
* @param m FetchRequest
|
|
49
|
+
* @param [o] Conversion options
|
|
50
|
+
* @returns Plain object
|
|
51
|
+
*/
|
|
52
|
+
public static toObject(m: FetchRequest, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Converts this FetchRequest to JSON.
|
|
56
|
+
* @returns JSON object
|
|
57
|
+
*/
|
|
58
|
+
public toJSON(): { [k: string]: any };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Properties of a FetchResponse. */
|
|
62
|
+
export interface IFetchResponse {
|
|
63
|
+
|
|
64
|
+
/** FetchResponse status */
|
|
65
|
+
status?: (FetchResponse.StatusCode|null);
|
|
66
|
+
|
|
67
|
+
/** FetchResponse data */
|
|
68
|
+
data?: (Uint8Array|null);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Represents a FetchResponse. */
|
|
72
|
+
export class FetchResponse implements IFetchResponse {
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Constructs a new FetchResponse.
|
|
76
|
+
* @param [p] Properties to set
|
|
77
|
+
*/
|
|
78
|
+
constructor(p?: IFetchResponse);
|
|
79
|
+
|
|
80
|
+
/** FetchResponse status. */
|
|
81
|
+
public status: FetchResponse.StatusCode;
|
|
82
|
+
|
|
83
|
+
/** FetchResponse data. */
|
|
84
|
+
public data: Uint8Array;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Encodes the specified FetchResponse message. Does not implicitly {@link FetchResponse.verify|verify} messages.
|
|
88
|
+
* @param m FetchResponse message or plain object to encode
|
|
89
|
+
* @param [w] Writer to encode to
|
|
90
|
+
* @returns Writer
|
|
91
|
+
*/
|
|
92
|
+
public static encode(m: IFetchResponse, w?: $protobuf.Writer): $protobuf.Writer;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Decodes a FetchResponse message from the specified reader or buffer.
|
|
96
|
+
* @param r Reader or buffer to decode from
|
|
97
|
+
* @param [l] Message length if known beforehand
|
|
98
|
+
* @returns FetchResponse
|
|
99
|
+
* @throws {Error} If the payload is not a reader or valid buffer
|
|
100
|
+
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
|
101
|
+
*/
|
|
102
|
+
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): FetchResponse;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Creates a FetchResponse message from a plain object. Also converts values to their respective internal types.
|
|
106
|
+
* @param d Plain object
|
|
107
|
+
* @returns FetchResponse
|
|
108
|
+
*/
|
|
109
|
+
public static fromObject(d: { [k: string]: any }): FetchResponse;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Creates a plain object from a FetchResponse message. Also converts values to other types if specified.
|
|
113
|
+
* @param m FetchResponse
|
|
114
|
+
* @param [o] Conversion options
|
|
115
|
+
* @returns Plain object
|
|
116
|
+
*/
|
|
117
|
+
public static toObject(m: FetchResponse, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Converts this FetchResponse to JSON.
|
|
121
|
+
* @returns JSON object
|
|
122
|
+
*/
|
|
123
|
+
public toJSON(): { [k: string]: any };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export namespace FetchResponse {
|
|
127
|
+
|
|
128
|
+
/** StatusCode enum. */
|
|
129
|
+
enum StatusCode {
|
|
130
|
+
OK = 0,
|
|
131
|
+
NOT_FOUND = 1,
|
|
132
|
+
ERROR = 2
|
|
133
|
+
}
|
|
134
|
+
}
|