hypercore 10.24.10 → 10.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 +9 -2
- package/lib/batch.js +21 -4
- package/lib/core.js +55 -28
- package/lib/replicator.js +62 -6
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -511,6 +511,9 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
511
511
|
// because it doesn't really make a lot of sense.
|
|
512
512
|
if (Protomux.isProtomux(isInitiator)) return this._attachToMuxer(isInitiator, opts)
|
|
513
513
|
|
|
514
|
+
// if same stream is passed twice, ignore the 2nd one before we make sessions etc
|
|
515
|
+
if (isStream(isInitiator) && this._isAttached(isInitiator)) return isInitiator
|
|
516
|
+
|
|
514
517
|
const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
|
|
515
518
|
const noiseStream = protocolStream.noiseStream
|
|
516
519
|
const protocol = noiseStream.userData
|
|
@@ -521,6 +524,10 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
521
524
|
return protocolStream
|
|
522
525
|
}
|
|
523
526
|
|
|
527
|
+
_isAttached (stream) {
|
|
528
|
+
return stream.userData && this.replicator && this.replicator.attached(stream.userData)
|
|
529
|
+
}
|
|
530
|
+
|
|
524
531
|
_attachToMuxer (mux, useSession) {
|
|
525
532
|
if (this.opened) {
|
|
526
533
|
this._attachToMuxerOpened(mux, useSession)
|
|
@@ -771,9 +778,9 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
771
778
|
return true
|
|
772
779
|
}
|
|
773
780
|
|
|
774
|
-
batch ({ autoClose = true, session = true } = {}) {
|
|
781
|
+
batch ({ checkout = -1, autoClose = true, session = true } = {}) {
|
|
775
782
|
if (this._batch !== null) throw BATCH_ALREADY_EXISTS()
|
|
776
|
-
const batch = new Batch(session ? this.session() : this, autoClose)
|
|
783
|
+
const batch = new Batch(session ? this.session() : this, checkout, autoClose)
|
|
777
784
|
for (const session of this.sessions) session._batch = batch
|
|
778
785
|
return batch
|
|
779
786
|
}
|
package/lib/batch.js
CHANGED
|
@@ -3,7 +3,7 @@ const EventEmitter = require('events')
|
|
|
3
3
|
const c = require('compact-encoding')
|
|
4
4
|
|
|
5
5
|
module.exports = class HypercoreBatch extends EventEmitter {
|
|
6
|
-
constructor (session, autoClose) {
|
|
6
|
+
constructor (session, checkoutLength, autoClose) {
|
|
7
7
|
super()
|
|
8
8
|
|
|
9
9
|
this.session = session
|
|
@@ -16,6 +16,7 @@ module.exports = class HypercoreBatch extends EventEmitter {
|
|
|
16
16
|
this.fork = 0
|
|
17
17
|
|
|
18
18
|
this._appends = []
|
|
19
|
+
this._checkoutLength = checkoutLength
|
|
19
20
|
this._byteLength = 0
|
|
20
21
|
this._sessionLength = 0
|
|
21
22
|
this._sessionByteLength = 0
|
|
@@ -64,9 +65,21 @@ module.exports = class HypercoreBatch extends EventEmitter {
|
|
|
64
65
|
async ready () {
|
|
65
66
|
await this.session.ready()
|
|
66
67
|
if (this.opened) return
|
|
67
|
-
|
|
68
|
-
this.
|
|
69
|
-
|
|
68
|
+
|
|
69
|
+
if (this._checkoutLength !== -1) {
|
|
70
|
+
const batch = await this.session.core.tree.truncate(this._checkoutLength, this.session.fork)
|
|
71
|
+
batch.upgraded = false
|
|
72
|
+
batch.treeLength = batch.ancestors = this._checkoutLength
|
|
73
|
+
if (this.opened) return
|
|
74
|
+
this._sessionLength = batch.length
|
|
75
|
+
this._sessionByteLength = batch.byteLength
|
|
76
|
+
this._sessionBatch = batch
|
|
77
|
+
} else {
|
|
78
|
+
this._sessionLength = this.session.length
|
|
79
|
+
this._sessionByteLength = this.session.byteLength
|
|
80
|
+
this._sessionBatch = this.session.createTreeBatch()
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
this.fork = this.session.fork
|
|
71
84
|
this.opened = true
|
|
72
85
|
this.emit('ready')
|
|
@@ -77,6 +90,10 @@ module.exports = class HypercoreBatch extends EventEmitter {
|
|
|
77
90
|
await this.session.update(opts)
|
|
78
91
|
}
|
|
79
92
|
|
|
93
|
+
treeHash () {
|
|
94
|
+
return this._sessionBatch.hash()
|
|
95
|
+
}
|
|
96
|
+
|
|
80
97
|
setUserData (key, value, opts) {
|
|
81
98
|
return this.session.setUserData(key, value, opts)
|
|
82
99
|
}
|
package/lib/core.js
CHANGED
|
@@ -217,20 +217,21 @@ module.exports = class Core {
|
|
|
217
217
|
return false
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
async copyFrom (src, signature) {
|
|
220
|
+
async copyFrom (src, signature, { length = src.tree.length } = {}) {
|
|
221
221
|
await this._mutex.lock()
|
|
222
222
|
|
|
223
223
|
try {
|
|
224
|
-
let pos =
|
|
224
|
+
let pos = 0
|
|
225
225
|
|
|
226
|
-
while (pos
|
|
227
|
-
const segmentStart = pos
|
|
228
|
-
const segmentEnd = src.bitfield.
|
|
226
|
+
while (pos < length) {
|
|
227
|
+
const segmentStart = maximumSegmentStart(pos, src.bitfield, this.bitfield)
|
|
228
|
+
const segmentEnd = minimumSegmentEnd(segmentStart, src.bitfield, this.bitfield)
|
|
229
229
|
|
|
230
|
-
if (segmentStart < 0) break
|
|
230
|
+
if (segmentStart >= length || segmentStart < 0) break
|
|
231
231
|
|
|
232
232
|
const segment = []
|
|
233
233
|
|
|
234
|
+
pos = segmentStart
|
|
234
235
|
while (pos < segmentEnd) {
|
|
235
236
|
const val = await src.blocks.get(pos++)
|
|
236
237
|
segment.push(val)
|
|
@@ -241,13 +242,10 @@ module.exports = class Core {
|
|
|
241
242
|
|
|
242
243
|
this.bitfield.setRange(segmentStart, segmentEnd, true)
|
|
243
244
|
|
|
244
|
-
pos =
|
|
245
|
+
pos = segmentEnd + 1
|
|
245
246
|
}
|
|
246
247
|
|
|
247
|
-
|
|
248
|
-
// TODO: make flat iterator that computes needed nodes
|
|
249
|
-
|
|
250
|
-
for (let i = 0; i <= src.tree.length * 2; i++) {
|
|
248
|
+
for (let i = 0; i < length * 2; i++) {
|
|
251
249
|
const node = await src.tree.get(i, false)
|
|
252
250
|
if (node === null) continue
|
|
253
251
|
|
|
@@ -256,25 +254,35 @@ module.exports = class Core {
|
|
|
256
254
|
|
|
257
255
|
await this.tree.flush()
|
|
258
256
|
|
|
259
|
-
this.tree.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
257
|
+
if (length > this.tree.length) {
|
|
258
|
+
this.tree.fork = src.tree.fork
|
|
259
|
+
this.tree.roots = [...src.tree.roots]
|
|
260
|
+
this.tree.length = src.tree.length
|
|
261
|
+
this.tree.byteLength = src.tree.byteLength
|
|
262
|
+
|
|
263
|
+
if (length < this.tree.length) {
|
|
264
|
+
const batch = await src.tree.truncate(length)
|
|
265
|
+
this.tree.roots = [...batch.roots]
|
|
266
|
+
this.tree.length = batch.length
|
|
267
|
+
this.tree.byteLength = batch.byteLength
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const batch = this.tree.batch()
|
|
272
|
+
batch.signature = signature
|
|
273
|
+
this._verifyBatchUpgrade(batch, this.header.manifest)
|
|
274
|
+
this.tree.signature = signature
|
|
275
|
+
} catch (err) {
|
|
276
|
+
this.tree.signature = null
|
|
277
|
+
// TODO: how to handle signature failure?
|
|
278
|
+
throw err
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this.header.tree.length = this.tree.length
|
|
282
|
+
this.header.tree.rootHash = this.tree.hash()
|
|
283
|
+
this.header.tree.signature = this.tree.signature
|
|
273
284
|
}
|
|
274
285
|
|
|
275
|
-
this.header.tree.length = this.tree.length
|
|
276
|
-
this.header.tree.rootHash = this.tree.hash()
|
|
277
|
-
this.header.tree.signature = this.tree.signature
|
|
278
286
|
this.header.userData = src.header.userData // should copy?
|
|
279
287
|
|
|
280
288
|
await this._flushOplog()
|
|
@@ -457,6 +465,7 @@ module.exports = class Core {
|
|
|
457
465
|
|
|
458
466
|
batch.upgraded = batch.length > this.tree.length
|
|
459
467
|
batch.treeLength = this.tree.length
|
|
468
|
+
batch.ancestors = this.tree.length
|
|
460
469
|
if (batch.upgraded) batch.signature = signature || this.verifier.sign(batch, keyPair)
|
|
461
470
|
|
|
462
471
|
let byteOffset = batch.byteLength
|
|
@@ -897,3 +906,21 @@ async function flushHeader (oplog, bigHeader, header) {
|
|
|
897
906
|
}
|
|
898
907
|
|
|
899
908
|
function noop () {}
|
|
909
|
+
|
|
910
|
+
function maximumSegmentStart (start, src, dst) {
|
|
911
|
+
const a = src.firstSet(start)
|
|
912
|
+
const b = dst.firstUnset(start)
|
|
913
|
+
|
|
914
|
+
if (a === -1) return -1
|
|
915
|
+
if (b === -1) return a
|
|
916
|
+
return a < b ? b : a
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function minimumSegmentEnd (start, src, dst) {
|
|
920
|
+
const a = src.firstUnset(start)
|
|
921
|
+
const b = dst.firstSet(start)
|
|
922
|
+
|
|
923
|
+
if (a === -1) return -1
|
|
924
|
+
if (b === -1) return a
|
|
925
|
+
return a < b ? a : b
|
|
926
|
+
}
|
package/lib/replicator.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const b4a = require('b4a')
|
|
2
2
|
const safetyCatch = require('safety-catch')
|
|
3
3
|
const RandomIterator = require('random-array-iterator')
|
|
4
|
+
const flatTree = require('flat-tree')
|
|
4
5
|
const ReceiverQueue = require('./receiver-queue')
|
|
5
6
|
const RemoteBitfield = require('./remote-bitfield')
|
|
6
7
|
const { REQUEST_CANCELLED, REQUEST_TIMEOUT, INVALID_CAPABILITY, SNAPSHOT_NOT_AVAILABLE } = require('hypercore-errors')
|
|
@@ -439,6 +440,16 @@ class Peer {
|
|
|
439
440
|
}
|
|
440
441
|
}
|
|
441
442
|
|
|
443
|
+
closeIfIdle () {
|
|
444
|
+
if (this.remoteDownloading === false && this.replicator.isDownloading() === false) {
|
|
445
|
+
// idling, shut it down...
|
|
446
|
+
this.channel.close()
|
|
447
|
+
return true
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return false
|
|
451
|
+
}
|
|
452
|
+
|
|
442
453
|
async onsync ({ fork, length, remoteLength, canUpgrade, uploading, downloading, hasManifest }) {
|
|
443
454
|
const lengthChanged = length !== this.remoteLength
|
|
444
455
|
const sameFork = fork === this.core.tree.fork
|
|
@@ -451,11 +462,7 @@ class Peer {
|
|
|
451
462
|
this.remoteDownloading = downloading
|
|
452
463
|
this.remoteHasManifest = hasManifest
|
|
453
464
|
|
|
454
|
-
if (this.
|
|
455
|
-
// idling, shut it down...
|
|
456
|
-
this.channel.close()
|
|
457
|
-
return
|
|
458
|
-
}
|
|
465
|
+
if (this.closeIfIdle()) return
|
|
459
466
|
|
|
460
467
|
this.lengthAcked = sameFork ? remoteLength : 0
|
|
461
468
|
this.syncsProcessing++
|
|
@@ -583,7 +590,7 @@ class Peer {
|
|
|
583
590
|
proof = await this._getProof(msg)
|
|
584
591
|
} catch (err) {
|
|
585
592
|
safetyCatch(err)
|
|
586
|
-
if (isCriticalError(err)) throw err
|
|
593
|
+
if (msg.fork === this.core.tree.fork && isCriticalError(err)) throw err
|
|
587
594
|
}
|
|
588
595
|
}
|
|
589
596
|
|
|
@@ -830,6 +837,7 @@ class Peer {
|
|
|
830
837
|
|
|
831
838
|
if (this.remoteBitfield.get(index) === false) continue
|
|
832
839
|
if (this.core.bitfield.get(index) === true) continue
|
|
840
|
+
if (!this._hasTreeParent(index)) continue
|
|
833
841
|
|
|
834
842
|
// Check if this block is currently inflight - if so pick another
|
|
835
843
|
const b = this.replicator._blocks.get(index)
|
|
@@ -860,6 +868,38 @@ class Peer {
|
|
|
860
868
|
return this.remoteBitfield.get(b.index)
|
|
861
869
|
}
|
|
862
870
|
|
|
871
|
+
_hasTreeParent (index) {
|
|
872
|
+
if (this.remoteLength >= this.core.tree.length) return true
|
|
873
|
+
|
|
874
|
+
const ite = flatTree.iterator(index * 2)
|
|
875
|
+
|
|
876
|
+
let span = 2
|
|
877
|
+
let length = 0
|
|
878
|
+
|
|
879
|
+
while (true) {
|
|
880
|
+
ite.parent()
|
|
881
|
+
|
|
882
|
+
const left = (ite.index - ite.factor / 2 + 1) / 2
|
|
883
|
+
length = left + span
|
|
884
|
+
|
|
885
|
+
// if larger than local AND larger than remote - they share the root so its ok
|
|
886
|
+
if (length > this.core.tree.length) {
|
|
887
|
+
if (length > this.remoteLength) return true
|
|
888
|
+
break
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// its less than local but larger than remote so skip it
|
|
892
|
+
if (length > this.remoteLength) break
|
|
893
|
+
|
|
894
|
+
span *= 2
|
|
895
|
+
const first = this.core.bitfield.findFirst(true, left)
|
|
896
|
+
if (first > -1 && first < length) return true
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// TODO: push to async queue and check against our local merkle tree if we actually can request this block
|
|
900
|
+
return false
|
|
901
|
+
}
|
|
902
|
+
|
|
863
903
|
_requestBlock (b) {
|
|
864
904
|
const { length, fork } = this.core.tree
|
|
865
905
|
|
|
@@ -867,6 +907,9 @@ class Peer {
|
|
|
867
907
|
this._maybeWant(b.index)
|
|
868
908
|
return false
|
|
869
909
|
}
|
|
910
|
+
if (!this._hasTreeParent(b.index)) {
|
|
911
|
+
return false
|
|
912
|
+
}
|
|
870
913
|
|
|
871
914
|
const req = this._makeRequest(b.index >= length, b.priority)
|
|
872
915
|
if (req === null) return false
|
|
@@ -916,6 +959,10 @@ class Peer {
|
|
|
916
959
|
continue
|
|
917
960
|
}
|
|
918
961
|
|
|
962
|
+
if (!this._hasTreeParent(index)) {
|
|
963
|
+
continue
|
|
964
|
+
}
|
|
965
|
+
|
|
919
966
|
const b = this.replicator._blocks.add(index, PRIORITY.NORMAL)
|
|
920
967
|
|
|
921
968
|
if (b.inflight.length > 0) {
|
|
@@ -1041,6 +1088,7 @@ module.exports = class Replicator {
|
|
|
1041
1088
|
this.allowFork = allowFork
|
|
1042
1089
|
this.onpeerupdate = onpeerupdate
|
|
1043
1090
|
this.onupload = onupload
|
|
1091
|
+
this.ondownloading = null // optional external hook for monitoring downloading status
|
|
1044
1092
|
this.peers = []
|
|
1045
1093
|
this.findingPeers = 0 // updateable from the outside
|
|
1046
1094
|
this.destroyed = false
|
|
@@ -1095,7 +1143,11 @@ module.exports = class Replicator {
|
|
|
1095
1143
|
if (protomux.opened({ protocol: 'hypercore/alpha', id: this.discoveryKey })) continue
|
|
1096
1144
|
this._makePeer(protomux, session && session.session({ active: false }))
|
|
1097
1145
|
}
|
|
1146
|
+
} else {
|
|
1147
|
+
for (const peer of this.peers) peer.closeIfIdle()
|
|
1098
1148
|
}
|
|
1149
|
+
|
|
1150
|
+
if (this.ondownloading !== null && downloading) this.ondownloading()
|
|
1099
1151
|
}
|
|
1100
1152
|
|
|
1101
1153
|
cork () {
|
|
@@ -1795,6 +1847,10 @@ module.exports = class Replicator {
|
|
|
1795
1847
|
session.close().catch(noop)
|
|
1796
1848
|
}
|
|
1797
1849
|
|
|
1850
|
+
attached (protomux) {
|
|
1851
|
+
return this._attached.has(protomux)
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1798
1854
|
attachTo (protomux, session) {
|
|
1799
1855
|
const makePeer = this._makePeer.bind(this, protomux, session)
|
|
1800
1856
|
|