@helia/bitswap 0.0.0-329652a
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 +64 -0
- package/dist/index.min.js +3 -0
- package/dist/src/bitswap.d.ts +50 -0
- package/dist/src/bitswap.d.ts.map +1 -0
- package/dist/src/bitswap.js +120 -0
- package/dist/src/bitswap.js.map +1 -0
- package/dist/src/constants.d.ts +12 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +12 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/index.d.ts +178 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +12 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/network.d.ts +84 -0
- package/dist/src/network.d.ts.map +1 -0
- package/dist/src/network.js +370 -0
- package/dist/src/network.js.map +1 -0
- package/dist/src/pb/message.d.ts +67 -0
- package/dist/src/pb/message.d.ts.map +1 -0
- package/dist/src/pb/message.js +359 -0
- package/dist/src/pb/message.js.map +1 -0
- package/dist/src/peer-want-lists/index.d.ts +44 -0
- package/dist/src/peer-want-lists/index.d.ts.map +1 -0
- package/dist/src/peer-want-lists/index.js +116 -0
- package/dist/src/peer-want-lists/index.js.map +1 -0
- package/dist/src/peer-want-lists/ledger.d.ts +54 -0
- package/dist/src/peer-want-lists/ledger.d.ts.map +1 -0
- package/dist/src/peer-want-lists/ledger.js +104 -0
- package/dist/src/peer-want-lists/ledger.js.map +1 -0
- package/dist/src/session.d.ts +20 -0
- package/dist/src/session.d.ts.map +1 -0
- package/dist/src/session.js +100 -0
- package/dist/src/session.js.map +1 -0
- package/dist/src/stats.d.ts +16 -0
- package/dist/src/stats.d.ts.map +1 -0
- package/dist/src/stats.js +49 -0
- package/dist/src/stats.js.map +1 -0
- package/dist/src/utils/cid-prefix.d.ts +3 -0
- package/dist/src/utils/cid-prefix.d.ts.map +1 -0
- package/dist/src/utils/cid-prefix.js +7 -0
- package/dist/src/utils/cid-prefix.js.map +1 -0
- package/dist/src/utils/varint-decoder.d.ts +3 -0
- package/dist/src/utils/varint-decoder.d.ts.map +1 -0
- package/dist/src/utils/varint-decoder.js +15 -0
- package/dist/src/utils/varint-decoder.js.map +1 -0
- package/dist/src/utils/varint-encoder.d.ts +3 -0
- package/dist/src/utils/varint-encoder.d.ts.map +1 -0
- package/dist/src/utils/varint-encoder.js +14 -0
- package/dist/src/utils/varint-encoder.js.map +1 -0
- package/dist/src/want-list.d.ts +120 -0
- package/dist/src/want-list.d.ts.map +1 -0
- package/dist/src/want-list.js +361 -0
- package/dist/src/want-list.js.map +1 -0
- package/package.json +200 -0
- package/src/bitswap.ts +152 -0
- package/src/constants.ts +11 -0
- package/src/index.ts +215 -0
- package/src/network.ts +506 -0
- package/src/pb/message.proto +42 -0
- package/src/pb/message.ts +450 -0
- package/src/peer-want-lists/index.ts +165 -0
- package/src/peer-want-lists/ledger.ts +161 -0
- package/src/session.ts +150 -0
- package/src/stats.ts +67 -0
- package/src/utils/cid-prefix.ts +8 -0
- package/src/utils/varint-decoder.ts +19 -0
- package/src/utils/varint-encoder.ts +18 -0
- package/src/want-list.ts +529 -0
package/src/network.ts
ADDED
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import { CodeError, TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
|
|
2
|
+
import { PeerQueue, type PeerQueueJobOptions } from '@libp2p/utils/peer-queue'
|
|
3
|
+
import { Circuit } from '@multiformats/multiaddr-matcher'
|
|
4
|
+
import { anySignal } from 'any-signal'
|
|
5
|
+
import debug from 'debug'
|
|
6
|
+
import drain from 'it-drain'
|
|
7
|
+
import * as lp from 'it-length-prefixed'
|
|
8
|
+
import { lpStream } from 'it-length-prefixed-stream'
|
|
9
|
+
import map from 'it-map'
|
|
10
|
+
import { pipe } from 'it-pipe'
|
|
11
|
+
import take from 'it-take'
|
|
12
|
+
import { base64 } from 'multiformats/bases/base64'
|
|
13
|
+
import { CID } from 'multiformats/cid'
|
|
14
|
+
import { CustomProgressEvent } from 'progress-events'
|
|
15
|
+
import { raceEvent } from 'race-event'
|
|
16
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
17
|
+
import { BITSWAP_120, DEFAULT_MAX_INBOUND_STREAMS, DEFAULT_MAX_OUTBOUND_STREAMS, DEFAULT_MAX_PROVIDERS_PER_REQUEST, DEFAULT_MESSAGE_RECEIVE_TIMEOUT, DEFAULT_MESSAGE_SEND_TIMEOUT, DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS } from './constants.js'
|
|
18
|
+
import { BitswapMessage } from './pb/message.js'
|
|
19
|
+
import type { WantOptions } from './bitswap.js'
|
|
20
|
+
import type { MultihashHasherLoader } from './index.js'
|
|
21
|
+
import type { Block, BlockPresence, WantlistEntry } from './pb/message.js'
|
|
22
|
+
import type { Provider, Routing } from '@helia/interface/routing'
|
|
23
|
+
import type { Libp2p, AbortOptions, Connection, PeerId, IncomingStreamData, Topology, MetricGroup, ComponentLogger, Metrics, IdentifyResult } from '@libp2p/interface'
|
|
24
|
+
import type { Logger } from '@libp2p/logger'
|
|
25
|
+
import type { ProgressEvent, ProgressOptions } from 'progress-events'
|
|
26
|
+
|
|
27
|
+
// Add a formatter for a bitswap message
|
|
28
|
+
debug.formatters.B = (b?: BitswapMessage): string => {
|
|
29
|
+
if (b == null) {
|
|
30
|
+
return 'undefined'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return JSON.stringify({
|
|
34
|
+
blocks: b.blocks?.map(b => ({
|
|
35
|
+
data: `${uint8ArrayToString(b.data, 'base64').substring(0, 10)}...`,
|
|
36
|
+
prefix: uint8ArrayToString(b.prefix, 'base64')
|
|
37
|
+
})),
|
|
38
|
+
blockPresences: b.blockPresences?.map(p => ({
|
|
39
|
+
...p,
|
|
40
|
+
cid: CID.decode(p.cid).toString()
|
|
41
|
+
})),
|
|
42
|
+
wantlist: b.wantlist == null
|
|
43
|
+
? undefined
|
|
44
|
+
: {
|
|
45
|
+
full: b.wantlist.full,
|
|
46
|
+
entries: b.wantlist.entries.map(e => ({
|
|
47
|
+
...e,
|
|
48
|
+
cid: CID.decode(e.cid).toString()
|
|
49
|
+
}))
|
|
50
|
+
}
|
|
51
|
+
}, null, 2)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type BitswapNetworkProgressEvents =
|
|
55
|
+
ProgressEvent<'bitswap:network:dial', PeerId>
|
|
56
|
+
|
|
57
|
+
export type BitswapNetworkWantProgressEvents =
|
|
58
|
+
ProgressEvent<'bitswap:network:send-wantlist', PeerId> |
|
|
59
|
+
ProgressEvent<'bitswap:network:send-wantlist:error', { peer: PeerId, error: Error }> |
|
|
60
|
+
ProgressEvent<'bitswap:network:find-providers', CID> |
|
|
61
|
+
BitswapNetworkProgressEvents
|
|
62
|
+
|
|
63
|
+
export type BitswapNetworkNotifyProgressEvents =
|
|
64
|
+
BitswapNetworkProgressEvents |
|
|
65
|
+
ProgressEvent<'bitswap:network:send-block', PeerId>
|
|
66
|
+
|
|
67
|
+
export interface NetworkInit {
|
|
68
|
+
hashLoader?: MultihashHasherLoader
|
|
69
|
+
maxInboundStreams?: number
|
|
70
|
+
maxOutboundStreams?: number
|
|
71
|
+
messageReceiveTimeout?: number
|
|
72
|
+
messageSendTimeout?: number
|
|
73
|
+
messageSendConcurrency?: number
|
|
74
|
+
protocols?: string[]
|
|
75
|
+
runOnTransientConnections?: boolean
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface NetworkComponents {
|
|
79
|
+
routing: Routing
|
|
80
|
+
logger: ComponentLogger
|
|
81
|
+
libp2p: Libp2p
|
|
82
|
+
metrics?: Metrics
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface BitswapMessageEventDetail {
|
|
86
|
+
peer: PeerId
|
|
87
|
+
message: BitswapMessage
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface NetworkEvents {
|
|
91
|
+
'bitswap:message': CustomEvent<{ peer: PeerId, message: BitswapMessage }>
|
|
92
|
+
'peer:connected': CustomEvent<PeerId>
|
|
93
|
+
'peer:disconnected': CustomEvent<PeerId>
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface SendMessageJobOptions extends AbortOptions, ProgressOptions, PeerQueueJobOptions {
|
|
97
|
+
message: BitswapMessage
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class Network extends TypedEventEmitter<NetworkEvents> {
|
|
101
|
+
private readonly log: Logger
|
|
102
|
+
private readonly libp2p: Libp2p
|
|
103
|
+
private readonly routing: Routing
|
|
104
|
+
private readonly protocols: string[]
|
|
105
|
+
private running: boolean
|
|
106
|
+
private readonly maxInboundStreams: number
|
|
107
|
+
private readonly maxOutboundStreams: number
|
|
108
|
+
private readonly messageReceiveTimeout: number
|
|
109
|
+
private registrarIds: string[]
|
|
110
|
+
private readonly metrics?: { blocksSent: MetricGroup, dataSent: MetricGroup }
|
|
111
|
+
private readonly sendQueue: PeerQueue<void, SendMessageJobOptions>
|
|
112
|
+
private readonly messageSendTimeout: number
|
|
113
|
+
private readonly runOnTransientConnections: boolean
|
|
114
|
+
|
|
115
|
+
constructor (components: NetworkComponents, init: NetworkInit = {}) {
|
|
116
|
+
super()
|
|
117
|
+
|
|
118
|
+
this.log = components.logger.forComponent('helia:bitswap:network')
|
|
119
|
+
this.libp2p = components.libp2p
|
|
120
|
+
this.routing = components.routing
|
|
121
|
+
this.protocols = init.protocols ?? [BITSWAP_120]
|
|
122
|
+
this.registrarIds = []
|
|
123
|
+
this.running = false
|
|
124
|
+
|
|
125
|
+
// bind event listeners
|
|
126
|
+
this._onStream = this._onStream.bind(this)
|
|
127
|
+
this.maxInboundStreams = init.maxInboundStreams ?? DEFAULT_MAX_INBOUND_STREAMS
|
|
128
|
+
this.maxOutboundStreams = init.maxOutboundStreams ?? DEFAULT_MAX_OUTBOUND_STREAMS
|
|
129
|
+
this.messageReceiveTimeout = init.messageReceiveTimeout ?? DEFAULT_MESSAGE_RECEIVE_TIMEOUT
|
|
130
|
+
this.messageSendTimeout = init.messageSendTimeout ?? DEFAULT_MESSAGE_SEND_TIMEOUT
|
|
131
|
+
this.runOnTransientConnections = init.runOnTransientConnections ?? DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS
|
|
132
|
+
|
|
133
|
+
if (components.metrics != null) {
|
|
134
|
+
this.metrics = {
|
|
135
|
+
blocksSent: components.metrics?.registerMetricGroup('ipfs_bitswap_sent_blocks'),
|
|
136
|
+
dataSent: components.metrics?.registerMetricGroup('ipfs_bitswap_sent_data_bytes')
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this.sendQueue = new PeerQueue({
|
|
141
|
+
concurrency: init.messageSendConcurrency,
|
|
142
|
+
metrics: components.metrics,
|
|
143
|
+
metricName: 'ipfs_bitswap_message_send_queue'
|
|
144
|
+
})
|
|
145
|
+
this.sendQueue.addEventListener('error', (evt) => {
|
|
146
|
+
this.log.error('error sending wantlist to peer', evt.detail)
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async start (): Promise<void> {
|
|
151
|
+
if (this.running) {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this.running = true
|
|
156
|
+
|
|
157
|
+
await this.libp2p.handle(this.protocols, this._onStream, {
|
|
158
|
+
maxInboundStreams: this.maxInboundStreams,
|
|
159
|
+
maxOutboundStreams: this.maxOutboundStreams,
|
|
160
|
+
runOnTransientConnection: this.runOnTransientConnections
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
// register protocol with topology
|
|
164
|
+
const topology: Topology = {
|
|
165
|
+
onConnect: (peerId: PeerId) => {
|
|
166
|
+
this.safeDispatchEvent('peer:connected', {
|
|
167
|
+
detail: peerId
|
|
168
|
+
})
|
|
169
|
+
},
|
|
170
|
+
onDisconnect: (peerId: PeerId) => {
|
|
171
|
+
this.safeDispatchEvent('peer:disconnected', {
|
|
172
|
+
detail: peerId
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
this.registrarIds = []
|
|
178
|
+
|
|
179
|
+
for (const protocol of this.protocols) {
|
|
180
|
+
this.registrarIds.push(await this.libp2p.register(protocol, topology))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// All existing connections are like new ones for us
|
|
184
|
+
this.libp2p.getConnections().forEach(conn => {
|
|
185
|
+
this.safeDispatchEvent('peer:connected', {
|
|
186
|
+
detail: conn.remotePeer
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async stop (): Promise<void> {
|
|
192
|
+
this.running = false
|
|
193
|
+
|
|
194
|
+
// Unhandle both, libp2p doesn't care if it's not already handled
|
|
195
|
+
await this.libp2p.unhandle(this.protocols)
|
|
196
|
+
|
|
197
|
+
// unregister protocol and handlers
|
|
198
|
+
if (this.registrarIds != null) {
|
|
199
|
+
for (const id of this.registrarIds) {
|
|
200
|
+
this.libp2p.unregister(id)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.registrarIds = []
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Handles incoming bitswap messages
|
|
209
|
+
*/
|
|
210
|
+
_onStream (info: IncomingStreamData): void {
|
|
211
|
+
if (!this.running) {
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const { stream, connection } = info
|
|
216
|
+
|
|
217
|
+
Promise.resolve().then(async () => {
|
|
218
|
+
this.log('incoming new bitswap %s stream from %p', stream.protocol, connection.remotePeer)
|
|
219
|
+
const abortListener = (): void => {
|
|
220
|
+
stream.abort(new CodeError('Incoming Bitswap stream timed out', 'ERR_TIMEOUT'))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let signal = AbortSignal.timeout(this.messageReceiveTimeout)
|
|
224
|
+
setMaxListeners(Infinity, signal)
|
|
225
|
+
signal.addEventListener('abort', abortListener)
|
|
226
|
+
|
|
227
|
+
await pipe(
|
|
228
|
+
stream,
|
|
229
|
+
(source) => lp.decode(source),
|
|
230
|
+
async (source) => {
|
|
231
|
+
for await (const data of source) {
|
|
232
|
+
try {
|
|
233
|
+
const message = BitswapMessage.decode(data)
|
|
234
|
+
this.log('incoming new bitswap %s message from %p %B', stream.protocol, connection.remotePeer, message)
|
|
235
|
+
|
|
236
|
+
this.safeDispatchEvent('bitswap:message', {
|
|
237
|
+
detail: {
|
|
238
|
+
peer: connection.remotePeer,
|
|
239
|
+
message
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
// we have received some data so reset the timeout controller
|
|
244
|
+
signal.removeEventListener('abort', abortListener)
|
|
245
|
+
signal = AbortSignal.timeout(this.messageReceiveTimeout)
|
|
246
|
+
setMaxListeners(Infinity, signal)
|
|
247
|
+
signal.addEventListener('abort', abortListener)
|
|
248
|
+
} catch (err: any) {
|
|
249
|
+
this.log.error('error reading incoming bitswap message from %p', connection.remotePeer, err)
|
|
250
|
+
stream.abort(err)
|
|
251
|
+
break
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
})
|
|
257
|
+
.catch(err => {
|
|
258
|
+
this.log.error('error handling incoming stream from %p', connection.remotePeer, err)
|
|
259
|
+
stream.abort(err)
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Find bitswap providers for a given `cid`.
|
|
265
|
+
*/
|
|
266
|
+
async * findProviders (cid: CID, options?: AbortOptions & ProgressOptions<BitswapNetworkWantProgressEvents>): AsyncIterable<Provider> {
|
|
267
|
+
options?.onProgress?.(new CustomProgressEvent<PeerId>('bitswap:network:find-providers', cid))
|
|
268
|
+
|
|
269
|
+
for await (const provider of this.routing.findProviders(cid, options)) {
|
|
270
|
+
// unless we explicitly run on transient connections, skip peers that only
|
|
271
|
+
// have circuit relay addresses as bitswap won't run over them
|
|
272
|
+
if (!this.runOnTransientConnections) {
|
|
273
|
+
let hasDirectAddress = false
|
|
274
|
+
|
|
275
|
+
for (let ma of provider.multiaddrs) {
|
|
276
|
+
if (ma.getPeerId() == null) {
|
|
277
|
+
ma = ma.encapsulate(`/p2p/${provider.id}`)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!Circuit.exactMatch(ma)) {
|
|
281
|
+
hasDirectAddress = true
|
|
282
|
+
break
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (!hasDirectAddress) {
|
|
287
|
+
continue
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ignore non-bitswap providers
|
|
292
|
+
if (provider.protocols?.includes('transport-bitswap') === false) {
|
|
293
|
+
continue
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
yield provider
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Find the providers of a given `cid` and connect to them.
|
|
302
|
+
*/
|
|
303
|
+
async findAndConnect (cid: CID, options?: WantOptions): Promise<void> {
|
|
304
|
+
await drain(
|
|
305
|
+
take(
|
|
306
|
+
map(this.findProviders(cid, options), async provider => this.connectTo(provider.id, options)),
|
|
307
|
+
options?.maxProviders ?? DEFAULT_MAX_PROVIDERS_PER_REQUEST
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
.catch(err => {
|
|
311
|
+
this.log.error(err)
|
|
312
|
+
})
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Connect to the given peer
|
|
317
|
+
* Send the given msg (instance of Message) to the given peer
|
|
318
|
+
*/
|
|
319
|
+
async sendMessage (peerId: PeerId, msg: Partial<BitswapMessage>, options?: AbortOptions & ProgressOptions<BitswapNetworkWantProgressEvents>): Promise<void> {
|
|
320
|
+
if (!this.running) {
|
|
321
|
+
throw new Error('network isn\'t running')
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const message: BitswapMessage = {
|
|
325
|
+
wantlist: {
|
|
326
|
+
full: msg.wantlist?.full ?? false,
|
|
327
|
+
entries: msg.wantlist?.entries ?? []
|
|
328
|
+
},
|
|
329
|
+
blocks: msg.blocks ?? [],
|
|
330
|
+
blockPresences: msg.blockPresences ?? [],
|
|
331
|
+
pendingBytes: msg.pendingBytes ?? 0
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const signal = anySignal([AbortSignal.timeout(this.messageSendTimeout), options?.signal])
|
|
335
|
+
setMaxListeners(Infinity, signal)
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
const existingJob = this.sendQueue.find(peerId)
|
|
339
|
+
|
|
340
|
+
if (existingJob?.status === 'queued') {
|
|
341
|
+
// merge messages instead of adding new job
|
|
342
|
+
existingJob.options.message = mergeMessages(existingJob.options.message, message)
|
|
343
|
+
|
|
344
|
+
await existingJob.join({
|
|
345
|
+
signal
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
await this.sendQueue.add(async (options) => {
|
|
352
|
+
const message = options?.message
|
|
353
|
+
|
|
354
|
+
if (message == null) {
|
|
355
|
+
throw new CodeError('No message to send', 'ERR_NO_MESSAGE')
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
this.log('sendMessage to %p %B', peerId, message)
|
|
359
|
+
|
|
360
|
+
options?.onProgress?.(new CustomProgressEvent<PeerId>('bitswap:network:send-wantlist', peerId))
|
|
361
|
+
|
|
362
|
+
const stream = await this.libp2p.dialProtocol(peerId, BITSWAP_120, options)
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
const lp = lpStream(stream)
|
|
366
|
+
await lp.write(BitswapMessage.encode(message), options)
|
|
367
|
+
await lp.unwrap().close(options)
|
|
368
|
+
} catch (err: any) {
|
|
369
|
+
options?.onProgress?.(new CustomProgressEvent<{ peer: PeerId, error: Error }>('bitswap:network:send-wantlist:error', { peer: peerId, error: err }))
|
|
370
|
+
this.log.error('error sending message to %p', peerId, err)
|
|
371
|
+
stream.abort(err)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
this._updateSentStats(peerId, message.blocks)
|
|
375
|
+
}, {
|
|
376
|
+
peerId,
|
|
377
|
+
signal,
|
|
378
|
+
message
|
|
379
|
+
})
|
|
380
|
+
} finally {
|
|
381
|
+
signal.clear()
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Connects to another peer
|
|
387
|
+
*/
|
|
388
|
+
async connectTo (peer: PeerId, options?: AbortOptions & ProgressOptions<BitswapNetworkProgressEvents>): Promise<Connection> { // eslint-disable-line require-await
|
|
389
|
+
if (!this.running) {
|
|
390
|
+
throw new CodeError('Network isn\'t running', 'ERR_NOT_STARTED')
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
options?.onProgress?.(new CustomProgressEvent<PeerId>('bitswap:network:dial', peer))
|
|
394
|
+
|
|
395
|
+
// dial and wait for identify - this is to avoid opening a protocol stream
|
|
396
|
+
// that we are not going to use but depends on the remote node running the
|
|
397
|
+
// identitfy protocol
|
|
398
|
+
const [
|
|
399
|
+
connection
|
|
400
|
+
] = await Promise.all([
|
|
401
|
+
this.libp2p.dial(peer, options),
|
|
402
|
+
raceEvent(this.libp2p, 'peer:identify', options?.signal, {
|
|
403
|
+
filter: (evt: CustomEvent<IdentifyResult>): boolean => {
|
|
404
|
+
if (!evt.detail.peerId.equals(peer)) {
|
|
405
|
+
return false
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (evt.detail.protocols.includes(BITSWAP_120)) {
|
|
409
|
+
return true
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
throw new CodeError(`${peer} did not support ${BITSWAP_120}`, 'ERR_BITSWAP_UNSUPPORTED_BY_PEER')
|
|
413
|
+
}
|
|
414
|
+
})
|
|
415
|
+
])
|
|
416
|
+
|
|
417
|
+
return connection
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
_updateSentStats (peerId: PeerId, blocks: Block[] = []): void {
|
|
421
|
+
if (this.metrics != null) {
|
|
422
|
+
let bytes = 0
|
|
423
|
+
|
|
424
|
+
for (const block of blocks.values()) {
|
|
425
|
+
bytes += block.data.byteLength
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
this.metrics.dataSent.increment({
|
|
429
|
+
global: bytes,
|
|
430
|
+
[peerId.toString()]: bytes
|
|
431
|
+
})
|
|
432
|
+
this.metrics.blocksSent.increment({
|
|
433
|
+
global: blocks.length,
|
|
434
|
+
[peerId.toString()]: blocks.length
|
|
435
|
+
})
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function mergeMessages (messageA: BitswapMessage, messageB: BitswapMessage): BitswapMessage {
|
|
441
|
+
const wantListEntries = new Map<string, WantlistEntry>(
|
|
442
|
+
(messageA.wantlist?.entries ?? []).map(entry => ([
|
|
443
|
+
base64.encode(entry.cid),
|
|
444
|
+
entry
|
|
445
|
+
]))
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
for (const entry of messageB.wantlist?.entries ?? []) {
|
|
449
|
+
const key = base64.encode(entry.cid)
|
|
450
|
+
const existingEntry = wantListEntries.get(key)
|
|
451
|
+
|
|
452
|
+
if (existingEntry != null) {
|
|
453
|
+
// take highest priority
|
|
454
|
+
if (existingEntry.priority > entry.priority) {
|
|
455
|
+
entry.priority = existingEntry.priority
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// take later values if passed, otherwise use earlier ones
|
|
459
|
+
entry.cancel = entry.cancel ?? existingEntry.cancel
|
|
460
|
+
entry.wantType = entry.wantType ?? existingEntry.wantType
|
|
461
|
+
entry.sendDontHave = entry.sendDontHave ?? existingEntry.sendDontHave
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
wantListEntries.set(key, entry)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const blockPresences = new Map<string, BlockPresence>(
|
|
468
|
+
messageA.blockPresences.map(presence => ([
|
|
469
|
+
base64.encode(presence.cid),
|
|
470
|
+
presence
|
|
471
|
+
]))
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
for (const blockPresence of messageB.blockPresences) {
|
|
475
|
+
const key = base64.encode(blockPresence.cid)
|
|
476
|
+
|
|
477
|
+
// override earlier block presence with later one as if duplicated it is
|
|
478
|
+
// likely to be more accurate since it is more recent
|
|
479
|
+
blockPresences.set(key, blockPresence)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const blocks = new Map<string, Block>(
|
|
483
|
+
messageA.blocks.map(block => ([
|
|
484
|
+
base64.encode(block.data),
|
|
485
|
+
block
|
|
486
|
+
]))
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
for (const block of messageB.blocks) {
|
|
490
|
+
const key = base64.encode(block.data)
|
|
491
|
+
|
|
492
|
+
blocks.set(key, block)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const output: BitswapMessage = {
|
|
496
|
+
wantlist: {
|
|
497
|
+
full: messageA.wantlist?.full ?? messageB.wantlist?.full ?? false,
|
|
498
|
+
entries: [...wantListEntries.values()]
|
|
499
|
+
},
|
|
500
|
+
blockPresences: [...blockPresences.values()],
|
|
501
|
+
blocks: [...blocks.values()],
|
|
502
|
+
pendingBytes: messageA.pendingBytes + messageB.pendingBytes
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return output
|
|
506
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// adapted from https://github.com/ipfs/boxo/blob/main/bitswap/message/pb/message.proto
|
|
2
|
+
syntax = "proto3";
|
|
3
|
+
|
|
4
|
+
enum WantType {
|
|
5
|
+
WantBlock = 0; // send me the block for the CID
|
|
6
|
+
WantHave = 1; // just tell me if you have the block for the CID or send it if it's really small
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
message WantlistEntry {
|
|
10
|
+
bytes cid = 1; // the block cid (cidV0 in bitswap 1.0.0, cidV1 in bitswap 1.1.0)
|
|
11
|
+
int32 priority = 2; // the priority (normalized). default to 1
|
|
12
|
+
optional bool cancel = 3; // whether this revokes an entry
|
|
13
|
+
optional WantType wantType = 4; // Note: defaults to enum 0, ie Block
|
|
14
|
+
optional bool sendDontHave = 5; // Note: defaults to false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
message Wantlist {
|
|
18
|
+
repeated WantlistEntry entries = 1; // a list of wantlist entries
|
|
19
|
+
optional bool full = 2; // whether this is the full wantlist. default to false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
message Block {
|
|
23
|
+
bytes prefix = 1; // CID prefix (cid version, multicodec and multihash prefix (type + length)
|
|
24
|
+
bytes data = 2;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
enum BlockPresenceType {
|
|
28
|
+
HaveBlock = 0;
|
|
29
|
+
DontHaveBlock = 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
message BlockPresence {
|
|
33
|
+
bytes cid = 1;
|
|
34
|
+
BlockPresenceType type = 2;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
message BitswapMessage {
|
|
38
|
+
Wantlist wantlist = 1;
|
|
39
|
+
repeated Block blocks = 3; // used to send Blocks in bitswap 1.1.0
|
|
40
|
+
repeated BlockPresence blockPresences = 4;
|
|
41
|
+
int32 pendingBytes = 5;
|
|
42
|
+
}
|