hypercore 11.22.1 → 11.23.0
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.
- package/lib/bitfield.js +26 -16
- package/lib/merkle-tree.js +3 -0
- package/lib/messages.js +28 -4
- package/lib/remote-bitfield.js +1 -0
- package/lib/replicator.js +259 -92
- package/lib/wants.js +307 -0
- package/package.json +1 -1
package/lib/bitfield.js
CHANGED
|
@@ -420,6 +420,26 @@ module.exports = class Bitfield {
|
|
|
420
420
|
return this.findLast(false, position)
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
+
hasUnset(start, length) {
|
|
424
|
+
const end = start + length
|
|
425
|
+
|
|
426
|
+
let j = start & (BITS_PER_SEGMENT - 1)
|
|
427
|
+
let i = (start - j) / BITS_PER_SEGMENT
|
|
428
|
+
|
|
429
|
+
while (i < this._segments.maxLength) {
|
|
430
|
+
const s = this._segments.get(i)
|
|
431
|
+
const index = s ? s.findFirst(false, j) : j
|
|
432
|
+
if (index !== -1) return i * BITS_PER_SEGMENT + index < end
|
|
433
|
+
|
|
434
|
+
j = 0
|
|
435
|
+
i++
|
|
436
|
+
|
|
437
|
+
if (i * BITS_PER_SEGMENT >= end) return false
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return true
|
|
441
|
+
}
|
|
442
|
+
|
|
423
443
|
hasSet(start, length) {
|
|
424
444
|
const end = start + length
|
|
425
445
|
|
|
@@ -475,23 +495,23 @@ module.exports = class Bitfield {
|
|
|
475
495
|
}
|
|
476
496
|
|
|
477
497
|
*want(start, length) {
|
|
478
|
-
|
|
498
|
+
let j = start & (BITS_PER_SEGMENT - 1)
|
|
479
499
|
let i = (start - j) / BITS_PER_SEGMENT
|
|
480
500
|
|
|
481
501
|
while (length > 0) {
|
|
482
502
|
const s = this._segments.get(i)
|
|
483
503
|
|
|
484
504
|
if (s) {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const end = ceilTo(clamp(length / 8, 4096, BYTES_PER_SEGMENT), 4096)
|
|
505
|
+
let end = Math.min(j + length, BITS_PER_SEGMENT)
|
|
506
|
+
if (end & 31) end += 32 - (end & 31)
|
|
488
507
|
|
|
489
508
|
yield {
|
|
490
|
-
start: i * BITS_PER_SEGMENT,
|
|
491
|
-
bitfield: s.bitfield.subarray(
|
|
509
|
+
start: i * BITS_PER_SEGMENT + j,
|
|
510
|
+
bitfield: s.bitfield.subarray(j / 32, end / 32)
|
|
492
511
|
}
|
|
493
512
|
}
|
|
494
513
|
|
|
514
|
+
j = 0
|
|
495
515
|
i++
|
|
496
516
|
length -= BITS_PER_SEGMENT
|
|
497
517
|
}
|
|
@@ -521,13 +541,3 @@ module.exports = class Bitfield {
|
|
|
521
541
|
return new Bitfield(buffer)
|
|
522
542
|
}
|
|
523
543
|
}
|
|
524
|
-
|
|
525
|
-
function clamp(n, min, max) {
|
|
526
|
-
return Math.min(Math.max(n, min), max)
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
function ceilTo(n, multiple = 1) {
|
|
530
|
-
const remainder = n % multiple
|
|
531
|
-
if (remainder === 0) return n
|
|
532
|
-
return n + multiple - remainder
|
|
533
|
-
}
|
package/lib/merkle-tree.js
CHANGED
package/lib/messages.js
CHANGED
|
@@ -543,19 +543,32 @@ wire.noData = {
|
|
|
543
543
|
}
|
|
544
544
|
}
|
|
545
545
|
|
|
546
|
+
// TODO: we should move to having these have a handle like in data, easier since they
|
|
547
|
+
// allocate state in practice
|
|
546
548
|
wire.want = {
|
|
547
549
|
preencode(state, m) {
|
|
548
550
|
c.uint.preencode(state, m.start)
|
|
549
551
|
c.uint.preencode(state, m.length)
|
|
552
|
+
|
|
553
|
+
const flags = m.any ? 1 : 0
|
|
554
|
+
if (flags !== 0) c.uint.preencode(state, flags)
|
|
550
555
|
},
|
|
551
556
|
encode(state, m) {
|
|
552
557
|
c.uint.encode(state, m.start)
|
|
553
558
|
c.uint.encode(state, m.length)
|
|
559
|
+
|
|
560
|
+
const flags = m.any ? 1 : 0
|
|
561
|
+
if (flags !== 0) c.uint.encode(state, flags)
|
|
554
562
|
},
|
|
555
563
|
decode(state) {
|
|
564
|
+
const start = c.uint.decode(state)
|
|
565
|
+
const length = c.uint.decode(state)
|
|
566
|
+
const flags = state.start < state.end ? c.uint.decode(state) : 0
|
|
567
|
+
|
|
556
568
|
return {
|
|
557
|
-
start
|
|
558
|
-
length
|
|
569
|
+
start,
|
|
570
|
+
length,
|
|
571
|
+
any: (flags & 1) !== 0
|
|
559
572
|
}
|
|
560
573
|
}
|
|
561
574
|
}
|
|
@@ -564,15 +577,26 @@ wire.unwant = {
|
|
|
564
577
|
preencode(state, m) {
|
|
565
578
|
c.uint.preencode(state, m.start)
|
|
566
579
|
c.uint.preencode(state, m.length)
|
|
580
|
+
|
|
581
|
+
const flags = m.any ? 1 : 0
|
|
582
|
+
if (flags !== 0) c.uint.preencode(state, flags)
|
|
567
583
|
},
|
|
568
584
|
encode(state, m) {
|
|
569
585
|
c.uint.encode(state, m.start)
|
|
570
586
|
c.uint.encode(state, m.length)
|
|
587
|
+
|
|
588
|
+
const flags = m.any ? 1 : 0
|
|
589
|
+
if (flags !== 0) c.uint.encode(state, flags)
|
|
571
590
|
},
|
|
572
591
|
decode(state, m) {
|
|
592
|
+
const start = c.uint.decode(state)
|
|
593
|
+
const length = c.uint.decode(state)
|
|
594
|
+
const flags = state.start < state.end ? c.uint.decode(state) : 0
|
|
595
|
+
|
|
573
596
|
return {
|
|
574
|
-
start
|
|
575
|
-
length
|
|
597
|
+
start,
|
|
598
|
+
length,
|
|
599
|
+
any: (flags & 1) !== 0
|
|
576
600
|
}
|
|
577
601
|
}
|
|
578
602
|
}
|
package/lib/remote-bitfield.js
CHANGED
package/lib/replicator.js
CHANGED
|
@@ -31,6 +31,7 @@ const ReceiverQueue = require('./receiver-queue')
|
|
|
31
31
|
const HotswapQueue = require('./hotswap-queue')
|
|
32
32
|
const RemoteBitfield = require('./remote-bitfield')
|
|
33
33
|
const { MerkleTree } = require('./merkle-tree')
|
|
34
|
+
const { LocalWants, RemoteWants, WANT_BATCH } = require('./wants')
|
|
34
35
|
const {
|
|
35
36
|
REQUEST_CANCELLED,
|
|
36
37
|
REQUEST_TIMEOUT,
|
|
@@ -43,11 +44,9 @@ const caps = require('./caps')
|
|
|
43
44
|
|
|
44
45
|
const DEFAULT_MAX_INFLIGHT = [16, 512]
|
|
45
46
|
const SCALE_LATENCY = 50
|
|
46
|
-
const DEFAULT_SEGMENT_SIZE = 256 * 1024 * 8 // 256 KiB in bits
|
|
47
47
|
const NOT_DOWNLOADING_SLACK = (20000 + Math.random() * 20000) | 0
|
|
48
48
|
const MAX_PEERS_UPGRADE = 3
|
|
49
49
|
const LAST_BLOCKS = 256
|
|
50
|
-
const MAX_REMOTE_SEGMENTS = 2048
|
|
51
50
|
|
|
52
51
|
const MAX_RANGES = 64
|
|
53
52
|
|
|
@@ -69,6 +68,17 @@ class Attachable {
|
|
|
69
68
|
this.resolved = false
|
|
70
69
|
this.processing = false
|
|
71
70
|
this.refs = []
|
|
71
|
+
this.wants = null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
addWant(w) {
|
|
75
|
+
if (this.wants === null) this.wants = new Set()
|
|
76
|
+
this.wants.add(w)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
removeWant(w) {
|
|
80
|
+
if (this.wants === null) return
|
|
81
|
+
this.wants.delete(w)
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
attach(session) {
|
|
@@ -118,7 +128,37 @@ class Attachable {
|
|
|
118
128
|
}
|
|
119
129
|
|
|
120
130
|
gc() {
|
|
121
|
-
if (this.refs.length
|
|
131
|
+
if (this.refs.length > 0 || this.processing) return
|
|
132
|
+
this._unref()
|
|
133
|
+
|
|
134
|
+
if (this.wants === null) return
|
|
135
|
+
|
|
136
|
+
const wants = this.wants
|
|
137
|
+
this.wants = null
|
|
138
|
+
|
|
139
|
+
let update = null
|
|
140
|
+
|
|
141
|
+
for (const w of wants) {
|
|
142
|
+
if (w.any) {
|
|
143
|
+
const removed = w.wants.removeAnyRange(w.start, w.length, this)
|
|
144
|
+
if (!removed) continue
|
|
145
|
+
if (update === null) update = new Set()
|
|
146
|
+
update.add(w.wants.peer)
|
|
147
|
+
// TODO: should also use a free list for simplicity
|
|
148
|
+
w.wants.peer.wireUnwant.send(removed)
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (w.wants.removeBatch(w.start, this)) {
|
|
153
|
+
if (update === null) update = new Set()
|
|
154
|
+
update.add(w.wants.peer)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (update === null) return
|
|
159
|
+
for (const peer of update) {
|
|
160
|
+
this.replicator.updatePeer(peer)
|
|
161
|
+
}
|
|
122
162
|
}
|
|
123
163
|
|
|
124
164
|
processed() {
|
|
@@ -139,6 +179,7 @@ class Attachable {
|
|
|
139
179
|
while (this.refs.length > 0) {
|
|
140
180
|
this._detach(this.refs[this.refs.length - 1]).resolve(val)
|
|
141
181
|
}
|
|
182
|
+
this.gc()
|
|
142
183
|
}
|
|
143
184
|
|
|
144
185
|
reject(err) {
|
|
@@ -146,6 +187,7 @@ class Attachable {
|
|
|
146
187
|
while (this.refs.length > 0) {
|
|
147
188
|
this._detach(this.refs[this.refs.length - 1]).reject(err)
|
|
148
189
|
}
|
|
190
|
+
this.gc()
|
|
149
191
|
}
|
|
150
192
|
|
|
151
193
|
setTimeout(r, ms) {
|
|
@@ -167,6 +209,7 @@ class BlockRequest extends Attachable {
|
|
|
167
209
|
}
|
|
168
210
|
|
|
169
211
|
_unref() {
|
|
212
|
+
if (this.resolved) return
|
|
170
213
|
this.queued = false
|
|
171
214
|
|
|
172
215
|
for (const req of this.inflight) {
|
|
@@ -178,6 +221,15 @@ class BlockRequest extends Attachable {
|
|
|
178
221
|
}
|
|
179
222
|
}
|
|
180
223
|
|
|
224
|
+
class RangeBatchRequest extends Attachable {
|
|
225
|
+
constructor(replicator, batch) {
|
|
226
|
+
super(replicator)
|
|
227
|
+
|
|
228
|
+
this.batch = batch
|
|
229
|
+
this.index = 0
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
181
233
|
class RangeRequest extends Attachable {
|
|
182
234
|
constructor(replicator, ranges, start, end, linear, ifAvailable, blocks) {
|
|
183
235
|
super(replicator)
|
|
@@ -188,6 +240,8 @@ class RangeRequest extends Attachable {
|
|
|
188
240
|
this.ifAvailable = ifAvailable
|
|
189
241
|
this.blocks = blocks
|
|
190
242
|
this.ranges = ranges
|
|
243
|
+
this.batches = []
|
|
244
|
+
|
|
191
245
|
ranges.push(this)
|
|
192
246
|
|
|
193
247
|
if (this.end === -1) {
|
|
@@ -199,6 +253,28 @@ class RangeRequest extends Attachable {
|
|
|
199
253
|
this.userEnd = end
|
|
200
254
|
}
|
|
201
255
|
|
|
256
|
+
addBatch(batch) {
|
|
257
|
+
for (let i = 0; i < this.batches.length; i++) {
|
|
258
|
+
const b = this.batches[i]
|
|
259
|
+
if (b.batch === batch) return false
|
|
260
|
+
}
|
|
261
|
+
const b = new RangeBatchRequest(this.replicator, batch)
|
|
262
|
+
b.index = this.batches.length
|
|
263
|
+
this.batches.push(b)
|
|
264
|
+
return true
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
removeBatch(batch) {
|
|
268
|
+
const head = this.batches.pop()
|
|
269
|
+
|
|
270
|
+
if (head !== batch) {
|
|
271
|
+
head.index = batch.index
|
|
272
|
+
this.batches[head.index] = head
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
batch.gc()
|
|
276
|
+
}
|
|
277
|
+
|
|
202
278
|
_unref() {
|
|
203
279
|
const rangeIndex = this.ranges.indexOf(this)
|
|
204
280
|
if (rangeIndex === -1) return
|
|
@@ -211,6 +287,10 @@ class RangeRequest extends Attachable {
|
|
|
211
287
|
if (this.end === -1) {
|
|
212
288
|
this.replicator._alwaysLatestBlock--
|
|
213
289
|
}
|
|
290
|
+
|
|
291
|
+
while (this.batches.length) {
|
|
292
|
+
this.batches.pop().gc()
|
|
293
|
+
}
|
|
214
294
|
}
|
|
215
295
|
|
|
216
296
|
_cancel(r) {
|
|
@@ -228,6 +308,7 @@ class UpgradeRequest extends Attachable {
|
|
|
228
308
|
}
|
|
229
309
|
|
|
230
310
|
_unref() {
|
|
311
|
+
if (this.resolved) return
|
|
231
312
|
if (this.replicator.eagerUpgrade === true || this.inflight.length > 0) return
|
|
232
313
|
this.replicator._upgrade = null
|
|
233
314
|
}
|
|
@@ -247,6 +328,7 @@ class SeekRequest extends Attachable {
|
|
|
247
328
|
}
|
|
248
329
|
|
|
249
330
|
_unref() {
|
|
331
|
+
if (this.resolved) return
|
|
250
332
|
if (this.inflight.length > 0) return
|
|
251
333
|
const i = this.seeks.indexOf(this)
|
|
252
334
|
if (i === -1) return
|
|
@@ -255,6 +337,15 @@ class SeekRequest extends Attachable {
|
|
|
255
337
|
}
|
|
256
338
|
}
|
|
257
339
|
|
|
340
|
+
class ForkRequest extends Attachable {
|
|
341
|
+
constructor(replicator, fork) {
|
|
342
|
+
super(replicator)
|
|
343
|
+
this.fork = fork
|
|
344
|
+
this.inflight = []
|
|
345
|
+
this.batch = null
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
258
349
|
class InflightTracker {
|
|
259
350
|
constructor() {
|
|
260
351
|
this._requests = []
|
|
@@ -400,7 +491,6 @@ class Peer {
|
|
|
400
491
|
this.remotePublicKey = this.stream.remotePublicKey
|
|
401
492
|
this.remoteSupportsSeeks = false
|
|
402
493
|
this.inflightRange = inflightRange
|
|
403
|
-
this.remoteSegmentsWanted = new Set()
|
|
404
494
|
this.fullyDownloadedSignaled = 0
|
|
405
495
|
|
|
406
496
|
this.paused = false
|
|
@@ -427,6 +517,7 @@ class Peer {
|
|
|
427
517
|
wireCancel: { tx: 0, rx: 0 },
|
|
428
518
|
wireData: { tx: 0, rx: 0 },
|
|
429
519
|
wireWant: { tx: 0, rx: 0 },
|
|
520
|
+
wireUnwant: { tx: 0, rx: 0 },
|
|
430
521
|
wireBitfield: { tx: 0, rx: 0 },
|
|
431
522
|
wireRange: { tx: 0, rx: 0 },
|
|
432
523
|
wireExtension: { tx: 0, rx: 0 },
|
|
@@ -477,6 +568,9 @@ class Peer {
|
|
|
477
568
|
this.segmentsWanted = new Set()
|
|
478
569
|
this.broadcastedNonSparse = false
|
|
479
570
|
|
|
571
|
+
this.wants = new LocalWants(this)
|
|
572
|
+
this.remoteWants = new RemoteWants(this)
|
|
573
|
+
|
|
480
574
|
this.lengthAcked = 0
|
|
481
575
|
|
|
482
576
|
this.extensions = new Map()
|
|
@@ -525,16 +619,10 @@ class Peer {
|
|
|
525
619
|
if (drop) this._unclearLocalRange(start, length)
|
|
526
620
|
else this._clearLocalRange(start, length)
|
|
527
621
|
|
|
528
|
-
const i = Math.floor(start / DEFAULT_SEGMENT_SIZE)
|
|
529
622
|
const fullyContig = this.core.header.hints.contiguousLength === this.core.state.length
|
|
530
623
|
|
|
531
|
-
if (
|
|
532
|
-
|
|
533
|
-
!this.remoteSegmentsWanted.has(i) &&
|
|
534
|
-
!drop &&
|
|
535
|
-
!fullyContig
|
|
536
|
-
) {
|
|
537
|
-
return
|
|
624
|
+
if (start + LAST_BLOCKS < this.core.state.length && !drop && !fullyContig) {
|
|
625
|
+
if (!this.remoteWants.hasRange(start, length)) return
|
|
538
626
|
}
|
|
539
627
|
|
|
540
628
|
let force = false
|
|
@@ -915,7 +1003,7 @@ class Peer {
|
|
|
915
1003
|
nodes: MerkleTree.maxMissingNodes(2 * index, remoteLength)
|
|
916
1004
|
}
|
|
917
1005
|
|
|
918
|
-
if (index >=
|
|
1006
|
+
if (index >= remoteLength) {
|
|
919
1007
|
msg.upgrade = {
|
|
920
1008
|
start: remoteLength,
|
|
921
1009
|
length: this.core.state.length - remoteLength
|
|
@@ -926,7 +1014,8 @@ class Peer {
|
|
|
926
1014
|
const batch = this.core.storage.read()
|
|
927
1015
|
try {
|
|
928
1016
|
req = await this._getProof(batch, msg, true)
|
|
929
|
-
} catch {
|
|
1017
|
+
} catch (err) {
|
|
1018
|
+
this.replicator._oninvalidrequest(err, msg, this)
|
|
930
1019
|
return
|
|
931
1020
|
}
|
|
932
1021
|
|
|
@@ -1180,15 +1269,13 @@ class Peer {
|
|
|
1180
1269
|
for (const id of flushed) this.replicator._inflight.reusable(id)
|
|
1181
1270
|
}
|
|
1182
1271
|
|
|
1183
|
-
onwant(
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
this.remoteSegmentsWanted.add(i)
|
|
1187
|
-
this.replicator._onwant(this, start, length)
|
|
1272
|
+
onwant(range) {
|
|
1273
|
+
if (!this.remoteWants.add(range)) return
|
|
1274
|
+
this.replicator._onwant(this, range.start, range.length, range.any)
|
|
1188
1275
|
}
|
|
1189
1276
|
|
|
1190
|
-
onunwant() {
|
|
1191
|
-
|
|
1277
|
+
onunwant(range) {
|
|
1278
|
+
this.remoteWants.remove(range)
|
|
1192
1279
|
}
|
|
1193
1280
|
|
|
1194
1281
|
onbitfield({ start, bitfield }) {
|
|
@@ -1243,7 +1330,7 @@ class Peer {
|
|
|
1243
1330
|
return
|
|
1244
1331
|
}
|
|
1245
1332
|
|
|
1246
|
-
const rem = start &
|
|
1333
|
+
const rem = start & (RemoteBitfield.BITS_PER_SEGMENT - 1)
|
|
1247
1334
|
if (rem > 0) {
|
|
1248
1335
|
start -= rem
|
|
1249
1336
|
length += rem
|
|
@@ -1258,7 +1345,7 @@ class Peer {
|
|
|
1258
1345
|
this.missingBlocks.insert(start, remote.bitfield)
|
|
1259
1346
|
}
|
|
1260
1347
|
|
|
1261
|
-
start +=
|
|
1348
|
+
start += RemoteBitfield.BITS_PER_SEGMENT
|
|
1262
1349
|
}
|
|
1263
1350
|
|
|
1264
1351
|
this._clearLocalRange(fixedStart, length)
|
|
@@ -1458,7 +1545,7 @@ class Peer {
|
|
|
1458
1545
|
return true
|
|
1459
1546
|
}
|
|
1460
1547
|
|
|
1461
|
-
this.
|
|
1548
|
+
this._maybeWantAnyRange(s.seeker.start, len, s)
|
|
1462
1549
|
return false
|
|
1463
1550
|
}
|
|
1464
1551
|
|
|
@@ -1497,7 +1584,7 @@ class Peer {
|
|
|
1497
1584
|
const { length, fork } = this.core.state
|
|
1498
1585
|
|
|
1499
1586
|
if (this._remoteHasBlock(b.index) === false || fork !== this.remoteFork) {
|
|
1500
|
-
this._maybeWant(b.index)
|
|
1587
|
+
if (!this.core.bitfield.get(b.index)) this._maybeWant(b.index, b)
|
|
1501
1588
|
return false
|
|
1502
1589
|
}
|
|
1503
1590
|
|
|
@@ -1511,6 +1598,43 @@ class Peer {
|
|
|
1511
1598
|
return true
|
|
1512
1599
|
}
|
|
1513
1600
|
|
|
1601
|
+
_maybeWant(index, handle) {
|
|
1602
|
+
if (index < this.remoteContiguousLength) return
|
|
1603
|
+
|
|
1604
|
+
const res = this.wants.add(index, handle)
|
|
1605
|
+
if (!res) return
|
|
1606
|
+
|
|
1607
|
+
const batch = res.want && res.unwant
|
|
1608
|
+
if (batch) this.protomux.cork()
|
|
1609
|
+
|
|
1610
|
+
if (res.unwant) {
|
|
1611
|
+
this.wireUnwant.send(res.unwant)
|
|
1612
|
+
incrementTx(this.stats.wireUnwant, this.replicator.stats.wireUnwant)
|
|
1613
|
+
}
|
|
1614
|
+
if (res.want) {
|
|
1615
|
+
this.wireWant.send(res.want)
|
|
1616
|
+
incrementTx(this.stats.wireWant, this.replicator.stats.wireWant)
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
if (batch) this.protomux.uncork()
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
_maybeWantAnyRange(start, length, handle) {
|
|
1623
|
+
if (handle.wants) {
|
|
1624
|
+
for (const w of handle.wants) {
|
|
1625
|
+
if ((w.start !== start || w.length !== length) && w.any) {
|
|
1626
|
+
const req = this.wants.removeAnyRange(w.start, w.length, handle)
|
|
1627
|
+
if (req) this.wireUnwant.send(req)
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
if (start + length <= this.remoteContiguousLength) return
|
|
1633
|
+
|
|
1634
|
+
const req = this.wants.addAnyRange(start, length, handle)
|
|
1635
|
+
if (req) this.wireWant.send(req)
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1514
1638
|
_requestRangeBlock(index, length) {
|
|
1515
1639
|
if (this.core.bitfield.get(index) === true || !this._canRequest(index)) return false
|
|
1516
1640
|
|
|
@@ -1547,68 +1671,119 @@ class Peer {
|
|
|
1547
1671
|
return this.missingBlocks.findFirst(true, i)
|
|
1548
1672
|
}
|
|
1549
1673
|
|
|
1550
|
-
|
|
1551
|
-
|
|
1674
|
+
_addRangeBatch(r, offset, end) {
|
|
1675
|
+
while (offset < end) {
|
|
1676
|
+
const next = this.core.bitfield.findFirst(false, offset)
|
|
1677
|
+
if (next === -1 || next >= end) return false
|
|
1552
1678
|
|
|
1553
|
-
|
|
1679
|
+
const b = (next - (next & (WANT_BATCH - 1))) / WANT_BATCH
|
|
1554
1680
|
|
|
1555
|
-
|
|
1556
|
-
let min = -1
|
|
1557
|
-
let max = -1
|
|
1681
|
+
if (r.addBatch(b)) return true
|
|
1558
1682
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
if (min === -1 || index < min) min = index
|
|
1562
|
-
if (max === -1 || index > max) max = index
|
|
1563
|
-
const has = index < this._remoteContiguousLength || this.missingBlocks.get(index) === true
|
|
1564
|
-
if (has === true && this._requestRangeBlock(index, length)) return true
|
|
1565
|
-
}
|
|
1683
|
+
offset = (b + 1) * WANT_BATCH
|
|
1684
|
+
}
|
|
1566
1685
|
|
|
1567
|
-
|
|
1568
|
-
|
|
1686
|
+
return false
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
_populateRangeBatches(r) {
|
|
1690
|
+
for (let i = r.batches.length - 1; i >= 0; i--) {
|
|
1691
|
+
const b = r.batches[i]
|
|
1692
|
+
const minStart = b.batch * WANT_BATCH
|
|
1693
|
+
const maxEnd = minStart + WANT_BATCH
|
|
1694
|
+
|
|
1695
|
+
const start = Math.max(r.start, minStart)
|
|
1696
|
+
const end = Math.min(r.end === -1 ? maxEnd : r.end, maxEnd)
|
|
1697
|
+
|
|
1698
|
+
if (start < end && this.core.bitfield.hasUnset(start, end - start)) continue
|
|
1699
|
+
|
|
1700
|
+
r.removeBatch(b)
|
|
1569
1701
|
}
|
|
1570
1702
|
|
|
1703
|
+
while (r.batches.length < 3) {
|
|
1704
|
+
const end = r.end === -1 ? this.core.state.length : r.end
|
|
1705
|
+
if (end <= r.start) return
|
|
1706
|
+
|
|
1707
|
+
const len = end - r.start
|
|
1708
|
+
const off = r.start + (r.linear ? 0 : Math.floor(Math.random() * len))
|
|
1709
|
+
|
|
1710
|
+
if (!this._addRangeBatch(r, off, end) && !this._addRangeBatch(r, r.start, end)) break
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
_requestRangeBatch(start, end, length) {
|
|
1715
|
+
let tries = this.inflight
|
|
1716
|
+
|
|
1717
|
+
do {
|
|
1718
|
+
start = this._findNext(start)
|
|
1719
|
+
if (start === -1 || start >= end) break
|
|
1720
|
+
|
|
1721
|
+
if (this._requestRangeBlock(start, length)) return true
|
|
1722
|
+
start++
|
|
1723
|
+
} while (tries-- > 0)
|
|
1724
|
+
|
|
1725
|
+
return false
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
_getValidEnd(maxEnd) {
|
|
1571
1729
|
// if we can upgrade the remote, or the remote is ahead, then all the remotes blocks are valid
|
|
1572
1730
|
// otherwise truncate to the last length the remote has acked for us
|
|
1573
1731
|
const maxLocalLength =
|
|
1574
1732
|
this.canUpgrade || this.remoteLength >= this.core.state.length
|
|
1575
1733
|
? this.core.state.length
|
|
1576
|
-
: fork === this.lastUpgradableFork
|
|
1734
|
+
: this.core.state.fork === this.lastUpgradableFork
|
|
1577
1735
|
? Math.min(this.lastUpgradableLength, this.core.state.length)
|
|
1578
1736
|
: 0
|
|
1579
1737
|
|
|
1580
|
-
|
|
1738
|
+
return Math.min(
|
|
1581
1739
|
maxLocalLength,
|
|
1582
|
-
Math.min(
|
|
1740
|
+
Math.min(maxEnd === -1 ? this.remoteLength : maxEnd, this.remoteLength)
|
|
1583
1741
|
)
|
|
1584
|
-
|
|
1742
|
+
}
|
|
1585
1743
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1744
|
+
_requestRange(r) {
|
|
1745
|
+
if (this.syncsProcessing > 0) return false
|
|
1588
1746
|
|
|
1589
|
-
|
|
1590
|
-
// should be way less than this, but this is worst case upper bound for the skiplist
|
|
1591
|
-
let tries = this.inflight
|
|
1747
|
+
const { length, fork } = this.core.state
|
|
1592
1748
|
|
|
1593
|
-
|
|
1594
|
-
i =
|
|
1595
|
-
|
|
1749
|
+
if (r.blocks) {
|
|
1750
|
+
for (let i = r.start; i < r.end; i++) {
|
|
1751
|
+
const index = r.blocks[i]
|
|
1752
|
+
const has = index < this._remoteContiguousLength || this.missingBlocks.get(index) === true
|
|
1753
|
+
if (has === true && this._requestRangeBlock(index, length)) return true
|
|
1754
|
+
this._maybeWant(index, r)
|
|
1755
|
+
}
|
|
1596
1756
|
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
} while (tries-- > 0)
|
|
1757
|
+
return false
|
|
1758
|
+
}
|
|
1600
1759
|
|
|
1601
|
-
|
|
1760
|
+
this._populateRangeBatches(r)
|
|
1602
1761
|
|
|
1603
|
-
|
|
1604
|
-
i = this._findNext(i)
|
|
1605
|
-
if (i === -1 || i >= off) break
|
|
1762
|
+
const end = this._getValidEnd(r.end)
|
|
1606
1763
|
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1764
|
+
if (end <= r.start || fork !== this.remoteFork) return false
|
|
1765
|
+
|
|
1766
|
+
for (let i = 0; i < r.batches.length; i++) {
|
|
1767
|
+
const b = r.batches[i]
|
|
1768
|
+
|
|
1769
|
+
const batchStart = Math.max(b.batch * WANT_BATCH, r.start)
|
|
1770
|
+
const batchEnd = Math.min((b.batch + 1) * WANT_BATCH, end)
|
|
1771
|
+
const len = batchEnd - batchStart
|
|
1772
|
+
const off = batchStart + (r.linear ? 0 : Math.floor(Math.random() * len))
|
|
1773
|
+
|
|
1774
|
+
if (
|
|
1775
|
+
this._requestRangeBatch(off, end, length) ||
|
|
1776
|
+
this._requestRangeBatch(batchStart, off, length)
|
|
1777
|
+
) {
|
|
1778
|
+
return true
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
this._maybeWant(batchStart, b)
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// Here we should consider making an OOB request (ie if the remote says they have a newer block)
|
|
1785
|
+
// but the upgrade code already does that, so no big deal for now
|
|
1610
1786
|
|
|
1611
|
-
this._maybeWant(r.start, len)
|
|
1612
1787
|
return false
|
|
1613
1788
|
}
|
|
1614
1789
|
|
|
@@ -1649,28 +1824,10 @@ class Peer {
|
|
|
1649
1824
|
return true
|
|
1650
1825
|
}
|
|
1651
1826
|
|
|
1652
|
-
this.
|
|
1827
|
+
this._maybeWantAnyRange(f.batch.want.start, len, f)
|
|
1653
1828
|
return false
|
|
1654
1829
|
}
|
|
1655
1830
|
|
|
1656
|
-
_maybeWant(start, length = 1) {
|
|
1657
|
-
if (start + length <= this.remoteContiguousLength) return
|
|
1658
|
-
|
|
1659
|
-
let i = Math.floor(start / DEFAULT_SEGMENT_SIZE)
|
|
1660
|
-
const n = Math.ceil((start + length) / DEFAULT_SEGMENT_SIZE)
|
|
1661
|
-
|
|
1662
|
-
for (; i < n; i++) {
|
|
1663
|
-
if (this.segmentsWanted.has(i)) continue
|
|
1664
|
-
this.segmentsWanted.add(i)
|
|
1665
|
-
|
|
1666
|
-
this.wireWant.send({
|
|
1667
|
-
start: i * DEFAULT_SEGMENT_SIZE,
|
|
1668
|
-
length: DEFAULT_SEGMENT_SIZE
|
|
1669
|
-
})
|
|
1670
|
-
incrementTx(this.stats.wireWant, this.replicator.stats.wireWant)
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
1831
|
isActive() {
|
|
1675
1832
|
if (this.paused || this.removed || this.core.header.frozen) return false
|
|
1676
1833
|
return true
|
|
@@ -1757,6 +1914,7 @@ module.exports = class Replicator {
|
|
|
1757
1914
|
wireCancel: { tx: 0, rx: 0 },
|
|
1758
1915
|
wireData: { tx: 0, rx: 0 },
|
|
1759
1916
|
wireWant: { tx: 0, rx: 0 },
|
|
1917
|
+
wireUnwant: { tx: 0, rx: 0 },
|
|
1760
1918
|
wireBitfield: { tx: 0, rx: 0 },
|
|
1761
1919
|
wireRange: { tx: 0, rx: 0 },
|
|
1762
1920
|
wireExtension: { tx: 0, rx: 0 },
|
|
@@ -2158,11 +2316,7 @@ module.exports = class Replicator {
|
|
|
2158
2316
|
if (f.fork === fork) return f
|
|
2159
2317
|
}
|
|
2160
2318
|
|
|
2161
|
-
const f =
|
|
2162
|
-
fork,
|
|
2163
|
-
inflight: [],
|
|
2164
|
-
batch: null
|
|
2165
|
-
}
|
|
2319
|
+
const f = new ForkRequest(this, fork)
|
|
2166
2320
|
|
|
2167
2321
|
this._reorgs.push(f)
|
|
2168
2322
|
|
|
@@ -2208,6 +2362,7 @@ module.exports = class Replicator {
|
|
|
2208
2362
|
|
|
2209
2363
|
_removePeer(peer) {
|
|
2210
2364
|
this.peers.splice(this.peers.indexOf(peer), 1)
|
|
2365
|
+
peer.wants.destroy()
|
|
2211
2366
|
|
|
2212
2367
|
if (this._manifestPeer === peer) this._manifestPeer = null
|
|
2213
2368
|
|
|
@@ -2548,12 +2703,12 @@ module.exports = class Replicator {
|
|
|
2548
2703
|
this.updatePeer(peer)
|
|
2549
2704
|
}
|
|
2550
2705
|
|
|
2551
|
-
_onwant(peer, start, length) {
|
|
2706
|
+
_onwant(peer, start, length, any) {
|
|
2552
2707
|
if (!peer.isActive()) return
|
|
2553
2708
|
|
|
2554
2709
|
const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)
|
|
2555
2710
|
|
|
2556
|
-
if (start + length < contig || this.core.state.length === contig) {
|
|
2711
|
+
if (start + length < contig || this.core.state.length === contig || (any && start < contig)) {
|
|
2557
2712
|
peer.wireRange.send({
|
|
2558
2713
|
drop: false,
|
|
2559
2714
|
start: 0,
|
|
@@ -2565,14 +2720,22 @@ module.exports = class Replicator {
|
|
|
2565
2720
|
|
|
2566
2721
|
length = Math.min(length, this.core.state.length - start)
|
|
2567
2722
|
|
|
2568
|
-
|
|
2723
|
+
if (any) {
|
|
2724
|
+
const bit = this.core.bitfield.firstSet(start)
|
|
2725
|
+
if (bit > -1 && bit < start + length) {
|
|
2726
|
+
peer.wireRange.send({
|
|
2727
|
+
drop: false,
|
|
2728
|
+
start: bit,
|
|
2729
|
+
length: 1
|
|
2730
|
+
})
|
|
2731
|
+
}
|
|
2732
|
+
return
|
|
2733
|
+
}
|
|
2569
2734
|
|
|
2570
2735
|
for (const msg of this.core.bitfield.want(start, length)) {
|
|
2571
2736
|
peer.wireBitfield.send(msg)
|
|
2572
2737
|
incrementTx(peer.stats.wireBitfield, this.stats.wireBitfield)
|
|
2573
2738
|
}
|
|
2574
|
-
|
|
2575
|
-
peer.protomux.uncork()
|
|
2576
2739
|
}
|
|
2577
2740
|
|
|
2578
2741
|
async _onreorgdata(peer, req, data) {
|
|
@@ -2612,6 +2775,7 @@ module.exports = class Replicator {
|
|
|
2612
2775
|
// should investigate the complexity in going the other way
|
|
2613
2776
|
|
|
2614
2777
|
const u = this._upgrade
|
|
2778
|
+
const old = this._reorgs
|
|
2615
2779
|
|
|
2616
2780
|
this._reorgs = [] // clear all as the nodes are against the old tree - easier
|
|
2617
2781
|
this._applyingReorg = this.core.reorg(f.batch, null) // TODO: null should be the first/last peer?
|
|
@@ -2637,6 +2801,9 @@ module.exports = class Replicator {
|
|
|
2637
2801
|
r.end = r.userEnd
|
|
2638
2802
|
}
|
|
2639
2803
|
|
|
2804
|
+
for (const f of old) f.resolve()
|
|
2805
|
+
f.resolve()
|
|
2806
|
+
|
|
2640
2807
|
this.updateAll()
|
|
2641
2808
|
}
|
|
2642
2809
|
|
package/lib/wants.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
const BATCH_UINTS = 128
|
|
2
|
+
const BATCH_BYTES = BATCH_UINTS * 4
|
|
3
|
+
const BATCH = BATCH_BYTES * 8 // in bits
|
|
4
|
+
const MAX_REMOTE_BATCHES = 4
|
|
5
|
+
const MAX_RANGE = 8 * 256 * 1024
|
|
6
|
+
const MIN_RANGE = 32 // 4bit ints
|
|
7
|
+
const MAX = 512
|
|
8
|
+
const MAX_ANY = 16
|
|
9
|
+
|
|
10
|
+
class LocalWants {
|
|
11
|
+
constructor(peer) {
|
|
12
|
+
this.destroyed = false
|
|
13
|
+
this.peer = peer
|
|
14
|
+
this.wants = new Map()
|
|
15
|
+
this.free = new Set()
|
|
16
|
+
this.any = null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
add(index, handle) {
|
|
20
|
+
const b = (index - (index & (BATCH - 1))) / BATCH
|
|
21
|
+
return this.addBatch(b, handle)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
addAnyRange(start, length, handle) {
|
|
25
|
+
if (this.destroyed) return null
|
|
26
|
+
if (this.any === null) this.any = []
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < this.any.length; i++) {
|
|
29
|
+
const w = this.any[i]
|
|
30
|
+
if (w.start === start && w.length === length) {
|
|
31
|
+
w.handles.add(handle)
|
|
32
|
+
handle.addWant(w)
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const w = { wants: this, start, length, any: true, handles: new Set([handle]) }
|
|
38
|
+
this.any.push(w)
|
|
39
|
+
handle.addWant(w)
|
|
40
|
+
|
|
41
|
+
return { start, length, any: true }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
removeAnyRange(start, length, handle) {
|
|
45
|
+
if (this.any === null) return null
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < this.any.length; i++) {
|
|
48
|
+
const w = this.any[i]
|
|
49
|
+
if (w.start !== start || w.length !== length) continue
|
|
50
|
+
|
|
51
|
+
w.handles.delete(handle)
|
|
52
|
+
handle.removeWant(w)
|
|
53
|
+
if (w.handles.size > 0) return null
|
|
54
|
+
|
|
55
|
+
const head = this.any.pop()
|
|
56
|
+
if (head !== w) this.any[i] = head
|
|
57
|
+
return { start, length, any: true }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
addBatch(index, handle) {
|
|
64
|
+
if (this.destroyed) return null
|
|
65
|
+
let w = this.wants.get(index)
|
|
66
|
+
|
|
67
|
+
if (w) {
|
|
68
|
+
const size = w.handles.size
|
|
69
|
+
w.handles.add(handle)
|
|
70
|
+
if (w.handles.size !== size) {
|
|
71
|
+
handle.addWant(w)
|
|
72
|
+
}
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// start here is the batch number for simplicity....
|
|
77
|
+
w = { wants: this, start: index, length: 0, any: false, handles: new Set([handle]) }
|
|
78
|
+
|
|
79
|
+
if (this.free.has(index)) {
|
|
80
|
+
this.free.delete(index)
|
|
81
|
+
this.wants.set(index, w)
|
|
82
|
+
handle.addWant(w)
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let unwant = null
|
|
87
|
+
if (this.wants.size + this.free.size === MAX) {
|
|
88
|
+
if (this.free.size === 0) {
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
unwant = this._unwant()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.wants.set(index, w)
|
|
95
|
+
handle.addWant(w)
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
want: { start: index * BATCH, length: BATCH, any: false },
|
|
99
|
+
unwant
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
remove(index, handle) {
|
|
104
|
+
const b = (index - (index & (BATCH - 1))) / BATCH
|
|
105
|
+
return this.removeBatch(b, handle)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
removeBatch(index, handle) {
|
|
109
|
+
if (this.destroyed) return false
|
|
110
|
+
|
|
111
|
+
const w = this.wants.get(index)
|
|
112
|
+
if (!w) {
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
w.handles.delete(handle)
|
|
117
|
+
handle.removeWant(w)
|
|
118
|
+
|
|
119
|
+
if (w.handles.size === 0) {
|
|
120
|
+
this.free.add(index)
|
|
121
|
+
this.wants.delete(index)
|
|
122
|
+
return this.free.size === 1 && this.wants.size === MAX - 1
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
destroy() {
|
|
129
|
+
if (this.destroyed) return
|
|
130
|
+
this.destroyed = true
|
|
131
|
+
|
|
132
|
+
for (const w of this.wants.values()) {
|
|
133
|
+
for (const handle of w.handles) {
|
|
134
|
+
handle.removeWant(w)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.wants.clear()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_unwant() {
|
|
142
|
+
for (const index of this.free) {
|
|
143
|
+
this.free.delete(index)
|
|
144
|
+
return { start: index * BATCH, length: BATCH, any: false }
|
|
145
|
+
}
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
class RemoteWants {
|
|
151
|
+
constructor() {
|
|
152
|
+
this.any = null
|
|
153
|
+
this.all = false
|
|
154
|
+
this.size = 0
|
|
155
|
+
this.batches = []
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
hasAny(start, length) {
|
|
159
|
+
for (let i = 0; i < this.any.length; i++) {
|
|
160
|
+
const a = this.any[i]
|
|
161
|
+
const e = start + length
|
|
162
|
+
const end = a.start + a.length
|
|
163
|
+
|
|
164
|
+
if (a.start <= start && start < end) return true
|
|
165
|
+
if (a.start < e && e <= end) return true
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return this.all
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
hasRange(start, length) {
|
|
172
|
+
if (this.any !== null && this.hasAny(start, length)) return true
|
|
173
|
+
if (length === 1) return this.has(start)
|
|
174
|
+
|
|
175
|
+
let smallest = -1
|
|
176
|
+
|
|
177
|
+
for (let i = 0; i < this.batches.length; i++) {
|
|
178
|
+
const b = this.batches[i]
|
|
179
|
+
if (b.length < smallest || smallest === -1) smallest = b.length
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (smallest === -1) return this.all
|
|
183
|
+
|
|
184
|
+
const r = start & (smallest - 1)
|
|
185
|
+
const end = start + length
|
|
186
|
+
|
|
187
|
+
let max = 3
|
|
188
|
+
|
|
189
|
+
for (let i = start - r; i < end; i += smallest) {
|
|
190
|
+
if (max-- === 0) return true // just to save work
|
|
191
|
+
if (this.has(i)) return true
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return this.all
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
has(index) {
|
|
198
|
+
for (let i = 0; i < this.batches.length; i++) {
|
|
199
|
+
const b = this.batches[i]
|
|
200
|
+
const block = (index - (index & (b.length - 1))) / b.length
|
|
201
|
+
if (b.ranges.has(block)) return true
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return this.all
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
add(range) {
|
|
208
|
+
if (range.any) {
|
|
209
|
+
if (this.any === null) this.any = []
|
|
210
|
+
if (this.any.length < MAX_ANY) this.any.push(range)
|
|
211
|
+
else this.all = true
|
|
212
|
+
return true
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (range.length > MAX_RANGE) return false
|
|
216
|
+
|
|
217
|
+
if (!validateBatchRange(range)) {
|
|
218
|
+
this.all = true
|
|
219
|
+
return true
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (this.size >= MAX) {
|
|
223
|
+
this.all = true
|
|
224
|
+
return true
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (let i = 0; i < this.batches.length; i++) {
|
|
228
|
+
const b = this.batches[i]
|
|
229
|
+
if (b.length === range.length) {
|
|
230
|
+
b.ranges.add(range.start / range.length)
|
|
231
|
+
this.size++
|
|
232
|
+
return true
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (this.batches.length >= MAX_REMOTE_BATCHES) {
|
|
237
|
+
this.all = true
|
|
238
|
+
return true
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.batches.push({
|
|
242
|
+
length: range.length,
|
|
243
|
+
ranges: new Set([range.start / range.length])
|
|
244
|
+
})
|
|
245
|
+
this.size++
|
|
246
|
+
|
|
247
|
+
return true
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
remove(range) {
|
|
251
|
+
if (range.any) {
|
|
252
|
+
if (this.any === null) return false
|
|
253
|
+
|
|
254
|
+
for (let i = 0; i < this.any.length; i++) {
|
|
255
|
+
const a = this.any[i]
|
|
256
|
+
|
|
257
|
+
if (a.start === range.start && a.length === range.length) {
|
|
258
|
+
const head = this.any.pop()
|
|
259
|
+
if (head !== a) this.any[i] = head
|
|
260
|
+
return true
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return false
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!validateBatchRange(range)) return false
|
|
268
|
+
|
|
269
|
+
for (let i = 0; i < this.batches.length; i++) {
|
|
270
|
+
const b = this.batches[i]
|
|
271
|
+
if (b.length !== range.length) continue
|
|
272
|
+
|
|
273
|
+
const size = b.ranges.size
|
|
274
|
+
b.ranges.delete(range.start / range.length)
|
|
275
|
+
if (b.ranges.size !== size) this.size--
|
|
276
|
+
|
|
277
|
+
if (b.ranges.size === 0) {
|
|
278
|
+
const head = this.batches.pop()
|
|
279
|
+
if (head !== b) this.batches[i] = head
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return true
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return false
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
exports.LocalWants = LocalWants
|
|
290
|
+
exports.RemoteWants = RemoteWants
|
|
291
|
+
exports.WANT_BATCH = BATCH
|
|
292
|
+
|
|
293
|
+
function validateBatchRange(range) {
|
|
294
|
+
if (range.length > MAX_RANGE || range.length < MIN_RANGE) {
|
|
295
|
+
return false
|
|
296
|
+
}
|
|
297
|
+
// check if power of two
|
|
298
|
+
if ((range.length & (range.length - 1)) !== 0) {
|
|
299
|
+
return false
|
|
300
|
+
}
|
|
301
|
+
// start must be a multiple of the length
|
|
302
|
+
if (range.start & (range.length - 1)) {
|
|
303
|
+
return false
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return true
|
|
307
|
+
}
|