@helia/bitswap 4.0.8 → 4.0.9

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.
@@ -4,8 +4,9 @@ import { Ledger } from './ledger.ts'
4
4
  import type { BitswapNotifyProgressEvents, PeerWantListEntry } from '../index.ts'
5
5
  import type { Network } from '../network.ts'
6
6
  import type { BitswapMessage } from '../pb/message.ts'
7
- import type { AbortOptions, ComponentLogger, Libp2p, Logger, Metrics, PeerId } from '@libp2p/interface'
7
+ import type { AbortOptions, ComponentLogger, Libp2p, Logger, Metrics, PeerId, Startable } from '@libp2p/interface'
8
8
  import type { PeerMap } from '@libp2p/peer-collections'
9
+ import type { Queue } from '@libp2p/utils'
9
10
  import type { Blockstore } from 'interface-blockstore'
10
11
  import type { ProgressOptions } from 'progress-events'
11
12
 
@@ -13,6 +14,8 @@ export interface PeerWantListsInit {
13
14
  maxSizeReplaceHasWithBlock?: number
14
15
  doNotResendBlockWindow?: number
15
16
  maxWantListSize?: number
17
+ sendBlocksConcurrency?: number
18
+ sendBlocksMaxQueueLength?: number
16
19
  }
17
20
 
18
21
  export interface PeerWantListsComponents {
@@ -29,15 +32,18 @@ export interface PeerLedger {
29
32
  sent: number
30
33
  received: number
31
34
  exchanged: number
35
+ sendQueue: Queue
32
36
  }
33
37
 
34
- export class PeerWantLists {
38
+ export class PeerWantLists implements Startable {
35
39
  public blockstore: Blockstore
36
40
  public network: Network
37
41
  public readonly ledgerMap: PeerMap<Ledger>
38
42
  private readonly maxSizeReplaceHasWithBlock?: number
39
43
  private readonly doNotResendBlockWindow?: number
40
44
  private readonly maxWantListSize?: number
45
+ private readonly sendBlocksConcurrency?: number
46
+ private readonly sendBlocksMaxQueueLength?: number
41
47
  private readonly log: Logger
42
48
  private readonly logger: ComponentLogger
43
49
 
@@ -47,6 +53,8 @@ export class PeerWantLists {
47
53
  this.maxSizeReplaceHasWithBlock = init.maxSizeReplaceHasWithBlock
48
54
  this.doNotResendBlockWindow = init.doNotResendBlockWindow
49
55
  this.maxWantListSize = init.maxWantListSize
56
+ this.sendBlocksConcurrency = init.sendBlocksConcurrency
57
+ this.sendBlocksMaxQueueLength = init.sendBlocksMaxQueueLength
50
58
  this.log = components.logger.forComponent('helia:bitswap:peer-want-lists')
51
59
  this.logger = components.logger
52
60
 
@@ -56,16 +64,27 @@ export class PeerWantLists {
56
64
  })
57
65
 
58
66
  this.network.addEventListener('bitswap:message', (evt) => {
59
- this.receiveMessage(evt.detail.connection.remotePeer, evt.detail.message)
60
- .catch(err => {
61
- this.log.error('error receiving bitswap message from %p - %e', evt.detail.connection.remotePeer, err)
62
- })
67
+ try {
68
+ this.receiveMessage(evt.detail.connection.remotePeer, evt.detail.message)
69
+ } catch (err) {
70
+ this.log.error('error receiving bitswap message from %p - %e', evt.detail.connection.remotePeer, err)
71
+ }
63
72
  })
64
73
  this.network.addEventListener('peer:disconnected', evt => {
65
74
  this.peerDisconnected(evt.detail)
66
75
  })
67
76
  }
68
77
 
78
+ start (): void {
79
+
80
+ }
81
+
82
+ stop (): void {
83
+ this.ledgerMap.forEach(ledger => {
84
+ ledger.stop()
85
+ })
86
+ }
87
+
69
88
  ledgerForPeer (peerId: PeerId): PeerLedger | undefined {
70
89
  const ledger = this.ledgerMap.get(peerId)
71
90
 
@@ -78,7 +97,8 @@ export class PeerWantLists {
78
97
  value: ledger.debtRatio(),
79
98
  sent: ledger.bytesSent,
80
99
  received: ledger.bytesReceived,
81
- exchanged: ledger.exchangeCount
100
+ exchanged: ledger.exchangeCount,
101
+ sendQueue: ledger.sendQueue
82
102
  }
83
103
  }
84
104
 
@@ -102,7 +122,7 @@ export class PeerWantLists {
102
122
  /**
103
123
  * Handle incoming messages
104
124
  */
105
- async receiveMessage (peerId: PeerId, message: BitswapMessage): Promise<void> {
125
+ receiveMessage (peerId: PeerId, message: BitswapMessage): void {
106
126
  let ledger = this.ledgerMap.get(peerId)
107
127
 
108
128
  if (ledger == null) {
@@ -114,22 +134,14 @@ export class PeerWantLists {
114
134
  }, {
115
135
  maxSizeReplaceHasWithBlock: this.maxSizeReplaceHasWithBlock,
116
136
  doNotResendBlockWindow: this.doNotResendBlockWindow,
117
- maxWantListSize: this.maxWantListSize
137
+ maxWantListSize: this.maxWantListSize,
138
+ sendBlocksConcurrency: this.sendBlocksConcurrency,
139
+ sendBlocksMaxQueueLength: this.sendBlocksMaxQueueLength
118
140
  })
119
141
  this.ledgerMap.set(peerId, ledger)
120
142
  }
121
143
 
122
- // record the amount of block data received
123
- ledger.receivedBytes(message.blocks?.reduce((acc, curr) => acc + curr.data.byteLength, 0) ?? 0)
124
-
125
- // remove any expired wants
126
- ledger.removeExpiredWants()
127
-
128
- // add new wants
129
- ledger.addWants(message.wantlist)
130
-
131
- this.log('send blocks to peer')
132
- await ledger.sendBlocksToPeer()
144
+ ledger.queueSendOperation(message)
133
145
  }
134
146
 
135
147
  async receivedBlock (cid: CID, options: ProgressOptions<BitswapNotifyProgressEvents> & AbortOptions): Promise<void> {
@@ -142,7 +154,7 @@ export class PeerWantLists {
142
154
  }
143
155
 
144
156
  await Promise.all(
145
- ledgers.map(async (ledger) => ledger.sendBlocksToPeer(options))
157
+ ledgers.map(async (ledger) => ledger.queueSendOperation(undefined, options))
146
158
  )
147
159
  }
148
160
 
@@ -1,14 +1,18 @@
1
+ import { Queue } from '@libp2p/utils'
2
+ import { NotFoundError } from 'interface-store'
1
3
  import toBuffer from 'it-to-buffer'
2
4
  import { CID } from 'multiformats/cid'
3
5
  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'
6
+ import { DEFAULT_MAX_SIZE_REPLACE_HAS_WITH_BLOCK, DEFAULT_DO_NOT_RESEND_BLOCK_WINDOW, DEFAULT_MAX_WANTLIST_SIZE, DEFAULT_PEER_MESSAGE_SEND_CONCURRENCY, DEFAULT_PEER_MAX_MESSAGE_SEND_QUEUE_LENGTH } from '../constants.ts'
5
7
  import { BlockPresenceType, WantType } from '../pb/message.ts'
6
8
  import { QueuedBitswapMessage } from '../utils/bitswap-message.ts'
7
9
  import { cidToPrefix } from '../utils/cid-prefix.ts'
10
+ import type { BitswapNotifyProgressEvents } from '../index.ts'
8
11
  import type { Network } from '../network.ts'
9
- import type { Wantlist } from '../pb/message.ts'
12
+ import type { BitswapMessage, Wantlist } from '../pb/message.ts'
10
13
  import type { AbortOptions, ComponentLogger, Logger, PeerId } from '@libp2p/interface'
11
14
  import type { Blockstore } from 'interface-blockstore'
15
+ import type { ProgressOptions } from 'progress-events'
12
16
 
13
17
  export interface LedgerComponents {
14
18
  peerId: PeerId
@@ -21,6 +25,9 @@ export interface LedgerInit {
21
25
  maxSizeReplaceHasWithBlock?: number
22
26
  doNotResendBlockWindow?: number
23
27
  maxWantListSize?: number
28
+ sendBlocksConcurrency?: number
29
+ sendBlocksMaxQueueLength?: number
30
+ sendBlocksTimeout?: number
24
31
  }
25
32
 
26
33
  export interface PeerWantListEntry {
@@ -89,6 +96,7 @@ export class Ledger {
89
96
  public bytesSent: number
90
97
  public bytesReceived: number
91
98
  public lastExchange?: number
99
+ public readonly sendQueue: Queue
92
100
  private readonly maxSizeReplaceHasWithBlock: number
93
101
  private readonly log: Logger
94
102
  private readonly doNotResendBlockWindow: number
@@ -107,6 +115,15 @@ export class Ledger {
107
115
  this.maxSizeReplaceHasWithBlock = init.maxSizeReplaceHasWithBlock ?? DEFAULT_MAX_SIZE_REPLACE_HAS_WITH_BLOCK
108
116
  this.doNotResendBlockWindow = init.doNotResendBlockWindow ?? DEFAULT_DO_NOT_RESEND_BLOCK_WINDOW
109
117
  this.maxWantListSize = init.maxWantListSize ?? DEFAULT_MAX_WANTLIST_SIZE
118
+
119
+ this.sendQueue = new Queue({
120
+ concurrency: init.sendBlocksConcurrency ?? DEFAULT_PEER_MESSAGE_SEND_CONCURRENCY,
121
+ maxSize: init.sendBlocksMaxQueueLength ?? DEFAULT_PEER_MAX_MESSAGE_SEND_QUEUE_LENGTH
122
+ })
123
+ }
124
+
125
+ stop (): void {
126
+ this.sendQueue.abort()
110
127
  }
111
128
 
112
129
  sentBytes (n: number): void {
@@ -261,7 +278,37 @@ export class Ledger {
261
278
  return this.wants.has(cidStr)
262
279
  }
263
280
 
264
- public async sendBlocksToPeer (options?: AbortOptions): Promise<void> {
281
+ public queueSendOperation (message?: BitswapMessage, options?: ProgressOptions<BitswapNotifyProgressEvents> & AbortOptions): void {
282
+ if (message != null) {
283
+ // record the amount of block data received
284
+ this.receivedBytes(message.blocks?.reduce((acc, curr) => acc + curr.data.byteLength, 0) ?? 0)
285
+
286
+ // add new wants
287
+ this.addWants(message.wantlist)
288
+ }
289
+
290
+ try {
291
+ this.sendQueue.add(async (options) => {
292
+ this.log('send blocks to peer')
293
+ await this.sendBlocksToPeer(options)
294
+ }, options)
295
+ .catch(err => {
296
+ if (err.name === 'QueueFullError') {
297
+ this.log.error('Dropping bitswap message as peer send queue is full')
298
+ } else {
299
+ this.log.error('Error when trying to add queue a send operation')
300
+ }
301
+ })
302
+ } catch (err: any) {
303
+ if (err.name === 'QueueFullError') {
304
+ this.log.error('Dropping bitswap message as peer send queue is full')
305
+ } else {
306
+ throw err
307
+ }
308
+ }
309
+ }
310
+
311
+ private async sendBlocksToPeer (options?: AbortOptions): Promise<void> {
265
312
  const message = new QueuedBitswapMessage()
266
313
  const sentBlocks = new Set<string>()
267
314
 
@@ -270,7 +317,15 @@ export class Ledger {
270
317
 
271
318
  // pick unsent wants
272
319
  const unsent = [...this.wants.entries()]
273
- .filter(([key, value]) => value.status === 'want')
320
+ .filter(([key, value]) => {
321
+ // don't process the same want more than once per send iteration
322
+ return value.status === 'want'
323
+ })
324
+
325
+ // break out of the for loop early if we have no work to do
326
+ if (unsent.length === 0) {
327
+ return
328
+ }
274
329
 
275
330
  // update status, ensure we don't send the same blocks repeatedly
276
331
  unsent.forEach(([key, value]) => {
@@ -279,6 +334,15 @@ export class Ledger {
279
334
 
280
335
  for (const [key, entry] of unsent) {
281
336
  try {
337
+ // call `.has` and not `.get` because `.get` can cause network
338
+ // operations to occur
339
+ const has = await this.blockstore.has(entry.cid, options)
340
+
341
+ if (!has) {
342
+ // can't sent a block we don't have
343
+ throw new NotFoundError()
344
+ }
345
+
282
346
  const block = await toBuffer(this.blockstore.get(entry.cid, options))
283
347
 
284
348
  // ensure we still need to send the block/status, status may have