hypercore 11.24.0 → 11.26.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
@@ -9,6 +9,10 @@ const Protomux = require('protomux')
9
9
  const id = require('hypercore-id-encoding')
10
10
  const safetyCatch = require('safety-catch')
11
11
  const unslab = require('unslab')
12
+ const flat = require('flat-tree')
13
+
14
+ const { SMALL_WANTS } = require('./lib/feature-flags')
15
+ const { UPDATE_COMPAT } = require('./lib/wants')
12
16
 
13
17
  const inspect = require('./lib/inspect')
14
18
  const Core = require('./lib/core')
@@ -20,6 +24,7 @@ const Replicator = require('./lib/replicator')
20
24
  const { manifestHash, createManifest } = require('./lib/verifier')
21
25
  const { ReadStream, WriteStream, ByteStream } = require('./lib/streams')
22
26
  const { MerkleTree } = require('./lib/merkle-tree')
27
+ const { proof, verify } = require('./lib/fully-remote-proof')
23
28
  const {
24
29
  ASSERTION,
25
30
  BAD_ARGUMENT,
@@ -85,6 +90,8 @@ class Hypercore extends EventEmitter {
85
90
  this._findingPeers = 0
86
91
  this._active = opts.weak ? !!opts.active : opts.active !== false
87
92
 
93
+ this.waits = 0
94
+
88
95
  this._sessionIndex = -1
89
96
  this._stateIndex = -1 // maintained by session state
90
97
  this._monitorIndex = -1 // maintained by replication state
@@ -103,6 +110,13 @@ class Hypercore extends EventEmitter {
103
110
 
104
111
  static DefaultEncryption = DefaultEncryption
105
112
 
113
+ static SMALL_WANTS = SMALL_WANTS
114
+
115
+ static enable(flag) {
116
+ const enableCompat = (flag & SMALL_WANTS) === 0
117
+ UPDATE_COMPAT(enableCompat)
118
+ }
119
+
106
120
  static key(manifest, { compat, version, namespace } = {}) {
107
121
  if (b4a.isBuffer(manifest)) {
108
122
  manifest = { version, signers: [{ publicKey: manifest, namespace }] }
@@ -284,6 +298,15 @@ class Hypercore extends EventEmitter {
284
298
  throw err
285
299
  }
286
300
 
301
+ // Setup automatic recovery if in repair mode
302
+ if (this.core._repairMode) {
303
+ const recoverTreeNodeFromPeersBound = this.recoverTreeNodeFromPeers.bind(this)
304
+ this.once('repaired', () => {
305
+ this.off('peer-add', recoverTreeNodeFromPeersBound)
306
+ })
307
+ this.on('peer-add', recoverTreeNodeFromPeersBound)
308
+ }
309
+
287
310
  this.emit('ready')
288
311
 
289
312
  // if we are a weak session the core might have closed...
@@ -858,6 +881,7 @@ class Hypercore extends EventEmitter {
858
881
 
859
882
  if (!this._shouldWait(opts, this.wait)) return null
860
883
 
884
+ this.waits++
861
885
  if (opts && opts.onwait) opts.onwait(index, this)
862
886
  if (this.onwait) this.onwait(index, this)
863
887
 
@@ -1021,6 +1045,52 @@ class Hypercore extends EventEmitter {
1021
1045
  return batch
1022
1046
  }
1023
1047
 
1048
+ generateRemoteProofForTreeNode(treeNodeIndex) {
1049
+ const blockProofIndex = flat.rightSpan(treeNodeIndex) / 2
1050
+ return proof(this, {
1051
+ index: blockProofIndex,
1052
+ // + 1 to length so the block is included
1053
+ upgrade: { start: 0, length: blockProofIndex + 1 }
1054
+ })
1055
+ }
1056
+
1057
+ async recoverFromRemoteProof(remoteProof) {
1058
+ this.core.replicator.setPushOnly(true)
1059
+ this.core._repairMode = true
1060
+ await this.core.state.mutex.lock()
1061
+ const p = await verify(this.core.db, remoteProof)
1062
+ if (!p) return false
1063
+
1064
+ const tx = this.core.storage.write()
1065
+ for (const node of p.proof.upgrade.nodes) {
1066
+ tx.putTreeNode(node)
1067
+ }
1068
+ await tx.flush()
1069
+
1070
+ this.core.state.mutex.unlock()
1071
+ const succeed = p.proof.upgrade.nodes.length !== 0
1072
+ if (succeed) {
1073
+ this.core.replicator.setPushOnly(false)
1074
+ }
1075
+ return succeed
1076
+ }
1077
+
1078
+ recoverTreeNodeFromPeers() {
1079
+ this.core.replicator.setPushOnly(true)
1080
+
1081
+ for (const peer of this.core.replicator.peers) {
1082
+ const req = {
1083
+ id: 0,
1084
+ fork: this.fork,
1085
+ upgrade: {
1086
+ start: 0,
1087
+ length: this.length
1088
+ }
1089
+ }
1090
+ peer.wireRequest.send(req)
1091
+ }
1092
+ }
1093
+
1024
1094
  registerExtension(name, handlers = {}) {
1025
1095
  if (this.extensions.has(name)) {
1026
1096
  const ext = this.extensions.get(name)
package/lib/core.js CHANGED
@@ -61,6 +61,7 @@ module.exports = class Core {
61
61
  this._verifies = null
62
62
  this._verifiesFlushed = null
63
63
  this._legacy = !!opts.legacy
64
+ this._repairMode = false
64
65
 
65
66
  this.opening = this._open(opts)
66
67
  this.opening.catch(noop)
@@ -269,12 +270,16 @@ module.exports = class Core {
269
270
  fork: header.tree.fork,
270
271
  length: header.tree.length,
271
272
  signature: header.tree.signature,
272
- roots: header.tree.length
273
- ? await MerkleTree.getRootsFromStorage(storage, header.tree.length)
274
- : [],
273
+ roots: await maybeGetRootsFromStorage(storage, header.tree.length),
275
274
  prologue
276
275
  }
277
276
 
277
+ this._repairMode = !treeInfo.roots.length && treeInfo.length && !overwrite
278
+ // Set push only mode if in repair mode
279
+ if (this._repairMode) {
280
+ this.replicator.setPushOnly(true)
281
+ }
282
+
278
283
  if (overwrite) {
279
284
  const tx = storage.write()
280
285
  tx.deleteTreeNodeRange(0, -1)
@@ -573,7 +578,7 @@ module.exports = class Core {
573
578
  return true
574
579
  }
575
580
 
576
- const roots = await MerkleTree.getRootsFromStorage(this.storage, proof.upgrade.length)
581
+ const roots = await maybeGetRootsFromStorage(this.storage, proof.upgrade.length)
577
582
  const remoteTreeHash = crypto.tree(proof.upgrade.nodes)
578
583
  const localTreeHash = crypto.tree(roots)
579
584
 
@@ -595,8 +600,10 @@ module.exports = class Core {
595
600
 
596
601
  const verifyBatch = MerkleTree.verifyFullyRemote(this.state, await treeProof.settle())
597
602
  this._verifyBatchUpgrade(verifyBatch, this.header.manifest)
598
- } catch {
599
- return true
603
+ } catch (err) {
604
+ if (err.code !== 'INVALID_OPERATION') return true
605
+
606
+ return !(await this._repairTreeNodes(proof))
600
607
  }
601
608
 
602
609
  // both proofs are valid, now check if they forked
@@ -637,6 +644,68 @@ module.exports = class Core {
637
644
  return true
638
645
  }
639
646
 
647
+ async _repairTreeNodes(proof) {
648
+ if (!this._repairMode) return false
649
+
650
+ await this.state.mutex.lock()
651
+
652
+ for (let i = this.monitors.length - 1; i >= 0; i--) {
653
+ this.monitors[i].emit('repairing', proof)
654
+ }
655
+
656
+ try {
657
+ const batch = MerkleTree.verifyFullyRemote(this.state, proof)
658
+ this._verifyBatchUpgrade(batch, proof.manifest)
659
+ } catch {
660
+ this.state.mutex.unlock()
661
+ for (let i = this.monitors.length - 1; i >= 0; i--) {
662
+ this.monitors[i].emit('repair-failed')
663
+ }
664
+ return false
665
+ }
666
+
667
+ // Attempt to repair local merkle tree with remote roots
668
+ const tx = this.storage.write()
669
+ for (const node of proof.upgrade.nodes) {
670
+ tx.putTreeNode(node)
671
+ }
672
+ await tx.flush()
673
+
674
+ // Try local proof again
675
+ try {
676
+ const rx = this.state.storage.read()
677
+ const treeProofPromise = MerkleTree.proof(this.state, rx, {
678
+ block: null,
679
+ hash: null,
680
+ seek: null,
681
+ upgrade: {
682
+ start: 0,
683
+ length: proof.upgrade.length
684
+ }
685
+ })
686
+
687
+ rx.tryFlush()
688
+
689
+ const treeProof = await treeProofPromise
690
+
691
+ const verifyBatch = MerkleTree.verifyFullyRemote(this.state, await treeProof.settle())
692
+ this._verifyBatchUpgrade(verifyBatch, this.header.manifest)
693
+ } catch (err) {
694
+ this.state.mutex.unlock()
695
+ for (let i = this.monitors.length - 1; i >= 0; i--) {
696
+ this.monitors[i].emit('repair-failed')
697
+ }
698
+ return false
699
+ }
700
+
701
+ for (let i = this.monitors.length - 1; i >= 0; i--) {
702
+ this.monitors[i].emit('repaired', proof)
703
+ }
704
+ this.replicator.setPushOnly(false)
705
+ this.state.mutex.unlock()
706
+ return true
707
+ }
708
+
640
709
  async verifyReorg(proof) {
641
710
  const batch = new ReorgBatch(this.state)
642
711
  await MerkleTree.reorg(this.state, proof, batch)
@@ -899,6 +968,16 @@ function parseHeader(info) {
899
968
  }
900
969
  }
901
970
 
971
+ async function maybeGetRootsFromStorage(storage, length) {
972
+ try {
973
+ return length ? await MerkleTree.getRootsFromStorage(storage, length) : []
974
+ } catch (err) {
975
+ if (err.code !== 'INVALID_OPERATION') throw err
976
+ // If INVALID_OPERATION, a root is likely missing so load empty roots to try to recover
977
+ return []
978
+ }
979
+ }
980
+
902
981
  function noop() {}
903
982
 
904
983
  async function getCoreInfo(storage) {
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ SMALL_WANTS: 1
3
+ }
@@ -65,7 +65,9 @@ async function verify(storage, buffer, { referrer = null } = {}) {
65
65
 
66
66
  rx.tryFlush()
67
67
 
68
- const roots = await Promise.all(rootPromises)
68
+ let roots = await Promise.all(rootPromises)
69
+ if (roots.some(isNull)) roots = []
70
+
69
71
  const length = head ? head.length : 0
70
72
 
71
73
  if (!auth.manifest || !auth.manifest.signers.length) return null
@@ -100,10 +102,10 @@ async function verify(storage, buffer, { referrer = null } = {}) {
100
102
  return result
101
103
  }
102
104
 
103
- async function proof(sender, { index, block = null } = {}) {
105
+ async function proof(sender, { index, block = null, upgrade = null } = {}) {
104
106
  const proof = await sender.proof({
105
107
  block: block ? { index, nodes: 0 } : null,
106
- upgrade: { start: 0, length: sender.length }
108
+ upgrade: upgrade ? upgrade : { start: 0, length: sender.length }
107
109
  })
108
110
 
109
111
  if (block) proof.block.value = block
@@ -122,3 +124,7 @@ async function proof(sender, { index, block = null } = {}) {
122
124
 
123
125
  return state.buffer
124
126
  }
127
+
128
+ function isNull(x) {
129
+ return x === null
130
+ }
@@ -231,6 +231,11 @@ class MerkleTreeBatch {
231
231
 
232
232
  commit(tx) {
233
233
  if (tx === undefined) throw INVALID_OPERATION('No database batch was passed')
234
+
235
+ if (this.session.core._repairMode) {
236
+ throw ASSERTION('Cannot commit while repair mode is on')
237
+ }
238
+
234
239
  if (!this.commitable()) {
235
240
  throw INVALID_OPERATION('Tree was modified during batch, refusing to commit')
236
241
  }
@@ -768,6 +773,18 @@ class MerkleTree {
768
773
  rx.tryFlush()
769
774
  return Promise.all([offset, size])
770
775
  }
776
+
777
+ static upgradeLength(proof) {
778
+ const extra = proof.additionalNodes
779
+
780
+ if (extra.length) {
781
+ const last = extra[extra.length - 1]
782
+ const index = flat.rightSpan(last.index)
783
+ return index / 2 + 1
784
+ }
785
+
786
+ return proof.start + proof.length
787
+ }
771
788
  }
772
789
 
773
790
  module.exports = {
package/lib/replicator.js CHANGED
@@ -31,7 +31,8 @@ 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
+ const w = require('./wants')
35
+ const { LocalWants, RemoteWants } = w
35
36
  const {
36
37
  REQUEST_CANCELLED,
37
38
  REQUEST_TIMEOUT,
@@ -675,6 +676,9 @@ class Peer {
675
676
  }
676
677
 
677
678
  sendSync() {
679
+ // Skip syncs if in repair mode
680
+ if (this.core._repairMode) return
681
+
678
682
  if (this.syncsProcessing !== 0) {
679
683
  this.needsSync = true
680
684
  return
@@ -997,55 +1001,92 @@ class Peer {
997
1001
  priority: 0
998
1002
  }
999
1003
 
1000
- const remoteLength = Math.max(this.remoteLength, this.pushedLength)
1004
+ await this.replicator._txLock.lock()
1005
+ try {
1006
+ const remoteLength = Math.max(this.remoteLength, this.pushedLength)
1001
1007
 
1002
- msg.block = {
1003
- index,
1004
- nodes: MerkleTree.maxMissingNodes(2 * index, remoteLength)
1005
- }
1008
+ msg.block = {
1009
+ index,
1010
+ nodes: MerkleTree.maxMissingNodes(2 * index, remoteLength)
1011
+ }
1006
1012
 
1007
- if (index >= remoteLength) {
1008
- msg.upgrade = {
1009
- start: remoteLength,
1010
- length: this.core.state.length - remoteLength
1013
+ if (index >= remoteLength) {
1014
+ msg.upgrade = {
1015
+ start: remoteLength,
1016
+ length: this.core.state.length - remoteLength
1017
+ }
1011
1018
  }
1012
- }
1013
1019
 
1014
- let req = null
1015
- const batch = this.core.storage.read()
1016
- try {
1017
- req = await this._getProof(batch, msg, true)
1018
- } catch (err) {
1019
- this.replicator._oninvalidrequest(err, msg, this)
1020
- return
1021
- }
1020
+ let req = null
1021
+ const batch = this.core.storage.read()
1022
+ try {
1023
+ req = await this._getProof(batch, msg, true)
1024
+ } catch (err) {
1025
+ this.replicator._oninvalidrequest(err, msg, this)
1026
+ return
1027
+ }
1022
1028
 
1023
- if (req === null) return
1024
- batch.tryFlush()
1029
+ if (req === null) return
1030
+ batch.tryFlush()
1025
1031
 
1026
- await this._fulfillRequest(req, true)
1032
+ await this._fulfillRequest(req, true)
1033
+ } finally {
1034
+ this.replicator._txLock.unlock()
1035
+ }
1027
1036
  }
1028
1037
 
1029
1038
  async _handleRequest(msg) {
1030
- const batch = this.core.storage.read()
1039
+ const locked = !!msg.upgrade && this.remoteAllowPush
1031
1040
 
1032
- // TODO: could still be answerable if (index, fork) is an ancestor of the current fork
1033
- const req =
1034
- msg.fork === this.core.state.fork
1035
- ? await this._getProof(batch, msg, false)
1036
- : new ProofRequest(msg, null, null, null)
1041
+ if (locked) {
1042
+ // ensure contig push proofs
1043
+ await this.replicator._txLock.lock()
1044
+ const end = msg.upgrade.start + msg.upgrade.length
1037
1045
 
1038
- if (req === null) {
1039
- this.wireNoData.send({ request: msg.id, reason: INVALID_REQUEST })
1040
- return
1046
+ if (this.pushedLength < end) {
1047
+ msg.upgrade.start = this.pushedLength
1048
+ msg.upgrade.length = end - this.pushedLength
1049
+ }
1041
1050
  }
1042
1051
 
1043
- batch.tryFlush()
1052
+ try {
1053
+ const batch = this.core.storage.read()
1044
1054
 
1045
- await this._fulfillRequest(req, false)
1055
+ // TODO: could still be answerable if (index, fork) is an ancestor of the current fork
1056
+ const req =
1057
+ msg.fork === this.core.state.fork
1058
+ ? await this._getProof(batch, msg, false)
1059
+ : new ProofRequest(msg, null, null, null)
1060
+
1061
+ if (req === null) {
1062
+ this.wireNoData.send({ request: msg.id, reason: INVALID_REQUEST })
1063
+ return
1064
+ }
1065
+
1066
+ batch.tryFlush()
1067
+
1068
+ await this._fulfillRequest(req, false)
1069
+ } finally {
1070
+ if (locked) this.replicator._txLock.unlock()
1071
+ }
1046
1072
  }
1047
1073
 
1048
1074
  async _fulfillRequest(req, pushing) {
1075
+ if (this.core._repairMode) {
1076
+ // if cancelled do not reply
1077
+ if (this.remoteRequests.get(req.msg.id) !== req.msg) {
1078
+ return
1079
+ }
1080
+
1081
+ // sync from now on, so safe to delete from the map
1082
+ this.remoteRequests.delete(req.msg.id)
1083
+
1084
+ // If cutting off the fulfillment, ensure promise is caught.
1085
+ // While repairing merkle tree nodes can be `nil` so can throw.
1086
+ req.fulfill().catch(safetyCatch)
1087
+ return
1088
+ }
1089
+
1049
1090
  const proof = await req.fulfill()
1050
1091
 
1051
1092
  if (!pushing) {
@@ -1090,7 +1131,7 @@ class Peer {
1090
1131
  }
1091
1132
 
1092
1133
  if (proof.upgrade) {
1093
- const remoteLength = proof.upgrade.start + proof.upgrade.length
1134
+ const remoteLength = MerkleTree.upgradeLength(proof.upgrade)
1094
1135
  if (remoteLength > this.pushedLength) this.pushedLength = remoteLength
1095
1136
  }
1096
1137
 
@@ -1151,13 +1192,13 @@ class Peer {
1151
1192
  }
1152
1193
 
1153
1194
  async ondata(data) {
1154
- if (data.request !== 0) return this._handleData(data)
1195
+ if (data.request !== 0 && !data.upgrade) return this._handleData(data)
1155
1196
 
1156
- await this.replicator._pushLock.lock()
1197
+ await this.replicator._rxLock.lock()
1157
1198
  try {
1158
1199
  await this._handleData(data)
1159
1200
  } finally {
1160
- this.replicator._pushLock.unlock()
1201
+ this.replicator._rxLock.unlock()
1161
1202
  }
1162
1203
  }
1163
1204
 
@@ -1177,6 +1218,12 @@ class Peer {
1177
1218
  return
1178
1219
  }
1179
1220
 
1221
+ // Skip accepting data if in repair mode to avoid corruption
1222
+ if (this.core._repairMode) {
1223
+ this.replicator._onnodata(this, req, NOT_AVAILABLE)
1224
+ return
1225
+ }
1226
+
1180
1227
  if (req === null && reorg === false && data.block) {
1181
1228
  // mark this as inflight to avoid parallel requests
1182
1229
  this.replicator._markInflight(data.block.index)
@@ -1582,6 +1629,8 @@ class Peer {
1582
1629
  }
1583
1630
 
1584
1631
  _sendBlockRequest(req, b) {
1632
+ if (this.core._repairMode) return
1633
+
1585
1634
  req.block = { index: b.index, nodes: 0 }
1586
1635
  this.replicator._markInflight(b.index)
1587
1636
 
@@ -1682,6 +1731,7 @@ class Peer {
1682
1731
  }
1683
1732
 
1684
1733
  _addRangeBatch(r, offset, end) {
1734
+ const { WANT_BATCH } = w
1685
1735
  while (offset < end) {
1686
1736
  const next = this.core.bitfield.findFirst(false, offset)
1687
1737
  if (next === -1 || next >= end) return false
@@ -1697,6 +1747,7 @@ class Peer {
1697
1747
  }
1698
1748
 
1699
1749
  _populateRangeBatches(r) {
1750
+ const { WANT_BATCH } = w
1700
1751
  for (let i = r.batches.length - 1; i >= 0; i--) {
1701
1752
  const b = r.batches[i]
1702
1753
  const minStart = b.batch * WANT_BATCH
@@ -1752,6 +1803,7 @@ class Peer {
1752
1803
  }
1753
1804
 
1754
1805
  _requestRange(r) {
1806
+ const { WANT_BATCH } = w
1755
1807
  if (this.syncsProcessing > 0) return false
1756
1808
 
1757
1809
  const { length, fork } = this.core.state
@@ -1952,7 +2004,9 @@ module.exports = class Replicator {
1952
2004
  this._reorgs = []
1953
2005
  this._ranges = []
1954
2006
 
1955
- this._pushLock = new Mutex()
2007
+ this._rxLock = new Mutex()
2008
+ this._txLock = new Mutex()
2009
+
1956
2010
  this._hadPeers = false
1957
2011
  this._active = 0
1958
2012
  this._ifAvailable = 0
@@ -4,7 +4,7 @@ const assert = require('nanoassert')
4
4
  const flat = require('flat-tree')
5
5
  const quickbit = require('quickbit-universal')
6
6
 
7
- const { INVALID_OPERATION, INVALID_SIGNATURE } = require('hypercore-errors')
7
+ const { INVALID_OPERATION, INVALID_SIGNATURE, ASSERTION } = require('hypercore-errors')
8
8
 
9
9
  const Mutex = require('./mutex')
10
10
  const Bitfield = require('./bitfield')
@@ -30,7 +30,7 @@ module.exports = class SessionState {
30
30
  // merkle state
31
31
  this.roots = treeInfo.roots.length ? treeInfo.roots : []
32
32
  this.fork = treeInfo.fork || 0
33
- this.length = MerkleTree.span(this.roots) / 2
33
+ this.length = this.roots.length ? MerkleTree.span(this.roots) / 2 : treeInfo.length
34
34
  this.byteLength = MerkleTree.size(this.roots)
35
35
  this.prologue = treeInfo.prologue || null
36
36
  this.signature = treeInfo.signature || null
@@ -917,6 +917,10 @@ module.exports = class SessionState {
917
917
  'Can only commit into default state'
918
918
  )
919
919
 
920
+ if (this.core._repairMode) {
921
+ throw ASSERTION('Cannot commit while repair mode is on')
922
+ }
923
+
920
924
  let srcLocked = false
921
925
  await this.mutex.lock()
922
926
 
package/lib/wants.js CHANGED
@@ -1,7 +1,7 @@
1
- const COMPAT = true // should be flipped once this is widely deployed
2
- const BATCH_UINTS = COMPAT ? 65536 : 128
3
- const BATCH_BYTES = BATCH_UINTS * 4
4
- const BATCH = BATCH_BYTES * 8 // in bits
1
+ let COMPAT = true
2
+ let BATCH_UINTS = COMPAT ? 65536 : 128
3
+ let BATCH_BYTES = BATCH_UINTS * 4
4
+ let BATCH = BATCH_BYTES * 8 // in bits
5
5
  const MAX_REMOTE_BATCHES = 4
6
6
  const MAX_RANGE = 8 * 256 * 1024
7
7
  const MIN_RANGE = 32 // 4bit ints
@@ -295,9 +295,18 @@ class RemoteWants {
295
295
  }
296
296
  }
297
297
 
298
+ function UPDATE_COMPAT(isCompat) {
299
+ COMPAT = isCompat
300
+ BATCH_UINTS = COMPAT ? 65536 : 128
301
+ BATCH_BYTES = BATCH_UINTS * 4
302
+ BATCH = BATCH_BYTES * 8 // in bits
303
+ exports.WANT_BATCH = BATCH
304
+ }
305
+
298
306
  exports.LocalWants = LocalWants
299
307
  exports.RemoteWants = RemoteWants
300
308
  exports.WANT_BATCH = BATCH
309
+ exports.UPDATE_COMPAT = UPDATE_COMPAT
301
310
 
302
311
  function validateBatchRange(range) {
303
312
  if (range.length > MAX_RANGE || range.length < MIN_RANGE) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "11.24.0",
3
+ "version": "11.26.0",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {