hypercore 11.24.0 → 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...
@@ -858,6 +871,7 @@ class Hypercore extends EventEmitter {
858
871
 
859
872
  if (!this._shouldWait(opts, this.wait)) return null
860
873
 
874
+ this.waits++
861
875
  if (opts && opts.onwait) opts.onwait(index, this)
862
876
  if (this.onwait) this.onwait(index, this)
863
877
 
@@ -1021,6 +1035,52 @@ class Hypercore extends EventEmitter {
1021
1035
  return batch
1022
1036
  }
1023
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
+
1024
1084
  registerExtension(name, handlers = {}) {
1025
1085
  if (this.extensions.has(name)) {
1026
1086
  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) {
@@ -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
@@ -675,6 +675,9 @@ class Peer {
675
675
  }
676
676
 
677
677
  sendSync() {
678
+ // Skip syncs if in repair mode
679
+ if (this.core._repairMode) return
680
+
678
681
  if (this.syncsProcessing !== 0) {
679
682
  this.needsSync = true
680
683
  return
@@ -997,55 +1000,92 @@ class Peer {
997
1000
  priority: 0
998
1001
  }
999
1002
 
1000
- 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)
1001
1006
 
1002
- msg.block = {
1003
- index,
1004
- nodes: MerkleTree.maxMissingNodes(2 * index, remoteLength)
1005
- }
1007
+ msg.block = {
1008
+ index,
1009
+ nodes: MerkleTree.maxMissingNodes(2 * index, remoteLength)
1010
+ }
1006
1011
 
1007
- if (index >= remoteLength) {
1008
- msg.upgrade = {
1009
- start: remoteLength,
1010
- length: this.core.state.length - remoteLength
1012
+ if (index >= remoteLength) {
1013
+ msg.upgrade = {
1014
+ start: remoteLength,
1015
+ length: this.core.state.length - remoteLength
1016
+ }
1011
1017
  }
1012
- }
1013
1018
 
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
- }
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
+ }
1022
1027
 
1023
- if (req === null) return
1024
- batch.tryFlush()
1028
+ if (req === null) return
1029
+ batch.tryFlush()
1025
1030
 
1026
- await this._fulfillRequest(req, true)
1031
+ await this._fulfillRequest(req, true)
1032
+ } finally {
1033
+ this.replicator._txLock.unlock()
1034
+ }
1027
1035
  }
1028
1036
 
1029
1037
  async _handleRequest(msg) {
1030
- const batch = this.core.storage.read()
1038
+ const locked = !!msg.upgrade && this.remoteAllowPush
1031
1039
 
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)
1040
+ if (locked) {
1041
+ // ensure contig push proofs
1042
+ await this.replicator._txLock.lock()
1043
+ const end = msg.upgrade.start + msg.upgrade.length
1037
1044
 
1038
- if (req === null) {
1039
- this.wireNoData.send({ request: msg.id, reason: INVALID_REQUEST })
1040
- return
1045
+ if (this.pushedLength < end) {
1046
+ msg.upgrade.start = this.pushedLength
1047
+ msg.upgrade.length = end - this.pushedLength
1048
+ }
1041
1049
  }
1042
1050
 
1043
- batch.tryFlush()
1051
+ try {
1052
+ const batch = this.core.storage.read()
1044
1053
 
1045
- await this._fulfillRequest(req, false)
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)
1059
+
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
+ }
1046
1071
  }
1047
1072
 
1048
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
+
1049
1089
  const proof = await req.fulfill()
1050
1090
 
1051
1091
  if (!pushing) {
@@ -1090,7 +1130,7 @@ class Peer {
1090
1130
  }
1091
1131
 
1092
1132
  if (proof.upgrade) {
1093
- const remoteLength = proof.upgrade.start + proof.upgrade.length
1133
+ const remoteLength = MerkleTree.upgradeLength(proof.upgrade)
1094
1134
  if (remoteLength > this.pushedLength) this.pushedLength = remoteLength
1095
1135
  }
1096
1136
 
@@ -1151,13 +1191,13 @@ class Peer {
1151
1191
  }
1152
1192
 
1153
1193
  async ondata(data) {
1154
- if (data.request !== 0) return this._handleData(data)
1194
+ if (data.request !== 0 && !data.upgrade) return this._handleData(data)
1155
1195
 
1156
- await this.replicator._pushLock.lock()
1196
+ await this.replicator._rxLock.lock()
1157
1197
  try {
1158
1198
  await this._handleData(data)
1159
1199
  } finally {
1160
- this.replicator._pushLock.unlock()
1200
+ this.replicator._rxLock.unlock()
1161
1201
  }
1162
1202
  }
1163
1203
 
@@ -1177,6 +1217,12 @@ class Peer {
1177
1217
  return
1178
1218
  }
1179
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
+
1180
1226
  if (req === null && reorg === false && data.block) {
1181
1227
  // mark this as inflight to avoid parallel requests
1182
1228
  this.replicator._markInflight(data.block.index)
@@ -1582,6 +1628,8 @@ class Peer {
1582
1628
  }
1583
1629
 
1584
1630
  _sendBlockRequest(req, b) {
1631
+ if (this.core._repairMode) return
1632
+
1585
1633
  req.block = { index: b.index, nodes: 0 }
1586
1634
  this.replicator._markInflight(b.index)
1587
1635
 
@@ -1952,7 +2000,9 @@ module.exports = class Replicator {
1952
2000
  this._reorgs = []
1953
2001
  this._ranges = []
1954
2002
 
1955
- this._pushLock = new Mutex()
2003
+ this._rxLock = new Mutex()
2004
+ this._txLock = new Mutex()
2005
+
1956
2006
  this._hadPeers = false
1957
2007
  this._active = 0
1958
2008
  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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "11.24.0",
3
+ "version": "11.25.0",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {