hypercore 11.34.1 → 11.35.1
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 +29 -4
- package/lib/core.js +7 -6
- package/lib/replicator.js +108 -36
- package/lib/verifier.js +2 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -23,7 +23,8 @@ const Download = require('./lib/download')
|
|
|
23
23
|
const DefaultEncryption = require('./lib/default-encryption')
|
|
24
24
|
const caps = require('./lib/caps')
|
|
25
25
|
const Replicator = require('./lib/replicator')
|
|
26
|
-
const
|
|
26
|
+
const Verifier = require('./lib/verifier')
|
|
27
|
+
const { manifestHash, createManifest, encodeManifest } = Verifier
|
|
27
28
|
const { ReadStream, WriteStream, ByteStream } = require('./lib/streams')
|
|
28
29
|
const { MerkleTree } = require('./lib/merkle-tree')
|
|
29
30
|
const { proof, verify } = require('./lib/fully-remote-proof')
|
|
@@ -35,7 +36,8 @@ const {
|
|
|
35
36
|
SESSION_NOT_WRITABLE,
|
|
36
37
|
SNAPSHOT_NOT_AVAILABLE,
|
|
37
38
|
DECODING_ERROR,
|
|
38
|
-
REQUEST_CANCELLED
|
|
39
|
+
REQUEST_CANCELLED,
|
|
40
|
+
INVALID_CHECKSUM
|
|
39
41
|
} = require('hypercore-errors')
|
|
40
42
|
|
|
41
43
|
// Hypercore actually does not have any notion of max/min block sizes
|
|
@@ -130,11 +132,29 @@ class Hypercore extends EventEmitter {
|
|
|
130
132
|
|
|
131
133
|
static key(manifest, { compat, version, namespace } = {}) {
|
|
132
134
|
if (b4a.isBuffer(manifest)) {
|
|
133
|
-
manifest =
|
|
135
|
+
manifest =
|
|
136
|
+
manifest.byteLength === 32
|
|
137
|
+
? { version, signers: [{ publicKey: manifest, namespace }] }
|
|
138
|
+
: this.parseManifest(manifest)
|
|
134
139
|
}
|
|
135
140
|
return compat ? manifest.signers[0].publicKey : manifestHash(createManifest(manifest))
|
|
136
141
|
}
|
|
137
142
|
|
|
143
|
+
static parseManifest(manifest, key) {
|
|
144
|
+
const parsed = createManifest(manifest)
|
|
145
|
+
if (parsed === null) return null
|
|
146
|
+
|
|
147
|
+
if (parsed.quorum > parsed.signers.length) {
|
|
148
|
+
throw BAD_ARGUMENT('Quorum should not be higher than the number of signers')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (key && !Verifier.isValidManifest(key, parsed)) {
|
|
152
|
+
throw INVALID_CHECKSUM('Manifest does not hash to provided key')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return parsed
|
|
156
|
+
}
|
|
157
|
+
|
|
138
158
|
static discoveryKey(key) {
|
|
139
159
|
return crypto.discoveryKey(key)
|
|
140
160
|
}
|
|
@@ -453,7 +473,7 @@ class Hypercore extends EventEmitter {
|
|
|
453
473
|
}
|
|
454
474
|
|
|
455
475
|
if (opts.manifest && !this.core.header.manifest) {
|
|
456
|
-
await this.core.setManifest(
|
|
476
|
+
await this.core.setManifest(opts.manifest)
|
|
457
477
|
}
|
|
458
478
|
|
|
459
479
|
this.core.replicator.updateActivity(this._active ? 1 : 0)
|
|
@@ -606,6 +626,11 @@ class Hypercore extends EventEmitter {
|
|
|
606
626
|
return this.core === null ? null : this.core.manifest
|
|
607
627
|
}
|
|
608
628
|
|
|
629
|
+
getManifest({ raw = false } = {}) {
|
|
630
|
+
if (this.manifest === null) return null
|
|
631
|
+
return raw ? encodeManifest(this.manifest) : this.manifest
|
|
632
|
+
}
|
|
633
|
+
|
|
609
634
|
get length() {
|
|
610
635
|
if (this._snapshot) return this._snapshot.length
|
|
611
636
|
return this.opened === false ? 0 : this.state.length
|
package/lib/core.js
CHANGED
|
@@ -259,15 +259,14 @@ module.exports = class Core {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
if (opts.manifest) {
|
|
262
|
+
const manifest = Verifier.createManifest(opts.manifest)
|
|
263
|
+
|
|
262
264
|
// if we provide a manifest and no key, verify that the stored key is the same
|
|
263
|
-
if (
|
|
264
|
-
!opts.key &&
|
|
265
|
-
!Verifier.isValidManifest(header.key, Verifier.createManifest(opts.manifest))
|
|
266
|
-
) {
|
|
265
|
+
if (!opts.key && !Verifier.isValidManifest(header.key, manifest)) {
|
|
267
266
|
throw STORAGE_CONFLICT('Manifest does not hash to provided key', this.discoveryKey)
|
|
268
267
|
}
|
|
269
268
|
|
|
270
|
-
if (!header.manifest) header.manifest =
|
|
269
|
+
if (!header.manifest) header.manifest = manifest
|
|
271
270
|
}
|
|
272
271
|
|
|
273
272
|
if (opts.key && !b4a.equals(header.key, opts.key)) {
|
|
@@ -393,12 +392,14 @@ module.exports = class Core {
|
|
|
393
392
|
|
|
394
393
|
try {
|
|
395
394
|
if (manifest && this.header.manifest === null) {
|
|
395
|
+
manifest = Verifier.createManifest(manifest)
|
|
396
|
+
|
|
396
397
|
if (!Verifier.isValidManifest(this.header.key, manifest)) {
|
|
397
398
|
throw INVALID_CHECKSUM('Manifest hash does not match', this.discoveryKey)
|
|
398
399
|
}
|
|
399
400
|
|
|
400
401
|
const tx = this.state.createWriteBatch()
|
|
401
|
-
this._setManifest(tx,
|
|
402
|
+
this._setManifest(tx, manifest, null)
|
|
402
403
|
await this.state.flush()
|
|
403
404
|
}
|
|
404
405
|
} finally {
|
package/lib/replicator.js
CHANGED
|
@@ -285,6 +285,10 @@ class RangeRequest extends Attachable {
|
|
|
285
285
|
this.ranges[rangeIndex] = h
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
if (!this.resolved && this.replicator._updatesRunning) {
|
|
289
|
+
this.replicator._updatesQueued = true
|
|
290
|
+
}
|
|
291
|
+
|
|
288
292
|
if (this.end === -1) {
|
|
289
293
|
this.replicator._alwaysLatestBlock--
|
|
290
294
|
}
|
|
@@ -1541,8 +1545,8 @@ class Peer {
|
|
|
1541
1545
|
}
|
|
1542
1546
|
|
|
1543
1547
|
_requestSeek(s) {
|
|
1544
|
-
// if replicator is updating the seeks
|
|
1545
|
-
if (this.replicator.
|
|
1548
|
+
// if replicator is updating the seeks, bail and wait for it to drain
|
|
1549
|
+
if (this.replicator._updatingSeeks) return false
|
|
1546
1550
|
if (this.replicator.pushOnly) return false
|
|
1547
1551
|
|
|
1548
1552
|
const { length, fork } = this.core.state
|
|
@@ -2028,7 +2032,9 @@ module.exports = class Replicator {
|
|
|
2028
2032
|
this._hadPeers = false
|
|
2029
2033
|
this._active = 0
|
|
2030
2034
|
this._ifAvailable = 0
|
|
2031
|
-
this.
|
|
2035
|
+
this._updatingSeeks = false
|
|
2036
|
+
this._updatesRunning = false
|
|
2037
|
+
this._updatesQueued = false
|
|
2032
2038
|
this._applyingReorg = null
|
|
2033
2039
|
this._manifestPeer = null
|
|
2034
2040
|
this._notDownloadingLinger = notDownloadingLinger
|
|
@@ -2614,55 +2620,117 @@ module.exports = class Replicator {
|
|
|
2614
2620
|
}
|
|
2615
2621
|
}
|
|
2616
2622
|
|
|
2623
|
+
_updateRanges(index, limit) {
|
|
2624
|
+
index = Math.min(index, this._ranges.length - 1)
|
|
2625
|
+
|
|
2626
|
+
let checked = 0
|
|
2627
|
+
let resolved = 0
|
|
2628
|
+
let updateAll = false
|
|
2629
|
+
let remaining = Math.min(limit, this._ranges.length)
|
|
2630
|
+
|
|
2631
|
+
while (remaining-- > 0 && this._ranges.length > 0) {
|
|
2632
|
+
if (index >= this._ranges.length) index = 0
|
|
2633
|
+
|
|
2634
|
+
const r = this._ranges[index]
|
|
2635
|
+
|
|
2636
|
+
clampRange(this.core, r)
|
|
2637
|
+
checked++
|
|
2638
|
+
|
|
2639
|
+
if (r.end !== -1 && r.start >= r.end) {
|
|
2640
|
+
this._resolveRangeRequest(r)
|
|
2641
|
+
resolved++
|
|
2642
|
+
// Crossing into the capped window exposes another range to peer scheduling.
|
|
2643
|
+
if (this._ranges.length === MAX_RANGES) updateAll = true
|
|
2644
|
+
} else {
|
|
2645
|
+
index++
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
index = this._ranges.length === 0 ? 0 : index % this._ranges.length
|
|
2650
|
+
|
|
2651
|
+
return { checked, resolved, index, updateAll }
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2617
2654
|
// "slow" updates here - async but not allowed to ever throw
|
|
2618
2655
|
async _updateNonPrimary(updateAll) {
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2656
|
+
this._updatesQueued = true
|
|
2657
|
+
if (this._updatesRunning) {
|
|
2658
|
+
if (this._inflight.idle || updateAll) this.queueUpdateAll()
|
|
2659
|
+
return
|
|
2660
|
+
}
|
|
2622
2661
|
|
|
2623
|
-
|
|
2624
|
-
const r = this._ranges[i]
|
|
2662
|
+
this._updatesRunning = true
|
|
2625
2663
|
|
|
2626
|
-
|
|
2664
|
+
while (this._updatesQueued) {
|
|
2665
|
+
this._updatesQueued = false
|
|
2627
2666
|
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
if (this._ranges.length === MAX_RANGES) updateAll = true
|
|
2633
|
-
}
|
|
2634
|
-
}
|
|
2667
|
+
let checkedSinceResolve = 0
|
|
2668
|
+
let rangeIndex = 0
|
|
2669
|
+
const drain = this._inflight.idle || updateAll
|
|
2670
|
+
const checkedSeeks = new Set()
|
|
2635
2671
|
|
|
2636
|
-
|
|
2637
|
-
const
|
|
2672
|
+
while (true) {
|
|
2673
|
+
const limit = Math.min(MAX_RANGES, this._ranges.length - checkedSinceResolve)
|
|
2674
|
+
const {
|
|
2675
|
+
checked,
|
|
2676
|
+
resolved,
|
|
2677
|
+
index,
|
|
2678
|
+
updateAll: resultUpdateAll
|
|
2679
|
+
} = this._updateRanges(rangeIndex, limit)
|
|
2680
|
+
rangeIndex = index
|
|
2638
2681
|
|
|
2639
|
-
|
|
2640
|
-
let res = null
|
|
2682
|
+
if (resultUpdateAll) updateAll = true
|
|
2641
2683
|
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2684
|
+
if (resolved > 0) checkedSinceResolve = 0
|
|
2685
|
+
else checkedSinceResolve += checked
|
|
2686
|
+
|
|
2687
|
+
const continueRanges = checked > 0 && checkedSinceResolve < this._ranges.length && drain
|
|
2688
|
+
|
|
2689
|
+
this._updatingSeeks = true
|
|
2690
|
+
|
|
2691
|
+
let checkedSeek = false
|
|
2692
|
+
for (const s of this._seeks.slice()) {
|
|
2693
|
+
if (checkedSeeks.has(s) || !this._seeks.includes(s)) continue
|
|
2694
|
+
checkedSeeks.add(s)
|
|
2695
|
+
checkedSeek = true
|
|
2696
|
+
|
|
2697
|
+
let err = null
|
|
2698
|
+
let res = null
|
|
2699
|
+
|
|
2700
|
+
try {
|
|
2701
|
+
res = await s.seeker.update()
|
|
2702
|
+
} catch (error) {
|
|
2703
|
+
err = error
|
|
2704
|
+
}
|
|
2647
2705
|
|
|
2648
|
-
|
|
2706
|
+
const seekIndex = this._seeks.indexOf(s)
|
|
2707
|
+
if (seekIndex === -1 || (!res && !err)) continue
|
|
2649
2708
|
|
|
2650
|
-
|
|
2651
|
-
|
|
2709
|
+
const h = this._seeks.pop()
|
|
2710
|
+
if (h !== s) this._seeks[seekIndex] = h
|
|
2652
2711
|
|
|
2653
|
-
|
|
2712
|
+
if (err) s.reject(err)
|
|
2713
|
+
else s.resolve(res)
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
this._updatingSeeks = false
|
|
2717
|
+
|
|
2718
|
+
if (
|
|
2719
|
+
(!continueRanges || (checkedSeek && this._seeks.length > 0)) &&
|
|
2720
|
+
(this._inflight.idle || updateAll)
|
|
2721
|
+
) {
|
|
2722
|
+
this.queueUpdateAll()
|
|
2723
|
+
}
|
|
2724
|
+
if (!continueRanges) break
|
|
2654
2725
|
|
|
2655
|
-
|
|
2656
|
-
|
|
2726
|
+
await yieldToLoop()
|
|
2727
|
+
if (this.destroyed) break
|
|
2657
2728
|
}
|
|
2658
2729
|
|
|
2659
|
-
|
|
2660
|
-
if (--this._updatesPending === 0) break
|
|
2661
|
-
// Debounce the additional updates - continue
|
|
2662
|
-
this._updatesPending = 0
|
|
2730
|
+
if (this.destroyed) break
|
|
2663
2731
|
}
|
|
2664
2732
|
|
|
2665
|
-
|
|
2733
|
+
this._updatesRunning = false
|
|
2666
2734
|
}
|
|
2667
2735
|
|
|
2668
2736
|
_clearRequest(peer, req) {
|
|
@@ -3398,6 +3466,10 @@ function incrementRx(stats1, stats2) {
|
|
|
3398
3466
|
|
|
3399
3467
|
function noop() {}
|
|
3400
3468
|
|
|
3469
|
+
function yieldToLoop() {
|
|
3470
|
+
return new Promise((resolve) => setImmediate(resolve))
|
|
3471
|
+
}
|
|
3472
|
+
|
|
3401
3473
|
function backoff(times) {
|
|
3402
3474
|
const sleep = times < 2 ? 200 : times < 5 ? 500 : times < 40 ? 1000 : 5000
|
|
3403
3475
|
return new Promise((resolve) => setTimeout(resolve, sleep))
|
package/lib/verifier.js
CHANGED
|
@@ -214,6 +214,8 @@ module.exports = class Verifier {
|
|
|
214
214
|
static createManifest(inp) {
|
|
215
215
|
if (!inp) return null
|
|
216
216
|
|
|
217
|
+
if (b4a.isBuffer(inp)) return Verifier.decodeManifest(inp)
|
|
218
|
+
|
|
217
219
|
if (inp.quorum && inp.quorum < 0) throw BAD_ARGUMENT('Quorum cannot be negative')
|
|
218
220
|
if (inp.quorum && inp.signers && inp.quorum > inp.signers.length) {
|
|
219
221
|
throw BAD_ARGUMENT('Quorum should not be higher than the number of signers')
|