@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.
Files changed (70) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +64 -0
  3. package/dist/index.min.js +3 -0
  4. package/dist/src/bitswap.d.ts +50 -0
  5. package/dist/src/bitswap.d.ts.map +1 -0
  6. package/dist/src/bitswap.js +120 -0
  7. package/dist/src/bitswap.js.map +1 -0
  8. package/dist/src/constants.d.ts +12 -0
  9. package/dist/src/constants.d.ts.map +1 -0
  10. package/dist/src/constants.js +12 -0
  11. package/dist/src/constants.js.map +1 -0
  12. package/dist/src/index.d.ts +178 -0
  13. package/dist/src/index.d.ts.map +1 -0
  14. package/dist/src/index.js +12 -0
  15. package/dist/src/index.js.map +1 -0
  16. package/dist/src/network.d.ts +84 -0
  17. package/dist/src/network.d.ts.map +1 -0
  18. package/dist/src/network.js +370 -0
  19. package/dist/src/network.js.map +1 -0
  20. package/dist/src/pb/message.d.ts +67 -0
  21. package/dist/src/pb/message.d.ts.map +1 -0
  22. package/dist/src/pb/message.js +359 -0
  23. package/dist/src/pb/message.js.map +1 -0
  24. package/dist/src/peer-want-lists/index.d.ts +44 -0
  25. package/dist/src/peer-want-lists/index.d.ts.map +1 -0
  26. package/dist/src/peer-want-lists/index.js +116 -0
  27. package/dist/src/peer-want-lists/index.js.map +1 -0
  28. package/dist/src/peer-want-lists/ledger.d.ts +54 -0
  29. package/dist/src/peer-want-lists/ledger.d.ts.map +1 -0
  30. package/dist/src/peer-want-lists/ledger.js +104 -0
  31. package/dist/src/peer-want-lists/ledger.js.map +1 -0
  32. package/dist/src/session.d.ts +20 -0
  33. package/dist/src/session.d.ts.map +1 -0
  34. package/dist/src/session.js +100 -0
  35. package/dist/src/session.js.map +1 -0
  36. package/dist/src/stats.d.ts +16 -0
  37. package/dist/src/stats.d.ts.map +1 -0
  38. package/dist/src/stats.js +49 -0
  39. package/dist/src/stats.js.map +1 -0
  40. package/dist/src/utils/cid-prefix.d.ts +3 -0
  41. package/dist/src/utils/cid-prefix.d.ts.map +1 -0
  42. package/dist/src/utils/cid-prefix.js +7 -0
  43. package/dist/src/utils/cid-prefix.js.map +1 -0
  44. package/dist/src/utils/varint-decoder.d.ts +3 -0
  45. package/dist/src/utils/varint-decoder.d.ts.map +1 -0
  46. package/dist/src/utils/varint-decoder.js +15 -0
  47. package/dist/src/utils/varint-decoder.js.map +1 -0
  48. package/dist/src/utils/varint-encoder.d.ts +3 -0
  49. package/dist/src/utils/varint-encoder.d.ts.map +1 -0
  50. package/dist/src/utils/varint-encoder.js +14 -0
  51. package/dist/src/utils/varint-encoder.js.map +1 -0
  52. package/dist/src/want-list.d.ts +120 -0
  53. package/dist/src/want-list.d.ts.map +1 -0
  54. package/dist/src/want-list.js +361 -0
  55. package/dist/src/want-list.js.map +1 -0
  56. package/package.json +200 -0
  57. package/src/bitswap.ts +152 -0
  58. package/src/constants.ts +11 -0
  59. package/src/index.ts +215 -0
  60. package/src/network.ts +506 -0
  61. package/src/pb/message.proto +42 -0
  62. package/src/pb/message.ts +450 -0
  63. package/src/peer-want-lists/index.ts +165 -0
  64. package/src/peer-want-lists/ledger.ts +161 -0
  65. package/src/session.ts +150 -0
  66. package/src/stats.ts +67 -0
  67. package/src/utils/cid-prefix.ts +8 -0
  68. package/src/utils/varint-decoder.ts +19 -0
  69. package/src/utils/varint-encoder.ts +18 -0
  70. package/src/want-list.ts +529 -0
@@ -0,0 +1,450 @@
1
+ /* eslint-disable import/export */
2
+ /* eslint-disable complexity */
3
+ /* eslint-disable @typescript-eslint/no-namespace */
4
+ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
5
+ /* eslint-disable @typescript-eslint/no-empty-interface */
6
+
7
+ import { type Codec, decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime'
8
+ import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc'
9
+ import type { Uint8ArrayList } from 'uint8arraylist'
10
+
11
+ export enum WantType {
12
+ WantBlock = 'WantBlock',
13
+ WantHave = 'WantHave'
14
+ }
15
+
16
+ enum __WantTypeValues {
17
+ WantBlock = 0,
18
+ WantHave = 1
19
+ }
20
+
21
+ export namespace WantType {
22
+ export const codec = (): Codec<WantType> => {
23
+ return enumeration<WantType>(__WantTypeValues)
24
+ }
25
+ }
26
+ export interface WantlistEntry {
27
+ cid: Uint8Array
28
+ priority: number
29
+ cancel?: boolean
30
+ wantType?: WantType
31
+ sendDontHave?: boolean
32
+ }
33
+
34
+ export namespace WantlistEntry {
35
+ let _codec: Codec<WantlistEntry>
36
+
37
+ export const codec = (): Codec<WantlistEntry> => {
38
+ if (_codec == null) {
39
+ _codec = message<WantlistEntry>((obj, w, opts = {}) => {
40
+ if (opts.lengthDelimited !== false) {
41
+ w.fork()
42
+ }
43
+
44
+ if ((obj.cid != null && obj.cid.byteLength > 0)) {
45
+ w.uint32(10)
46
+ w.bytes(obj.cid)
47
+ }
48
+
49
+ if ((obj.priority != null && obj.priority !== 0)) {
50
+ w.uint32(16)
51
+ w.int32(obj.priority)
52
+ }
53
+
54
+ if (obj.cancel != null) {
55
+ w.uint32(24)
56
+ w.bool(obj.cancel)
57
+ }
58
+
59
+ if (obj.wantType != null) {
60
+ w.uint32(32)
61
+ WantType.codec().encode(obj.wantType, w)
62
+ }
63
+
64
+ if (obj.sendDontHave != null) {
65
+ w.uint32(40)
66
+ w.bool(obj.sendDontHave)
67
+ }
68
+
69
+ if (opts.lengthDelimited !== false) {
70
+ w.ldelim()
71
+ }
72
+ }, (reader, length) => {
73
+ const obj: any = {
74
+ cid: uint8ArrayAlloc(0),
75
+ priority: 0
76
+ }
77
+
78
+ const end = length == null ? reader.len : reader.pos + length
79
+
80
+ while (reader.pos < end) {
81
+ const tag = reader.uint32()
82
+
83
+ switch (tag >>> 3) {
84
+ case 1: {
85
+ obj.cid = reader.bytes()
86
+ break
87
+ }
88
+ case 2: {
89
+ obj.priority = reader.int32()
90
+ break
91
+ }
92
+ case 3: {
93
+ obj.cancel = reader.bool()
94
+ break
95
+ }
96
+ case 4: {
97
+ obj.wantType = WantType.codec().decode(reader)
98
+ break
99
+ }
100
+ case 5: {
101
+ obj.sendDontHave = reader.bool()
102
+ break
103
+ }
104
+ default: {
105
+ reader.skipType(tag & 7)
106
+ break
107
+ }
108
+ }
109
+ }
110
+
111
+ return obj
112
+ })
113
+ }
114
+
115
+ return _codec
116
+ }
117
+
118
+ export const encode = (obj: Partial<WantlistEntry>): Uint8Array => {
119
+ return encodeMessage(obj, WantlistEntry.codec())
120
+ }
121
+
122
+ export const decode = (buf: Uint8Array | Uint8ArrayList): WantlistEntry => {
123
+ return decodeMessage(buf, WantlistEntry.codec())
124
+ }
125
+ }
126
+
127
+ export interface Wantlist {
128
+ entries: WantlistEntry[]
129
+ full?: boolean
130
+ }
131
+
132
+ export namespace Wantlist {
133
+ let _codec: Codec<Wantlist>
134
+
135
+ export const codec = (): Codec<Wantlist> => {
136
+ if (_codec == null) {
137
+ _codec = message<Wantlist>((obj, w, opts = {}) => {
138
+ if (opts.lengthDelimited !== false) {
139
+ w.fork()
140
+ }
141
+
142
+ if (obj.entries != null) {
143
+ for (const value of obj.entries) {
144
+ w.uint32(10)
145
+ WantlistEntry.codec().encode(value, w)
146
+ }
147
+ }
148
+
149
+ if (obj.full != null) {
150
+ w.uint32(16)
151
+ w.bool(obj.full)
152
+ }
153
+
154
+ if (opts.lengthDelimited !== false) {
155
+ w.ldelim()
156
+ }
157
+ }, (reader, length) => {
158
+ const obj: any = {
159
+ entries: []
160
+ }
161
+
162
+ const end = length == null ? reader.len : reader.pos + length
163
+
164
+ while (reader.pos < end) {
165
+ const tag = reader.uint32()
166
+
167
+ switch (tag >>> 3) {
168
+ case 1: {
169
+ obj.entries.push(WantlistEntry.codec().decode(reader, reader.uint32()))
170
+ break
171
+ }
172
+ case 2: {
173
+ obj.full = reader.bool()
174
+ break
175
+ }
176
+ default: {
177
+ reader.skipType(tag & 7)
178
+ break
179
+ }
180
+ }
181
+ }
182
+
183
+ return obj
184
+ })
185
+ }
186
+
187
+ return _codec
188
+ }
189
+
190
+ export const encode = (obj: Partial<Wantlist>): Uint8Array => {
191
+ return encodeMessage(obj, Wantlist.codec())
192
+ }
193
+
194
+ export const decode = (buf: Uint8Array | Uint8ArrayList): Wantlist => {
195
+ return decodeMessage(buf, Wantlist.codec())
196
+ }
197
+ }
198
+
199
+ export interface Block {
200
+ prefix: Uint8Array
201
+ data: Uint8Array
202
+ }
203
+
204
+ export namespace Block {
205
+ let _codec: Codec<Block>
206
+
207
+ export const codec = (): Codec<Block> => {
208
+ if (_codec == null) {
209
+ _codec = message<Block>((obj, w, opts = {}) => {
210
+ if (opts.lengthDelimited !== false) {
211
+ w.fork()
212
+ }
213
+
214
+ if ((obj.prefix != null && obj.prefix.byteLength > 0)) {
215
+ w.uint32(10)
216
+ w.bytes(obj.prefix)
217
+ }
218
+
219
+ if ((obj.data != null && obj.data.byteLength > 0)) {
220
+ w.uint32(18)
221
+ w.bytes(obj.data)
222
+ }
223
+
224
+ if (opts.lengthDelimited !== false) {
225
+ w.ldelim()
226
+ }
227
+ }, (reader, length) => {
228
+ const obj: any = {
229
+ prefix: uint8ArrayAlloc(0),
230
+ data: uint8ArrayAlloc(0)
231
+ }
232
+
233
+ const end = length == null ? reader.len : reader.pos + length
234
+
235
+ while (reader.pos < end) {
236
+ const tag = reader.uint32()
237
+
238
+ switch (tag >>> 3) {
239
+ case 1: {
240
+ obj.prefix = reader.bytes()
241
+ break
242
+ }
243
+ case 2: {
244
+ obj.data = reader.bytes()
245
+ break
246
+ }
247
+ default: {
248
+ reader.skipType(tag & 7)
249
+ break
250
+ }
251
+ }
252
+ }
253
+
254
+ return obj
255
+ })
256
+ }
257
+
258
+ return _codec
259
+ }
260
+
261
+ export const encode = (obj: Partial<Block>): Uint8Array => {
262
+ return encodeMessage(obj, Block.codec())
263
+ }
264
+
265
+ export const decode = (buf: Uint8Array | Uint8ArrayList): Block => {
266
+ return decodeMessage(buf, Block.codec())
267
+ }
268
+ }
269
+
270
+ export enum BlockPresenceType {
271
+ HaveBlock = 'HaveBlock',
272
+ DontHaveBlock = 'DontHaveBlock'
273
+ }
274
+
275
+ enum __BlockPresenceTypeValues {
276
+ HaveBlock = 0,
277
+ DontHaveBlock = 1
278
+ }
279
+
280
+ export namespace BlockPresenceType {
281
+ export const codec = (): Codec<BlockPresenceType> => {
282
+ return enumeration<BlockPresenceType>(__BlockPresenceTypeValues)
283
+ }
284
+ }
285
+ export interface BlockPresence {
286
+ cid: Uint8Array
287
+ type: BlockPresenceType
288
+ }
289
+
290
+ export namespace BlockPresence {
291
+ let _codec: Codec<BlockPresence>
292
+
293
+ export const codec = (): Codec<BlockPresence> => {
294
+ if (_codec == null) {
295
+ _codec = message<BlockPresence>((obj, w, opts = {}) => {
296
+ if (opts.lengthDelimited !== false) {
297
+ w.fork()
298
+ }
299
+
300
+ if ((obj.cid != null && obj.cid.byteLength > 0)) {
301
+ w.uint32(10)
302
+ w.bytes(obj.cid)
303
+ }
304
+
305
+ if (obj.type != null && __BlockPresenceTypeValues[obj.type] !== 0) {
306
+ w.uint32(16)
307
+ BlockPresenceType.codec().encode(obj.type, w)
308
+ }
309
+
310
+ if (opts.lengthDelimited !== false) {
311
+ w.ldelim()
312
+ }
313
+ }, (reader, length) => {
314
+ const obj: any = {
315
+ cid: uint8ArrayAlloc(0),
316
+ type: BlockPresenceType.HaveBlock
317
+ }
318
+
319
+ const end = length == null ? reader.len : reader.pos + length
320
+
321
+ while (reader.pos < end) {
322
+ const tag = reader.uint32()
323
+
324
+ switch (tag >>> 3) {
325
+ case 1: {
326
+ obj.cid = reader.bytes()
327
+ break
328
+ }
329
+ case 2: {
330
+ obj.type = BlockPresenceType.codec().decode(reader)
331
+ break
332
+ }
333
+ default: {
334
+ reader.skipType(tag & 7)
335
+ break
336
+ }
337
+ }
338
+ }
339
+
340
+ return obj
341
+ })
342
+ }
343
+
344
+ return _codec
345
+ }
346
+
347
+ export const encode = (obj: Partial<BlockPresence>): Uint8Array => {
348
+ return encodeMessage(obj, BlockPresence.codec())
349
+ }
350
+
351
+ export const decode = (buf: Uint8Array | Uint8ArrayList): BlockPresence => {
352
+ return decodeMessage(buf, BlockPresence.codec())
353
+ }
354
+ }
355
+
356
+ export interface BitswapMessage {
357
+ wantlist?: Wantlist
358
+ blocks: Block[]
359
+ blockPresences: BlockPresence[]
360
+ pendingBytes: number
361
+ }
362
+
363
+ export namespace BitswapMessage {
364
+ let _codec: Codec<BitswapMessage>
365
+
366
+ export const codec = (): Codec<BitswapMessage> => {
367
+ if (_codec == null) {
368
+ _codec = message<BitswapMessage>((obj, w, opts = {}) => {
369
+ if (opts.lengthDelimited !== false) {
370
+ w.fork()
371
+ }
372
+
373
+ if (obj.wantlist != null) {
374
+ w.uint32(10)
375
+ Wantlist.codec().encode(obj.wantlist, w)
376
+ }
377
+
378
+ if (obj.blocks != null) {
379
+ for (const value of obj.blocks) {
380
+ w.uint32(26)
381
+ Block.codec().encode(value, w)
382
+ }
383
+ }
384
+
385
+ if (obj.blockPresences != null) {
386
+ for (const value of obj.blockPresences) {
387
+ w.uint32(34)
388
+ BlockPresence.codec().encode(value, w)
389
+ }
390
+ }
391
+
392
+ if ((obj.pendingBytes != null && obj.pendingBytes !== 0)) {
393
+ w.uint32(40)
394
+ w.int32(obj.pendingBytes)
395
+ }
396
+
397
+ if (opts.lengthDelimited !== false) {
398
+ w.ldelim()
399
+ }
400
+ }, (reader, length) => {
401
+ const obj: any = {
402
+ blocks: [],
403
+ blockPresences: [],
404
+ pendingBytes: 0
405
+ }
406
+
407
+ const end = length == null ? reader.len : reader.pos + length
408
+
409
+ while (reader.pos < end) {
410
+ const tag = reader.uint32()
411
+
412
+ switch (tag >>> 3) {
413
+ case 1: {
414
+ obj.wantlist = Wantlist.codec().decode(reader, reader.uint32())
415
+ break
416
+ }
417
+ case 3: {
418
+ obj.blocks.push(Block.codec().decode(reader, reader.uint32()))
419
+ break
420
+ }
421
+ case 4: {
422
+ obj.blockPresences.push(BlockPresence.codec().decode(reader, reader.uint32()))
423
+ break
424
+ }
425
+ case 5: {
426
+ obj.pendingBytes = reader.int32()
427
+ break
428
+ }
429
+ default: {
430
+ reader.skipType(tag & 7)
431
+ break
432
+ }
433
+ }
434
+ }
435
+
436
+ return obj
437
+ })
438
+ }
439
+
440
+ return _codec
441
+ }
442
+
443
+ export const encode = (obj: Partial<BitswapMessage>): Uint8Array => {
444
+ return encodeMessage(obj, BitswapMessage.codec())
445
+ }
446
+
447
+ export const decode = (buf: Uint8Array | Uint8ArrayList): BitswapMessage => {
448
+ return decodeMessage(buf, BitswapMessage.codec())
449
+ }
450
+ }
@@ -0,0 +1,165 @@
1
+ import { trackedPeerMap } from '@libp2p/peer-collections'
2
+ import { CID } from 'multiformats/cid'
3
+ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
4
+ import { WantType } from '../pb/message.js'
5
+ import { Ledger } from './ledger.js'
6
+ import type { BitswapNotifyProgressEvents, WantListEntry } from '../index.js'
7
+ import type { Network } from '../network.js'
8
+ import type { BitswapMessage } from '../pb/message.js'
9
+ import type { ComponentLogger, Logger, Metrics, PeerId } from '@libp2p/interface'
10
+ import type { PeerMap } from '@libp2p/peer-collections'
11
+ import type { Blockstore } from 'interface-blockstore'
12
+ import type { AbortOptions } from 'it-length-prefixed-stream'
13
+ import type { ProgressOptions } from 'progress-events'
14
+
15
+ export interface PeerWantListsInit {
16
+ maxSizeReplaceHasWithBlock?: number
17
+ }
18
+
19
+ export interface PeerWantListsComponents {
20
+ blockstore: Blockstore
21
+ network: Network
22
+ metrics?: Metrics
23
+ logger: ComponentLogger
24
+ }
25
+
26
+ export interface PeerLedger {
27
+ peer: PeerId
28
+ value: number
29
+ sent: number
30
+ received: number
31
+ exchanged: number
32
+ }
33
+
34
+ export class PeerWantLists {
35
+ public blockstore: Blockstore
36
+ public network: Network
37
+ public readonly ledgerMap: PeerMap<Ledger>
38
+ private readonly maxSizeReplaceHasWithBlock?: number
39
+ private readonly log: Logger
40
+
41
+ constructor (components: PeerWantListsComponents, init: PeerWantListsInit = {}) {
42
+ this.blockstore = components.blockstore
43
+ this.network = components.network
44
+ this.maxSizeReplaceHasWithBlock = init.maxSizeReplaceHasWithBlock
45
+ this.log = components.logger.forComponent('helia:bitswap:peer-want-lists')
46
+
47
+ this.ledgerMap = trackedPeerMap({
48
+ name: 'ipfs_bitswap_ledger_map',
49
+ metrics: components.metrics
50
+ })
51
+
52
+ this.network.addEventListener('bitswap:message', (evt) => {
53
+ this.receiveMessage(evt.detail.peer, evt.detail.message)
54
+ .catch(err => {
55
+ this.log.error('error receiving bitswap message from %p', evt.detail.peer, err)
56
+ })
57
+ })
58
+ this.network.addEventListener('peer:disconnected', evt => {
59
+ this.peerDisconnected(evt.detail)
60
+ })
61
+ }
62
+
63
+ ledgerForPeer (peerId: PeerId): PeerLedger | undefined {
64
+ const ledger = this.ledgerMap.get(peerId)
65
+
66
+ if (ledger == null) {
67
+ return undefined
68
+ }
69
+
70
+ return {
71
+ peer: ledger.peerId,
72
+ value: ledger.debtRatio(),
73
+ sent: ledger.bytesSent,
74
+ received: ledger.bytesReceived,
75
+ exchanged: ledger.exchangeCount
76
+ }
77
+ }
78
+
79
+ wantListForPeer (peerId: PeerId): WantListEntry[] | undefined {
80
+ const ledger = this.ledgerMap.get(peerId)
81
+
82
+ if (ledger == null) {
83
+ return undefined
84
+ }
85
+
86
+ return [...ledger.wants.values()]
87
+ }
88
+
89
+ peers (): PeerId[] {
90
+ return Array.from(this.ledgerMap.values()).map((l) => l.peerId)
91
+ }
92
+
93
+ /**
94
+ * Handle incoming messages
95
+ */
96
+ async receiveMessage (peerId: PeerId, message: BitswapMessage): Promise<void> {
97
+ let ledger = this.ledgerMap.get(peerId)
98
+
99
+ if (ledger == null) {
100
+ ledger = new Ledger({
101
+ peerId,
102
+ blockstore: this.blockstore,
103
+ network: this.network
104
+ }, {
105
+ maxSizeReplaceHasWithBlock: this.maxSizeReplaceHasWithBlock
106
+ })
107
+ this.ledgerMap.set(peerId, ledger)
108
+ }
109
+
110
+ // record the amount of block data received
111
+ ledger.receivedBytes(message.blocks?.reduce((acc, curr) => acc + curr.data.byteLength, 0) ?? 0)
112
+
113
+ if (message.wantlist != null) {
114
+ // if the message has a full wantlist, clear the current wantlist
115
+ if (message.wantlist.full === true) {
116
+ ledger.wants.clear()
117
+ }
118
+
119
+ // clear cancelled wants and add new wants to the ledger
120
+ for (const entry of message.wantlist.entries) {
121
+ const cid = CID.decode(entry.cid)
122
+ const cidStr = uint8ArrayToString(cid.multihash.bytes, 'base64')
123
+
124
+ if (entry.cancel === true) {
125
+ this.log('peer %p cancelled want of block for %c', peerId, cid)
126
+ ledger.wants.delete(cidStr)
127
+ } else {
128
+ if (entry.wantType === WantType.WantHave) {
129
+ this.log('peer %p wanted block presence for %c', peerId, cid)
130
+ } else {
131
+ this.log('peer %p wanted block for %c', peerId, cid)
132
+ }
133
+
134
+ ledger.wants.set(cidStr, {
135
+ cid,
136
+ priority: entry.priority,
137
+ wantType: entry.wantType ?? WantType.WantBlock,
138
+ sendDontHave: entry.sendDontHave ?? false
139
+ })
140
+ }
141
+ }
142
+ }
143
+
144
+ await ledger.sendBlocksToPeer()
145
+ }
146
+
147
+ async receivedBlock (cid: CID, options: ProgressOptions<BitswapNotifyProgressEvents> & AbortOptions): Promise<void> {
148
+ const cidStr = uint8ArrayToString(cid.multihash.bytes, 'base64')
149
+ const ledgers: Ledger[] = []
150
+
151
+ for (const ledger of this.ledgerMap.values()) {
152
+ if (ledger.wants.has(cidStr)) {
153
+ ledgers.push(ledger)
154
+ }
155
+ }
156
+
157
+ await Promise.all(
158
+ ledgers.map(async (ledger) => ledger.sendBlocksToPeer(options))
159
+ )
160
+ }
161
+
162
+ peerDisconnected (peerId: PeerId): void {
163
+ this.ledgerMap.delete(peerId)
164
+ }
165
+ }