hypercore 11.22.2 → 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 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
- const j = start & (BITS_PER_SEGMENT - 1)
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
- // We always send at least 4 KiB worth of bitfield in a want, rounding
486
- // to the nearest 4 KiB.
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(0, end / 4)
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/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: c.uint.decode(state),
558
- length: c.uint.decode(state)
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: c.uint.decode(state),
575
- length: c.uint.decode(state)
597
+ start,
598
+ length,
599
+ any: (flags & 1) !== 0
576
600
  }
577
601
  }
578
602
  }
@@ -147,6 +147,7 @@ class RemoteBitfieldSegment {
147
147
 
148
148
  module.exports = class RemoteBitfield {
149
149
  static BITS_PER_PAGE = BITS_PER_PAGE
150
+ static BITS_PER_SEGMENT = BITS_PER_SEGMENT
150
151
 
151
152
  constructor() {
152
153
  this._pages = new BigSparseArray()
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 === 0 && !this.processing) this._unref()
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
- start + LAST_BLOCKS < this.core.state.length &&
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
@@ -1181,15 +1269,13 @@ class Peer {
1181
1269
  for (const id of flushed) this.replicator._inflight.reusable(id)
1182
1270
  }
1183
1271
 
1184
- onwant({ start, length }) {
1185
- const i = Math.floor(start / DEFAULT_SEGMENT_SIZE)
1186
- if (this.remoteSegmentsWanted.size >= MAX_REMOTE_SEGMENTS) this.remoteSegmentsWanted.clear() // just in case of abuse
1187
- this.remoteSegmentsWanted.add(i)
1188
- 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)
1189
1275
  }
1190
1276
 
1191
- onunwant() {
1192
- // TODO
1277
+ onunwant(range) {
1278
+ this.remoteWants.remove(range)
1193
1279
  }
1194
1280
 
1195
1281
  onbitfield({ start, bitfield }) {
@@ -1244,7 +1330,7 @@ class Peer {
1244
1330
  return
1245
1331
  }
1246
1332
 
1247
- const rem = start & 2097151
1333
+ const rem = start & (RemoteBitfield.BITS_PER_SEGMENT - 1)
1248
1334
  if (rem > 0) {
1249
1335
  start -= rem
1250
1336
  length += rem
@@ -1259,7 +1345,7 @@ class Peer {
1259
1345
  this.missingBlocks.insert(start, remote.bitfield)
1260
1346
  }
1261
1347
 
1262
- start += 2097152
1348
+ start += RemoteBitfield.BITS_PER_SEGMENT
1263
1349
  }
1264
1350
 
1265
1351
  this._clearLocalRange(fixedStart, length)
@@ -1459,7 +1545,7 @@ class Peer {
1459
1545
  return true
1460
1546
  }
1461
1547
 
1462
- this._maybeWant(s.seeker.start, len)
1548
+ this._maybeWantAnyRange(s.seeker.start, len, s)
1463
1549
  return false
1464
1550
  }
1465
1551
 
@@ -1498,7 +1584,7 @@ class Peer {
1498
1584
  const { length, fork } = this.core.state
1499
1585
 
1500
1586
  if (this._remoteHasBlock(b.index) === false || fork !== this.remoteFork) {
1501
- this._maybeWant(b.index)
1587
+ if (!this.core.bitfield.get(b.index)) this._maybeWant(b.index, b)
1502
1588
  return false
1503
1589
  }
1504
1590
 
@@ -1512,6 +1598,43 @@ class Peer {
1512
1598
  return true
1513
1599
  }
1514
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
+
1515
1638
  _requestRangeBlock(index, length) {
1516
1639
  if (this.core.bitfield.get(index) === true || !this._canRequest(index)) return false
1517
1640
 
@@ -1548,68 +1671,119 @@ class Peer {
1548
1671
  return this.missingBlocks.findFirst(true, i)
1549
1672
  }
1550
1673
 
1551
- _requestRange(r) {
1552
- if (this.syncsProcessing > 0) return false
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
1553
1678
 
1554
- const { length, fork } = this.core.state
1679
+ const b = (next - (next & (WANT_BATCH - 1))) / WANT_BATCH
1555
1680
 
1556
- if (r.blocks) {
1557
- let min = -1
1558
- let max = -1
1681
+ if (r.addBatch(b)) return true
1559
1682
 
1560
- for (let i = r.start; i < r.end; i++) {
1561
- const index = r.blocks[i]
1562
- if (min === -1 || index < min) min = index
1563
- if (max === -1 || index > max) max = index
1564
- const has = index < this._remoteContiguousLength || this.missingBlocks.get(index) === true
1565
- if (has === true && this._requestRangeBlock(index, length)) return true
1566
- }
1683
+ offset = (b + 1) * WANT_BATCH
1684
+ }
1567
1685
 
1568
- if (min > -1) this._maybeWant(min, max - min)
1569
- return false
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)
1701
+ }
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
1570
1711
  }
1712
+ }
1713
+
1714
+ _requestRangeBatch(start, end, length) {
1715
+ let tries = this.inflight
1571
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) {
1572
1729
  // if we can upgrade the remote, or the remote is ahead, then all the remotes blocks are valid
1573
1730
  // otherwise truncate to the last length the remote has acked for us
1574
1731
  const maxLocalLength =
1575
1732
  this.canUpgrade || this.remoteLength >= this.core.state.length
1576
1733
  ? this.core.state.length
1577
- : fork === this.lastUpgradableFork
1734
+ : this.core.state.fork === this.lastUpgradableFork
1578
1735
  ? Math.min(this.lastUpgradableLength, this.core.state.length)
1579
1736
  : 0
1580
1737
 
1581
- const end = Math.min(
1738
+ return Math.min(
1582
1739
  maxLocalLength,
1583
- Math.min(r.end === -1 ? this.remoteLength : r.end, this.remoteLength)
1740
+ Math.min(maxEnd === -1 ? this.remoteLength : maxEnd, this.remoteLength)
1584
1741
  )
1585
- if (end <= r.start || fork !== this.remoteFork) return false
1742
+ }
1586
1743
 
1587
- const len = end - r.start
1588
- const off = r.start + (r.linear ? 0 : Math.floor(Math.random() * len))
1744
+ _requestRange(r) {
1745
+ if (this.syncsProcessing > 0) return false
1589
1746
 
1590
- let i = off
1591
- // should be way less than this, but this is worst case upper bound for the skiplist
1592
- let tries = this.inflight
1747
+ const { length, fork } = this.core.state
1593
1748
 
1594
- do {
1595
- i = this._findNext(i)
1596
- if (i === -1 || i >= end) break
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
+ }
1597
1756
 
1598
- if (this._requestRangeBlock(i, length)) return true
1599
- i++
1600
- } while (tries-- > 0)
1757
+ return false
1758
+ }
1601
1759
 
1602
- i = r.start
1760
+ this._populateRangeBatches(r)
1603
1761
 
1604
- do {
1605
- i = this._findNext(i)
1606
- if (i === -1 || i >= off) break
1762
+ const end = this._getValidEnd(r.end)
1607
1763
 
1608
- if (this._requestRangeBlock(i, length)) return true
1609
- i++
1610
- } while (tries-- > 0)
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
1611
1786
 
1612
- this._maybeWant(r.start, len)
1613
1787
  return false
1614
1788
  }
1615
1789
 
@@ -1650,28 +1824,10 @@ class Peer {
1650
1824
  return true
1651
1825
  }
1652
1826
 
1653
- this._maybeWant(f.batch.want.start, len)
1827
+ this._maybeWantAnyRange(f.batch.want.start, len, f)
1654
1828
  return false
1655
1829
  }
1656
1830
 
1657
- _maybeWant(start, length = 1) {
1658
- if (start + length <= this.remoteContiguousLength) return
1659
-
1660
- let i = Math.floor(start / DEFAULT_SEGMENT_SIZE)
1661
- const n = Math.ceil((start + length) / DEFAULT_SEGMENT_SIZE)
1662
-
1663
- for (; i < n; i++) {
1664
- if (this.segmentsWanted.has(i)) continue
1665
- this.segmentsWanted.add(i)
1666
-
1667
- this.wireWant.send({
1668
- start: i * DEFAULT_SEGMENT_SIZE,
1669
- length: DEFAULT_SEGMENT_SIZE
1670
- })
1671
- incrementTx(this.stats.wireWant, this.replicator.stats.wireWant)
1672
- }
1673
- }
1674
-
1675
1831
  isActive() {
1676
1832
  if (this.paused || this.removed || this.core.header.frozen) return false
1677
1833
  return true
@@ -1758,6 +1914,7 @@ module.exports = class Replicator {
1758
1914
  wireCancel: { tx: 0, rx: 0 },
1759
1915
  wireData: { tx: 0, rx: 0 },
1760
1916
  wireWant: { tx: 0, rx: 0 },
1917
+ wireUnwant: { tx: 0, rx: 0 },
1761
1918
  wireBitfield: { tx: 0, rx: 0 },
1762
1919
  wireRange: { tx: 0, rx: 0 },
1763
1920
  wireExtension: { tx: 0, rx: 0 },
@@ -2159,11 +2316,7 @@ module.exports = class Replicator {
2159
2316
  if (f.fork === fork) return f
2160
2317
  }
2161
2318
 
2162
- const f = {
2163
- fork,
2164
- inflight: [],
2165
- batch: null
2166
- }
2319
+ const f = new ForkRequest(this, fork)
2167
2320
 
2168
2321
  this._reorgs.push(f)
2169
2322
 
@@ -2209,6 +2362,7 @@ module.exports = class Replicator {
2209
2362
 
2210
2363
  _removePeer(peer) {
2211
2364
  this.peers.splice(this.peers.indexOf(peer), 1)
2365
+ peer.wants.destroy()
2212
2366
 
2213
2367
  if (this._manifestPeer === peer) this._manifestPeer = null
2214
2368
 
@@ -2549,12 +2703,12 @@ module.exports = class Replicator {
2549
2703
  this.updatePeer(peer)
2550
2704
  }
2551
2705
 
2552
- _onwant(peer, start, length) {
2706
+ _onwant(peer, start, length, any) {
2553
2707
  if (!peer.isActive()) return
2554
2708
 
2555
2709
  const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)
2556
2710
 
2557
- if (start + length < contig || this.core.state.length === contig) {
2711
+ if (start + length < contig || this.core.state.length === contig || (any && start < contig)) {
2558
2712
  peer.wireRange.send({
2559
2713
  drop: false,
2560
2714
  start: 0,
@@ -2566,14 +2720,22 @@ module.exports = class Replicator {
2566
2720
 
2567
2721
  length = Math.min(length, this.core.state.length - start)
2568
2722
 
2569
- peer.protomux.cork()
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
+ }
2570
2734
 
2571
2735
  for (const msg of this.core.bitfield.want(start, length)) {
2572
2736
  peer.wireBitfield.send(msg)
2573
2737
  incrementTx(peer.stats.wireBitfield, this.stats.wireBitfield)
2574
2738
  }
2575
-
2576
- peer.protomux.uncork()
2577
2739
  }
2578
2740
 
2579
2741
  async _onreorgdata(peer, req, data) {
@@ -2613,6 +2775,7 @@ module.exports = class Replicator {
2613
2775
  // should investigate the complexity in going the other way
2614
2776
 
2615
2777
  const u = this._upgrade
2778
+ const old = this._reorgs
2616
2779
 
2617
2780
  this._reorgs = [] // clear all as the nodes are against the old tree - easier
2618
2781
  this._applyingReorg = this.core.reorg(f.batch, null) // TODO: null should be the first/last peer?
@@ -2638,6 +2801,9 @@ module.exports = class Replicator {
2638
2801
  r.end = r.userEnd
2639
2802
  }
2640
2803
 
2804
+ for (const f of old) f.resolve()
2805
+ f.resolve()
2806
+
2641
2807
  this.updateAll()
2642
2808
  }
2643
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "11.22.2",
3
+ "version": "11.23.0",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {