@helia/bitswap 3.1.2-92480ee8 → 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,9 +1,5 @@
1
- /* eslint-disable max-depth */
2
-
3
1
  import { trackedPeerMap } from '@libp2p/peer-collections'
4
2
  import { CID } from 'multiformats/cid'
5
- import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
6
- import { WantType } from '../pb/message.js'
7
3
  import { Ledger } from './ledger.js'
8
4
  import type { BitswapNotifyProgressEvents, PeerWantListEntry } from '../index.js'
9
5
  import type { Network } from '../network.js'
@@ -16,6 +12,7 @@ import type { ProgressOptions } from 'progress-events'
16
12
  export interface PeerWantListsInit {
17
13
  maxSizeReplaceHasWithBlock?: number
18
14
  doNotResendBlockWindow?: number
15
+ maxWantListSize?: number
19
16
  }
20
17
 
21
18
  export interface PeerWantListsComponents {
@@ -40,6 +37,7 @@ export class PeerWantLists {
40
37
  public readonly ledgerMap: PeerMap<Ledger>
41
38
  private readonly maxSizeReplaceHasWithBlock?: number
42
39
  private readonly doNotResendBlockWindow?: number
40
+ private readonly maxWantListSize?: number
43
41
  private readonly log: Logger
44
42
  private readonly logger: ComponentLogger
45
43
 
@@ -48,6 +46,7 @@ export class PeerWantLists {
48
46
  this.network = components.network
49
47
  this.maxSizeReplaceHasWithBlock = init.maxSizeReplaceHasWithBlock
50
48
  this.doNotResendBlockWindow = init.doNotResendBlockWindow
49
+ this.maxWantListSize = init.maxWantListSize
51
50
  this.log = components.logger.forComponent('helia:bitswap:peer-want-lists')
52
51
  this.logger = components.logger
53
52
 
@@ -93,7 +92,7 @@ export class PeerWantLists {
93
92
  // remove any expired wants
94
93
  ledger.removeExpiredWants()
95
94
 
96
- return [...ledger.wants.values()]
95
+ return ledger.getWants()
97
96
  }
98
97
 
99
98
  peers (): PeerId[] {
@@ -114,7 +113,8 @@ export class PeerWantLists {
114
113
  logger: this.logger
115
114
  }, {
116
115
  maxSizeReplaceHasWithBlock: this.maxSizeReplaceHasWithBlock,
117
- doNotResendBlockWindow: this.doNotResendBlockWindow
116
+ doNotResendBlockWindow: this.doNotResendBlockWindow,
117
+ maxWantListSize: this.maxWantListSize
118
118
  })
119
119
  this.ledgerMap.set(peerId, ledger)
120
120
  }
@@ -125,68 +125,18 @@ export class PeerWantLists {
125
125
  // remove any expired wants
126
126
  ledger.removeExpiredWants()
127
127
 
128
- if (message.wantlist != null) {
129
- // if the message has a full wantlist, clear the current wantlist
130
- if (message.wantlist.full === true) {
131
- ledger.wants.clear()
132
- }
133
-
134
- // clear cancelled wants and add new wants to the ledger
135
- for (const entry of message.wantlist.entries) {
136
- const cid = CID.decode(entry.cid)
137
- const cidStr = uint8ArrayToString(cid.multihash.bytes, 'base64')
138
-
139
- if (entry.cancel === true) {
140
- this.log('peer %p cancelled want of block for %c', peerId, cid)
141
- ledger.wants.delete(cidStr)
142
- } else {
143
- if (entry.wantType === WantType.WantHave) {
144
- this.log('peer %p wanted block presence for %c', peerId, cid)
145
- } else {
146
- this.log('peer %p wanted block for %c', peerId, cid)
147
- }
148
-
149
- const existingWant = ledger.wants.get(cidStr)
150
-
151
- // we are already tracking a want for this CID, just update the fields
152
- if (existingWant != null) {
153
- const sentOrSending = existingWant.status === 'sent' || existingWant.status === 'sending'
154
- const wantTypeUpgrade = existingWant.wantType === WantType.WantHave && (entry.wantType == null || entry.wantType === WantType.WantBlock)
155
-
156
- // allow upgrade from WantHave to WantBlock if we've previously
157
- // sent or are sending a WantHave
158
- if (sentOrSending && wantTypeUpgrade) {
159
- existingWant.status = 'want'
160
- }
161
-
162
- existingWant.priority = entry.priority
163
- existingWant.wantType = entry.wantType ?? WantType.WantBlock
164
- existingWant.sendDontHave = entry.sendDontHave ?? false
165
- continue
166
- }
167
-
168
- // add a new want
169
- ledger.wants.set(cidStr, {
170
- cid,
171
- priority: entry.priority,
172
- wantType: entry.wantType ?? WantType.WantBlock,
173
- sendDontHave: entry.sendDontHave ?? false,
174
- status: 'want'
175
- })
176
- }
177
- }
178
- }
128
+ // add new wants
129
+ ledger.addWants(message.wantlist)
179
130
 
180
131
  this.log('send blocks to peer')
181
132
  await ledger.sendBlocksToPeer()
182
133
  }
183
134
 
184
135
  async receivedBlock (cid: CID, options: ProgressOptions<BitswapNotifyProgressEvents> & AbortOptions): Promise<void> {
185
- const cidStr = uint8ArrayToString(cid.multihash.bytes, 'base64')
186
136
  const ledgers: Ledger[] = []
187
137
 
188
138
  for (const ledger of this.ledgerMap.values()) {
189
- if (ledger.wants.has(cidStr)) {
139
+ if (ledger.hasWant(cid)) {
190
140
  ledgers.push(ledger)
191
141
  }
192
142
  }
@@ -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