@helia/bitswap 3.1.2-2c225e85 → 3.1.2-92480ee8

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,5 +1,5 @@
1
1
  import toBuffer from 'it-to-buffer'
2
- import { DEFAULT_MAX_SIZE_REPLACE_HAS_WITH_BLOCK } from '../constants.js'
2
+ import { DEFAULT_MAX_SIZE_REPLACE_HAS_WITH_BLOCK, DEFAULT_DO_NOT_RESEND_BLOCK_WINDOW } from '../constants.js'
3
3
  import { BlockPresenceType, WantType } from '../pb/message.js'
4
4
  import { QueuedBitswapMessage } from '../utils/bitswap-message.js'
5
5
  import { cidToPrefix } from '../utils/cid-prefix.js'
@@ -17,6 +17,7 @@ export interface LedgerComponents {
17
17
 
18
18
  export interface LedgerInit {
19
19
  maxSizeReplaceHasWithBlock?: number
20
+ doNotResendBlockWindow?: number
20
21
  }
21
22
 
22
23
  export interface PeerWantListEntry {
@@ -45,6 +46,19 @@ export interface PeerWantListEntry {
45
46
  * If we don't have the block and we've told them we don't have the block
46
47
  */
47
48
  sentDoNotHave?: boolean
49
+
50
+ /**
51
+ * If the status is `sending` or `sent`, the block for this CID is or has been
52
+ * sent to the peer so we should not attempt to send it again
53
+ */
54
+ status: 'want' | 'sending' | 'sent'
55
+
56
+ /**
57
+ * A timestamp for when this want should be removed from the list, typically
58
+ * this is set with the `sent` status to prevent sending duplicate blocks to a
59
+ * peer. Once it has expired the peer can request the block a subsequent time.
60
+ */
61
+ expires?: number
48
62
  }
49
63
 
50
64
  export class Ledger {
@@ -58,6 +72,7 @@ export class Ledger {
58
72
  public lastExchange?: number
59
73
  private readonly maxSizeReplaceHasWithBlock: number
60
74
  private readonly log: Logger
75
+ private readonly doNotResendBlockWindow: number
61
76
 
62
77
  constructor (components: LedgerComponents, init: LedgerInit) {
63
78
  this.peerId = components.peerId
@@ -70,6 +85,7 @@ export class Ledger {
70
85
  this.bytesSent = 0
71
86
  this.bytesReceived = 0
72
87
  this.maxSizeReplaceHasWithBlock = init.maxSizeReplaceHasWithBlock ?? DEFAULT_MAX_SIZE_REPLACE_HAS_WITH_BLOCK
88
+ this.doNotResendBlockWindow = init.doNotResendBlockWindow ?? DEFAULT_DO_NOT_RESEND_BLOCK_WINDOW
73
89
  }
74
90
 
75
91
  sentBytes (n: number): void {
@@ -88,18 +104,47 @@ export class Ledger {
88
104
  return (this.bytesSent / (this.bytesReceived + 1)) // +1 is to prevent division by zero
89
105
  }
90
106
 
107
+ removeExpiredWants (): void {
108
+ // remove any expired wants
109
+ this.wants.forEach((value, key) => {
110
+ if (value.expires != null && value.expires < Date.now()) {
111
+ this.wants.delete(key)
112
+ }
113
+ })
114
+ }
115
+
91
116
  public async sendBlocksToPeer (options?: AbortOptions): Promise<void> {
92
117
  const message = new QueuedBitswapMessage()
93
118
  const sentBlocks = new Set<string>()
94
119
 
95
- for (const [key, entry] of this.wants.entries()) {
120
+ // remove any expired wants
121
+ this.removeExpiredWants()
122
+
123
+ // pick unsent wants
124
+ const unsent = [...this.wants.entries()]
125
+ .filter(([key, value]) => value.status === 'want')
126
+
127
+ // update status, ensure we don't send the same blocks repeatedly
128
+ unsent.forEach(([key, value]) => {
129
+ value.status = 'sending'
130
+ })
131
+
132
+ for (const [key, entry] of unsent) {
96
133
  try {
97
134
  const block = await toBuffer(this.blockstore.get(entry.cid, options))
98
135
 
136
+ // ensure we still need to send the block/status, status may have
137
+ // changed due to incoming message while we were waiting for async block
138
+ // load
139
+ if (entry.status !== 'sending') {
140
+ continue
141
+ }
142
+
99
143
  // do they want the block or just us to tell them we have the block
100
144
  if (entry.wantType === WantType.WantHave) {
101
145
  if (block.byteLength < this.maxSizeReplaceHasWithBlock) {
102
146
  this.log('sending have and block for %c', entry.cid)
147
+
103
148
  // if the block is small we just send it to them
104
149
  sentBlocks.add(key)
105
150
  message.addBlock(entry.cid, {
@@ -123,11 +168,17 @@ export class Ledger {
123
168
  prefix: cidToPrefix(entry.cid)
124
169
  })
125
170
  }
171
+
172
+ entry.status = 'sent'
173
+ entry.expires = Date.now() + this.doNotResendBlockWindow
126
174
  } catch (err: any) {
127
175
  if (err.name !== 'NotFoundError') {
128
176
  throw err
129
177
  }
130
178
 
179
+ // reset status to try again later
180
+ entry.status = 'want'
181
+
131
182
  this.log('do not have block for %c', entry.cid)
132
183
 
133
184
  // we don't have the requested block and the remote is not interested
@@ -157,12 +208,6 @@ export class Ledger {
157
208
 
158
209
  // update accounting
159
210
  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
211
  }
167
212
  }
168
213
  }