@helia/bitswap 3.1.2 → 3.1.3-83e1f40d

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 (45) hide show
  1. package/dist/index.min.js +1 -1
  2. package/dist/index.min.js.map +3 -3
  3. package/dist/src/bitswap.d.ts +6 -6
  4. package/dist/src/bitswap.d.ts.map +1 -1
  5. package/dist/src/bitswap.js +5 -5
  6. package/dist/src/constants.d.ts +2 -0
  7. package/dist/src/constants.d.ts.map +1 -1
  8. package/dist/src/constants.js +2 -0
  9. package/dist/src/constants.js.map +1 -1
  10. package/dist/src/index.d.ts +20 -3
  11. package/dist/src/index.d.ts.map +1 -1
  12. package/dist/src/index.js +1 -1
  13. package/dist/src/index.js.map +1 -1
  14. package/dist/src/network.d.ts +5 -5
  15. package/dist/src/network.js +4 -4
  16. package/dist/src/peer-want-lists/index.d.ts +9 -5
  17. package/dist/src/peer-want-lists/index.d.ts.map +1 -1
  18. package/dist/src/peer-want-lists/index.js +16 -36
  19. package/dist/src/peer-want-lists/index.js.map +1 -1
  20. package/dist/src/peer-want-lists/ledger.d.ts +39 -4
  21. package/dist/src/peer-want-lists/ledger.d.ts.map +1 -1
  22. package/dist/src/peer-want-lists/ledger.js +146 -10
  23. package/dist/src/peer-want-lists/ledger.js.map +1 -1
  24. package/dist/src/session.d.ts +3 -3
  25. package/dist/src/utils/bitswap-message.d.ts +1 -1
  26. package/dist/src/utils/cid-prefix.js +1 -1
  27. package/dist/src/utils/merge-messages.d.ts +1 -1
  28. package/dist/src/utils/split-message.d.ts +1 -1
  29. package/dist/src/utils/split-message.js +2 -2
  30. package/dist/src/want-list.d.ts +3 -3
  31. package/dist/src/want-list.js +4 -4
  32. package/package.json +3 -3
  33. package/src/bitswap.ts +7 -7
  34. package/src/constants.ts +2 -0
  35. package/src/index.ts +25 -4
  36. package/src/network.ts +8 -8
  37. package/src/peer-want-lists/index.ts +23 -40
  38. package/src/peer-want-lists/ledger.ts +210 -14
  39. package/src/session.ts +3 -3
  40. package/src/utils/bitswap-message.ts +1 -1
  41. package/src/utils/cid-prefix.ts +1 -1
  42. package/src/utils/merge-messages.ts +1 -1
  43. package/src/utils/split-message.ts +3 -3
  44. package/src/want-list.ts +7 -7
  45. package/dist/typedoc-urls.json +0 -26
@@ -1,12 +1,14 @@
1
1
  import toBuffer from 'it-to-buffer'
2
- import { DEFAULT_MAX_SIZE_REPLACE_HAS_WITH_BLOCK } from '../constants.js'
3
- import { BlockPresenceType, WantType } from '../pb/message.js'
4
- import { QueuedBitswapMessage } from '../utils/bitswap-message.js'
5
- import { cidToPrefix } from '../utils/cid-prefix.js'
6
- import type { Network } from '../network.js'
2
+ import { CID } from 'multiformats/cid'
3
+ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
4
+ import { DEFAULT_MAX_SIZE_REPLACE_HAS_WITH_BLOCK, DEFAULT_DO_NOT_RESEND_BLOCK_WINDOW, DEFAULT_MAX_WANTLIST_SIZE } from '../constants.ts'
5
+ import { BlockPresenceType, WantType } from '../pb/message.ts'
6
+ import { QueuedBitswapMessage } from '../utils/bitswap-message.ts'
7
+ import { cidToPrefix } from '../utils/cid-prefix.ts'
8
+ import type { Network } from '../network.ts'
9
+ import type { Wantlist } from '../pb/message.ts'
7
10
  import type { AbortOptions, ComponentLogger, Logger, PeerId } from '@libp2p/interface'
8
11
  import type { Blockstore } from 'interface-blockstore'
9
- import type { CID } from 'multiformats/cid'
10
12
 
11
13
  export interface LedgerComponents {
12
14
  peerId: PeerId
@@ -17,6 +19,8 @@ export interface LedgerComponents {
17
19
 
18
20
  export interface LedgerInit {
19
21
  maxSizeReplaceHasWithBlock?: number
22
+ doNotResendBlockWindow?: number
23
+ maxWantListSize?: number
20
24
  }
21
25
 
22
26
  export interface PeerWantListEntry {
@@ -45,19 +49,50 @@ export interface PeerWantListEntry {
45
49
  * If we don't have the block and we've told them we don't have the block
46
50
  */
47
51
  sentDoNotHave?: boolean
52
+
53
+ /**
54
+ * If the status is `sending` or `sent`, the block for this CID is or has been
55
+ * sent to the peer so we should not attempt to send it again
56
+ */
57
+ status: 'want' | 'sending' | 'sent'
58
+
59
+ /**
60
+ * A timestamp for when this want should be removed from the list, typically
61
+ * this is set with the `sent` status to prevent sending duplicate blocks to a
62
+ * peer. Once it has expired the peer can request the block a subsequent time.
63
+ */
64
+ expires?: number
65
+
66
+ /**
67
+ * A timestamp of when this entry was created
68
+ */
69
+ created: number
70
+
71
+ /**
72
+ * If this field is false, we have attempted to send this WantList entry but
73
+ * found there is no block for the CID in the blockstore and we are
74
+ * optimistically waiting to see if we come across it later.
75
+ *
76
+ * We only perform the check when we are about to send the block, by which
77
+ * point the entry status is 'sending' so this value with either be false or
78
+ * not set
79
+ */
80
+ haveBlock?: false
48
81
  }
49
82
 
50
83
  export class Ledger {
51
84
  public peerId: PeerId
52
85
  private readonly blockstore: Blockstore
53
86
  private readonly network: Network
54
- public wants: Map<string, PeerWantListEntry>
87
+ private wants: Map<string, PeerWantListEntry>
55
88
  public exchangeCount: number
56
89
  public bytesSent: number
57
90
  public bytesReceived: number
58
91
  public lastExchange?: number
59
92
  private readonly maxSizeReplaceHasWithBlock: number
60
93
  private readonly log: Logger
94
+ private readonly doNotResendBlockWindow: number
95
+ private readonly maxWantListSize: number
61
96
 
62
97
  constructor (components: LedgerComponents, init: LedgerInit) {
63
98
  this.peerId = components.peerId
@@ -70,6 +105,8 @@ export class Ledger {
70
105
  this.bytesSent = 0
71
106
  this.bytesReceived = 0
72
107
  this.maxSizeReplaceHasWithBlock = init.maxSizeReplaceHasWithBlock ?? DEFAULT_MAX_SIZE_REPLACE_HAS_WITH_BLOCK
108
+ this.doNotResendBlockWindow = init.doNotResendBlockWindow ?? DEFAULT_DO_NOT_RESEND_BLOCK_WINDOW
109
+ this.maxWantListSize = init.maxWantListSize ?? DEFAULT_MAX_WANTLIST_SIZE
73
110
  }
74
111
 
75
112
  sentBytes (n: number): void {
@@ -88,18 +125,174 @@ export class Ledger {
88
125
  return (this.bytesSent / (this.bytesReceived + 1)) // +1 is to prevent division by zero
89
126
  }
90
127
 
128
+ removeExpiredWants (): void {
129
+ // remove any expired wants
130
+ this.wants.forEach((value, key) => {
131
+ if (value.expires != null && value.expires < Date.now()) {
132
+ this.wants.delete(key)
133
+ }
134
+ })
135
+ }
136
+
137
+ public addWants (wantlist?: Wantlist): void {
138
+ if (wantlist == null) {
139
+ return
140
+ }
141
+
142
+ // if the message has a full wantlist, remove all entries not currently
143
+ // being sent to the peer
144
+ if (wantlist.full === true) {
145
+ this.wants.forEach((value, key) => {
146
+ if (value.status === 'want') {
147
+ this.wants.delete(key)
148
+ }
149
+ })
150
+ }
151
+
152
+ // clear cancelled wants and add new wants to the ledger
153
+ for (const entry of wantlist.entries) {
154
+ const cid = CID.decode(entry.cid)
155
+ const cidStr = uint8ArrayToString(cid.multihash.bytes, 'base64')
156
+
157
+ if (entry.cancel === true) {
158
+ this.log('peer %p cancelled want of block for %c', this.peerId, cid)
159
+ this.wants.delete(cidStr)
160
+ } else {
161
+ if (entry.wantType === WantType.WantHave) {
162
+ this.log('peer %p wanted block presence for %c', this.peerId, cid)
163
+ } else {
164
+ this.log('peer %p wanted block for %c', this.peerId, cid)
165
+ }
166
+
167
+ const existingWant = this.wants.get(cidStr)
168
+
169
+ // we are already tracking a want for this CID, just update the fields
170
+ if (existingWant != null) {
171
+ const sentOrSending = existingWant.status === 'sent' || existingWant.status === 'sending'
172
+ const wantTypeUpgrade = existingWant.wantType === WantType.WantHave && (entry.wantType == null || entry.wantType === WantType.WantBlock)
173
+
174
+ // allow upgrade from WantHave to WantBlock if we've previously
175
+ // sent or are sending a WantHave
176
+ if (sentOrSending && wantTypeUpgrade) {
177
+ existingWant.status = 'want'
178
+ }
179
+
180
+ existingWant.priority = entry.priority
181
+ existingWant.wantType = entry.wantType ?? WantType.WantBlock
182
+ existingWant.sendDontHave = entry.sendDontHave ?? false
183
+ continue
184
+ }
185
+
186
+ // add a new want
187
+ this.wants.set(cidStr, {
188
+ cid,
189
+ priority: entry.priority,
190
+ wantType: entry.wantType ?? WantType.WantBlock,
191
+ sendDontHave: entry.sendDontHave ?? false,
192
+ status: 'want',
193
+ created: Date.now()
194
+ })
195
+ }
196
+ }
197
+
198
+ // if we have exceeded maxWantListSize, truncate the list - first select
199
+ // wants that are not currently being sent to the user
200
+ const wants = [...this.wants.entries()]
201
+ .filter(([key, entry]) => entry.status === 'want')
202
+
203
+ if (wants.length > this.maxWantListSize) {
204
+ this.truncateWants(wants)
205
+ }
206
+ }
207
+
208
+ private truncateWants (wants: Array<[string, PeerWantListEntry]>): void {
209
+ // sort wants by priority, lack of block presence, then age so the wants
210
+ // to be evicted are a older, low priority wants that we don't have the
211
+ // block for
212
+ wants = wants
213
+ .sort((a, b) => {
214
+ if (a[1].created < b[1].created) {
215
+ return -1
216
+ }
217
+
218
+ if (b[1].created < a[1].created) {
219
+ return 1
220
+ }
221
+
222
+ return 0
223
+ })
224
+ .sort((a, b) => {
225
+ if (a[1].haveBlock === false) {
226
+ return -1
227
+ }
228
+
229
+ if (b[1].haveBlock === false) {
230
+ return 1
231
+ }
232
+
233
+ return 0
234
+ })
235
+ .sort((a, b) => {
236
+ if (a[1].priority < b[1].priority) {
237
+ return -1
238
+ }
239
+
240
+ if (b[1].priority < a[1].priority) {
241
+ return 1
242
+ }
243
+
244
+ return 0
245
+ })
246
+
247
+ const toRemove = wants.length - this.maxWantListSize
248
+
249
+ for (let i = 0; i < toRemove; i++) {
250
+ this.wants.delete(wants[i][0])
251
+ }
252
+ }
253
+
254
+ public getWants (): PeerWantListEntry[] {
255
+ return [...this.wants.values()]
256
+ }
257
+
258
+ public hasWant (cid: CID): boolean {
259
+ const cidStr = uint8ArrayToString(cid.multihash.bytes, 'base64')
260
+
261
+ return this.wants.has(cidStr)
262
+ }
263
+
91
264
  public async sendBlocksToPeer (options?: AbortOptions): Promise<void> {
92
265
  const message = new QueuedBitswapMessage()
93
266
  const sentBlocks = new Set<string>()
94
267
 
95
- for (const [key, entry] of this.wants.entries()) {
268
+ // remove any expired wants
269
+ this.removeExpiredWants()
270
+
271
+ // pick unsent wants
272
+ const unsent = [...this.wants.entries()]
273
+ .filter(([key, value]) => value.status === 'want')
274
+
275
+ // update status, ensure we don't send the same blocks repeatedly
276
+ unsent.forEach(([key, value]) => {
277
+ value.status = 'sending'
278
+ })
279
+
280
+ for (const [key, entry] of unsent) {
96
281
  try {
97
282
  const block = await toBuffer(this.blockstore.get(entry.cid, options))
98
283
 
284
+ // ensure we still need to send the block/status, status may have
285
+ // changed due to incoming message while we were waiting for async block
286
+ // load
287
+ if (entry.status !== 'sending') {
288
+ continue
289
+ }
290
+
99
291
  // do they want the block or just us to tell them we have the block
100
292
  if (entry.wantType === WantType.WantHave) {
101
293
  if (block.byteLength < this.maxSizeReplaceHasWithBlock) {
102
294
  this.log('sending have and block for %c', entry.cid)
295
+
103
296
  // if the block is small we just send it to them
104
297
  sentBlocks.add(key)
105
298
  message.addBlock(entry.cid, {
@@ -123,11 +316,20 @@ export class Ledger {
123
316
  prefix: cidToPrefix(entry.cid)
124
317
  })
125
318
  }
319
+
320
+ entry.status = 'sent'
321
+ entry.expires = Date.now() + this.doNotResendBlockWindow
126
322
  } catch (err: any) {
127
323
  if (err.name !== 'NotFoundError') {
128
324
  throw err
129
325
  }
130
326
 
327
+ // reset status to try again later
328
+ entry.status = 'want'
329
+
330
+ // used to maybe delete this want later if the want list grows too large
331
+ entry.haveBlock = false
332
+
131
333
  this.log('do not have block for %c', entry.cid)
132
334
 
133
335
  // we don't have the requested block and the remote is not interested
@@ -157,12 +359,6 @@ export class Ledger {
157
359
 
158
360
  // update accounting
159
361
  this.sentBytes([...message.blocks.values()].reduce((acc, curr) => acc + curr.data.byteLength, 0))
160
-
161
- // remove sent blocks from local copy of their want list - they can still
162
- // re-request if required
163
- for (const key of sentBlocks) {
164
- this.wants.delete(key)
165
- }
166
362
  }
167
363
  }
168
364
  }
package/src/session.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { AbstractSession } from '@helia/utils'
2
2
  import { isPeerId } from '@libp2p/interface'
3
3
  import { CustomProgressEvent } from 'progress-events'
4
- import type { BitswapProvider, BitswapWantProgressEvents } from './index.js'
5
- import type { Network } from './network.js'
6
- import type { WantList } from './want-list.js'
4
+ import type { BitswapProvider, BitswapWantProgressEvents } from './index.ts'
5
+ import type { Network } from './network.ts'
6
+ import type { WantList } from './want-list.ts'
7
7
  import type { BlockRetrievalOptions, CreateSessionOptions } from '@helia/interface'
8
8
  import type { ComponentLogger, Libp2p, PeerId } from '@libp2p/interface'
9
9
  import type { Multiaddr } from '@multiformats/multiaddr'
@@ -1,5 +1,5 @@
1
1
  import { base64 } from 'multiformats/bases/base64'
2
- import type { Block, BlockPresence, WantlistEntry } from '../pb/message.js'
2
+ import type { Block, BlockPresence, WantlistEntry } from '../pb/message.ts'
3
3
  import type { CID } from 'multiformats'
4
4
 
5
5
  /**
@@ -1,4 +1,4 @@
1
- import ve from './varint-encoder.js'
1
+ import ve from './varint-encoder.ts'
2
2
  import type { CID } from 'multiformats/cid'
3
3
 
4
4
  export function cidToPrefix (cid: CID): Uint8Array {
@@ -1,4 +1,4 @@
1
- import type { QueuedBitswapMessage } from './bitswap-message.js'
1
+ import type { QueuedBitswapMessage } from './bitswap-message.ts'
2
2
 
3
3
  export function mergeMessages (existingMessage: QueuedBitswapMessage, newMessage: QueuedBitswapMessage): QueuedBitswapMessage {
4
4
  for (const [key, entry] of newMessage.wantlist.entries()) {
@@ -1,7 +1,7 @@
1
1
  import { encodingLength } from 'uint8-varint'
2
- import { BlockTooLargeError } from '../errors.js'
3
- import { BitswapMessage, Block, BlockPresence, WantlistEntry } from '../pb/message.js'
4
- import type { QueuedBitswapMessage } from './bitswap-message.js'
2
+ import { BlockTooLargeError } from '../errors.ts'
3
+ import { BitswapMessage, Block, BlockPresence, WantlistEntry } from '../pb/message.ts'
4
+ import type { QueuedBitswapMessage } from './bitswap-message.ts'
5
5
 
6
6
  /**
7
7
  * https://github.com/ipfs/kubo/issues/4473#issuecomment-350390693
package/src/want-list.ts CHANGED
@@ -7,13 +7,13 @@ import pDefer from 'p-defer'
7
7
  import { raceEvent } from 'race-event'
8
8
  import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
9
9
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
10
- import { DEFAULT_MESSAGE_SEND_DELAY } from './constants.js'
11
- import { BlockPresenceType, WantType } from './pb/message.js'
12
- import { QueuedBitswapMessage } from './utils/bitswap-message.js'
13
- import vd from './utils/varint-decoder.js'
14
- import type { BitswapNotifyProgressEvents, MultihashHasherLoader } from './index.js'
15
- import type { BitswapNetworkWantProgressEvents, Network } from './network.js'
16
- import type { BitswapMessage } from './pb/message.js'
10
+ import { DEFAULT_MESSAGE_SEND_DELAY } from './constants.ts'
11
+ import { BlockPresenceType, WantType } from './pb/message.ts'
12
+ import { QueuedBitswapMessage } from './utils/bitswap-message.ts'
13
+ import vd from './utils/varint-decoder.ts'
14
+ import type { BitswapNotifyProgressEvents, MultihashHasherLoader } from './index.ts'
15
+ import type { BitswapNetworkWantProgressEvents, Network } from './network.ts'
16
+ import type { BitswapMessage } from './pb/message.ts'
17
17
  import type { ComponentLogger, PeerId, Startable, AbortOptions, Libp2p, TypedEventTarget, Metrics } from '@libp2p/interface'
18
18
  import type { Logger } from '@libp2p/logger'
19
19
  import type { PeerMap } from '@libp2p/peer-collections'
@@ -1,26 +0,0 @@
1
- {
2
- "codec": "https://ipfs.github.io/helia/functions/_helia_bitswap.WantType.codec.html",
3
- "WantType": "https://ipfs.github.io/helia/enums/_helia_bitswap.WantType.html",
4
- "Bitswap": "https://ipfs.github.io/helia/interfaces/_helia_bitswap.Bitswap.html",
5
- ".:Bitswap": "https://ipfs.github.io/helia/interfaces/_helia_bitswap.Bitswap.html",
6
- "BitswapComponents": "https://ipfs.github.io/helia/interfaces/_helia_bitswap.BitswapComponents.html",
7
- ".:BitswapComponents": "https://ipfs.github.io/helia/interfaces/_helia_bitswap.BitswapComponents.html",
8
- "BitswapOptions": "https://ipfs.github.io/helia/interfaces/_helia_bitswap.BitswapOptions.html",
9
- ".:BitswapOptions": "https://ipfs.github.io/helia/interfaces/_helia_bitswap.BitswapOptions.html",
10
- "BitswapProvider": "https://ipfs.github.io/helia/interfaces/_helia_bitswap.BitswapProvider.html",
11
- "MultihashHasherLoader": "https://ipfs.github.io/helia/interfaces/_helia_bitswap.MultihashHasherLoader.html",
12
- ".:MultihashHasherLoader": "https://ipfs.github.io/helia/interfaces/_helia_bitswap.MultihashHasherLoader.html",
13
- "WantListEntry": "https://ipfs.github.io/helia/interfaces/_helia_bitswap.WantListEntry.html",
14
- ".:WantListEntry": "https://ipfs.github.io/helia/interfaces/_helia_bitswap.WantListEntry.html",
15
- "BitswapNetworkNotifyProgressEvents": "https://ipfs.github.io/helia/types/_helia_bitswap.BitswapNetworkNotifyProgressEvents.html",
16
- "BitswapNetworkProgressEvents": "https://ipfs.github.io/helia/types/_helia_bitswap.BitswapNetworkProgressEvents.html",
17
- "BitswapNetworkWantProgressEvents": "https://ipfs.github.io/helia/types/_helia_bitswap.BitswapNetworkWantProgressEvents.html",
18
- "BitswapNotifyProgressEvents": "https://ipfs.github.io/helia/types/_helia_bitswap.BitswapNotifyProgressEvents.html",
19
- ".:BitswapNotifyProgressEvents": "https://ipfs.github.io/helia/types/_helia_bitswap.BitswapNotifyProgressEvents.html",
20
- "BitswapWantBlockProgressEvents": "https://ipfs.github.io/helia/types/_helia_bitswap.BitswapWantBlockProgressEvents.html",
21
- ".:BitswapWantBlockProgressEvents": "https://ipfs.github.io/helia/types/_helia_bitswap.BitswapWantBlockProgressEvents.html",
22
- "BitswapWantProgressEvents": "https://ipfs.github.io/helia/types/_helia_bitswap.BitswapWantProgressEvents.html",
23
- ".:BitswapWantProgressEvents": "https://ipfs.github.io/helia/types/_helia_bitswap.BitswapWantProgressEvents.html",
24
- "createBitswap": "https://ipfs.github.io/helia/functions/_helia_bitswap.createBitswap.html",
25
- ".:createBitswap": "https://ipfs.github.io/helia/functions/_helia_bitswap.createBitswap.html"
26
- }