@helia/bitswap 3.1.2-b3783c0d → 3.1.2-eaeb734d

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.
@@ -1,12 +1,14 @@
1
1
  import toBuffer from 'it-to-buffer'
2
- import { DEFAULT_MAX_SIZE_REPLACE_HAS_WITH_BLOCK, DEFAULT_DO_NOT_RESEND_BLOCK_WINDOW } from '../constants.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.js'
3
5
  import { BlockPresenceType, WantType } from '../pb/message.js'
4
6
  import { QueuedBitswapMessage } from '../utils/bitswap-message.js'
5
7
  import { cidToPrefix } from '../utils/cid-prefix.js'
6
8
  import type { Network } from '../network.js'
9
+ import type { Wantlist } from '../pb/message.js'
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
@@ -18,6 +20,7 @@ export interface LedgerComponents {
18
20
  export interface LedgerInit {
19
21
  maxSizeReplaceHasWithBlock?: number
20
22
  doNotResendBlockWindow?: number
23
+ maxWantListSize?: number
21
24
  }
22
25
 
23
26
  export interface PeerWantListEntry {
@@ -59,13 +62,29 @@ export interface PeerWantListEntry {
59
62
  * peer. Once it has expired the peer can request the block a subsequent time.
60
63
  */
61
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
62
81
  }
63
82
 
64
83
  export class Ledger {
65
84
  public peerId: PeerId
66
85
  private readonly blockstore: Blockstore
67
86
  private readonly network: Network
68
- public wants: Map<string, PeerWantListEntry>
87
+ private wants: Map<string, PeerWantListEntry>
69
88
  public exchangeCount: number
70
89
  public bytesSent: number
71
90
  public bytesReceived: number
@@ -73,6 +92,7 @@ export class Ledger {
73
92
  private readonly maxSizeReplaceHasWithBlock: number
74
93
  private readonly log: Logger
75
94
  private readonly doNotResendBlockWindow: number
95
+ private readonly maxWantListSize: number
76
96
 
77
97
  constructor (components: LedgerComponents, init: LedgerInit) {
78
98
  this.peerId = components.peerId
@@ -86,6 +106,7 @@ export class Ledger {
86
106
  this.bytesReceived = 0
87
107
  this.maxSizeReplaceHasWithBlock = init.maxSizeReplaceHasWithBlock ?? DEFAULT_MAX_SIZE_REPLACE_HAS_WITH_BLOCK
88
108
  this.doNotResendBlockWindow = init.doNotResendBlockWindow ?? DEFAULT_DO_NOT_RESEND_BLOCK_WINDOW
109
+ this.maxWantListSize = init.maxWantListSize ?? DEFAULT_MAX_WANTLIST_SIZE
89
110
  }
90
111
 
91
112
  sentBytes (n: number): void {
@@ -113,6 +134,133 @@ export class Ledger {
113
134
  })
114
135
  }
115
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
+
116
264
  public async sendBlocksToPeer (options?: AbortOptions): Promise<void> {
117
265
  const message = new QueuedBitswapMessage()
118
266
  const sentBlocks = new Set<string>()
@@ -179,6 +327,9 @@ export class Ledger {
179
327
  // reset status to try again later
180
328
  entry.status = 'want'
181
329
 
330
+ // used to maybe delete this want later if the want list grows too large
331
+ entry.haveBlock = false
332
+
182
333
  this.log('do not have block for %c', entry.cid)
183
334
 
184
335
  // we don't have the requested block and the remote is not interested