hypercore 10.16.1 → 10.18.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/index.js CHANGED
@@ -359,7 +359,7 @@ module.exports = class Hypercore extends EventEmitter {
359
359
  this.id = z32.encode(this.key)
360
360
 
361
361
  this.replicator = new Replicator(this.core, this.key, {
362
- eagerUpdate: true,
362
+ eagerUpgrade: true,
363
363
  allowFork: opts.allowFork !== false,
364
364
  onpeerupdate: this._onpeerupdate.bind(this),
365
365
  onupload: this._onupload.bind(this)
package/lib/messages.js CHANGED
@@ -103,9 +103,10 @@ wire.request = {
103
103
  if (m.hash) requestBlock.preencode(state, m.hash)
104
104
  if (m.seek) requestSeek.preencode(state, m.seek)
105
105
  if (m.upgrade) requestUpgrade.preencode(state, m.upgrade)
106
+ if (m.priority) c.uint.preencode(state, m.priority)
106
107
  },
107
108
  encode (state, m) {
108
- const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0)
109
+ const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0) | (m.priority ? 16 : 0)
109
110
 
110
111
  c.uint.encode(state, flags)
111
112
  c.uint.encode(state, m.id)
@@ -115,6 +116,7 @@ wire.request = {
115
116
  if (m.hash) requestBlock.encode(state, m.hash)
116
117
  if (m.seek) requestSeek.encode(state, m.seek)
117
118
  if (m.upgrade) requestUpgrade.encode(state, m.upgrade)
119
+ if (m.priority) c.uint.encode(state, m.priority)
118
120
  },
119
121
  decode (state) {
120
122
  const flags = c.uint.decode(state)
@@ -125,7 +127,8 @@ wire.request = {
125
127
  block: flags & 1 ? requestBlock.decode(state) : null,
126
128
  hash: flags & 2 ? requestBlock.decode(state) : null,
127
129
  seek: flags & 4 ? requestSeek.decode(state) : null,
128
- upgrade: flags & 8 ? requestUpgrade.decode(state) : null
130
+ upgrade: flags & 8 ? requestUpgrade.decode(state) : null,
131
+ priority: flags & 16 ? c.uint.decode(state) : 0
129
132
  }
130
133
  }
131
134
  }
@@ -0,0 +1,63 @@
1
+ const FIFO = require('fast-fifo')
2
+
3
+ module.exports = class ReceiverQueue {
4
+ constructor () {
5
+ this.queue = new FIFO()
6
+ this.priority = []
7
+ this.requests = new Map()
8
+ this.length = 0
9
+ }
10
+
11
+ push (req) {
12
+ // TODO: use a heap at some point if we wanna support multiple prios
13
+ if (req.priority > 0) this.priority.push(req)
14
+ else this.queue.push(req)
15
+
16
+ this.requests.set(req.id, req)
17
+ this.length++
18
+ }
19
+
20
+ shift () {
21
+ while (this.priority.length > 0) {
22
+ const msg = this.priority.pop()
23
+ const req = this._processRequest(msg)
24
+ if (req !== null) return req
25
+ }
26
+
27
+ while (this.queue.length > 0) {
28
+ const msg = this.queue.shift()
29
+ const req = this._processRequest(msg)
30
+ if (req !== null) return req
31
+ }
32
+
33
+ return null
34
+ }
35
+
36
+ _processRequest (req) {
37
+ if (req.block || req.hash || req.seek || req.upgrade) {
38
+ this.requests.delete(req.id)
39
+ this.length--
40
+ return req
41
+ }
42
+
43
+ return null
44
+ }
45
+
46
+ delete (id) {
47
+ const req = this.requests.get(id)
48
+ if (!req) return
49
+
50
+ req.block = null
51
+ req.hash = null
52
+ req.seek = null
53
+ req.upgrade = null
54
+
55
+ this.requests.delete(id)
56
+ this.length--
57
+
58
+ if (this.length === 0) {
59
+ this.queue.clear()
60
+ this.priority = []
61
+ }
62
+ }
63
+ }
package/lib/replicator.js CHANGED
@@ -1,14 +1,22 @@
1
1
  const b4a = require('b4a')
2
2
  const safetyCatch = require('safety-catch')
3
3
  const RandomIterator = require('random-array-iterator')
4
+ const ReceiverQueue = require('./receiver-queue')
4
5
  const RemoteBitfield = require('./remote-bitfield')
5
6
  const { REQUEST_CANCELLED, REQUEST_TIMEOUT, INVALID_CAPABILITY, SNAPSHOT_NOT_AVAILABLE } = require('./errors')
6
7
  const m = require('./messages')
7
8
  const caps = require('./caps')
8
9
 
9
- const DEFAULT_MAX_INFLIGHT = 32
10
+ const DEFAULT_MAX_INFLIGHT = [32, 512]
11
+ const SCALE_LATENCY = 50
10
12
  const DEFAULT_SEGMENT_SIZE = 128 * 1024 // 128 KiB
11
13
 
14
+ const PRIORITY = {
15
+ NORMAL: 0,
16
+ HIGH: 1,
17
+ VERY_HIGH: 2
18
+ }
19
+
12
20
  class Attachable {
13
21
  constructor () {
14
22
  this.resolved = false
@@ -94,17 +102,21 @@ class Attachable {
94
102
  }
95
103
 
96
104
  class BlockRequest extends Attachable {
97
- constructor (tracker, index) {
105
+ constructor (tracker, index, priority) {
98
106
  super()
99
107
 
100
108
  this.index = index
109
+ this.priority = priority
101
110
  this.inflight = []
102
111
  this.queued = false
103
112
  this.tracker = tracker
104
113
  }
105
114
 
106
115
  _unref () {
107
- if (this.inflight.length > 0) return
116
+ for (const req of this.inflight) {
117
+ req.peer._cancelRequest(req.id)
118
+ }
119
+
108
120
  this.tracker.remove(this.index)
109
121
  }
110
122
  }
@@ -232,11 +244,11 @@ class BlockTracker {
232
244
  return this._map.get(index) || null
233
245
  }
234
246
 
235
- add (index) {
247
+ add (index, priority) {
236
248
  let b = this._map.get(index)
237
249
  if (b) return b
238
250
 
239
- b = new BlockRequest(this, index)
251
+ b = new BlockRequest(this, index, priority)
240
252
  this._map.set(index, b)
241
253
 
242
254
  return b
@@ -266,7 +278,7 @@ class Peer {
266
278
 
267
279
  this.wireSync = this.channel.messages[0]
268
280
  this.wireRequest = this.channel.messages[1]
269
- this.wireCancel = null
281
+ this.wireCancel = this.channel.messages[2]
270
282
  this.wireData = this.channel.messages[3]
271
283
  this.wireNoData = this.channel.messages[4]
272
284
  this.wireWant = this.channel.messages[5]
@@ -275,8 +287,11 @@ class Peer {
275
287
  this.wireRange = this.channel.messages[8]
276
288
  this.wireExtension = this.channel.messages[9]
277
289
 
290
+ this.receiverQueue = new ReceiverQueue()
291
+ this.receiverBusy = false
292
+
278
293
  this.inflight = 0
279
- this.maxInflight = DEFAULT_MAX_INFLIGHT
294
+ this.inflightRange = DEFAULT_MAX_INFLIGHT
280
295
  this.dataProcessing = 0
281
296
 
282
297
  this.canUpgrade = true
@@ -310,6 +325,18 @@ class Peer {
310
325
  replicator._ifAvailable++
311
326
  }
312
327
 
328
+ get remoteContiguousLength () {
329
+ return this.remoteBitfield.findFirst(false, 0)
330
+ }
331
+
332
+ getMaxInflight () {
333
+ const stream = this.stream.rawStream
334
+ if (!stream.udx) return Math.min(this.inflightRange[1], this.inflightRange[0] * 3)
335
+
336
+ const scale = stream.rtt <= SCALE_LATENCY ? 1 : stream.rtt / SCALE_LATENCY
337
+ return Math.round(Math.min(this.inflightRange[1], this.inflightRange[0] * scale))
338
+ }
339
+
313
340
  signalUpgrade () {
314
341
  if (this._shouldUpdateCanUpgrade() === true) this._updateCanUpgradeAndSync()
315
342
  else this.sendSync()
@@ -495,6 +522,35 @@ class Peer {
495
522
  }
496
523
 
497
524
  async onrequest (msg) {
525
+ if (!this.protomux.drained || this.receiverQueue.length) {
526
+ this.receiverQueue.push(msg)
527
+ return
528
+ }
529
+
530
+ await this._handleRequest(msg)
531
+ }
532
+
533
+ oncancel (msg) {
534
+ this.receiverQueue.delete(msg.request)
535
+ }
536
+
537
+ ondrain () {
538
+ return this._handleRequests()
539
+ }
540
+
541
+ async _handleRequests () {
542
+ if (this.receiverBusy) return
543
+ this.receiverBusy = true
544
+
545
+ while (this.remoteOpened && this.protomux.drained && this.receiverQueue.length > 0) {
546
+ const msg = this.receiverQueue.shift()
547
+ await this._handleRequest(msg)
548
+ }
549
+
550
+ this.receiverBusy = false
551
+ }
552
+
553
+ async _handleRequest (msg) {
498
554
  let proof = null
499
555
 
500
556
  // TODO: could still be answerable if (index, fork) is an ancestor of the current fork
@@ -527,6 +583,16 @@ class Peer {
527
583
  })
528
584
  }
529
585
 
586
+ _cancelRequest (id) {
587
+ const exists = this.replicator._inflight.get(id)
588
+ if (!exists) return
589
+
590
+ this.inflight--
591
+ this.replicator._inflight.remove(id)
592
+
593
+ this.wireCancel.send({ request: id })
594
+ }
595
+
530
596
  _checkIfConflict (err) {
531
597
  this.paused = true
532
598
 
@@ -619,7 +685,16 @@ class Peer {
619
685
  onrange ({ drop, start, length }) {
620
686
  const has = drop === false
621
687
 
622
- this.remoteBitfield.setRange(start, length, has)
688
+ if (length === 1) {
689
+ this.remoteBitfield.setRange(start, length, has)
690
+ } else {
691
+ const rangeStart = this.remoteBitfield.findFirst(!has, start)
692
+ const rangeLength = length - (rangeStart - start)
693
+
694
+ if (rangeLength > 0) {
695
+ this.remoteBitfield.setRange(rangeStart, rangeLength, has)
696
+ }
697
+ }
623
698
 
624
699
  if (drop === false) this._update()
625
700
  }
@@ -653,7 +728,7 @@ class Peer {
653
728
  this.protomux.uncork()
654
729
  }
655
730
 
656
- _makeRequest (needsUpgrade) {
731
+ _makeRequest (needsUpgrade, priority) {
657
732
  if (needsUpgrade === true && this.replicator._shouldUpgrade(this) === false) {
658
733
  return null
659
734
  }
@@ -671,7 +746,8 @@ class Peer {
671
746
  seek: null,
672
747
  upgrade: needsUpgrade === false
673
748
  ? null
674
- : { start: this.core.tree.length, length: this.remoteLength - this.core.tree.length }
749
+ : { start: this.core.tree.length, length: this.remoteLength - this.core.tree.length },
750
+ priority
675
751
  }
676
752
  }
677
753
 
@@ -718,10 +794,10 @@ class Peer {
718
794
  if (b !== null && b.inflight.length > 0) continue
719
795
 
720
796
  // Block is not inflight, but we only want the hash, check if that is inflight
721
- const h = this.replicator._hashes.add(index)
797
+ const h = this.replicator._hashes.add(index, PRIORITY.NORMAL)
722
798
  if (h.inflight.length > 0) continue
723
799
 
724
- const req = this._makeRequest(false)
800
+ const req = this._makeRequest(false, h.priority)
725
801
 
726
802
  req.hash = { index: 2 * index, nodes: 0 }
727
803
  req.seek = { bytes: s.seeker.bytes }
@@ -750,7 +826,7 @@ class Peer {
750
826
  return false
751
827
  }
752
828
 
753
- const req = this._makeRequest(b.index >= length)
829
+ const req = this._makeRequest(b.index >= length, b.priority)
754
830
  if (req === null) return false
755
831
 
756
832
  req.block = { index: b.index, nodes: 0 }
@@ -783,10 +859,10 @@ class Peer {
783
859
  if (this.remoteBitfield.get(index) === false) continue
784
860
  if (this.core.bitfield.get(index) === true) continue
785
861
 
786
- const b = this.replicator._blocks.add(index)
862
+ const b = this.replicator._blocks.add(index, PRIORITY.NORMAL)
787
863
  if (b.inflight.length > 0) continue
788
864
 
789
- const req = this._makeRequest(index >= length)
865
+ const req = this._makeRequest(index >= length, b.priority)
790
866
 
791
867
  // If the request cannot be satisfied, dealloc the block request if no one is subscribed to it
792
868
  if (req === null) {
@@ -849,6 +925,8 @@ class Peer {
849
925
  }
850
926
 
851
927
  _maybeWant (start, length = 1) {
928
+ if (start + length <= this.remoteContiguousLength) return
929
+
852
930
  let i = Math.floor(start / DEFAULT_SEGMENT_SIZE)
853
931
  const n = Math.ceil((start + length) / DEFAULT_SEGMENT_SIZE)
854
932
 
@@ -902,6 +980,7 @@ module.exports = class Replicator {
902
980
  this.peers = []
903
981
  this.findingPeers = 0 // updateable from the outside
904
982
 
983
+ this._attached = new Set()
905
984
  this._inflight = new InflightTracker()
906
985
  this._blocks = new BlockTracker()
907
986
  this._hashes = new BlockTracker()
@@ -917,6 +996,13 @@ module.exports = class Replicator {
917
996
  this._ifAvailable = 0
918
997
  this._updatesPending = 0
919
998
  this._applyingReorg = null
999
+
1000
+ const self = this
1001
+ this._onstreamclose = onstreamclose
1002
+
1003
+ function onstreamclose () {
1004
+ self.detachFrom(this.userData)
1005
+ }
920
1006
  }
921
1007
 
922
1008
  cork () {
@@ -998,7 +1084,7 @@ module.exports = class Replicator {
998
1084
  }
999
1085
 
1000
1086
  addBlock (session, index) {
1001
- const b = this._blocks.add(index)
1087
+ const b = this._blocks.add(index, PRIORITY.HIGH)
1002
1088
  const ref = b.attach(session)
1003
1089
 
1004
1090
  this._queueBlock(b)
@@ -1492,7 +1578,7 @@ module.exports = class Replicator {
1492
1578
  }
1493
1579
 
1494
1580
  _updatePeer (peer) {
1495
- if (peer.paused || peer.inflight >= peer.maxInflight) {
1581
+ if (peer.paused || peer.inflight >= peer.getMaxInflight()) {
1496
1582
  return false
1497
1583
  }
1498
1584
 
@@ -1518,7 +1604,7 @@ module.exports = class Replicator {
1518
1604
  }
1519
1605
 
1520
1606
  _updatePeerNonPrimary (peer) {
1521
- if (peer.paused || peer.inflight >= peer.maxInflight) {
1607
+ if (peer.paused || peer.inflight >= peer.getMaxInflight()) {
1522
1608
  return false
1523
1609
  }
1524
1610
 
@@ -1587,21 +1673,36 @@ module.exports = class Replicator {
1587
1673
  attachTo (protomux, session) {
1588
1674
  const makePeer = this._makePeer.bind(this, protomux, session)
1589
1675
 
1676
+ this._attached.add(protomux)
1590
1677
  protomux.pair({ protocol: 'hypercore/alpha', id: this.discoveryKey }, makePeer)
1678
+ protomux.stream.setMaxListeners(0)
1679
+ protomux.stream.on('close', this._onstreamclose)
1680
+
1591
1681
  this._ifAvailable++
1592
1682
  protomux.stream.opened.then((opened) => {
1593
1683
  this._ifAvailable--
1684
+
1594
1685
  if (opened) makePeer()
1595
1686
  else if (session) session.close().catch(noop)
1596
1687
  this._checkUpgradeIfAvailable()
1597
1688
  })
1598
1689
  }
1599
1690
 
1691
+ detachFrom (protomux) {
1692
+ if (this._attached.delete(protomux)) {
1693
+ protomux.stream.removeListener('close', this._onstreamclose)
1694
+ protomux.unpair({ protocol: 'hypercore/alpha', id: this.discoveryKey })
1695
+ }
1696
+ }
1697
+
1600
1698
  destroy () {
1601
1699
  for (const peer of this.peers) {
1602
- peer.protomux.unpair({ protocol: 'hypercore/alpha', id: this.discoveryKey })
1700
+ this.detachFrom(peer.protomux)
1603
1701
  peer.channel.close()
1604
1702
  }
1703
+ for (const protomux of this._attached) {
1704
+ this.detachFrom(protomux)
1705
+ }
1605
1706
  }
1606
1707
 
1607
1708
  _makePeer (protomux, session) {
@@ -1616,7 +1717,7 @@ module.exports = class Replicator {
1616
1717
  messages: [
1617
1718
  { encoding: m.wire.sync, onmessage: onwiresync },
1618
1719
  { encoding: m.wire.request, onmessage: onwirerequest },
1619
- null, // oncancel
1720
+ { encoding: m.wire.cancel, onmessage: onwirecancel },
1620
1721
  { encoding: m.wire.data, onmessage: onwiredata },
1621
1722
  { encoding: m.wire.noData, onmessage: onwirenodata },
1622
1723
  { encoding: m.wire.want, onmessage: onwirewant },
@@ -1626,7 +1727,8 @@ module.exports = class Replicator {
1626
1727
  { encoding: m.wire.extension, onmessage: onwireextension }
1627
1728
  ],
1628
1729
  onopen: onwireopen,
1629
- onclose: onwireclose
1730
+ onclose: onwireclose,
1731
+ ondrain: onwiredrain
1630
1732
  })
1631
1733
 
1632
1734
  if (channel === null) return onnochannel()
@@ -1708,6 +1810,10 @@ function onwireclose (isRemote, c) {
1708
1810
  return c.userData.onclose(isRemote)
1709
1811
  }
1710
1812
 
1813
+ function onwiredrain (c) {
1814
+ return c.userData.ondrain()
1815
+ }
1816
+
1711
1817
  function onwiresync (m, c) {
1712
1818
  return c.userData.onsync(m)
1713
1819
  }
@@ -1716,6 +1822,10 @@ function onwirerequest (m, c) {
1716
1822
  return c.userData.onrequest(m)
1717
1823
  }
1718
1824
 
1825
+ function onwirecancel (m, c) {
1826
+ return c.userData.oncancel(m)
1827
+ }
1828
+
1719
1829
  function onwiredata (m, c) {
1720
1830
  return c.userData.ondata(m)
1721
1831
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.16.1",
3
+ "version": "10.18.0",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -41,10 +41,11 @@
41
41
  "compact-encoding": "^2.11.0",
42
42
  "crc-universal": "^1.0.2",
43
43
  "events": "^3.3.0",
44
+ "fast-fifo": "^1.3.0",
44
45
  "flat-tree": "^1.9.0",
45
46
  "hypercore-crypto": "^3.2.1",
46
47
  "is-options": "^1.0.1",
47
- "protomux": "^3.4.0",
48
+ "protomux": "^3.5.0",
48
49
  "quickbit-universal": "^2.1.1",
49
50
  "random-access-file": "^4.0.0",
50
51
  "random-array-iterator": "^1.0.0",
@@ -60,7 +61,10 @@
60
61
  "random-access-memory": "^6.1.0",
61
62
  "random-access-memory-overlay": "^3.0.0",
62
63
  "range-parser": "^1.2.1",
64
+ "speedometer": "^1.1.0",
63
65
  "standard": "^17.0.0",
64
- "tmp-promise": "^3.0.2"
66
+ "tiny-byte-size": "^1.1.0",
67
+ "tmp-promise": "^3.0.2",
68
+ "udx-native": "^1.6.1"
65
69
  }
66
70
  }