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 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
- if (!batch.commitable()) return false
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
- async updateRemoteContiguousLength(length) {
745
+ updateRemoteContiguousLength(length) {
723
746
  this.header.hints.remoteContiguousLength = length
724
- await this.state.flushHints()
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)
@@ -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
- const i = this.ranges.indexOf(this)
198
- if (i === -1) return
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 (i < this.ranges.length) this.ranges[i] = h
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({ fork, length, remoteLength, canUpgrade, uploading, downloading, hasManifest }) {
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
- // if cancelled do not reply
892
- if (this.remoteRequests.get(req.msg.id) !== req.msg) {
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) return
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
- if (isBlockRequest(req)) this.replicator._unmarkInflight(req.block.index)
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 (!matchingRequest(req, data) || !(await this.core.verify(data, this))) {
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
- if (isBlockRequest(req)) this.replicator._unmarkInflight(req.block.index)
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
- this.paused = true
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
- if (req === null || req.peer !== this) return
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
- await this.core.updateRemoteContiguousLength(length)
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._send(req)
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, this._ranges.length - 1)
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, index) {
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, i--)
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, i--)
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
- this._clearRequest(peer, req)
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
- req.elapsed = Date.now() - req.timestamp
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)
@@ -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 (!batch.commitable()) return false
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "11.20.1",
3
+ "version": "11.21.0",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {