hypercore 11.23.1 → 11.25.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,7 @@ 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')
12
13
 
13
14
  const inspect = require('./lib/inspect')
14
15
  const Core = require('./lib/core')
@@ -20,6 +21,7 @@ const Replicator = require('./lib/replicator')
20
21
  const { manifestHash, createManifest } = require('./lib/verifier')
21
22
  const { ReadStream, WriteStream, ByteStream } = require('./lib/streams')
22
23
  const { MerkleTree } = require('./lib/merkle-tree')
24
+ const { proof, verify } = require('./lib/fully-remote-proof')
23
25
  const {
24
26
  ASSERTION,
25
27
  BAD_ARGUMENT,
@@ -85,6 +87,8 @@ class Hypercore extends EventEmitter {
85
87
  this._findingPeers = 0
86
88
  this._active = opts.weak ? !!opts.active : opts.active !== false
87
89
 
90
+ this.waits = 0
91
+
88
92
  this._sessionIndex = -1
89
93
  this._stateIndex = -1 // maintained by session state
90
94
  this._monitorIndex = -1 // maintained by replication state
@@ -284,6 +288,15 @@ class Hypercore extends EventEmitter {
284
288
  throw err
285
289
  }
286
290
 
291
+ // Setup automatic recovery if in repair mode
292
+ if (this.core._repairMode) {
293
+ const recoverTreeNodeFromPeersBound = this.recoverTreeNodeFromPeers.bind(this)
294
+ this.once('repaired', () => {
295
+ this.off('peer-add', recoverTreeNodeFromPeersBound)
296
+ })
297
+ this.on('peer-add', recoverTreeNodeFromPeersBound)
298
+ }
299
+
287
300
  this.emit('ready')
288
301
 
289
302
  // if we are a weak session the core might have closed...
@@ -318,6 +331,11 @@ class Hypercore extends EventEmitter {
318
331
  this.encodeBatch = opts.encodeBatch
319
332
  }
320
333
 
334
+ // one session sets for pushOnly for all
335
+ if (opts.pushOnly === true) {
336
+ this.core.replicator.setPushOnly(true)
337
+ }
338
+
321
339
  if (parent) {
322
340
  if (parent._stateIndex === -1) await parent.ready()
323
341
  if (!this.keyPair) this.keyPair = parent.keyPair
@@ -853,12 +871,14 @@ class Hypercore extends EventEmitter {
853
871
 
854
872
  if (!this._shouldWait(opts, this.wait)) return null
855
873
 
874
+ this.waits++
856
875
  if (opts && opts.onwait) opts.onwait(index, this)
857
876
  if (this.onwait) this.onwait(index, this)
858
877
 
859
878
  const activeRequests = (opts && opts.activeRequests) || this.activeRequests
860
879
 
861
- const req = this.core.replicator.addBlock(activeRequests, index)
880
+ const force = opts ? opts.force === true : false
881
+ const req = this.core.replicator.addBlock(activeRequests, index, force)
862
882
  req.snapshot = index < this.length
863
883
 
864
884
  const timeout = opts && opts.timeout !== undefined ? opts.timeout : this.timeout
@@ -980,6 +1000,7 @@ class Hypercore extends EventEmitter {
980
1000
  async treeHash(length = -1) {
981
1001
  if (this.opened === false) await this.opening
982
1002
  if (length === -1) length = this.length
1003
+ if (length > 0 && !(await this.has(length - 1))) await this.get(length - 1)
983
1004
 
984
1005
  const roots = await MerkleTree.getRoots(this.state, length)
985
1006
  return crypto.tree(roots)
@@ -1014,6 +1035,52 @@ class Hypercore extends EventEmitter {
1014
1035
  return batch
1015
1036
  }
1016
1037
 
1038
+ generateRemoteProofForTreeNode(treeNodeIndex) {
1039
+ const blockProofIndex = flat.rightSpan(treeNodeIndex) / 2
1040
+ return proof(this, {
1041
+ index: blockProofIndex,
1042
+ // + 1 to length so the block is included
1043
+ upgrade: { start: 0, length: blockProofIndex + 1 }
1044
+ })
1045
+ }
1046
+
1047
+ async recoverFromRemoteProof(remoteProof) {
1048
+ this.core.replicator.setPushOnly(true)
1049
+ this.core._repairMode = true
1050
+ await this.core.state.mutex.lock()
1051
+ const p = await verify(this.core.db, remoteProof)
1052
+ if (!p) return false
1053
+
1054
+ const tx = this.core.storage.write()
1055
+ for (const node of p.proof.upgrade.nodes) {
1056
+ tx.putTreeNode(node)
1057
+ }
1058
+ await tx.flush()
1059
+
1060
+ this.core.state.mutex.unlock()
1061
+ const succeed = p.proof.upgrade.nodes.length !== 0
1062
+ if (succeed) {
1063
+ this.core.replicator.setPushOnly(false)
1064
+ }
1065
+ return succeed
1066
+ }
1067
+
1068
+ recoverTreeNodeFromPeers() {
1069
+ this.core.replicator.setPushOnly(true)
1070
+
1071
+ for (const peer of this.core.replicator.peers) {
1072
+ const req = {
1073
+ id: 0,
1074
+ fork: this.fork,
1075
+ upgrade: {
1076
+ start: 0,
1077
+ length: this.length
1078
+ }
1079
+ }
1080
+ peer.wireRequest.send(req)
1081
+ }
1082
+ }
1083
+
1017
1084
  registerExtension(name, handlers = {}) {
1018
1085
  if (this.extensions.has(name)) {
1019
1086
  const ext = this.extensions.get(name)
@@ -1150,6 +1217,7 @@ function initOnce(session, storage, key, opts) {
1150
1217
  notDownloadingLinger: opts.notDownloadingLinger,
1151
1218
  allowFork: opts.allowFork !== false,
1152
1219
  allowPush: !!opts.allowPush,
1220
+ pushOnly: !!opts.pushOnly,
1153
1221
  alwaysLatestBlock: !!opts.allowLatestBlock,
1154
1222
  inflightRange: opts.inflightRange,
1155
1223
  compat: opts.compat === true,
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) {
@@ -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
@@ -197,7 +197,7 @@ class Attachable {
197
197
  }
198
198
 
199
199
  class BlockRequest extends Attachable {
200
- constructor(replicator, tracker, index, priority) {
200
+ constructor(replicator, tracker, index, priority, force) {
201
201
  super(replicator)
202
202
 
203
203
  this.index = index
@@ -206,6 +206,7 @@ class BlockRequest extends Attachable {
206
206
  this.queued = false
207
207
  this.hotswap = null
208
208
  this.tracker = tracker
209
+ this.force = force
209
210
  }
210
211
 
211
212
  _unref() {
@@ -409,11 +410,11 @@ class BlockTracker {
409
410
  return this._map.get(index) || null
410
411
  }
411
412
 
412
- add(index, priority) {
413
+ add(index, priority, force) {
413
414
  let b = this._map.get(index)
414
415
  if (b) return b
415
416
 
416
- b = new BlockRequest(this._replicator, this, index, priority)
417
+ b = new BlockRequest(this._replicator, this, index, priority, force)
417
418
  this._map.set(index, b)
418
419
 
419
420
  return b
@@ -674,6 +675,9 @@ class Peer {
674
675
  }
675
676
 
676
677
  sendSync() {
678
+ // Skip syncs if in repair mode
679
+ if (this.core._repairMode) return
680
+
677
681
  if (this.syncsProcessing !== 0) {
678
682
  this.needsSync = true
679
683
  return
@@ -996,55 +1000,92 @@ class Peer {
996
1000
  priority: 0
997
1001
  }
998
1002
 
999
- const remoteLength = Math.max(this.remoteLength, this.pushedLength)
1003
+ await this.replicator._txLock.lock()
1004
+ try {
1005
+ const remoteLength = Math.max(this.remoteLength, this.pushedLength)
1000
1006
 
1001
- msg.block = {
1002
- index,
1003
- nodes: MerkleTree.maxMissingNodes(2 * index, remoteLength)
1004
- }
1007
+ msg.block = {
1008
+ index,
1009
+ nodes: MerkleTree.maxMissingNodes(2 * index, remoteLength)
1010
+ }
1005
1011
 
1006
- if (index >= remoteLength) {
1007
- msg.upgrade = {
1008
- start: remoteLength,
1009
- length: this.core.state.length - remoteLength
1012
+ if (index >= remoteLength) {
1013
+ msg.upgrade = {
1014
+ start: remoteLength,
1015
+ length: this.core.state.length - remoteLength
1016
+ }
1010
1017
  }
1011
- }
1012
1018
 
1013
- let req = null
1014
- const batch = this.core.storage.read()
1015
- try {
1016
- req = await this._getProof(batch, msg, true)
1017
- } catch (err) {
1018
- this.replicator._oninvalidrequest(err, msg, this)
1019
- return
1020
- }
1019
+ let req = null
1020
+ const batch = this.core.storage.read()
1021
+ try {
1022
+ req = await this._getProof(batch, msg, true)
1023
+ } catch (err) {
1024
+ this.replicator._oninvalidrequest(err, msg, this)
1025
+ return
1026
+ }
1021
1027
 
1022
- if (req === null) return
1023
- batch.tryFlush()
1028
+ if (req === null) return
1029
+ batch.tryFlush()
1024
1030
 
1025
- await this._fulfillRequest(req, true)
1031
+ await this._fulfillRequest(req, true)
1032
+ } finally {
1033
+ this.replicator._txLock.unlock()
1034
+ }
1026
1035
  }
1027
1036
 
1028
1037
  async _handleRequest(msg) {
1029
- const batch = this.core.storage.read()
1038
+ const locked = !!msg.upgrade && this.remoteAllowPush
1030
1039
 
1031
- // TODO: could still be answerable if (index, fork) is an ancestor of the current fork
1032
- const req =
1033
- msg.fork === this.core.state.fork
1034
- ? await this._getProof(batch, msg, false)
1035
- : new ProofRequest(msg, null, null, null)
1040
+ if (locked) {
1041
+ // ensure contig push proofs
1042
+ await this.replicator._txLock.lock()
1043
+ const end = msg.upgrade.start + msg.upgrade.length
1036
1044
 
1037
- if (req === null) {
1038
- this.wireNoData.send({ request: msg.id, reason: INVALID_REQUEST })
1039
- return
1045
+ if (this.pushedLength < end) {
1046
+ msg.upgrade.start = this.pushedLength
1047
+ msg.upgrade.length = end - this.pushedLength
1048
+ }
1040
1049
  }
1041
1050
 
1042
- batch.tryFlush()
1051
+ try {
1052
+ const batch = this.core.storage.read()
1053
+
1054
+ // TODO: could still be answerable if (index, fork) is an ancestor of the current fork
1055
+ const req =
1056
+ msg.fork === this.core.state.fork
1057
+ ? await this._getProof(batch, msg, false)
1058
+ : new ProofRequest(msg, null, null, null)
1043
1059
 
1044
- await this._fulfillRequest(req, false)
1060
+ if (req === null) {
1061
+ this.wireNoData.send({ request: msg.id, reason: INVALID_REQUEST })
1062
+ return
1063
+ }
1064
+
1065
+ batch.tryFlush()
1066
+
1067
+ await this._fulfillRequest(req, false)
1068
+ } finally {
1069
+ if (locked) this.replicator._txLock.unlock()
1070
+ }
1045
1071
  }
1046
1072
 
1047
1073
  async _fulfillRequest(req, pushing) {
1074
+ if (this.core._repairMode) {
1075
+ // if cancelled do not reply
1076
+ if (this.remoteRequests.get(req.msg.id) !== req.msg) {
1077
+ return
1078
+ }
1079
+
1080
+ // sync from now on, so safe to delete from the map
1081
+ this.remoteRequests.delete(req.msg.id)
1082
+
1083
+ // If cutting off the fulfillment, ensure promise is caught.
1084
+ // While repairing merkle tree nodes can be `nil` so can throw.
1085
+ req.fulfill().catch(safetyCatch)
1086
+ return
1087
+ }
1088
+
1048
1089
  const proof = await req.fulfill()
1049
1090
 
1050
1091
  if (!pushing) {
@@ -1089,7 +1130,7 @@ class Peer {
1089
1130
  }
1090
1131
 
1091
1132
  if (proof.upgrade) {
1092
- const remoteLength = proof.upgrade.start + proof.upgrade.length
1133
+ const remoteLength = MerkleTree.upgradeLength(proof.upgrade)
1093
1134
  if (remoteLength > this.pushedLength) this.pushedLength = remoteLength
1094
1135
  }
1095
1136
 
@@ -1150,13 +1191,13 @@ class Peer {
1150
1191
  }
1151
1192
 
1152
1193
  async ondata(data) {
1153
- if (data.request !== 0) return this._handleData(data)
1194
+ if (data.request !== 0 && !data.upgrade) return this._handleData(data)
1154
1195
 
1155
- await this.replicator._pushLock.lock()
1196
+ await this.replicator._rxLock.lock()
1156
1197
  try {
1157
1198
  await this._handleData(data)
1158
1199
  } finally {
1159
- this.replicator._pushLock.unlock()
1200
+ this.replicator._rxLock.unlock()
1160
1201
  }
1161
1202
  }
1162
1203
 
@@ -1176,6 +1217,12 @@ class Peer {
1176
1217
  return
1177
1218
  }
1178
1219
 
1220
+ // Skip accepting data if in repair mode to avoid corruption
1221
+ if (this.core._repairMode) {
1222
+ this.replicator._onnodata(this, req, NOT_AVAILABLE)
1223
+ return
1224
+ }
1225
+
1179
1226
  if (req === null && reorg === false && data.block) {
1180
1227
  // mark this as inflight to avoid parallel requests
1181
1228
  this.replicator._markInflight(data.block.index)
@@ -1452,6 +1499,8 @@ class Peer {
1452
1499
 
1453
1500
  _requestManifest() {
1454
1501
  const req = this._makeRequest(false, 0, 0)
1502
+ if (req === null) return
1503
+
1455
1504
  this._send(req)
1456
1505
  }
1457
1506
 
@@ -1465,15 +1514,17 @@ class Peer {
1465
1514
 
1466
1515
  // do the normal checks
1467
1516
  if (!this._remoteHasBlock(index)) return null
1468
- if (!this._canRequest(index)) return null
1517
+ if (!this._canRequest(index, false)) return null
1469
1518
 
1470
1519
  // atm we only ever do one upgrade request in parallel, if that changes
1471
1520
  // then we should add some check here for inflights to avoid over requesting
1472
- const b = this.replicator._blocks.add(index, PRIORITY.NORMAL)
1521
+ const b = this.replicator._blocks.add(index, PRIORITY.NORMAL, false)
1473
1522
  return b
1474
1523
  }
1475
1524
 
1476
1525
  _requestUpgrade(u) {
1526
+ if (this.replicator.pushOnly) return false
1527
+
1477
1528
  const req = this._makeRequest(true, 0, 0)
1478
1529
  if (req === null) return false
1479
1530
 
@@ -1488,6 +1539,7 @@ class Peer {
1488
1539
  _requestSeek(s) {
1489
1540
  // if replicator is updating the seeks etc, bail and wait for it to drain
1490
1541
  if (this.replicator._updatesPending > 0) return false
1542
+ if (this.replicator.pushOnly) return false
1491
1543
 
1492
1544
  const { length, fork } = this.core.state
1493
1545
 
@@ -1518,14 +1570,14 @@ class Peer {
1518
1570
 
1519
1571
  if (this._remoteHasBlock(index) === false) continue
1520
1572
  if (this.core.bitfield.get(index) === true) continue
1521
- if (!this._canRequest(index)) continue
1573
+ if (!this._canRequest(index, false)) continue
1522
1574
 
1523
1575
  // Check if this block is currently inflight - if so pick another
1524
1576
  const b = this.replicator._blocks.get(index)
1525
1577
  if (b !== null && b.inflight.length > 0) continue
1526
1578
 
1527
1579
  // Block is not inflight, but we only want the hash, check if that is inflight
1528
- const h = this.replicator._hashes.add(index, PRIORITY.NORMAL)
1580
+ const h = this.replicator._hashes.add(index, PRIORITY.NORMAL, false)
1529
1581
  if (h.inflight.length > 0) continue
1530
1582
 
1531
1583
  const req = this._makeRequest(false, h.priority, index + 1)
@@ -1549,9 +1601,13 @@ class Peer {
1549
1601
  return false
1550
1602
  }
1551
1603
 
1552
- _canRequest(index) {
1604
+ _canRequest(index, force) {
1553
1605
  if (!(index >= 0)) throw ASSERTION('bad index to _canRequest: ' + index)
1554
1606
 
1607
+ if (!force && this.replicator.pushOnly) {
1608
+ return false
1609
+ }
1610
+
1555
1611
  if (this.remoteLength >= this.core.state.length) {
1556
1612
  return true
1557
1613
  }
@@ -1572,6 +1628,8 @@ class Peer {
1572
1628
  }
1573
1629
 
1574
1630
  _sendBlockRequest(req, b) {
1631
+ if (this.core._repairMode) return
1632
+
1575
1633
  req.block = { index: b.index, nodes: 0 }
1576
1634
  this.replicator._markInflight(b.index)
1577
1635
 
@@ -1588,7 +1646,7 @@ class Peer {
1588
1646
  return false
1589
1647
  }
1590
1648
 
1591
- if (!this._canRequest(b.index)) return false
1649
+ if (!this._canRequest(b.index, b.force)) return false
1592
1650
 
1593
1651
  const req = this._makeRequest(b.index >= length, b.priority, b.index + 1)
1594
1652
  if (req === null) return false
@@ -1636,9 +1694,9 @@ class Peer {
1636
1694
  }
1637
1695
 
1638
1696
  _requestRangeBlock(index, length) {
1639
- if (this.core.bitfield.get(index) === true || !this._canRequest(index)) return false
1697
+ if (this.core.bitfield.get(index) === true || !this._canRequest(index, false)) return false
1640
1698
 
1641
- const b = this.replicator._blocks.add(index, PRIORITY.NORMAL)
1699
+ const b = this.replicator._blocks.add(index, PRIORITY.NORMAL, false)
1642
1700
  if (b.inflight.length > 0) {
1643
1701
  this.missingBlocks.set(index, false) // in case we missed some states just set them ondemand, nbd
1644
1702
  return false
@@ -1789,8 +1847,10 @@ class Peer {
1789
1847
 
1790
1848
  _requestForkProof(f) {
1791
1849
  if (!this.remoteLength) return
1850
+ if (this.replicator.pushOnly) return
1792
1851
 
1793
1852
  const req = this._makeRequest(false, 0, 0)
1853
+ if (req === null) return
1794
1854
 
1795
1855
  req.upgrade = { start: 0, length: this.remoteLength }
1796
1856
  req.manifest = !this.core.header.manifest
@@ -1801,6 +1861,7 @@ class Peer {
1801
1861
 
1802
1862
  _requestForkRange(f) {
1803
1863
  if (f.fork !== this.remoteFork || f.batch.want === null) return false
1864
+ if (this.replicator.pushOnly) return false
1804
1865
 
1805
1866
  const end = Math.min(f.batch.want.end, this.remoteLength)
1806
1867
  if (end < f.batch.want.start) return false
@@ -1815,6 +1876,7 @@ class Peer {
1815
1876
  if (this._remoteHasBlock(index) === false) continue
1816
1877
 
1817
1878
  const req = this._makeRequest(false, 0, 0)
1879
+ if (req === null) return false
1818
1880
 
1819
1881
  req.hash = { index: 2 * index, nodes: f.batch.want.nodes }
1820
1882
 
@@ -1901,6 +1963,7 @@ module.exports = class Replicator {
1901
1963
  this.findingPeers = 0 // updatable from the outside
1902
1964
  this.destroyed = false
1903
1965
  this.downloading = false
1966
+ this.pushOnly = false
1904
1967
  this.activeSessions = 0
1905
1968
 
1906
1969
  this.hotswaps = new HotswapQueue()
@@ -1937,7 +2000,9 @@ module.exports = class Replicator {
1937
2000
  this._reorgs = []
1938
2001
  this._ranges = []
1939
2002
 
1940
- this._pushLock = new Mutex()
2003
+ this._rxLock = new Mutex()
2004
+ this._txLock = new Mutex()
2005
+
1941
2006
  this._hadPeers = false
1942
2007
  this._active = 0
1943
2008
  this._ifAvailable = 0
@@ -1988,6 +2053,11 @@ module.exports = class Replicator {
1988
2053
  return this.downloading
1989
2054
  }
1990
2055
 
2056
+ setPushOnly(pushOnly) {
2057
+ if (pushOnly === this.pushOnly) return
2058
+ this.pushOnly = pushOnly
2059
+ }
2060
+
1991
2061
  setDownloading(downloading) {
1992
2062
  const allowPush = this.isAllowingPush()
1993
2063
  const linger = this._notDownloadingLinger
@@ -2124,8 +2194,8 @@ module.exports = class Replicator {
2124
2194
  return ref
2125
2195
  }
2126
2196
 
2127
- addBlock(session, index) {
2128
- const b = this._blocks.add(index, PRIORITY.HIGH)
2197
+ addBlock(session, index, force) {
2198
+ const b = this._blocks.add(index, PRIORITY.HIGH, force)
2129
2199
  const ref = b.attach(session)
2130
2200
 
2131
2201
  this._queueBlock(b)
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "11.23.1",
3
+ "version": "11.25.0",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {