@helia/bitswap 1.0.0-338885f → 1.0.0-5d62dfb
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 +1 -1
- package/dist/src/constants.d.ts +2 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +2 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +20 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/network.d.ts +5 -3
- package/dist/src/network.d.ts.map +1 -1
- package/dist/src/network.js +52 -128
- package/dist/src/network.js.map +1 -1
- package/dist/src/pb/message.d.ts +6 -6
- package/dist/src/pb/message.d.ts.map +1 -1
- package/dist/src/pb/message.js +37 -20
- package/dist/src/pb/message.js.map +1 -1
- package/dist/src/peer-want-lists/index.d.ts +1 -0
- package/dist/src/peer-want-lists/index.d.ts.map +1 -1
- package/dist/src/peer-want-lists/index.js +5 -1
- package/dist/src/peer-want-lists/index.js.map +1 -1
- package/dist/src/peer-want-lists/ledger.d.ts +3 -1
- package/dist/src/peer-want-lists/ledger.d.ts.map +1 -1
- package/dist/src/peer-want-lists/ledger.js +8 -0
- package/dist/src/peer-want-lists/ledger.js.map +1 -1
- package/dist/src/utils/merge-messages.d.ts +3 -0
- package/dist/src/utils/merge-messages.d.ts.map +1 -0
- package/dist/src/utils/merge-messages.js +51 -0
- package/dist/src/utils/merge-messages.js.map +1 -0
- package/dist/src/utils/split-message.d.ts +18 -0
- package/dist/src/utils/split-message.d.ts.map +1 -0
- package/dist/src/utils/split-message.js +101 -0
- package/dist/src/utils/split-message.js.map +1 -0
- package/dist/src/want-list.d.ts.map +1 -1
- package/dist/src/want-list.js +6 -3
- package/dist/src/want-list.js.map +1 -1
- package/package.json +4 -4
- package/src/constants.ts +2 -0
- package/src/index.ts +22 -8
- package/src/network.ts +60 -150
- package/src/pb/message.ts +40 -20
- package/src/peer-want-lists/index.ts +5 -1
- package/src/peer-want-lists/ledger.ts +11 -1
- package/src/utils/merge-messages.ts +70 -0
- package/src/utils/split-message.ts +133 -0
- package/src/want-list.ts +8 -3
package/src/network.ts
CHANGED
|
@@ -1,55 +1,25 @@
|
|
|
1
1
|
import { CodeError, TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
|
|
2
2
|
import { PeerQueue, type PeerQueueJobOptions } from '@libp2p/utils/peer-queue'
|
|
3
|
-
import { anySignal } from 'any-signal'
|
|
4
|
-
import debug from 'debug'
|
|
5
3
|
import drain from 'it-drain'
|
|
6
4
|
import * as lp from 'it-length-prefixed'
|
|
7
|
-
import { lpStream } from 'it-length-prefixed-stream'
|
|
8
5
|
import map from 'it-map'
|
|
9
6
|
import { pipe } from 'it-pipe'
|
|
10
7
|
import take from 'it-take'
|
|
11
|
-
import { base64 } from 'multiformats/bases/base64'
|
|
12
|
-
import { CID } from 'multiformats/cid'
|
|
13
8
|
import { CustomProgressEvent } from 'progress-events'
|
|
14
9
|
import { raceEvent } from 'race-event'
|
|
15
|
-
import {
|
|
16
|
-
import { BITSWAP_120, DEFAULT_MAX_INBOUND_STREAMS, DEFAULT_MAX_OUTBOUND_STREAMS, DEFAULT_MAX_PROVIDERS_PER_REQUEST, DEFAULT_MESSAGE_RECEIVE_TIMEOUT, DEFAULT_MESSAGE_SEND_CONCURRENCY, DEFAULT_MESSAGE_SEND_TIMEOUT, DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS } from './constants.js'
|
|
10
|
+
import { BITSWAP_120, DEFAULT_MAX_INBOUND_STREAMS, DEFAULT_MAX_INCOMING_MESSAGE_SIZE, DEFAULT_MAX_OUTBOUND_STREAMS, DEFAULT_MAX_OUTGOING_MESSAGE_SIZE, DEFAULT_MAX_PROVIDERS_PER_REQUEST, DEFAULT_MESSAGE_RECEIVE_TIMEOUT, DEFAULT_MESSAGE_SEND_CONCURRENCY, DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS } from './constants.js'
|
|
17
11
|
import { BitswapMessage } from './pb/message.js'
|
|
12
|
+
import { mergeMessages } from './utils/merge-messages.js'
|
|
13
|
+
import { splitMessage } from './utils/split-message.js'
|
|
18
14
|
import type { WantOptions } from './bitswap.js'
|
|
19
15
|
import type { MultihashHasherLoader } from './index.js'
|
|
20
|
-
import type { Block
|
|
16
|
+
import type { Block } from './pb/message.js'
|
|
21
17
|
import type { Provider, Routing } from '@helia/interface/routing'
|
|
22
18
|
import type { Libp2p, AbortOptions, Connection, PeerId, IncomingStreamData, Topology, ComponentLogger, IdentifyResult, Counter } from '@libp2p/interface'
|
|
23
19
|
import type { Logger } from '@libp2p/logger'
|
|
20
|
+
import type { CID } from 'multiformats/cid'
|
|
24
21
|
import type { ProgressEvent, ProgressOptions } from 'progress-events'
|
|
25
22
|
|
|
26
|
-
// Add a formatter for a bitswap message
|
|
27
|
-
debug.formatters.B = (b?: BitswapMessage): string => {
|
|
28
|
-
if (b == null) {
|
|
29
|
-
return 'undefined'
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return JSON.stringify({
|
|
33
|
-
blocks: b.blocks?.map(b => ({
|
|
34
|
-
data: `${uint8ArrayToString(b.data, 'base64').substring(0, 10)}...`,
|
|
35
|
-
prefix: uint8ArrayToString(b.prefix, 'base64')
|
|
36
|
-
})),
|
|
37
|
-
blockPresences: b.blockPresences?.map(p => ({
|
|
38
|
-
...p,
|
|
39
|
-
cid: CID.decode(p.cid).toString()
|
|
40
|
-
})),
|
|
41
|
-
wantlist: b.wantlist == null
|
|
42
|
-
? undefined
|
|
43
|
-
: {
|
|
44
|
-
full: b.wantlist.full,
|
|
45
|
-
entries: b.wantlist.entries.map(e => ({
|
|
46
|
-
...e,
|
|
47
|
-
cid: CID.decode(e.cid).toString()
|
|
48
|
-
}))
|
|
49
|
-
}
|
|
50
|
-
}, null, 2)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
23
|
export type BitswapNetworkProgressEvents =
|
|
54
24
|
ProgressEvent<'bitswap:network:dial', PeerId>
|
|
55
25
|
|
|
@@ -68,10 +38,11 @@ export interface NetworkInit {
|
|
|
68
38
|
maxInboundStreams?: number
|
|
69
39
|
maxOutboundStreams?: number
|
|
70
40
|
messageReceiveTimeout?: number
|
|
71
|
-
messageSendTimeout?: number
|
|
72
41
|
messageSendConcurrency?: number
|
|
73
42
|
protocols?: string[]
|
|
74
43
|
runOnTransientConnections?: boolean
|
|
44
|
+
maxOutgoingMessageSize?: number
|
|
45
|
+
maxIncomingMessageSize?: number
|
|
75
46
|
}
|
|
76
47
|
|
|
77
48
|
export interface NetworkComponents {
|
|
@@ -107,8 +78,9 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
|
|
|
107
78
|
private registrarIds: string[]
|
|
108
79
|
private readonly metrics: { blocksSent?: Counter, dataSent?: Counter }
|
|
109
80
|
private readonly sendQueue: PeerQueue<void, SendMessageJobOptions>
|
|
110
|
-
private readonly messageSendTimeout: number
|
|
111
81
|
private readonly runOnTransientConnections: boolean
|
|
82
|
+
private readonly maxOutgoingMessageSize: number
|
|
83
|
+
private readonly maxIncomingMessageSize: number
|
|
112
84
|
|
|
113
85
|
constructor (components: NetworkComponents, init: NetworkInit = {}) {
|
|
114
86
|
super()
|
|
@@ -125,8 +97,9 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
|
|
|
125
97
|
this.maxInboundStreams = init.maxInboundStreams ?? DEFAULT_MAX_INBOUND_STREAMS
|
|
126
98
|
this.maxOutboundStreams = init.maxOutboundStreams ?? DEFAULT_MAX_OUTBOUND_STREAMS
|
|
127
99
|
this.messageReceiveTimeout = init.messageReceiveTimeout ?? DEFAULT_MESSAGE_RECEIVE_TIMEOUT
|
|
128
|
-
this.messageSendTimeout = init.messageSendTimeout ?? DEFAULT_MESSAGE_SEND_TIMEOUT
|
|
129
100
|
this.runOnTransientConnections = init.runOnTransientConnections ?? DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS
|
|
101
|
+
this.maxIncomingMessageSize = init.maxIncomingMessageSize ?? DEFAULT_MAX_OUTGOING_MESSAGE_SIZE
|
|
102
|
+
this.maxOutgoingMessageSize = init.maxOutgoingMessageSize ?? init.maxIncomingMessageSize ?? DEFAULT_MAX_INCOMING_MESSAGE_SIZE
|
|
130
103
|
this.metrics = {
|
|
131
104
|
blocksSent: components.libp2p.metrics?.registerCounter('helia_bitswap_sent_blocks_total'),
|
|
132
105
|
dataSent: components.libp2p.metrics?.registerCounter('helia_bitswap_sent_data_bytes_total')
|
|
@@ -212,21 +185,29 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
|
|
|
212
185
|
Promise.resolve().then(async () => {
|
|
213
186
|
this.log('incoming new bitswap %s stream from %p', stream.protocol, connection.remotePeer)
|
|
214
187
|
const abortListener = (): void => {
|
|
215
|
-
|
|
188
|
+
if (stream.status === 'open') {
|
|
189
|
+
stream.abort(new CodeError('Incoming Bitswap stream timed out', 'ERR_TIMEOUT'))
|
|
190
|
+
} else {
|
|
191
|
+
this.log('stream aborted with status', stream.status)
|
|
192
|
+
}
|
|
216
193
|
}
|
|
217
194
|
|
|
218
195
|
let signal = AbortSignal.timeout(this.messageReceiveTimeout)
|
|
219
196
|
setMaxListeners(Infinity, signal)
|
|
220
197
|
signal.addEventListener('abort', abortListener)
|
|
221
198
|
|
|
199
|
+
await stream.closeWrite()
|
|
200
|
+
|
|
222
201
|
await pipe(
|
|
223
202
|
stream,
|
|
224
|
-
(source) => lp.decode(source
|
|
203
|
+
(source) => lp.decode(source, {
|
|
204
|
+
maxDataLength: this.maxIncomingMessageSize
|
|
205
|
+
}),
|
|
225
206
|
async (source) => {
|
|
226
207
|
for await (const data of source) {
|
|
227
208
|
try {
|
|
228
209
|
const message = BitswapMessage.decode(data)
|
|
229
|
-
this.log('incoming new bitswap %s message from %p
|
|
210
|
+
this.log('incoming new bitswap %s message from %p on stream', stream.protocol, connection.remotePeer, stream.id)
|
|
230
211
|
|
|
231
212
|
this.safeDispatchEvent('bitswap:message', {
|
|
232
213
|
detail: {
|
|
@@ -241,7 +222,7 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
|
|
|
241
222
|
setMaxListeners(Infinity, signal)
|
|
242
223
|
signal.addEventListener('abort', abortListener)
|
|
243
224
|
} catch (err: any) {
|
|
244
|
-
this.log.error('error reading incoming bitswap message from %p', connection.remotePeer, err)
|
|
225
|
+
this.log.error('error reading incoming bitswap message from %p on stream', connection.remotePeer, stream.id, err)
|
|
245
226
|
stream.abort(err)
|
|
246
227
|
break
|
|
247
228
|
}
|
|
@@ -309,58 +290,55 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
|
|
|
309
290
|
pendingBytes: msg.pendingBytes ?? 0
|
|
310
291
|
}
|
|
311
292
|
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
293
|
+
const existingJob = this.sendQueue.queue.find(job => {
|
|
294
|
+
return peerId.equals(job.options.peerId) && job.status === 'queued'
|
|
295
|
+
})
|
|
315
296
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
297
|
+
if (existingJob != null) {
|
|
298
|
+
// merge messages instead of adding new job
|
|
299
|
+
existingJob.options.message = mergeMessages(existingJob.options.message, message)
|
|
300
|
+
|
|
301
|
+
await existingJob.join({
|
|
302
|
+
signal: options?.signal
|
|
319
303
|
})
|
|
320
304
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
existingJob.options.message = mergeMessages(existingJob.options.message, message)
|
|
305
|
+
return
|
|
306
|
+
}
|
|
324
307
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
})
|
|
308
|
+
await this.sendQueue.add(async (options) => {
|
|
309
|
+
const message = options?.message
|
|
328
310
|
|
|
329
|
-
|
|
311
|
+
if (message == null) {
|
|
312
|
+
throw new CodeError('No message to send', 'ERR_NO_MESSAGE')
|
|
330
313
|
}
|
|
331
314
|
|
|
332
|
-
|
|
333
|
-
const message = options?.message
|
|
315
|
+
this.log('sendMessage to %p', peerId)
|
|
334
316
|
|
|
335
|
-
|
|
336
|
-
throw new CodeError('No message to send', 'ERR_NO_MESSAGE')
|
|
337
|
-
}
|
|
317
|
+
options?.onProgress?.(new CustomProgressEvent<PeerId>('bitswap:network:send-wantlist', peerId))
|
|
338
318
|
|
|
339
|
-
|
|
319
|
+
const stream = await this.libp2p.dialProtocol(peerId, BITSWAP_120, options)
|
|
320
|
+
await stream.closeRead()
|
|
340
321
|
|
|
341
|
-
|
|
322
|
+
try {
|
|
323
|
+
await pipe(
|
|
324
|
+
splitMessage(message, this.maxOutgoingMessageSize),
|
|
325
|
+
(source) => lp.encode(source),
|
|
326
|
+
stream
|
|
327
|
+
)
|
|
342
328
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
} catch (err: any) {
|
|
350
|
-
options?.onProgress?.(new CustomProgressEvent<{ peer: PeerId, error: Error }>('bitswap:network:send-wantlist:error', { peer: peerId, error: err }))
|
|
351
|
-
this.log.error('error sending message to %p', peerId, err)
|
|
352
|
-
stream.abort(err)
|
|
353
|
-
}
|
|
329
|
+
await stream.close(options)
|
|
330
|
+
} catch (err: any) {
|
|
331
|
+
options?.onProgress?.(new CustomProgressEvent<{ peer: PeerId, error: Error }>('bitswap:network:send-wantlist:error', { peer: peerId, error: err }))
|
|
332
|
+
this.log.error('error sending message to %p', peerId, err)
|
|
333
|
+
stream.abort(err)
|
|
334
|
+
}
|
|
354
335
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
} finally {
|
|
362
|
-
signal.clear()
|
|
363
|
-
}
|
|
336
|
+
this._updateSentStats(message.blocks)
|
|
337
|
+
}, {
|
|
338
|
+
peerId,
|
|
339
|
+
signal: options?.signal,
|
|
340
|
+
message
|
|
341
|
+
})
|
|
364
342
|
}
|
|
365
343
|
|
|
366
344
|
/**
|
|
@@ -409,71 +387,3 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
|
|
|
409
387
|
this.metrics.blocksSent?.increment(blocks.length)
|
|
410
388
|
}
|
|
411
389
|
}
|
|
412
|
-
|
|
413
|
-
function mergeMessages (messageA: BitswapMessage, messageB: BitswapMessage): BitswapMessage {
|
|
414
|
-
const wantListEntries = new Map<string, WantlistEntry>(
|
|
415
|
-
(messageA.wantlist?.entries ?? []).map(entry => ([
|
|
416
|
-
base64.encode(entry.cid),
|
|
417
|
-
entry
|
|
418
|
-
]))
|
|
419
|
-
)
|
|
420
|
-
|
|
421
|
-
for (const entry of messageB.wantlist?.entries ?? []) {
|
|
422
|
-
const key = base64.encode(entry.cid)
|
|
423
|
-
const existingEntry = wantListEntries.get(key)
|
|
424
|
-
|
|
425
|
-
if (existingEntry != null) {
|
|
426
|
-
// take highest priority
|
|
427
|
-
if (existingEntry.priority > entry.priority) {
|
|
428
|
-
entry.priority = existingEntry.priority
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// take later values if passed, otherwise use earlier ones
|
|
432
|
-
entry.cancel = entry.cancel ?? existingEntry.cancel
|
|
433
|
-
entry.wantType = entry.wantType ?? existingEntry.wantType
|
|
434
|
-
entry.sendDontHave = entry.sendDontHave ?? existingEntry.sendDontHave
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
wantListEntries.set(key, entry)
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
const blockPresences = new Map<string, BlockPresence>(
|
|
441
|
-
messageA.blockPresences.map(presence => ([
|
|
442
|
-
base64.encode(presence.cid),
|
|
443
|
-
presence
|
|
444
|
-
]))
|
|
445
|
-
)
|
|
446
|
-
|
|
447
|
-
for (const blockPresence of messageB.blockPresences) {
|
|
448
|
-
const key = base64.encode(blockPresence.cid)
|
|
449
|
-
|
|
450
|
-
// override earlier block presence with later one as if duplicated it is
|
|
451
|
-
// likely to be more accurate since it is more recent
|
|
452
|
-
blockPresences.set(key, blockPresence)
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
const blocks = new Map<string, Block>(
|
|
456
|
-
messageA.blocks.map(block => ([
|
|
457
|
-
base64.encode(block.data),
|
|
458
|
-
block
|
|
459
|
-
]))
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
for (const block of messageB.blocks) {
|
|
463
|
-
const key = base64.encode(block.data)
|
|
464
|
-
|
|
465
|
-
blocks.set(key, block)
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
const output: BitswapMessage = {
|
|
469
|
-
wantlist: {
|
|
470
|
-
full: messageA.wantlist?.full ?? messageB.wantlist?.full ?? false,
|
|
471
|
-
entries: [...wantListEntries.values()]
|
|
472
|
-
},
|
|
473
|
-
blockPresences: [...blockPresences.values()],
|
|
474
|
-
blocks: [...blocks.values()],
|
|
475
|
-
pendingBytes: messageA.pendingBytes + messageB.pendingBytes
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return output
|
|
479
|
-
}
|
package/src/pb/message.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
|
|
5
5
|
/* eslint-disable @typescript-eslint/no-empty-interface */
|
|
6
6
|
|
|
7
|
-
import { type Codec, decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime'
|
|
7
|
+
import { type Codec, CodeError, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime'
|
|
8
8
|
import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc'
|
|
9
9
|
import type { Uint8ArrayList } from 'uint8arraylist'
|
|
10
10
|
|
|
@@ -69,7 +69,7 @@ export namespace WantlistEntry {
|
|
|
69
69
|
if (opts.lengthDelimited !== false) {
|
|
70
70
|
w.ldelim()
|
|
71
71
|
}
|
|
72
|
-
}, (reader, length) => {
|
|
72
|
+
}, (reader, length, opts = {}) => {
|
|
73
73
|
const obj: any = {
|
|
74
74
|
cid: uint8ArrayAlloc(0),
|
|
75
75
|
priority: 0
|
|
@@ -119,8 +119,8 @@ export namespace WantlistEntry {
|
|
|
119
119
|
return encodeMessage(obj, WantlistEntry.codec())
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
export const decode = (buf: Uint8Array | Uint8ArrayList): WantlistEntry => {
|
|
123
|
-
return decodeMessage(buf, WantlistEntry.codec())
|
|
122
|
+
export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<WantlistEntry>): WantlistEntry => {
|
|
123
|
+
return decodeMessage(buf, WantlistEntry.codec(), opts)
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -154,7 +154,7 @@ export namespace Wantlist {
|
|
|
154
154
|
if (opts.lengthDelimited !== false) {
|
|
155
155
|
w.ldelim()
|
|
156
156
|
}
|
|
157
|
-
}, (reader, length) => {
|
|
157
|
+
}, (reader, length, opts = {}) => {
|
|
158
158
|
const obj: any = {
|
|
159
159
|
entries: []
|
|
160
160
|
}
|
|
@@ -166,7 +166,13 @@ export namespace Wantlist {
|
|
|
166
166
|
|
|
167
167
|
switch (tag >>> 3) {
|
|
168
168
|
case 1: {
|
|
169
|
-
obj.entries.
|
|
169
|
+
if (opts.limits?.entries != null && obj.entries.length === opts.limits.entries) {
|
|
170
|
+
throw new CodeError('decode error - map field "entries" had too many elements', 'ERR_MAX_LENGTH')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
obj.entries.push(WantlistEntry.codec().decode(reader, reader.uint32(), {
|
|
174
|
+
limits: opts.limits?.entries$
|
|
175
|
+
}))
|
|
170
176
|
break
|
|
171
177
|
}
|
|
172
178
|
case 2: {
|
|
@@ -191,8 +197,8 @@ export namespace Wantlist {
|
|
|
191
197
|
return encodeMessage(obj, Wantlist.codec())
|
|
192
198
|
}
|
|
193
199
|
|
|
194
|
-
export const decode = (buf: Uint8Array | Uint8ArrayList): Wantlist => {
|
|
195
|
-
return decodeMessage(buf, Wantlist.codec())
|
|
200
|
+
export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<Wantlist>): Wantlist => {
|
|
201
|
+
return decodeMessage(buf, Wantlist.codec(), opts)
|
|
196
202
|
}
|
|
197
203
|
}
|
|
198
204
|
|
|
@@ -224,7 +230,7 @@ export namespace Block {
|
|
|
224
230
|
if (opts.lengthDelimited !== false) {
|
|
225
231
|
w.ldelim()
|
|
226
232
|
}
|
|
227
|
-
}, (reader, length) => {
|
|
233
|
+
}, (reader, length, opts = {}) => {
|
|
228
234
|
const obj: any = {
|
|
229
235
|
prefix: uint8ArrayAlloc(0),
|
|
230
236
|
data: uint8ArrayAlloc(0)
|
|
@@ -262,8 +268,8 @@ export namespace Block {
|
|
|
262
268
|
return encodeMessage(obj, Block.codec())
|
|
263
269
|
}
|
|
264
270
|
|
|
265
|
-
export const decode = (buf: Uint8Array | Uint8ArrayList): Block => {
|
|
266
|
-
return decodeMessage(buf, Block.codec())
|
|
271
|
+
export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<Block>): Block => {
|
|
272
|
+
return decodeMessage(buf, Block.codec(), opts)
|
|
267
273
|
}
|
|
268
274
|
}
|
|
269
275
|
|
|
@@ -310,7 +316,7 @@ export namespace BlockPresence {
|
|
|
310
316
|
if (opts.lengthDelimited !== false) {
|
|
311
317
|
w.ldelim()
|
|
312
318
|
}
|
|
313
|
-
}, (reader, length) => {
|
|
319
|
+
}, (reader, length, opts = {}) => {
|
|
314
320
|
const obj: any = {
|
|
315
321
|
cid: uint8ArrayAlloc(0),
|
|
316
322
|
type: BlockPresenceType.HaveBlock
|
|
@@ -348,8 +354,8 @@ export namespace BlockPresence {
|
|
|
348
354
|
return encodeMessage(obj, BlockPresence.codec())
|
|
349
355
|
}
|
|
350
356
|
|
|
351
|
-
export const decode = (buf: Uint8Array | Uint8ArrayList): BlockPresence => {
|
|
352
|
-
return decodeMessage(buf, BlockPresence.codec())
|
|
357
|
+
export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<BlockPresence>): BlockPresence => {
|
|
358
|
+
return decodeMessage(buf, BlockPresence.codec(), opts)
|
|
353
359
|
}
|
|
354
360
|
}
|
|
355
361
|
|
|
@@ -397,7 +403,7 @@ export namespace BitswapMessage {
|
|
|
397
403
|
if (opts.lengthDelimited !== false) {
|
|
398
404
|
w.ldelim()
|
|
399
405
|
}
|
|
400
|
-
}, (reader, length) => {
|
|
406
|
+
}, (reader, length, opts = {}) => {
|
|
401
407
|
const obj: any = {
|
|
402
408
|
blocks: [],
|
|
403
409
|
blockPresences: [],
|
|
@@ -411,15 +417,29 @@ export namespace BitswapMessage {
|
|
|
411
417
|
|
|
412
418
|
switch (tag >>> 3) {
|
|
413
419
|
case 1: {
|
|
414
|
-
obj.wantlist = Wantlist.codec().decode(reader, reader.uint32()
|
|
420
|
+
obj.wantlist = Wantlist.codec().decode(reader, reader.uint32(), {
|
|
421
|
+
limits: opts.limits?.wantlist
|
|
422
|
+
})
|
|
415
423
|
break
|
|
416
424
|
}
|
|
417
425
|
case 3: {
|
|
418
|
-
obj.blocks.
|
|
426
|
+
if (opts.limits?.blocks != null && obj.blocks.length === opts.limits.blocks) {
|
|
427
|
+
throw new CodeError('decode error - map field "blocks" had too many elements', 'ERR_MAX_LENGTH')
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
obj.blocks.push(Block.codec().decode(reader, reader.uint32(), {
|
|
431
|
+
limits: opts.limits?.blocks$
|
|
432
|
+
}))
|
|
419
433
|
break
|
|
420
434
|
}
|
|
421
435
|
case 4: {
|
|
422
|
-
obj.blockPresences.
|
|
436
|
+
if (opts.limits?.blockPresences != null && obj.blockPresences.length === opts.limits.blockPresences) {
|
|
437
|
+
throw new CodeError('decode error - map field "blockPresences" had too many elements', 'ERR_MAX_LENGTH')
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
obj.blockPresences.push(BlockPresence.codec().decode(reader, reader.uint32(), {
|
|
441
|
+
limits: opts.limits?.blockPresences$
|
|
442
|
+
}))
|
|
423
443
|
break
|
|
424
444
|
}
|
|
425
445
|
case 5: {
|
|
@@ -444,7 +464,7 @@ export namespace BitswapMessage {
|
|
|
444
464
|
return encodeMessage(obj, BitswapMessage.codec())
|
|
445
465
|
}
|
|
446
466
|
|
|
447
|
-
export const decode = (buf: Uint8Array | Uint8ArrayList): BitswapMessage => {
|
|
448
|
-
return decodeMessage(buf, BitswapMessage.codec())
|
|
467
|
+
export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<BitswapMessage>): BitswapMessage => {
|
|
468
|
+
return decodeMessage(buf, BitswapMessage.codec(), opts)
|
|
449
469
|
}
|
|
450
470
|
}
|
|
@@ -37,12 +37,14 @@ export class PeerWantLists {
|
|
|
37
37
|
public readonly ledgerMap: PeerMap<Ledger>
|
|
38
38
|
private readonly maxSizeReplaceHasWithBlock?: number
|
|
39
39
|
private readonly log: Logger
|
|
40
|
+
private readonly logger: ComponentLogger
|
|
40
41
|
|
|
41
42
|
constructor (components: PeerWantListsComponents, init: PeerWantListsInit = {}) {
|
|
42
43
|
this.blockstore = components.blockstore
|
|
43
44
|
this.network = components.network
|
|
44
45
|
this.maxSizeReplaceHasWithBlock = init.maxSizeReplaceHasWithBlock
|
|
45
46
|
this.log = components.logger.forComponent('helia:bitswap:peer-want-lists')
|
|
47
|
+
this.logger = components.logger
|
|
46
48
|
|
|
47
49
|
this.ledgerMap = trackedPeerMap({
|
|
48
50
|
name: 'helia_bitswap_ledger_map',
|
|
@@ -100,7 +102,8 @@ export class PeerWantLists {
|
|
|
100
102
|
ledger = new Ledger({
|
|
101
103
|
peerId,
|
|
102
104
|
blockstore: this.blockstore,
|
|
103
|
-
network: this.network
|
|
105
|
+
network: this.network,
|
|
106
|
+
logger: this.logger
|
|
104
107
|
}, {
|
|
105
108
|
maxSizeReplaceHasWithBlock: this.maxSizeReplaceHasWithBlock
|
|
106
109
|
})
|
|
@@ -141,6 +144,7 @@ export class PeerWantLists {
|
|
|
141
144
|
}
|
|
142
145
|
}
|
|
143
146
|
|
|
147
|
+
this.log('send blocks to peer')
|
|
144
148
|
await ledger.sendBlocksToPeer()
|
|
145
149
|
}
|
|
146
150
|
|
|
@@ -3,7 +3,7 @@ import { DEFAULT_MAX_SIZE_REPLACE_HAS_WITH_BLOCK } from '../constants.js'
|
|
|
3
3
|
import { BlockPresenceType, type BitswapMessage, WantType } from '../pb/message.js'
|
|
4
4
|
import { cidToPrefix } from '../utils/cid-prefix.js'
|
|
5
5
|
import type { Network } from '../network.js'
|
|
6
|
-
import type { PeerId } from '@libp2p/interface'
|
|
6
|
+
import type { ComponentLogger, Logger, PeerId } from '@libp2p/interface'
|
|
7
7
|
import type { Blockstore } from 'interface-blockstore'
|
|
8
8
|
import type { AbortOptions } from 'it-length-prefixed-stream'
|
|
9
9
|
import type { CID } from 'multiformats/cid'
|
|
@@ -12,6 +12,7 @@ export interface LedgerComponents {
|
|
|
12
12
|
peerId: PeerId
|
|
13
13
|
blockstore: Blockstore
|
|
14
14
|
network: Network
|
|
15
|
+
logger: ComponentLogger
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export interface LedgerInit {
|
|
@@ -56,12 +57,14 @@ export class Ledger {
|
|
|
56
57
|
public bytesReceived: number
|
|
57
58
|
public lastExchange?: number
|
|
58
59
|
private readonly maxSizeReplaceHasWithBlock: number
|
|
60
|
+
private readonly log: Logger
|
|
59
61
|
|
|
60
62
|
constructor (components: LedgerComponents, init: LedgerInit) {
|
|
61
63
|
this.peerId = components.peerId
|
|
62
64
|
this.blockstore = components.blockstore
|
|
63
65
|
this.network = components.network
|
|
64
66
|
this.wants = new Map()
|
|
67
|
+
this.log = components.logger.forComponent(`helia:bitswap:ledger:${components.peerId}`)
|
|
65
68
|
|
|
66
69
|
this.exchangeCount = 0
|
|
67
70
|
this.bytesSent = 0
|
|
@@ -96,6 +99,8 @@ export class Ledger {
|
|
|
96
99
|
const has = await this.blockstore.has(entry.cid, options)
|
|
97
100
|
|
|
98
101
|
if (!has) {
|
|
102
|
+
this.log('do not have block for %c', entry.cid)
|
|
103
|
+
|
|
99
104
|
// we don't have the requested block and the remote is not interested
|
|
100
105
|
// in us telling them that
|
|
101
106
|
if (!entry.sendDontHave) {
|
|
@@ -121,6 +126,7 @@ export class Ledger {
|
|
|
121
126
|
// do they want the block or just us to tell them we have the block
|
|
122
127
|
if (entry.wantType === WantType.WantHave) {
|
|
123
128
|
if (block.byteLength < this.maxSizeReplaceHasWithBlock) {
|
|
129
|
+
this.log('sending have and block for %c', entry.cid)
|
|
124
130
|
// if the block is small we just send it to them
|
|
125
131
|
sentBlocks.add(key)
|
|
126
132
|
message.blocks.push({
|
|
@@ -128,6 +134,7 @@ export class Ledger {
|
|
|
128
134
|
prefix: cidToPrefix(entry.cid)
|
|
129
135
|
})
|
|
130
136
|
} else {
|
|
137
|
+
this.log('sending have for %c', entry.cid)
|
|
131
138
|
// otherwise tell them we have the block
|
|
132
139
|
message.blockPresences.push({
|
|
133
140
|
cid: entry.cid.bytes,
|
|
@@ -135,6 +142,7 @@ export class Ledger {
|
|
|
135
142
|
})
|
|
136
143
|
}
|
|
137
144
|
} else {
|
|
145
|
+
this.log('sending block for %c', entry.cid)
|
|
138
146
|
// they want the block, send it to them
|
|
139
147
|
sentBlocks.add(key)
|
|
140
148
|
message.blocks.push({
|
|
@@ -146,7 +154,9 @@ export class Ledger {
|
|
|
146
154
|
|
|
147
155
|
// only send the message if we actually have something to send
|
|
148
156
|
if (message.blocks.length > 0 || message.blockPresences.length > 0) {
|
|
157
|
+
this.log('sending message')
|
|
149
158
|
await this.network.sendMessage(this.peerId, message, options)
|
|
159
|
+
this.log('sent message')
|
|
150
160
|
|
|
151
161
|
// update accounting
|
|
152
162
|
this.sentBytes(message.blocks.reduce((acc, curr) => acc + curr.data.byteLength, 0))
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { base64 } from 'multiformats/bases/base64'
|
|
2
|
+
import type { BitswapMessage, Block, BlockPresence, WantlistEntry } from '../pb/message.js'
|
|
3
|
+
|
|
4
|
+
export function mergeMessages (messageA: BitswapMessage, messageB: BitswapMessage): BitswapMessage {
|
|
5
|
+
const wantListEntries = new Map<string, WantlistEntry>(
|
|
6
|
+
(messageA.wantlist?.entries ?? []).map(entry => ([
|
|
7
|
+
base64.encode(entry.cid),
|
|
8
|
+
entry
|
|
9
|
+
]))
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
for (const entry of messageB.wantlist?.entries ?? []) {
|
|
13
|
+
const key = base64.encode(entry.cid)
|
|
14
|
+
const existingEntry = wantListEntries.get(key)
|
|
15
|
+
|
|
16
|
+
if (existingEntry != null) {
|
|
17
|
+
// take highest priority
|
|
18
|
+
if (existingEntry.priority > entry.priority) {
|
|
19
|
+
entry.priority = existingEntry.priority
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// take later values if passed, otherwise use earlier ones
|
|
23
|
+
entry.cancel = entry.cancel ?? existingEntry.cancel
|
|
24
|
+
entry.wantType = entry.wantType ?? existingEntry.wantType
|
|
25
|
+
entry.sendDontHave = entry.sendDontHave ?? existingEntry.sendDontHave
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
wantListEntries.set(key, entry)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const blockPresences = new Map<string, BlockPresence>(
|
|
32
|
+
messageA.blockPresences.map(presence => ([
|
|
33
|
+
base64.encode(presence.cid),
|
|
34
|
+
presence
|
|
35
|
+
]))
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
for (const blockPresence of messageB.blockPresences) {
|
|
39
|
+
const key = base64.encode(blockPresence.cid)
|
|
40
|
+
|
|
41
|
+
// override earlier block presence with later one as if duplicated it is
|
|
42
|
+
// likely to be more accurate since it is more recent
|
|
43
|
+
blockPresences.set(key, blockPresence)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const blocks = new Map<string, Block>(
|
|
47
|
+
messageA.blocks.map(block => ([
|
|
48
|
+
base64.encode(block.data),
|
|
49
|
+
block
|
|
50
|
+
]))
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
for (const block of messageB.blocks) {
|
|
54
|
+
const key = base64.encode(block.data)
|
|
55
|
+
|
|
56
|
+
blocks.set(key, block)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const output: BitswapMessage = {
|
|
60
|
+
wantlist: {
|
|
61
|
+
full: messageA.wantlist?.full ?? messageB.wantlist?.full ?? false,
|
|
62
|
+
entries: [...wantListEntries.values()]
|
|
63
|
+
},
|
|
64
|
+
blockPresences: [...blockPresences.values()],
|
|
65
|
+
blocks: [...blocks.values()],
|
|
66
|
+
pendingBytes: messageA.pendingBytes + messageB.pendingBytes
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return output
|
|
70
|
+
}
|