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 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 { manifestHash, createManifest } = require('./lib/verifier')
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 = { version, signers: [{ publicKey: manifest, namespace }] }
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(createManifest(opts.manifest))
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 = opts.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, Verifier.createManifest(manifest), null)
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 etc, bail and wait for it to drain
1545
- if (this.replicator._updatesPending > 0) return false
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._updatesPending = 0
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
- // Check if running, if so skip it and the running one will issue another update for us (debounce)
2620
- while (++this._updatesPending === 1) {
2621
- let len = Math.min(MAX_RANGES, this._ranges.length)
2656
+ this._updatesQueued = true
2657
+ if (this._updatesRunning) {
2658
+ if (this._inflight.idle || updateAll) this.queueUpdateAll()
2659
+ return
2660
+ }
2622
2661
 
2623
- for (let i = 0; i < len; i++) {
2624
- const r = this._ranges[i]
2662
+ this._updatesRunning = true
2625
2663
 
2626
- clampRange(this.core, r)
2664
+ while (this._updatesQueued) {
2665
+ this._updatesQueued = false
2627
2666
 
2628
- if (r.end !== -1 && r.start >= r.end) {
2629
- this._resolveRangeRequest(r)
2630
- i--
2631
- if (len > this._ranges.length) len--
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
- for (let i = 0; i < this._seeks.length; i++) {
2637
- const s = this._seeks[i]
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
- let err = null
2640
- let res = null
2682
+ if (resultUpdateAll) updateAll = true
2641
2683
 
2642
- try {
2643
- res = await s.seeker.update()
2644
- } catch (error) {
2645
- err = error
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
- if (!res && !err) continue
2706
+ const seekIndex = this._seeks.indexOf(s)
2707
+ if (seekIndex === -1 || (!res && !err)) continue
2649
2708
 
2650
- if (i < this._seeks.length - 1) this._seeks[i] = this._seeks.pop()
2651
- else this._seeks.pop()
2709
+ const h = this._seeks.pop()
2710
+ if (h !== s) this._seeks[seekIndex] = h
2652
2711
 
2653
- i--
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
- if (err) s.reject(err)
2656
- else s.resolve(res)
2726
+ await yieldToLoop()
2727
+ if (this.destroyed) break
2657
2728
  }
2658
2729
 
2659
- // No additional updates scheduled - break
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
- if (this._inflight.idle || updateAll) this.queueUpdateAll()
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')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "11.34.1",
3
+ "version": "11.35.1",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {