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 +69 -1
- package/lib/core.js +85 -6
- package/lib/fully-remote-proof.js +9 -3
- package/lib/merkle-tree.js +17 -0
- package/lib/replicator.js +120 -50
- package/lib/session-state.js +6 -2
- package/package.json +1 -1
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
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|
package/lib/merkle-tree.js
CHANGED
|
@@ -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
|
-
|
|
1003
|
+
await this.replicator._txLock.lock()
|
|
1004
|
+
try {
|
|
1005
|
+
const remoteLength = Math.max(this.remoteLength, this.pushedLength)
|
|
1000
1006
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1007
|
+
msg.block = {
|
|
1008
|
+
index,
|
|
1009
|
+
nodes: MerkleTree.maxMissingNodes(2 * index, remoteLength)
|
|
1010
|
+
}
|
|
1005
1011
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1012
|
+
if (index >= remoteLength) {
|
|
1013
|
+
msg.upgrade = {
|
|
1014
|
+
start: remoteLength,
|
|
1015
|
+
length: this.core.state.length - remoteLength
|
|
1016
|
+
}
|
|
1010
1017
|
}
|
|
1011
|
-
}
|
|
1012
1018
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
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
|
-
|
|
1023
|
-
|
|
1028
|
+
if (req === null) return
|
|
1029
|
+
batch.tryFlush()
|
|
1024
1030
|
|
|
1025
|
-
|
|
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
|
|
1038
|
+
const locked = !!msg.upgrade && this.remoteAllowPush
|
|
1030
1039
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1045
|
+
if (this.pushedLength < end) {
|
|
1046
|
+
msg.upgrade.start = this.pushedLength
|
|
1047
|
+
msg.upgrade.length = end - this.pushedLength
|
|
1048
|
+
}
|
|
1040
1049
|
}
|
|
1041
1050
|
|
|
1042
|
-
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
1196
|
+
await this.replicator._rxLock.lock()
|
|
1156
1197
|
try {
|
|
1157
1198
|
await this._handleData(data)
|
|
1158
1199
|
} finally {
|
|
1159
|
-
this.replicator.
|
|
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.
|
|
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)
|
package/lib/session-state.js
CHANGED
|
@@ -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
|
|