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 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
- }
@@ -723,6 +723,9 @@ class MerkleTree {
723
723
  if (r === index) return 0
724
724
  }
725
725
 
726
+ // need missing nodes not inclusive of index
727
+ ite.parent()
728
+
726
729
  let cnt = 0
727
730
  while (!ite.contains(head)) {
728
731
  cnt++
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
@@ -915,7 +1003,7 @@ class Peer {
915
1003
  nodes: MerkleTree.maxMissingNodes(2 * index, remoteLength)
916
1004
  }
917
1005
 
918
- if (index >= this.remoteLength) {
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({ start, length }) {
1184
- const i = Math.floor(start / DEFAULT_SEGMENT_SIZE)
1185
- if (this.remoteSegmentsWanted.size >= MAX_REMOTE_SEGMENTS) this.remoteSegmentsWanted.clear() // just in case of abuse
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
- // TODO
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 & 2097151
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 += 2097152
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._maybeWant(s.seeker.start, len)
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
- _requestRange(r) {
1551
- 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
1552
1678
 
1553
- const { length, fork } = this.core.state
1679
+ const b = (next - (next & (WANT_BATCH - 1))) / WANT_BATCH
1554
1680
 
1555
- if (r.blocks) {
1556
- let min = -1
1557
- let max = -1
1681
+ if (r.addBatch(b)) return true
1558
1682
 
1559
- for (let i = r.start; i < r.end; i++) {
1560
- const index = r.blocks[i]
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
- if (min > -1) this._maybeWant(min, max - min)
1568
- 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)
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
- const end = Math.min(
1738
+ return Math.min(
1581
1739
  maxLocalLength,
1582
- Math.min(r.end === -1 ? this.remoteLength : r.end, this.remoteLength)
1740
+ Math.min(maxEnd === -1 ? this.remoteLength : maxEnd, this.remoteLength)
1583
1741
  )
1584
- if (end <= r.start || fork !== this.remoteFork) return false
1742
+ }
1585
1743
 
1586
- const len = end - r.start
1587
- const off = r.start + (r.linear ? 0 : Math.floor(Math.random() * len))
1744
+ _requestRange(r) {
1745
+ if (this.syncsProcessing > 0) return false
1588
1746
 
1589
- let i = off
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
- do {
1594
- i = this._findNext(i)
1595
- 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
+ }
1596
1756
 
1597
- if (this._requestRangeBlock(i, length)) return true
1598
- i++
1599
- } while (tries-- > 0)
1757
+ return false
1758
+ }
1600
1759
 
1601
- i = r.start
1760
+ this._populateRangeBatches(r)
1602
1761
 
1603
- do {
1604
- i = this._findNext(i)
1605
- if (i === -1 || i >= off) break
1762
+ const end = this._getValidEnd(r.end)
1606
1763
 
1607
- if (this._requestRangeBlock(i, length)) return true
1608
- i++
1609
- } 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
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._maybeWant(f.batch.want.start, len)
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
- 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
+ }
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "11.22.1",
3
+ "version": "11.23.0",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {