hypercore 11.20.2 → 11.21.1

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
@@ -441,6 +441,7 @@ module.exports = class Core {
441
441
  const verifier =
442
442
  this.verifier ||
443
443
  new Verifier(this.header.key, Verifier.createManifest(manifest), { legacy: this._legacy })
444
+
444
445
  if (!verifier.verify(batch, batch.signature)) {
445
446
  throw INVALID_SIGNATURE('Proof contains an invalid signature', this.discoveryKey)
446
447
  }
@@ -451,10 +452,6 @@ module.exports = class Core {
451
452
  async _verifyExclusive({ batch, bitfield, value, manifest }) {
452
453
  manifest = this._verifyBatchUpgrade(batch, manifest)
453
454
 
454
- if (!batch.commitable()) return false
455
-
456
- if (this.preupdate !== null) await this.preupdate(batch, this.header.key)
457
-
458
455
  if (
459
456
  !(await this.state._verifyBlock(
460
457
  batch,
@@ -668,7 +665,14 @@ module.exports = class Core {
668
665
  if (proof.fork !== this.state.fork) return false
669
666
 
670
667
  const batch = await MerkleTree.verify(this.state, proof)
671
- 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
+ }
672
676
 
673
677
  const value = (proof.block && proof.block.value) || null
674
678
  const op = {
@@ -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
+ ranges.push(this)
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,17 @@ class RangeRequest extends Attachable {
194
199
  }
195
200
 
196
201
  _unref() {
197
- const i = this.ranges.indexOf(this)
198
- if (i === -1) return
202
+ const rangeIndex = this.ranges.indexOf(this)
203
+ if (rangeIndex === -1) 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[rangeIndex] = h
208
+ }
209
+
210
+ if (this.end === -1) {
211
+ this.replicator._alwaysLatestBlock--
212
+ }
201
213
  }
202
214
 
203
215
  _cancel(r) {
@@ -371,7 +383,6 @@ class ProofRequest {
371
383
  const [proof, block] = await Promise.all([this.proof.settle(), this.block])
372
384
 
373
385
  if (this.manifest) proof.manifest = this.manifest
374
-
375
386
  if (!block && proof.block) return null
376
387
 
377
388
  if (block) proof.block.value = block
@@ -450,6 +461,8 @@ class Peer {
450
461
  this.remoteBitfield = new RemoteBitfield()
451
462
  this.missingBlocks = new RemoteBitfield()
452
463
 
464
+ this.pushing = false
465
+
453
466
  this.remoteFork = 0
454
467
  this.remoteLength = 0
455
468
  this.remoteCanUpgrade = false
@@ -457,6 +470,7 @@ class Peer {
457
470
  this.remoteDownloading = true
458
471
  this.remoteSynced = false
459
472
  this.remoteHasManifest = false
473
+ this.remoteAllowPush = false
460
474
  this.remoteRequests = new Map()
461
475
 
462
476
  this.segmentsWanted = new Set()
@@ -592,7 +606,8 @@ class Peer {
592
606
  canUpgrade: this.canUpgrade,
593
607
  uploading: true,
594
608
  downloading: this.replicator.isDownloading(),
595
- hasManifest: !!this.core.header.manifest && this.core.compat === false
609
+ hasManifest: !!this.core.header.manifest && this.core.compat === false,
610
+ allowPush: this.replicator.allowPush
596
611
  })
597
612
  incrementTx(this.stats.wireSync, this.replicator.stats.wireSync)
598
613
  }
@@ -677,7 +692,18 @@ class Peer {
677
692
  return false
678
693
  }
679
694
 
680
- async onsync({ fork, length, remoteLength, canUpgrade, uploading, downloading, hasManifest }) {
695
+ async onsync(msg) {
696
+ const {
697
+ fork,
698
+ length,
699
+ remoteLength,
700
+ canUpgrade,
701
+ uploading,
702
+ downloading,
703
+ hasManifest,
704
+ allowPush
705
+ } = msg
706
+
681
707
  const lengthChanged = length !== this.remoteLength
682
708
  const sameFork = fork === this.core.state.fork
683
709
 
@@ -688,6 +714,7 @@ class Peer {
688
714
  this.remoteUploading = uploading
689
715
  this.remoteDownloading = downloading
690
716
  this.remoteHasManifest = hasManifest
717
+ this.remoteAllowPush = allowPush
691
718
 
692
719
  if (this.closeIfIdle()) return
693
720
 
@@ -783,13 +810,13 @@ class Peer {
783
810
  }
784
811
  }
785
812
 
786
- async _getProof(batch, msg) {
813
+ async _getProof(batch, msg, pushing) {
787
814
  let block = null
788
815
 
789
816
  if (msg.block) {
790
817
  const index = msg.block.index
791
818
 
792
- if (msg.fork !== this.core.state.fork || !this.core.bitfield.get(index)) {
819
+ if (!pushing && (msg.fork !== this.core.state.fork || !this.core.bitfield.get(index))) {
793
820
  return new ProofRequest(msg, null, null, null)
794
821
  }
795
822
 
@@ -866,13 +893,50 @@ class Peer {
866
893
  this.receiverBusy = false
867
894
  }
868
895
 
896
+ async push(index) {
897
+ if (!this.remoteAllowPush) return
898
+ if (this.core.state.fork !== this.remoteFork) return
899
+ if (this.remoteBitfield.get(index)) return
900
+
901
+ const msg = {
902
+ id: 0,
903
+ fork: this.core.state.fork,
904
+ block: null,
905
+ hash: null,
906
+ seek: null,
907
+ upgrade: null,
908
+ manifest: this.remoteLength === 0,
909
+ priority: 0
910
+ }
911
+
912
+ msg.block = {
913
+ index,
914
+ nodes: MerkleTree.maxMissingNodes(2 * index, this.remoteLength)
915
+ }
916
+
917
+ if (index >= this.remoteLength) {
918
+ msg.upgrade = {
919
+ start: this.remoteLength,
920
+ length: this.core.state.length - this.remoteLength
921
+ }
922
+ }
923
+
924
+ const batch = this.core.storage.read()
925
+ const req = await this._getProof(batch, msg, true)
926
+ if (req === null) return
927
+
928
+ batch.tryFlush()
929
+
930
+ await this._fulfillRequest(req, true)
931
+ }
932
+
869
933
  async _handleRequest(msg) {
870
934
  const batch = this.core.storage.read()
871
935
 
872
936
  // TODO: could still be answerable if (index, fork) is an ancestor of the current fork
873
937
  const req =
874
938
  msg.fork === this.core.state.fork
875
- ? await this._getProof(batch, msg)
939
+ ? await this._getProof(batch, msg, false)
876
940
  : new ProofRequest(msg, null, null, null)
877
941
 
878
942
  if (req === null) {
@@ -882,20 +946,25 @@ class Peer {
882
946
 
883
947
  batch.tryFlush()
884
948
 
885
- await this._fulfillRequest(req)
949
+ await this._fulfillRequest(req, false)
886
950
  }
887
951
 
888
- async _fulfillRequest(req) {
952
+ async _fulfillRequest(req, pushing) {
889
953
  const proof = await req.fulfill()
890
954
 
891
- // if cancelled do not reply
892
- if (this.remoteRequests.get(req.msg.id) !== req.msg) {
955
+ if (!pushing) {
956
+ // if cancelled do not reply
957
+ if (this.remoteRequests.get(req.msg.id) !== req.msg) {
958
+ return
959
+ }
960
+
961
+ // sync from now on, so safe to delete from the map
962
+ this.remoteRequests.delete(req.msg.id)
963
+ } else if (!this.remoteAllowPush) {
964
+ // if pushing but remote disabled it, just drop it
893
965
  return
894
966
  }
895
967
 
896
- // sync from now on, so safe to delete from the map
897
- this.remoteRequests.delete(req.msg.id)
898
-
899
968
  if (!this.isActive() && proof.block !== null) {
900
969
  return
901
970
  }
@@ -924,6 +993,15 @@ class Peer {
924
993
  this.replicator._onupload(proof.block.index, proof.block.value.byteLength, this)
925
994
  }
926
995
 
996
+ // TODO: we should prob move to a sep length prop for this. This is just quick and dirty
997
+ // to produce better push upgrade proofs as we can better predict what length the remote
998
+ // is going to be at.
999
+ if ((pushing || this.pushing) && proof.upgrade) {
1000
+ this.pushing = true
1001
+ const remoteLength = proof.upgrade.start + proof.upgrade.length
1002
+ if (remoteLength > this.remoteLength) this.remoteLength = remoteLength
1003
+ }
1004
+
927
1005
  this.wireData.send({
928
1006
  request: req.msg.id,
929
1007
  fork: req.msg.fork,
@@ -975,6 +1053,11 @@ class Peer {
975
1053
  incrementTx(this.stats.wireRequest, this.replicator.stats.wireRequest)
976
1054
  }
977
1055
 
1056
+ _unmarkInflightBlockRequest(req, data) {
1057
+ if (isBlockRequest(req)) this.replicator._unmarkInflight(req.block.index)
1058
+ else if (data.block && !req) this.replicator._unmarkInflight(data.block.index)
1059
+ }
1060
+
978
1061
  async ondata(data) {
979
1062
  // always allow a fork conflict proof to be sent
980
1063
  if (data.request === 0 && data.upgrade && data.upgrade.start === 0) {
@@ -987,7 +1070,14 @@ class Peer {
987
1070
 
988
1071
  // no push atm, TODO: check if this satisfies another pending request
989
1072
  // 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
1073
+ if (req === null && reorg === false && !this.replicator.allowPush) {
1074
+ return
1075
+ }
1076
+
1077
+ if (req === null && reorg === false && data.block) {
1078
+ // mark this as inflight to avoid parallel requests
1079
+ this.replicator._markInflight(data.block.index)
1080
+ }
991
1081
 
992
1082
  if (req !== null) {
993
1083
  if (req.peer !== this) return
@@ -998,7 +1088,7 @@ class Peer {
998
1088
  if (reorg === true) return await this.replicator._onreorgdata(this, req, data)
999
1089
  } catch (err) {
1000
1090
  safetyCatch(err)
1001
- if (isBlockRequest(req)) this.replicator._unmarkInflight(req.block.index)
1091
+ this._unmarkInflightBlockRequest(req, data)
1002
1092
 
1003
1093
  this.paused = true
1004
1094
  this.replicator._oninvaliddata(err, req, data, this)
@@ -1009,13 +1099,13 @@ class Peer {
1009
1099
  if (isBlockRequest(req)) this.replicator._markProcessing(req.block.index)
1010
1100
 
1011
1101
  try {
1012
- if (!matchingRequest(req, data) || !(await this.core.verify(data, this))) {
1102
+ if (!this.replicator._matchingRequest(req, data) || !(await this.core.verify(data, this))) {
1013
1103
  this.replicator._onnodata(this, req, 0)
1014
1104
  return
1015
1105
  }
1016
1106
  } catch (err) {
1017
1107
  safetyCatch(err)
1018
- if (isBlockRequest(req)) this.replicator._unmarkInflight(req.block.index)
1108
+ this._unmarkInflightBlockRequest(req, data)
1019
1109
 
1020
1110
  if (err.code === 'WRITE_FAILED') {
1021
1111
  // For example, we don't want to keep pulling data when storage is full
@@ -1031,7 +1121,12 @@ class Peer {
1031
1121
  this._checkIfConflict()
1032
1122
  }
1033
1123
 
1034
- this.paused = true
1124
+ // if push mode, we MIGHT get an occasional bad message
1125
+ // no need to mega bail for that. TODO: rate limit instead as a general thing
1126
+ if (!this.replicator.allowPush) {
1127
+ this.paused = true
1128
+ }
1129
+
1035
1130
  this.replicator._onnodata(this, req, 0)
1036
1131
  this.replicator._oninvaliddata(err, req, data, this)
1037
1132
  return
@@ -1049,7 +1144,11 @@ class Peer {
1049
1144
 
1050
1145
  onnodata({ request, reason }) {
1051
1146
  const req = request > 0 ? this.replicator._inflight.get(request) : null
1052
- if (req === null || req.peer !== this) return
1147
+
1148
+ if (req === null || req.peer !== this) {
1149
+ this.replicator.updateAll()
1150
+ return
1151
+ }
1053
1152
 
1054
1153
  this._onrequestroundtrip(req)
1055
1154
  this.replicator._onnodata(this, req, reason)
@@ -1256,11 +1355,32 @@ class Peer {
1256
1355
  this._send(req)
1257
1356
  }
1258
1357
 
1358
+ _includeLastBlock() {
1359
+ if (this.replicator._alwaysLatestBlock === 0) return null
1360
+
1361
+ const index = this.remoteLength - 1
1362
+
1363
+ // only valid if its an OOB get
1364
+ if (index < this.core.state.length) return null
1365
+
1366
+ // do the normal checks
1367
+ if (!this._remoteHasBlock(index)) return null
1368
+ if (!this._canRequest(index)) return null
1369
+
1370
+ // atm we only ever do one upgrade request in parallel, if that changes
1371
+ // then we should add some check here for inflights to avoid over requesting
1372
+ const b = this.replicator._blocks.add(index, PRIORITY.NORMAL)
1373
+ return b
1374
+ }
1375
+
1259
1376
  _requestUpgrade(u) {
1260
1377
  const req = this._makeRequest(true, 0, 0)
1261
1378
  if (req === null) return false
1262
1379
 
1263
- this._send(req)
1380
+ const b = this._includeLastBlock()
1381
+
1382
+ if (b) this._sendBlockRequest(req, b)
1383
+ else this._send(req)
1264
1384
 
1265
1385
  return true
1266
1386
  }
@@ -1597,12 +1717,15 @@ module.exports = class Replicator {
1597
1717
  notDownloadingLinger = NOT_DOWNLOADING_SLACK,
1598
1718
  eagerUpgrade = true,
1599
1719
  allowFork = true,
1720
+ allowPush = false,
1721
+ alwaysLatestBlock = false,
1600
1722
  inflightRange = null
1601
1723
  } = {}
1602
1724
  ) {
1603
1725
  this.core = core
1604
1726
  this.eagerUpgrade = eagerUpgrade
1605
1727
  this.allowFork = allowFork
1728
+ this.allowPush = allowPush
1606
1729
  this.ondownloading = null // optional external hook for monitoring downloading status
1607
1730
  this.peers = []
1608
1731
  this.findingPeers = 0 // updatable from the outside
@@ -1635,6 +1758,7 @@ module.exports = class Replicator {
1635
1758
  this._blocks = new BlockTracker(this)
1636
1759
  this._hashes = new BlockTracker(this)
1637
1760
 
1761
+ this._alwaysLatestBlock = alwaysLatestBlock ? 1 : 0
1638
1762
  this._queued = []
1639
1763
 
1640
1764
  this._seeks = []
@@ -1668,6 +1792,20 @@ module.exports = class Replicator {
1668
1792
  return this.downloading || !this._inflight.idle
1669
1793
  }
1670
1794
 
1795
+ async push(index) {
1796
+ const all = []
1797
+ for (const peer of this.peers) {
1798
+ all.push(peer.push(index))
1799
+ }
1800
+ await Promise.all(all)
1801
+ }
1802
+
1803
+ setAllowPush(allowPush) {
1804
+ if (allowPush === this.allowPush) return
1805
+ this.allowPush = allowPush
1806
+ for (const peer of this.peers) peer.sendSync()
1807
+ }
1808
+
1671
1809
  setDownloading(downloading) {
1672
1810
  clearTimeout(this._downloadingTimer)
1673
1811
 
@@ -1843,10 +1981,8 @@ module.exports = class Replicator {
1843
1981
  // Also auto compresses the range based on local bitfield
1844
1982
  clampRange(this.core, r)
1845
1983
 
1846
- this._ranges.push(r)
1847
-
1848
1984
  if (r.end !== -1 && r.start >= r.end) {
1849
- this._resolveRangeRequest(r, this._ranges.length - 1)
1985
+ this._resolveRangeRequest(r)
1850
1986
  return ref
1851
1987
  }
1852
1988
 
@@ -1886,6 +2022,25 @@ module.exports = class Replicator {
1886
2022
  if (cleared) this.updateAll()
1887
2023
  }
1888
2024
 
2025
+ _matchingRequest(req, data) {
2026
+ if (this.allowPush) {
2027
+ return true
2028
+ }
2029
+ if (data.block !== null && (req.block === null || req.block.index !== data.block.index)) {
2030
+ return false
2031
+ }
2032
+ if (data.hash !== null && (req.hash === null || req.hash.index !== data.hash.index)) {
2033
+ return false
2034
+ }
2035
+ if (data.seek !== null && (req.seek === null || req.seek.bytes !== data.seek.bytes)) {
2036
+ return false
2037
+ }
2038
+ if (data.upgrade !== null && req.upgrade === null) {
2039
+ return false
2040
+ }
2041
+ return req.fork === data.fork
2042
+ }
2043
+
1889
2044
  _addUpgradeMaybe() {
1890
2045
  return this.eagerUpgrade === true ? this._addUpgrade() : this._upgrade
1891
2046
  }
@@ -2115,12 +2270,9 @@ module.exports = class Replicator {
2115
2270
  return true
2116
2271
  }
2117
2272
 
2118
- _resolveRangeRequest(req, index) {
2119
- const head = this._ranges.pop()
2120
-
2121
- if (index < this._ranges.length) this._ranges[index] = head
2122
-
2273
+ _resolveRangeRequest(req) {
2123
2274
  req.resolve(true)
2275
+ req.gc()
2124
2276
  }
2125
2277
 
2126
2278
  _clearInflightBlock(tracker, req) {
@@ -2143,7 +2295,7 @@ module.exports = class Replicator {
2143
2295
  }
2144
2296
 
2145
2297
  _clearInflightUpgrade(req) {
2146
- if (removeInflight(this._upgrade.inflight, req) === false) return
2298
+ if (this._upgrade === null || removeInflight(this._upgrade.inflight, req) === false) return
2147
2299
  this._upgrade.gc()
2148
2300
  }
2149
2301
 
@@ -2182,7 +2334,8 @@ module.exports = class Replicator {
2182
2334
  clampRange(this.core, r)
2183
2335
 
2184
2336
  if (r.end !== -1 && r.start >= r.end) {
2185
- this._resolveRangeRequest(r, i--)
2337
+ this._resolveRangeRequest(r)
2338
+ i--
2186
2339
  if (len > this._ranges.length) len--
2187
2340
  if (this._ranges.length === MAX_RANGES) updateAll = true
2188
2341
  }
@@ -2231,7 +2384,8 @@ module.exports = class Replicator {
2231
2384
  const r = this._ranges[i]
2232
2385
 
2233
2386
  if (r.ifAvailable) {
2234
- this._resolveRangeRequest(r, i--)
2387
+ this._resolveRangeRequest(r)
2388
+ i--
2235
2389
  }
2236
2390
  }
2237
2391
  }
@@ -2269,7 +2423,10 @@ module.exports = class Replicator {
2269
2423
  }
2270
2424
  }
2271
2425
 
2272
- this._clearRequest(peer, req)
2426
+ if (req) {
2427
+ this._clearRequest(peer, req)
2428
+ }
2429
+
2273
2430
  this.updateAll()
2274
2431
  }
2275
2432
 
@@ -2322,7 +2479,10 @@ module.exports = class Replicator {
2322
2479
  }
2323
2480
 
2324
2481
  _ondata(peer, req, data) {
2325
- req.elapsed = Date.now() - req.timestamp
2482
+ if (req) {
2483
+ req.elapsed = Date.now() - req.timestamp
2484
+ }
2485
+
2326
2486
  if (data.block !== null) {
2327
2487
  this._resolveBlockRequest(this._blocks, data.block.index, data.block.value, req)
2328
2488
  this._ondownload(data.block.index, data.block.value.byteLength, peer, req)
@@ -2770,16 +2930,6 @@ module.exports = class Replicator {
2770
2930
  }
2771
2931
  }
2772
2932
 
2773
- function matchingRequest(req, data) {
2774
- if (data.block !== null && (req.block === null || req.block.index !== data.block.index)) {
2775
- return false
2776
- }
2777
- if (data.hash !== null && (req.hash === null || req.hash.index !== data.hash.index)) return false
2778
- if (data.seek !== null && (req.seek === null || req.seek.bytes !== data.seek.bytes)) return false
2779
- if (data.upgrade !== null && req.upgrade === null) return false
2780
- return req.fork === data.fork
2781
- }
2782
-
2783
2933
  function removeHotswap(block) {
2784
2934
  if (block.hotswap === null) return false
2785
2935
  block.hotswap.ref.remove(block)
@@ -325,7 +325,18 @@ module.exports = class SessionState {
325
325
  await this.mutex.lock()
326
326
 
327
327
  try {
328
- 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
+
329
340
  const tx = this.createWriteBatch()
330
341
  this.updating = true
331
342
 
@@ -551,7 +562,7 @@ module.exports = class SessionState {
551
562
  }
552
563
  }
553
564
 
554
- async append(values, { signature, keyPair, preappend, maxLength = -1 } = {}) {
565
+ async append(values, { signature, keyPair, preappend, postappend, maxLength = -1 } = {}) {
555
566
  if (!keyPair && this.isDefault()) keyPair = this.core.header.keyPair
556
567
 
557
568
  await this.mutex.lock()
@@ -623,6 +634,8 @@ module.exports = class SessionState {
623
634
  this.byteLength = batch.byteLength
624
635
  this.signature = batch.signature
625
636
 
637
+ if (postappend) await postappend(values)
638
+
626
639
  this.onappend(tree, bitfield, flushed)
627
640
 
628
641
  if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "11.20.2",
3
+ "version": "11.21.1",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {