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 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
- this._sessionLength = this.session.length
68
- this._sessionByteLength = this.session.byteLength
69
- this._sessionBatch = this.session.createTreeBatch()
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 = src.bitfield.firstSet(0)
224
+ let pos = 0
225
225
 
226
- while (pos >= 0) {
227
- const segmentStart = pos
228
- const segmentEnd = src.bitfield.firstUnset(pos)
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 = src.bitfield.firstSet(segmentEnd + 1)
245
+ pos = segmentEnd + 1
245
246
  }
246
247
 
247
- // TODO: is it an issue to move the nodes directly?
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.fork = src.tree.fork
260
- this.tree.roots = [...src.tree.roots]
261
- this.tree.length = src.tree.length
262
- this.tree.byteLength = src.tree.byteLength
263
-
264
- try {
265
- const batch = this.tree.batch()
266
- batch.signature = signature
267
- this._verifyBatchUpgrade(batch, this.header.manifest)
268
- this.tree.signature = signature
269
- } catch (err) {
270
- this.tree.signature = null
271
- // TODO: how to handle signature failure?
272
- throw err
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.remoteDownloading === false && this.replicator.isDownloading() === false) {
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.24.10",
3
+ "version": "10.25.0",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {