hypercore 11.20.1 → 11.21.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/README.md +0 -1
- package/index.js +5 -3
- package/lib/core.js +30 -8
- package/lib/merkle-tree.js +48 -0
- package/lib/messages.js +4 -2
- package/lib/replicator.js +199 -47
- package/lib/session-state.js +30 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -762,7 +762,6 @@ Create an encrypted noise stream with a protomux instance attached used for Hype
|
|
|
762
762
|
|
|
763
763
|
```
|
|
764
764
|
{
|
|
765
|
-
keepAlive: true, // Whether to keep the stream alive
|
|
766
765
|
ondiscoverykey: () => {}, // A handler for when a discovery key is set over the stream for corestore management
|
|
767
766
|
}
|
|
768
767
|
```
|
package/index.js
CHANGED
|
@@ -146,7 +146,7 @@ class Hypercore extends EventEmitter {
|
|
|
146
146
|
if (!noiseStream.userData) {
|
|
147
147
|
const protocol = Protomux.from(noiseStream)
|
|
148
148
|
|
|
149
|
-
if (opts.keepAlive !== false) {
|
|
149
|
+
if (opts.keepAlive !== false && noiseStream.keepAlive === 0) {
|
|
150
150
|
noiseStream.setKeepAlive(5000)
|
|
151
151
|
}
|
|
152
152
|
noiseStream.userData = protocol
|
|
@@ -938,7 +938,7 @@ class Hypercore extends EventEmitter {
|
|
|
938
938
|
const isDefault = this.state === this.core.state
|
|
939
939
|
const defaultKeyPair = this.state.name === null ? this.keyPair : null
|
|
940
940
|
|
|
941
|
-
const { keyPair = defaultKeyPair, signature = null, maxLength } = opts
|
|
941
|
+
const { keyPair = defaultKeyPair, signature = null, maxLength, postappend = null } = opts
|
|
942
942
|
const writable =
|
|
943
943
|
!isDefault || !!signature || !!(keyPair && keyPair.secretKey) || opts.writable === true
|
|
944
944
|
|
|
@@ -966,7 +966,7 @@ class Hypercore extends EventEmitter {
|
|
|
966
966
|
}
|
|
967
967
|
}
|
|
968
968
|
|
|
969
|
-
return this.state.append(buffers, { keyPair, signature, preappend, maxLength })
|
|
969
|
+
return this.state.append(buffers, { keyPair, signature, preappend, postappend, maxLength })
|
|
970
970
|
}
|
|
971
971
|
|
|
972
972
|
async signable(length = -1, fork = -1) {
|
|
@@ -1149,6 +1149,8 @@ function initOnce(session, storage, key, opts) {
|
|
|
1149
1149
|
eagerUpgrade: opts.eagerUpgrade !== false,
|
|
1150
1150
|
notDownloadingLinger: opts.notDownloadingLinger,
|
|
1151
1151
|
allowFork: opts.allowFork !== false,
|
|
1152
|
+
allowPush: !!opts.allowPush,
|
|
1153
|
+
alwaysLatestBlock: !!opts.allowLatestBlock,
|
|
1152
1154
|
inflightRange: opts.inflightRange,
|
|
1153
1155
|
compat: opts.compat === true,
|
|
1154
1156
|
force: opts.force,
|
package/lib/core.js
CHANGED
|
@@ -55,6 +55,8 @@ module.exports = class Core {
|
|
|
55
55
|
this.destroyed = false
|
|
56
56
|
this.closed = false
|
|
57
57
|
|
|
58
|
+
this.hintsChanged = false
|
|
59
|
+
|
|
58
60
|
this._bitfield = null
|
|
59
61
|
this._verifies = null
|
|
60
62
|
this._verifiesFlushed = null
|
|
@@ -439,6 +441,7 @@ module.exports = class Core {
|
|
|
439
441
|
const verifier =
|
|
440
442
|
this.verifier ||
|
|
441
443
|
new Verifier(this.header.key, Verifier.createManifest(manifest), { legacy: this._legacy })
|
|
444
|
+
|
|
442
445
|
if (!verifier.verify(batch, batch.signature)) {
|
|
443
446
|
throw INVALID_SIGNATURE('Proof contains an invalid signature', this.discoveryKey)
|
|
444
447
|
}
|
|
@@ -449,10 +452,6 @@ module.exports = class Core {
|
|
|
449
452
|
async _verifyExclusive({ batch, bitfield, value, manifest }) {
|
|
450
453
|
manifest = this._verifyBatchUpgrade(batch, manifest)
|
|
451
454
|
|
|
452
|
-
if (!batch.commitable()) return false
|
|
453
|
-
|
|
454
|
-
if (this.preupdate !== null) await this.preupdate(batch, this.header.key)
|
|
455
|
-
|
|
456
455
|
if (
|
|
457
456
|
!(await this.state._verifyBlock(
|
|
458
457
|
batch,
|
|
@@ -531,6 +530,8 @@ module.exports = class Core {
|
|
|
531
530
|
this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
|
|
532
531
|
}
|
|
533
532
|
}
|
|
533
|
+
|
|
534
|
+
if (this.hintsChanged) await this.flushHints()
|
|
534
535
|
} finally {
|
|
535
536
|
this.state._clearActiveBatch()
|
|
536
537
|
this.state.mutex.unlock()
|
|
@@ -539,6 +540,20 @@ module.exports = class Core {
|
|
|
539
540
|
return verifies[0] !== null
|
|
540
541
|
}
|
|
541
542
|
|
|
543
|
+
async flushHints() {
|
|
544
|
+
if (!this.hintsChanged) return
|
|
545
|
+
this.hintsChanged = false // we unset this immediately as a "debounce"
|
|
546
|
+
|
|
547
|
+
const tx = this.state.storage.write()
|
|
548
|
+
|
|
549
|
+
tx.setHints({
|
|
550
|
+
contiguousLength: this.header.hints.contiguousLength,
|
|
551
|
+
remoteContiguousLength: this.header.hints.remoteContiguousLength
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
await tx.flush()
|
|
555
|
+
}
|
|
556
|
+
|
|
542
557
|
async checkConflict(proof, from) {
|
|
543
558
|
if (this.state.length < proof.upgrade.length || proof.fork !== this.state.fork) {
|
|
544
559
|
// out of date this proof - ignore for now
|
|
@@ -650,7 +665,14 @@ module.exports = class Core {
|
|
|
650
665
|
if (proof.fork !== this.state.fork) return false
|
|
651
666
|
|
|
652
667
|
const batch = await MerkleTree.verify(this.state, proof)
|
|
653
|
-
|
|
668
|
+
|
|
669
|
+
if (batch.upgraded && batch.length <= this.state.length) {
|
|
670
|
+
await batch.downgrade()
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (!batch.commitable()) {
|
|
674
|
+
return false
|
|
675
|
+
}
|
|
654
676
|
|
|
655
677
|
const value = (proof.block && proof.block.value) || null
|
|
656
678
|
const op = {
|
|
@@ -716,13 +738,13 @@ module.exports = class Core {
|
|
|
716
738
|
|
|
717
739
|
if (contig.length !== -1 && contig.length !== this.header.hints.contiguousLength) {
|
|
718
740
|
this.header.hints.contiguousLength = contig.length
|
|
741
|
+
this.hintsChanged = true
|
|
719
742
|
}
|
|
720
743
|
}
|
|
721
744
|
|
|
722
|
-
|
|
745
|
+
updateRemoteContiguousLength(length) {
|
|
723
746
|
this.header.hints.remoteContiguousLength = length
|
|
724
|
-
|
|
725
|
-
if (this.header.hints.remoteContiguousLength !== length) return
|
|
747
|
+
this.hintsChanged = true
|
|
726
748
|
|
|
727
749
|
for (let i = this.monitors.length - 1; i >= 0; i--) {
|
|
728
750
|
this.monitors[i].emit('remote-contiguous-length', length)
|
package/lib/merkle-tree.js
CHANGED
|
@@ -293,6 +293,32 @@ class MerkleTreeBatch {
|
|
|
293
293
|
return offset
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
async downgrade() {
|
|
297
|
+
if (!this.upgraded) return true
|
|
298
|
+
|
|
299
|
+
const rx = this.storage.read()
|
|
300
|
+
const nodePromises = []
|
|
301
|
+
|
|
302
|
+
for (const r of this.roots) {
|
|
303
|
+
nodePromises.push(rx.getTreeNode(r.index))
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
rx.tryFlush()
|
|
307
|
+
|
|
308
|
+
const nodes = await Promise.all(nodePromises)
|
|
309
|
+
|
|
310
|
+
for (let i = 0; i < this.roots.length; i++) {
|
|
311
|
+
const root = this.roots[i]
|
|
312
|
+
const node = nodes[i]
|
|
313
|
+
|
|
314
|
+
if (!node) return false
|
|
315
|
+
if (!b4a.equals(node.hash, root.hash)) return false
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
this.upgraded = false
|
|
319
|
+
return true
|
|
320
|
+
}
|
|
321
|
+
|
|
296
322
|
async restore(length) {
|
|
297
323
|
if (length === this.length) return this
|
|
298
324
|
|
|
@@ -684,6 +710,28 @@ class MerkleTree {
|
|
|
684
710
|
return generateProof(session, rx, block, hash, seek, upgrade)
|
|
685
711
|
}
|
|
686
712
|
|
|
713
|
+
static maxMissingNodes(index, length) {
|
|
714
|
+
const head = 2 * length
|
|
715
|
+
const ite = flat.iterator(index)
|
|
716
|
+
|
|
717
|
+
// See iterator.rightSpan()
|
|
718
|
+
const iteRightSpan = ite.index + ite.factor / 2 - 1
|
|
719
|
+
// If the index is not in the current tree, we do not know how many missing nodes there are...
|
|
720
|
+
if (iteRightSpan >= head) return 0
|
|
721
|
+
|
|
722
|
+
for (const r of flat.fullRoots(head)) {
|
|
723
|
+
if (r === index) return 0
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
let cnt = 0
|
|
727
|
+
while (!ite.contains(head)) {
|
|
728
|
+
cnt++
|
|
729
|
+
ite.parent()
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return cnt
|
|
733
|
+
}
|
|
734
|
+
|
|
687
735
|
static async missingNodes(session, index, length) {
|
|
688
736
|
const head = 2 * length
|
|
689
737
|
const ite = flat.iterator(index)
|
package/lib/messages.js
CHANGED
|
@@ -629,7 +629,8 @@ wire.sync = {
|
|
|
629
629
|
(m.canUpgrade ? 1 : 0) |
|
|
630
630
|
(m.uploading ? 2 : 0) |
|
|
631
631
|
(m.downloading ? 4 : 0) |
|
|
632
|
-
(m.hasManifest ? 8 : 0)
|
|
632
|
+
(m.hasManifest ? 8 : 0) |
|
|
633
|
+
(m.allowPush ? 16 : 0)
|
|
633
634
|
)
|
|
634
635
|
c.uint.encode(state, m.fork)
|
|
635
636
|
c.uint.encode(state, m.length)
|
|
@@ -645,7 +646,8 @@ wire.sync = {
|
|
|
645
646
|
canUpgrade: (flags & 1) !== 0,
|
|
646
647
|
uploading: (flags & 2) !== 0,
|
|
647
648
|
downloading: (flags & 4) !== 0,
|
|
648
|
-
hasManifest: (flags & 8) !== 0
|
|
649
|
+
hasManifest: (flags & 8) !== 0,
|
|
650
|
+
allowPush: (flags & 16) !== 0
|
|
649
651
|
}
|
|
650
652
|
}
|
|
651
653
|
}
|
package/lib/replicator.js
CHANGED
|
@@ -187,6 +187,11 @@ class RangeRequest extends Attachable {
|
|
|
187
187
|
this.ifAvailable = ifAvailable
|
|
188
188
|
this.blocks = blocks
|
|
189
189
|
this.ranges = ranges
|
|
190
|
+
this.rangeIndex = ranges.push(this) - 1
|
|
191
|
+
|
|
192
|
+
if (this.end === -1) {
|
|
193
|
+
this.replicator._alwaysLatestBlock++
|
|
194
|
+
}
|
|
190
195
|
|
|
191
196
|
// As passed by the user, immut
|
|
192
197
|
this.userStart = start
|
|
@@ -194,10 +199,18 @@ class RangeRequest extends Attachable {
|
|
|
194
199
|
}
|
|
195
200
|
|
|
196
201
|
_unref() {
|
|
197
|
-
|
|
198
|
-
if (
|
|
202
|
+
if (this.rangeIndex >= this.ranges.length) return
|
|
203
|
+
if (this.ranges[this.rangeIndex] !== this) return
|
|
204
|
+
|
|
199
205
|
const h = this.ranges.pop()
|
|
200
|
-
if (
|
|
206
|
+
if (h !== this) {
|
|
207
|
+
this.ranges[this.rangeIndex] = h
|
|
208
|
+
h.rangeIndex = this.rangeIndex
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (this.end === -1) {
|
|
212
|
+
this.replicator._alwaysLatestBlock--
|
|
213
|
+
}
|
|
201
214
|
}
|
|
202
215
|
|
|
203
216
|
_cancel(r) {
|
|
@@ -371,7 +384,6 @@ class ProofRequest {
|
|
|
371
384
|
const [proof, block] = await Promise.all([this.proof.settle(), this.block])
|
|
372
385
|
|
|
373
386
|
if (this.manifest) proof.manifest = this.manifest
|
|
374
|
-
|
|
375
387
|
if (!block && proof.block) return null
|
|
376
388
|
|
|
377
389
|
if (block) proof.block.value = block
|
|
@@ -450,6 +462,8 @@ class Peer {
|
|
|
450
462
|
this.remoteBitfield = new RemoteBitfield()
|
|
451
463
|
this.missingBlocks = new RemoteBitfield()
|
|
452
464
|
|
|
465
|
+
this.pushing = false
|
|
466
|
+
|
|
453
467
|
this.remoteFork = 0
|
|
454
468
|
this.remoteLength = 0
|
|
455
469
|
this.remoteCanUpgrade = false
|
|
@@ -457,6 +471,7 @@ class Peer {
|
|
|
457
471
|
this.remoteDownloading = true
|
|
458
472
|
this.remoteSynced = false
|
|
459
473
|
this.remoteHasManifest = false
|
|
474
|
+
this.remoteAllowPush = false
|
|
460
475
|
this.remoteRequests = new Map()
|
|
461
476
|
|
|
462
477
|
this.segmentsWanted = new Set()
|
|
@@ -592,7 +607,8 @@ class Peer {
|
|
|
592
607
|
canUpgrade: this.canUpgrade,
|
|
593
608
|
uploading: true,
|
|
594
609
|
downloading: this.replicator.isDownloading(),
|
|
595
|
-
hasManifest: !!this.core.header.manifest && this.core.compat === false
|
|
610
|
+
hasManifest: !!this.core.header.manifest && this.core.compat === false,
|
|
611
|
+
allowPush: this.replicator.allowPush
|
|
596
612
|
})
|
|
597
613
|
incrementTx(this.stats.wireSync, this.replicator.stats.wireSync)
|
|
598
614
|
}
|
|
@@ -677,7 +693,18 @@ class Peer {
|
|
|
677
693
|
return false
|
|
678
694
|
}
|
|
679
695
|
|
|
680
|
-
async onsync(
|
|
696
|
+
async onsync(msg) {
|
|
697
|
+
const {
|
|
698
|
+
fork,
|
|
699
|
+
length,
|
|
700
|
+
remoteLength,
|
|
701
|
+
canUpgrade,
|
|
702
|
+
uploading,
|
|
703
|
+
downloading,
|
|
704
|
+
hasManifest,
|
|
705
|
+
allowPush
|
|
706
|
+
} = msg
|
|
707
|
+
|
|
681
708
|
const lengthChanged = length !== this.remoteLength
|
|
682
709
|
const sameFork = fork === this.core.state.fork
|
|
683
710
|
|
|
@@ -688,6 +715,7 @@ class Peer {
|
|
|
688
715
|
this.remoteUploading = uploading
|
|
689
716
|
this.remoteDownloading = downloading
|
|
690
717
|
this.remoteHasManifest = hasManifest
|
|
718
|
+
this.remoteAllowPush = allowPush
|
|
691
719
|
|
|
692
720
|
if (this.closeIfIdle()) return
|
|
693
721
|
|
|
@@ -783,13 +811,13 @@ class Peer {
|
|
|
783
811
|
}
|
|
784
812
|
}
|
|
785
813
|
|
|
786
|
-
async _getProof(batch, msg) {
|
|
814
|
+
async _getProof(batch, msg, pushing) {
|
|
787
815
|
let block = null
|
|
788
816
|
|
|
789
817
|
if (msg.block) {
|
|
790
818
|
const index = msg.block.index
|
|
791
819
|
|
|
792
|
-
if (msg.fork !== this.core.state.fork || !this.core.bitfield.get(index)) {
|
|
820
|
+
if (!pushing && (msg.fork !== this.core.state.fork || !this.core.bitfield.get(index))) {
|
|
793
821
|
return new ProofRequest(msg, null, null, null)
|
|
794
822
|
}
|
|
795
823
|
|
|
@@ -866,13 +894,50 @@ class Peer {
|
|
|
866
894
|
this.receiverBusy = false
|
|
867
895
|
}
|
|
868
896
|
|
|
897
|
+
async push(index) {
|
|
898
|
+
if (!this.remoteAllowPush) return
|
|
899
|
+
if (this.core.state.fork !== this.remoteFork) return
|
|
900
|
+
if (this.remoteBitfield.get(index)) return
|
|
901
|
+
|
|
902
|
+
const msg = {
|
|
903
|
+
id: 0,
|
|
904
|
+
fork: this.core.state.fork,
|
|
905
|
+
block: null,
|
|
906
|
+
hash: null,
|
|
907
|
+
seek: null,
|
|
908
|
+
upgrade: null,
|
|
909
|
+
manifest: this.remoteLength === 0,
|
|
910
|
+
priority: 0
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
msg.block = {
|
|
914
|
+
index,
|
|
915
|
+
nodes: MerkleTree.maxMissingNodes(2 * index, this.remoteLength)
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (index >= this.remoteLength) {
|
|
919
|
+
msg.upgrade = {
|
|
920
|
+
start: this.remoteLength,
|
|
921
|
+
length: this.core.state.length - this.remoteLength
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const batch = this.core.storage.read()
|
|
926
|
+
const req = await this._getProof(batch, msg, true)
|
|
927
|
+
if (req === null) return
|
|
928
|
+
|
|
929
|
+
batch.tryFlush()
|
|
930
|
+
|
|
931
|
+
await this._fulfillRequest(req, true)
|
|
932
|
+
}
|
|
933
|
+
|
|
869
934
|
async _handleRequest(msg) {
|
|
870
935
|
const batch = this.core.storage.read()
|
|
871
936
|
|
|
872
937
|
// TODO: could still be answerable if (index, fork) is an ancestor of the current fork
|
|
873
938
|
const req =
|
|
874
939
|
msg.fork === this.core.state.fork
|
|
875
|
-
? await this._getProof(batch, msg)
|
|
940
|
+
? await this._getProof(batch, msg, false)
|
|
876
941
|
: new ProofRequest(msg, null, null, null)
|
|
877
942
|
|
|
878
943
|
if (req === null) {
|
|
@@ -882,20 +947,25 @@ class Peer {
|
|
|
882
947
|
|
|
883
948
|
batch.tryFlush()
|
|
884
949
|
|
|
885
|
-
await this._fulfillRequest(req)
|
|
950
|
+
await this._fulfillRequest(req, false)
|
|
886
951
|
}
|
|
887
952
|
|
|
888
|
-
async _fulfillRequest(req) {
|
|
953
|
+
async _fulfillRequest(req, pushing) {
|
|
889
954
|
const proof = await req.fulfill()
|
|
890
955
|
|
|
891
|
-
|
|
892
|
-
|
|
956
|
+
if (!pushing) {
|
|
957
|
+
// if cancelled do not reply
|
|
958
|
+
if (this.remoteRequests.get(req.msg.id) !== req.msg) {
|
|
959
|
+
return
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// sync from now on, so safe to delete from the map
|
|
963
|
+
this.remoteRequests.delete(req.msg.id)
|
|
964
|
+
} else if (!this.remoteAllowPush) {
|
|
965
|
+
// if pushing but remote disabled it, just drop it
|
|
893
966
|
return
|
|
894
967
|
}
|
|
895
968
|
|
|
896
|
-
// sync from now on, so safe to delete from the map
|
|
897
|
-
this.remoteRequests.delete(req.msg.id)
|
|
898
|
-
|
|
899
969
|
if (!this.isActive() && proof.block !== null) {
|
|
900
970
|
return
|
|
901
971
|
}
|
|
@@ -924,6 +994,15 @@ class Peer {
|
|
|
924
994
|
this.replicator._onupload(proof.block.index, proof.block.value.byteLength, this)
|
|
925
995
|
}
|
|
926
996
|
|
|
997
|
+
// TODO: we should prob move to a sep length prop for this. This is just quick and dirty
|
|
998
|
+
// to produce better push upgrade proofs as we can better predict what length the remote
|
|
999
|
+
// is going to be at.
|
|
1000
|
+
if ((pushing || this.pushing) && proof.upgrade) {
|
|
1001
|
+
this.pushing = true
|
|
1002
|
+
const remoteLength = proof.upgrade.start + proof.upgrade.length
|
|
1003
|
+
if (remoteLength > this.remoteLength) this.remoteLength = remoteLength
|
|
1004
|
+
}
|
|
1005
|
+
|
|
927
1006
|
this.wireData.send({
|
|
928
1007
|
request: req.msg.id,
|
|
929
1008
|
fork: req.msg.fork,
|
|
@@ -975,6 +1054,11 @@ class Peer {
|
|
|
975
1054
|
incrementTx(this.stats.wireRequest, this.replicator.stats.wireRequest)
|
|
976
1055
|
}
|
|
977
1056
|
|
|
1057
|
+
_unmarkInflightBlockRequest(req, data) {
|
|
1058
|
+
if (isBlockRequest(req)) this.replicator._unmarkInflight(req.block.index)
|
|
1059
|
+
else if (data.block && !req) this.replicator._unmarkInflight(data.block.index)
|
|
1060
|
+
}
|
|
1061
|
+
|
|
978
1062
|
async ondata(data) {
|
|
979
1063
|
// always allow a fork conflict proof to be sent
|
|
980
1064
|
if (data.request === 0 && data.upgrade && data.upgrade.start === 0) {
|
|
@@ -987,7 +1071,14 @@ class Peer {
|
|
|
987
1071
|
|
|
988
1072
|
// no push atm, TODO: check if this satisfies another pending request
|
|
989
1073
|
// allow reorg pushes tho as those are not written to storage so we'll take all the help we can get
|
|
990
|
-
if (req === null && reorg === false)
|
|
1074
|
+
if (req === null && reorg === false && !this.replicator.allowPush) {
|
|
1075
|
+
return
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
if (req === null && reorg === false && data.block) {
|
|
1079
|
+
// mark this as inflight to avoid parallel requests
|
|
1080
|
+
this.replicator._markInflight(data.block.index)
|
|
1081
|
+
}
|
|
991
1082
|
|
|
992
1083
|
if (req !== null) {
|
|
993
1084
|
if (req.peer !== this) return
|
|
@@ -998,7 +1089,7 @@ class Peer {
|
|
|
998
1089
|
if (reorg === true) return await this.replicator._onreorgdata(this, req, data)
|
|
999
1090
|
} catch (err) {
|
|
1000
1091
|
safetyCatch(err)
|
|
1001
|
-
|
|
1092
|
+
this._unmarkInflightBlockRequest(req, data)
|
|
1002
1093
|
|
|
1003
1094
|
this.paused = true
|
|
1004
1095
|
this.replicator._oninvaliddata(err, req, data, this)
|
|
@@ -1009,13 +1100,13 @@ class Peer {
|
|
|
1009
1100
|
if (isBlockRequest(req)) this.replicator._markProcessing(req.block.index)
|
|
1010
1101
|
|
|
1011
1102
|
try {
|
|
1012
|
-
if (!
|
|
1103
|
+
if (!this.replicator._matchingRequest(req, data) || !(await this.core.verify(data, this))) {
|
|
1013
1104
|
this.replicator._onnodata(this, req, 0)
|
|
1014
1105
|
return
|
|
1015
1106
|
}
|
|
1016
1107
|
} catch (err) {
|
|
1017
1108
|
safetyCatch(err)
|
|
1018
|
-
|
|
1109
|
+
this._unmarkInflightBlockRequest(req, data)
|
|
1019
1110
|
|
|
1020
1111
|
if (err.code === 'WRITE_FAILED') {
|
|
1021
1112
|
// For example, we don't want to keep pulling data when storage is full
|
|
@@ -1031,7 +1122,12 @@ class Peer {
|
|
|
1031
1122
|
this._checkIfConflict()
|
|
1032
1123
|
}
|
|
1033
1124
|
|
|
1034
|
-
|
|
1125
|
+
// if push mode, we MIGHT get an occasional bad message
|
|
1126
|
+
// no need to mega bail for that. TODO: rate limit instead as a general thing
|
|
1127
|
+
if (!this.replicator.allowPush) {
|
|
1128
|
+
this.paused = true
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1035
1131
|
this.replicator._onnodata(this, req, 0)
|
|
1036
1132
|
this.replicator._oninvaliddata(err, req, data, this)
|
|
1037
1133
|
return
|
|
@@ -1049,7 +1145,11 @@ class Peer {
|
|
|
1049
1145
|
|
|
1050
1146
|
onnodata({ request, reason }) {
|
|
1051
1147
|
const req = request > 0 ? this.replicator._inflight.get(request) : null
|
|
1052
|
-
|
|
1148
|
+
|
|
1149
|
+
if (req === null || req.peer !== this) {
|
|
1150
|
+
this.replicator.updateAll()
|
|
1151
|
+
return
|
|
1152
|
+
}
|
|
1053
1153
|
|
|
1054
1154
|
this._onrequestroundtrip(req)
|
|
1055
1155
|
this.replicator._onnodata(this, req, reason)
|
|
@@ -1166,7 +1266,7 @@ class Peer {
|
|
|
1166
1266
|
this.remoteFork === this.core.state.fork &&
|
|
1167
1267
|
length > this.core.header.hints.remoteContiguousLength
|
|
1168
1268
|
) {
|
|
1169
|
-
|
|
1269
|
+
this.core.updateRemoteContiguousLength(length)
|
|
1170
1270
|
}
|
|
1171
1271
|
}
|
|
1172
1272
|
} else if (length === 1) {
|
|
@@ -1184,6 +1284,7 @@ class Peer {
|
|
|
1184
1284
|
}
|
|
1185
1285
|
}
|
|
1186
1286
|
|
|
1287
|
+
if (this.core.hintsChanged) await this.core.flushHints()
|
|
1187
1288
|
if (drop === false) this._update()
|
|
1188
1289
|
}
|
|
1189
1290
|
|
|
@@ -1255,11 +1356,32 @@ class Peer {
|
|
|
1255
1356
|
this._send(req)
|
|
1256
1357
|
}
|
|
1257
1358
|
|
|
1359
|
+
_includeLastBlock() {
|
|
1360
|
+
if (this.replicator._alwaysLatestBlock === 0) return null
|
|
1361
|
+
|
|
1362
|
+
const index = this.remoteLength - 1
|
|
1363
|
+
|
|
1364
|
+
// only valid if its an OOB get
|
|
1365
|
+
if (index < this.core.state.length) return null
|
|
1366
|
+
|
|
1367
|
+
// do the normal checks
|
|
1368
|
+
if (!this._remoteHasBlock(index)) return null
|
|
1369
|
+
if (!this._canRequest(index)) return null
|
|
1370
|
+
|
|
1371
|
+
// atm we only ever do one upgrade request in parallel, if that changes
|
|
1372
|
+
// then we should add some check here for inflights to avoid over requesting
|
|
1373
|
+
const b = this.replicator._blocks.add(index, PRIORITY.NORMAL)
|
|
1374
|
+
return b
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1258
1377
|
_requestUpgrade(u) {
|
|
1259
1378
|
const req = this._makeRequest(true, 0, 0)
|
|
1260
1379
|
if (req === null) return false
|
|
1261
1380
|
|
|
1262
|
-
this.
|
|
1381
|
+
const b = this._includeLastBlock()
|
|
1382
|
+
|
|
1383
|
+
if (b) this._sendBlockRequest(req, b)
|
|
1384
|
+
else this._send(req)
|
|
1263
1385
|
|
|
1264
1386
|
return true
|
|
1265
1387
|
}
|
|
@@ -1596,12 +1718,15 @@ module.exports = class Replicator {
|
|
|
1596
1718
|
notDownloadingLinger = NOT_DOWNLOADING_SLACK,
|
|
1597
1719
|
eagerUpgrade = true,
|
|
1598
1720
|
allowFork = true,
|
|
1721
|
+
allowPush = false,
|
|
1722
|
+
alwaysLatestBlock = false,
|
|
1599
1723
|
inflightRange = null
|
|
1600
1724
|
} = {}
|
|
1601
1725
|
) {
|
|
1602
1726
|
this.core = core
|
|
1603
1727
|
this.eagerUpgrade = eagerUpgrade
|
|
1604
1728
|
this.allowFork = allowFork
|
|
1729
|
+
this.allowPush = allowPush
|
|
1605
1730
|
this.ondownloading = null // optional external hook for monitoring downloading status
|
|
1606
1731
|
this.peers = []
|
|
1607
1732
|
this.findingPeers = 0 // updatable from the outside
|
|
@@ -1634,6 +1759,7 @@ module.exports = class Replicator {
|
|
|
1634
1759
|
this._blocks = new BlockTracker(this)
|
|
1635
1760
|
this._hashes = new BlockTracker(this)
|
|
1636
1761
|
|
|
1762
|
+
this._alwaysLatestBlock = alwaysLatestBlock ? 1 : 0
|
|
1637
1763
|
this._queued = []
|
|
1638
1764
|
|
|
1639
1765
|
this._seeks = []
|
|
@@ -1667,6 +1793,20 @@ module.exports = class Replicator {
|
|
|
1667
1793
|
return this.downloading || !this._inflight.idle
|
|
1668
1794
|
}
|
|
1669
1795
|
|
|
1796
|
+
async push(index) {
|
|
1797
|
+
const all = []
|
|
1798
|
+
for (const peer of this.peers) {
|
|
1799
|
+
all.push(peer.push(index))
|
|
1800
|
+
}
|
|
1801
|
+
await Promise.all(all)
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
setAllowPush(allowPush) {
|
|
1805
|
+
if (allowPush === this.allowPush) return
|
|
1806
|
+
this.allowPush = allowPush
|
|
1807
|
+
for (const peer of this.peers) peer.sendSync()
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1670
1810
|
setDownloading(downloading) {
|
|
1671
1811
|
clearTimeout(this._downloadingTimer)
|
|
1672
1812
|
|
|
@@ -1842,10 +1982,8 @@ module.exports = class Replicator {
|
|
|
1842
1982
|
// Also auto compresses the range based on local bitfield
|
|
1843
1983
|
clampRange(this.core, r)
|
|
1844
1984
|
|
|
1845
|
-
this._ranges.push(r)
|
|
1846
|
-
|
|
1847
1985
|
if (r.end !== -1 && r.start >= r.end) {
|
|
1848
|
-
this._resolveRangeRequest(r
|
|
1986
|
+
this._resolveRangeRequest(r)
|
|
1849
1987
|
return ref
|
|
1850
1988
|
}
|
|
1851
1989
|
|
|
@@ -1885,6 +2023,25 @@ module.exports = class Replicator {
|
|
|
1885
2023
|
if (cleared) this.updateAll()
|
|
1886
2024
|
}
|
|
1887
2025
|
|
|
2026
|
+
_matchingRequest(req, data) {
|
|
2027
|
+
if (this.allowPush) {
|
|
2028
|
+
return true
|
|
2029
|
+
}
|
|
2030
|
+
if (data.block !== null && (req.block === null || req.block.index !== data.block.index)) {
|
|
2031
|
+
return false
|
|
2032
|
+
}
|
|
2033
|
+
if (data.hash !== null && (req.hash === null || req.hash.index !== data.hash.index)) {
|
|
2034
|
+
return false
|
|
2035
|
+
}
|
|
2036
|
+
if (data.seek !== null && (req.seek === null || req.seek.bytes !== data.seek.bytes)) {
|
|
2037
|
+
return false
|
|
2038
|
+
}
|
|
2039
|
+
if (data.upgrade !== null && req.upgrade === null) {
|
|
2040
|
+
return false
|
|
2041
|
+
}
|
|
2042
|
+
return req.fork === data.fork
|
|
2043
|
+
}
|
|
2044
|
+
|
|
1888
2045
|
_addUpgradeMaybe() {
|
|
1889
2046
|
return this.eagerUpgrade === true ? this._addUpgrade() : this._upgrade
|
|
1890
2047
|
}
|
|
@@ -2114,12 +2271,9 @@ module.exports = class Replicator {
|
|
|
2114
2271
|
return true
|
|
2115
2272
|
}
|
|
2116
2273
|
|
|
2117
|
-
_resolveRangeRequest(req
|
|
2118
|
-
const head = this._ranges.pop()
|
|
2119
|
-
|
|
2120
|
-
if (index < this._ranges.length) this._ranges[index] = head
|
|
2121
|
-
|
|
2274
|
+
_resolveRangeRequest(req) {
|
|
2122
2275
|
req.resolve(true)
|
|
2276
|
+
req.gc()
|
|
2123
2277
|
}
|
|
2124
2278
|
|
|
2125
2279
|
_clearInflightBlock(tracker, req) {
|
|
@@ -2142,7 +2296,7 @@ module.exports = class Replicator {
|
|
|
2142
2296
|
}
|
|
2143
2297
|
|
|
2144
2298
|
_clearInflightUpgrade(req) {
|
|
2145
|
-
if (removeInflight(this._upgrade.inflight, req) === false) return
|
|
2299
|
+
if (this._upgrade === null || removeInflight(this._upgrade.inflight, req) === false) return
|
|
2146
2300
|
this._upgrade.gc()
|
|
2147
2301
|
}
|
|
2148
2302
|
|
|
@@ -2181,7 +2335,8 @@ module.exports = class Replicator {
|
|
|
2181
2335
|
clampRange(this.core, r)
|
|
2182
2336
|
|
|
2183
2337
|
if (r.end !== -1 && r.start >= r.end) {
|
|
2184
|
-
this._resolveRangeRequest(r
|
|
2338
|
+
this._resolveRangeRequest(r)
|
|
2339
|
+
i--
|
|
2185
2340
|
if (len > this._ranges.length) len--
|
|
2186
2341
|
if (this._ranges.length === MAX_RANGES) updateAll = true
|
|
2187
2342
|
}
|
|
@@ -2230,7 +2385,8 @@ module.exports = class Replicator {
|
|
|
2230
2385
|
const r = this._ranges[i]
|
|
2231
2386
|
|
|
2232
2387
|
if (r.ifAvailable) {
|
|
2233
|
-
this._resolveRangeRequest(r
|
|
2388
|
+
this._resolveRangeRequest(r)
|
|
2389
|
+
i--
|
|
2234
2390
|
}
|
|
2235
2391
|
}
|
|
2236
2392
|
}
|
|
@@ -2268,7 +2424,10 @@ module.exports = class Replicator {
|
|
|
2268
2424
|
}
|
|
2269
2425
|
}
|
|
2270
2426
|
|
|
2271
|
-
|
|
2427
|
+
if (req) {
|
|
2428
|
+
this._clearRequest(peer, req)
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2272
2431
|
this.updateAll()
|
|
2273
2432
|
}
|
|
2274
2433
|
|
|
@@ -2321,7 +2480,10 @@ module.exports = class Replicator {
|
|
|
2321
2480
|
}
|
|
2322
2481
|
|
|
2323
2482
|
_ondata(peer, req, data) {
|
|
2324
|
-
|
|
2483
|
+
if (req) {
|
|
2484
|
+
req.elapsed = Date.now() - req.timestamp
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2325
2487
|
if (data.block !== null) {
|
|
2326
2488
|
this._resolveBlockRequest(this._blocks, data.block.index, data.block.value, req)
|
|
2327
2489
|
this._ondownload(data.block.index, data.block.value.byteLength, peer, req)
|
|
@@ -2769,16 +2931,6 @@ module.exports = class Replicator {
|
|
|
2769
2931
|
}
|
|
2770
2932
|
}
|
|
2771
2933
|
|
|
2772
|
-
function matchingRequest(req, data) {
|
|
2773
|
-
if (data.block !== null && (req.block === null || req.block.index !== data.block.index)) {
|
|
2774
|
-
return false
|
|
2775
|
-
}
|
|
2776
|
-
if (data.hash !== null && (req.hash === null || req.hash.index !== data.hash.index)) return false
|
|
2777
|
-
if (data.seek !== null && (req.seek === null || req.seek.bytes !== data.seek.bytes)) return false
|
|
2778
|
-
if (data.upgrade !== null && req.upgrade === null) return false
|
|
2779
|
-
return req.fork === data.fork
|
|
2780
|
-
}
|
|
2781
|
-
|
|
2782
2934
|
function removeHotswap(block) {
|
|
2783
2935
|
if (block.hotswap === null) return false
|
|
2784
2936
|
block.hotswap.ref.remove(block)
|
package/lib/session-state.js
CHANGED
|
@@ -300,6 +300,8 @@ module.exports = class SessionState {
|
|
|
300
300
|
const append = b ? { start: b.start, length: b.appends, drop: false } : null
|
|
301
301
|
|
|
302
302
|
this.onappend(tree, append, true)
|
|
303
|
+
|
|
304
|
+
if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
|
|
303
305
|
} finally {
|
|
304
306
|
this.mutex.unlock()
|
|
305
307
|
this.core.checkIfIdle()
|
|
@@ -323,7 +325,18 @@ module.exports = class SessionState {
|
|
|
323
325
|
await this.mutex.lock()
|
|
324
326
|
|
|
325
327
|
try {
|
|
326
|
-
if (
|
|
328
|
+
if (batch.upgraded && batch.length <= this.length) {
|
|
329
|
+
await batch.downgrade()
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (!batch.commitable()) {
|
|
333
|
+
return false
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (this.core.preupdate !== null) {
|
|
337
|
+
await this.core.preupdate(batch, this.core.header.key)
|
|
338
|
+
}
|
|
339
|
+
|
|
327
340
|
const tx = this.createWriteBatch()
|
|
328
341
|
this.updating = true
|
|
329
342
|
|
|
@@ -360,6 +373,8 @@ module.exports = class SessionState {
|
|
|
360
373
|
|
|
361
374
|
this.onappend(head, bitfield, flushed)
|
|
362
375
|
}
|
|
376
|
+
|
|
377
|
+
if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
|
|
363
378
|
} finally {
|
|
364
379
|
this._clearActiveBatch()
|
|
365
380
|
this.updating = false
|
|
@@ -419,6 +434,8 @@ module.exports = class SessionState {
|
|
|
419
434
|
if (dependency) this.storage.setDependencyHead(dependency)
|
|
420
435
|
|
|
421
436
|
this.ontruncate(tree, tree.length, batch.treeLength, flushed)
|
|
437
|
+
|
|
438
|
+
if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
|
|
422
439
|
} finally {
|
|
423
440
|
this._unlock()
|
|
424
441
|
}
|
|
@@ -445,6 +462,8 @@ module.exports = class SessionState {
|
|
|
445
462
|
if (dependency) this.storage.setDependencyHead(dependency)
|
|
446
463
|
|
|
447
464
|
this.ontruncate(tree, batch.ancestors, batch.treeLength, flushed)
|
|
465
|
+
|
|
466
|
+
if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
|
|
448
467
|
} finally {
|
|
449
468
|
this._unlock()
|
|
450
469
|
}
|
|
@@ -487,23 +506,6 @@ module.exports = class SessionState {
|
|
|
487
506
|
return { dependency, tree, roots: batch.roots }
|
|
488
507
|
}
|
|
489
508
|
|
|
490
|
-
async flushHints() {
|
|
491
|
-
await this.mutex.lock()
|
|
492
|
-
|
|
493
|
-
try {
|
|
494
|
-
const tx = this.createWriteBatch()
|
|
495
|
-
|
|
496
|
-
tx.setHints({
|
|
497
|
-
contiguousLength: this.core.header.hints.contiguousLength,
|
|
498
|
-
remoteContiguousLength: this.core.header.hints.remoteContiguousLength
|
|
499
|
-
})
|
|
500
|
-
|
|
501
|
-
await tx.flush()
|
|
502
|
-
} finally {
|
|
503
|
-
this._unlock()
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
509
|
async clear(start, end, cleared) {
|
|
508
510
|
await this.mutex.lock()
|
|
509
511
|
|
|
@@ -548,16 +550,19 @@ module.exports = class SessionState {
|
|
|
548
550
|
// todo: atomic event handle
|
|
549
551
|
if (this.isDefault() && flushed) {
|
|
550
552
|
const length = end - start
|
|
553
|
+
|
|
551
554
|
this.core.updateContiguousLength({ start, length, drop: true })
|
|
552
555
|
this.core._setBitfieldRanges(start, end, false)
|
|
553
556
|
this.core.replicator.onhave(start, length, true)
|
|
557
|
+
|
|
558
|
+
if (this.core.hintsChanged) await this.core.flushHints()
|
|
554
559
|
}
|
|
555
560
|
} finally {
|
|
556
561
|
this._unlock()
|
|
557
562
|
}
|
|
558
563
|
}
|
|
559
564
|
|
|
560
|
-
async append(values, { signature, keyPair, preappend, maxLength = -1 } = {}) {
|
|
565
|
+
async append(values, { signature, keyPair, preappend, postappend, maxLength = -1 } = {}) {
|
|
561
566
|
if (!keyPair && this.isDefault()) keyPair = this.core.header.keyPair
|
|
562
567
|
|
|
563
568
|
await this.mutex.lock()
|
|
@@ -629,8 +634,12 @@ module.exports = class SessionState {
|
|
|
629
634
|
this.byteLength = batch.byteLength
|
|
630
635
|
this.signature = batch.signature
|
|
631
636
|
|
|
637
|
+
if (postappend) await postappend(values)
|
|
638
|
+
|
|
632
639
|
this.onappend(tree, bitfield, flushed)
|
|
633
640
|
|
|
641
|
+
if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
|
|
642
|
+
|
|
634
643
|
return { length: this.length, byteLength: this.byteLength }
|
|
635
644
|
} finally {
|
|
636
645
|
this._unlock()
|
|
@@ -780,6 +789,8 @@ module.exports = class SessionState {
|
|
|
780
789
|
|
|
781
790
|
if (truncating) this.ontruncate(tree, sharedLength, origLength, flushed)
|
|
782
791
|
if (sharedLength < length) this.onappend(tree, null, flushed)
|
|
792
|
+
|
|
793
|
+
if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
|
|
783
794
|
} finally {
|
|
784
795
|
this.mutex.unlock()
|
|
785
796
|
}
|