hypercore 10.17.0 → 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 +1 -1
- package/lib/messages.js +5 -2
- package/lib/receiver-queue.js +63 -0
- package/lib/replicator.js +118 -17
- package/package.json +3 -2
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
|
-
|
|
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,6 +1,7 @@
|
|
|
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')
|
|
@@ -10,6 +11,12 @@ const DEFAULT_MAX_INFLIGHT = [32, 512]
|
|
|
10
11
|
const SCALE_LATENCY = 50
|
|
11
12
|
const DEFAULT_SEGMENT_SIZE = 128 * 1024 // 128 KiB
|
|
12
13
|
|
|
14
|
+
const PRIORITY = {
|
|
15
|
+
NORMAL: 0,
|
|
16
|
+
HIGH: 1,
|
|
17
|
+
VERY_HIGH: 2
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
class Attachable {
|
|
14
21
|
constructor () {
|
|
15
22
|
this.resolved = false
|
|
@@ -95,17 +102,21 @@ class Attachable {
|
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
class BlockRequest extends Attachable {
|
|
98
|
-
constructor (tracker, index) {
|
|
105
|
+
constructor (tracker, index, priority) {
|
|
99
106
|
super()
|
|
100
107
|
|
|
101
108
|
this.index = index
|
|
109
|
+
this.priority = priority
|
|
102
110
|
this.inflight = []
|
|
103
111
|
this.queued = false
|
|
104
112
|
this.tracker = tracker
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
_unref () {
|
|
108
|
-
|
|
116
|
+
for (const req of this.inflight) {
|
|
117
|
+
req.peer._cancelRequest(req.id)
|
|
118
|
+
}
|
|
119
|
+
|
|
109
120
|
this.tracker.remove(this.index)
|
|
110
121
|
}
|
|
111
122
|
}
|
|
@@ -233,11 +244,11 @@ class BlockTracker {
|
|
|
233
244
|
return this._map.get(index) || null
|
|
234
245
|
}
|
|
235
246
|
|
|
236
|
-
add (index) {
|
|
247
|
+
add (index, priority) {
|
|
237
248
|
let b = this._map.get(index)
|
|
238
249
|
if (b) return b
|
|
239
250
|
|
|
240
|
-
b = new BlockRequest(this, index)
|
|
251
|
+
b = new BlockRequest(this, index, priority)
|
|
241
252
|
this._map.set(index, b)
|
|
242
253
|
|
|
243
254
|
return b
|
|
@@ -267,7 +278,7 @@ class Peer {
|
|
|
267
278
|
|
|
268
279
|
this.wireSync = this.channel.messages[0]
|
|
269
280
|
this.wireRequest = this.channel.messages[1]
|
|
270
|
-
this.wireCancel =
|
|
281
|
+
this.wireCancel = this.channel.messages[2]
|
|
271
282
|
this.wireData = this.channel.messages[3]
|
|
272
283
|
this.wireNoData = this.channel.messages[4]
|
|
273
284
|
this.wireWant = this.channel.messages[5]
|
|
@@ -276,6 +287,9 @@ class Peer {
|
|
|
276
287
|
this.wireRange = this.channel.messages[8]
|
|
277
288
|
this.wireExtension = this.channel.messages[9]
|
|
278
289
|
|
|
290
|
+
this.receiverQueue = new ReceiverQueue()
|
|
291
|
+
this.receiverBusy = false
|
|
292
|
+
|
|
279
293
|
this.inflight = 0
|
|
280
294
|
this.inflightRange = DEFAULT_MAX_INFLIGHT
|
|
281
295
|
this.dataProcessing = 0
|
|
@@ -311,6 +325,10 @@ class Peer {
|
|
|
311
325
|
replicator._ifAvailable++
|
|
312
326
|
}
|
|
313
327
|
|
|
328
|
+
get remoteContiguousLength () {
|
|
329
|
+
return this.remoteBitfield.findFirst(false, 0)
|
|
330
|
+
}
|
|
331
|
+
|
|
314
332
|
getMaxInflight () {
|
|
315
333
|
const stream = this.stream.rawStream
|
|
316
334
|
if (!stream.udx) return Math.min(this.inflightRange[1], this.inflightRange[0] * 3)
|
|
@@ -504,6 +522,35 @@ class Peer {
|
|
|
504
522
|
}
|
|
505
523
|
|
|
506
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) {
|
|
507
554
|
let proof = null
|
|
508
555
|
|
|
509
556
|
// TODO: could still be answerable if (index, fork) is an ancestor of the current fork
|
|
@@ -536,6 +583,16 @@ class Peer {
|
|
|
536
583
|
})
|
|
537
584
|
}
|
|
538
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
|
+
|
|
539
596
|
_checkIfConflict (err) {
|
|
540
597
|
this.paused = true
|
|
541
598
|
|
|
@@ -628,7 +685,16 @@ class Peer {
|
|
|
628
685
|
onrange ({ drop, start, length }) {
|
|
629
686
|
const has = drop === false
|
|
630
687
|
|
|
631
|
-
|
|
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
|
+
}
|
|
632
698
|
|
|
633
699
|
if (drop === false) this._update()
|
|
634
700
|
}
|
|
@@ -662,7 +728,7 @@ class Peer {
|
|
|
662
728
|
this.protomux.uncork()
|
|
663
729
|
}
|
|
664
730
|
|
|
665
|
-
_makeRequest (needsUpgrade) {
|
|
731
|
+
_makeRequest (needsUpgrade, priority) {
|
|
666
732
|
if (needsUpgrade === true && this.replicator._shouldUpgrade(this) === false) {
|
|
667
733
|
return null
|
|
668
734
|
}
|
|
@@ -680,7 +746,8 @@ class Peer {
|
|
|
680
746
|
seek: null,
|
|
681
747
|
upgrade: needsUpgrade === false
|
|
682
748
|
? null
|
|
683
|
-
: { 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
|
|
684
751
|
}
|
|
685
752
|
}
|
|
686
753
|
|
|
@@ -727,10 +794,10 @@ class Peer {
|
|
|
727
794
|
if (b !== null && b.inflight.length > 0) continue
|
|
728
795
|
|
|
729
796
|
// Block is not inflight, but we only want the hash, check if that is inflight
|
|
730
|
-
const h = this.replicator._hashes.add(index)
|
|
797
|
+
const h = this.replicator._hashes.add(index, PRIORITY.NORMAL)
|
|
731
798
|
if (h.inflight.length > 0) continue
|
|
732
799
|
|
|
733
|
-
const req = this._makeRequest(false)
|
|
800
|
+
const req = this._makeRequest(false, h.priority)
|
|
734
801
|
|
|
735
802
|
req.hash = { index: 2 * index, nodes: 0 }
|
|
736
803
|
req.seek = { bytes: s.seeker.bytes }
|
|
@@ -759,7 +826,7 @@ class Peer {
|
|
|
759
826
|
return false
|
|
760
827
|
}
|
|
761
828
|
|
|
762
|
-
const req = this._makeRequest(b.index >= length)
|
|
829
|
+
const req = this._makeRequest(b.index >= length, b.priority)
|
|
763
830
|
if (req === null) return false
|
|
764
831
|
|
|
765
832
|
req.block = { index: b.index, nodes: 0 }
|
|
@@ -792,10 +859,10 @@ class Peer {
|
|
|
792
859
|
if (this.remoteBitfield.get(index) === false) continue
|
|
793
860
|
if (this.core.bitfield.get(index) === true) continue
|
|
794
861
|
|
|
795
|
-
const b = this.replicator._blocks.add(index)
|
|
862
|
+
const b = this.replicator._blocks.add(index, PRIORITY.NORMAL)
|
|
796
863
|
if (b.inflight.length > 0) continue
|
|
797
864
|
|
|
798
|
-
const req = this._makeRequest(index >= length)
|
|
865
|
+
const req = this._makeRequest(index >= length, b.priority)
|
|
799
866
|
|
|
800
867
|
// If the request cannot be satisfied, dealloc the block request if no one is subscribed to it
|
|
801
868
|
if (req === null) {
|
|
@@ -858,6 +925,8 @@ class Peer {
|
|
|
858
925
|
}
|
|
859
926
|
|
|
860
927
|
_maybeWant (start, length = 1) {
|
|
928
|
+
if (start + length <= this.remoteContiguousLength) return
|
|
929
|
+
|
|
861
930
|
let i = Math.floor(start / DEFAULT_SEGMENT_SIZE)
|
|
862
931
|
const n = Math.ceil((start + length) / DEFAULT_SEGMENT_SIZE)
|
|
863
932
|
|
|
@@ -911,6 +980,7 @@ module.exports = class Replicator {
|
|
|
911
980
|
this.peers = []
|
|
912
981
|
this.findingPeers = 0 // updateable from the outside
|
|
913
982
|
|
|
983
|
+
this._attached = new Set()
|
|
914
984
|
this._inflight = new InflightTracker()
|
|
915
985
|
this._blocks = new BlockTracker()
|
|
916
986
|
this._hashes = new BlockTracker()
|
|
@@ -926,6 +996,13 @@ module.exports = class Replicator {
|
|
|
926
996
|
this._ifAvailable = 0
|
|
927
997
|
this._updatesPending = 0
|
|
928
998
|
this._applyingReorg = null
|
|
999
|
+
|
|
1000
|
+
const self = this
|
|
1001
|
+
this._onstreamclose = onstreamclose
|
|
1002
|
+
|
|
1003
|
+
function onstreamclose () {
|
|
1004
|
+
self.detachFrom(this.userData)
|
|
1005
|
+
}
|
|
929
1006
|
}
|
|
930
1007
|
|
|
931
1008
|
cork () {
|
|
@@ -1007,7 +1084,7 @@ module.exports = class Replicator {
|
|
|
1007
1084
|
}
|
|
1008
1085
|
|
|
1009
1086
|
addBlock (session, index) {
|
|
1010
|
-
const b = this._blocks.add(index)
|
|
1087
|
+
const b = this._blocks.add(index, PRIORITY.HIGH)
|
|
1011
1088
|
const ref = b.attach(session)
|
|
1012
1089
|
|
|
1013
1090
|
this._queueBlock(b)
|
|
@@ -1596,21 +1673,36 @@ module.exports = class Replicator {
|
|
|
1596
1673
|
attachTo (protomux, session) {
|
|
1597
1674
|
const makePeer = this._makePeer.bind(this, protomux, session)
|
|
1598
1675
|
|
|
1676
|
+
this._attached.add(protomux)
|
|
1599
1677
|
protomux.pair({ protocol: 'hypercore/alpha', id: this.discoveryKey }, makePeer)
|
|
1678
|
+
protomux.stream.setMaxListeners(0)
|
|
1679
|
+
protomux.stream.on('close', this._onstreamclose)
|
|
1680
|
+
|
|
1600
1681
|
this._ifAvailable++
|
|
1601
1682
|
protomux.stream.opened.then((opened) => {
|
|
1602
1683
|
this._ifAvailable--
|
|
1684
|
+
|
|
1603
1685
|
if (opened) makePeer()
|
|
1604
1686
|
else if (session) session.close().catch(noop)
|
|
1605
1687
|
this._checkUpgradeIfAvailable()
|
|
1606
1688
|
})
|
|
1607
1689
|
}
|
|
1608
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
|
+
|
|
1609
1698
|
destroy () {
|
|
1610
1699
|
for (const peer of this.peers) {
|
|
1611
|
-
peer.protomux
|
|
1700
|
+
this.detachFrom(peer.protomux)
|
|
1612
1701
|
peer.channel.close()
|
|
1613
1702
|
}
|
|
1703
|
+
for (const protomux of this._attached) {
|
|
1704
|
+
this.detachFrom(protomux)
|
|
1705
|
+
}
|
|
1614
1706
|
}
|
|
1615
1707
|
|
|
1616
1708
|
_makePeer (protomux, session) {
|
|
@@ -1625,7 +1717,7 @@ module.exports = class Replicator {
|
|
|
1625
1717
|
messages: [
|
|
1626
1718
|
{ encoding: m.wire.sync, onmessage: onwiresync },
|
|
1627
1719
|
{ encoding: m.wire.request, onmessage: onwirerequest },
|
|
1628
|
-
|
|
1720
|
+
{ encoding: m.wire.cancel, onmessage: onwirecancel },
|
|
1629
1721
|
{ encoding: m.wire.data, onmessage: onwiredata },
|
|
1630
1722
|
{ encoding: m.wire.noData, onmessage: onwirenodata },
|
|
1631
1723
|
{ encoding: m.wire.want, onmessage: onwirewant },
|
|
@@ -1635,7 +1727,8 @@ module.exports = class Replicator {
|
|
|
1635
1727
|
{ encoding: m.wire.extension, onmessage: onwireextension }
|
|
1636
1728
|
],
|
|
1637
1729
|
onopen: onwireopen,
|
|
1638
|
-
onclose: onwireclose
|
|
1730
|
+
onclose: onwireclose,
|
|
1731
|
+
ondrain: onwiredrain
|
|
1639
1732
|
})
|
|
1640
1733
|
|
|
1641
1734
|
if (channel === null) return onnochannel()
|
|
@@ -1717,6 +1810,10 @@ function onwireclose (isRemote, c) {
|
|
|
1717
1810
|
return c.userData.onclose(isRemote)
|
|
1718
1811
|
}
|
|
1719
1812
|
|
|
1813
|
+
function onwiredrain (c) {
|
|
1814
|
+
return c.userData.ondrain()
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1720
1817
|
function onwiresync (m, c) {
|
|
1721
1818
|
return c.userData.onsync(m)
|
|
1722
1819
|
}
|
|
@@ -1725,6 +1822,10 @@ function onwirerequest (m, c) {
|
|
|
1725
1822
|
return c.userData.onrequest(m)
|
|
1726
1823
|
}
|
|
1727
1824
|
|
|
1825
|
+
function onwirecancel (m, c) {
|
|
1826
|
+
return c.userData.oncancel(m)
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1728
1829
|
function onwiredata (m, c) {
|
|
1729
1830
|
return c.userData.ondata(m)
|
|
1730
1831
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hypercore",
|
|
3
|
-
"version": "10.
|
|
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.
|
|
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",
|