hypercore 11.20.2 → 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
@@ -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
+ 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)
@@ -1256,11 +1356,32 @@ class Peer {
1256
1356
  this._send(req)
1257
1357
  }
1258
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
+
1259
1377
  _requestUpgrade(u) {
1260
1378
  const req = this._makeRequest(true, 0, 0)
1261
1379
  if (req === null) return false
1262
1380
 
1263
- this._send(req)
1381
+ const b = this._includeLastBlock()
1382
+
1383
+ if (b) this._sendBlockRequest(req, b)
1384
+ else this._send(req)
1264
1385
 
1265
1386
  return true
1266
1387
  }
@@ -1597,12 +1718,15 @@ module.exports = class Replicator {
1597
1718
  notDownloadingLinger = NOT_DOWNLOADING_SLACK,
1598
1719
  eagerUpgrade = true,
1599
1720
  allowFork = true,
1721
+ allowPush = false,
1722
+ alwaysLatestBlock = false,
1600
1723
  inflightRange = null
1601
1724
  } = {}
1602
1725
  ) {
1603
1726
  this.core = core
1604
1727
  this.eagerUpgrade = eagerUpgrade
1605
1728
  this.allowFork = allowFork
1729
+ this.allowPush = allowPush
1606
1730
  this.ondownloading = null // optional external hook for monitoring downloading status
1607
1731
  this.peers = []
1608
1732
  this.findingPeers = 0 // updatable from the outside
@@ -1635,6 +1759,7 @@ module.exports = class Replicator {
1635
1759
  this._blocks = new BlockTracker(this)
1636
1760
  this._hashes = new BlockTracker(this)
1637
1761
 
1762
+ this._alwaysLatestBlock = alwaysLatestBlock ? 1 : 0
1638
1763
  this._queued = []
1639
1764
 
1640
1765
  this._seeks = []
@@ -1668,6 +1793,20 @@ module.exports = class Replicator {
1668
1793
  return this.downloading || !this._inflight.idle
1669
1794
  }
1670
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
+
1671
1810
  setDownloading(downloading) {
1672
1811
  clearTimeout(this._downloadingTimer)
1673
1812
 
@@ -1843,10 +1982,8 @@ module.exports = class Replicator {
1843
1982
  // Also auto compresses the range based on local bitfield
1844
1983
  clampRange(this.core, r)
1845
1984
 
1846
- this._ranges.push(r)
1847
-
1848
1985
  if (r.end !== -1 && r.start >= r.end) {
1849
- this._resolveRangeRequest(r, this._ranges.length - 1)
1986
+ this._resolveRangeRequest(r)
1850
1987
  return ref
1851
1988
  }
1852
1989
 
@@ -1886,6 +2023,25 @@ module.exports = class Replicator {
1886
2023
  if (cleared) this.updateAll()
1887
2024
  }
1888
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
+
1889
2045
  _addUpgradeMaybe() {
1890
2046
  return this.eagerUpgrade === true ? this._addUpgrade() : this._upgrade
1891
2047
  }
@@ -2115,12 +2271,9 @@ module.exports = class Replicator {
2115
2271
  return true
2116
2272
  }
2117
2273
 
2118
- _resolveRangeRequest(req, index) {
2119
- const head = this._ranges.pop()
2120
-
2121
- if (index < this._ranges.length) this._ranges[index] = head
2122
-
2274
+ _resolveRangeRequest(req) {
2123
2275
  req.resolve(true)
2276
+ req.gc()
2124
2277
  }
2125
2278
 
2126
2279
  _clearInflightBlock(tracker, req) {
@@ -2143,7 +2296,7 @@ module.exports = class Replicator {
2143
2296
  }
2144
2297
 
2145
2298
  _clearInflightUpgrade(req) {
2146
- if (removeInflight(this._upgrade.inflight, req) === false) return
2299
+ if (this._upgrade === null || removeInflight(this._upgrade.inflight, req) === false) return
2147
2300
  this._upgrade.gc()
2148
2301
  }
2149
2302
 
@@ -2182,7 +2335,8 @@ module.exports = class Replicator {
2182
2335
  clampRange(this.core, r)
2183
2336
 
2184
2337
  if (r.end !== -1 && r.start >= r.end) {
2185
- this._resolveRangeRequest(r, i--)
2338
+ this._resolveRangeRequest(r)
2339
+ i--
2186
2340
  if (len > this._ranges.length) len--
2187
2341
  if (this._ranges.length === MAX_RANGES) updateAll = true
2188
2342
  }
@@ -2231,7 +2385,8 @@ module.exports = class Replicator {
2231
2385
  const r = this._ranges[i]
2232
2386
 
2233
2387
  if (r.ifAvailable) {
2234
- this._resolveRangeRequest(r, i--)
2388
+ this._resolveRangeRequest(r)
2389
+ i--
2235
2390
  }
2236
2391
  }
2237
2392
  }
@@ -2269,7 +2424,10 @@ module.exports = class Replicator {
2269
2424
  }
2270
2425
  }
2271
2426
 
2272
- this._clearRequest(peer, req)
2427
+ if (req) {
2428
+ this._clearRequest(peer, req)
2429
+ }
2430
+
2273
2431
  this.updateAll()
2274
2432
  }
2275
2433
 
@@ -2322,7 +2480,10 @@ module.exports = class Replicator {
2322
2480
  }
2323
2481
 
2324
2482
  _ondata(peer, req, data) {
2325
- req.elapsed = Date.now() - req.timestamp
2483
+ if (req) {
2484
+ req.elapsed = Date.now() - req.timestamp
2485
+ }
2486
+
2326
2487
  if (data.block !== null) {
2327
2488
  this._resolveBlockRequest(this._blocks, data.block.index, data.block.value, req)
2328
2489
  this._ondownload(data.block.index, data.block.value.byteLength, peer, req)
@@ -2770,16 +2931,6 @@ module.exports = class Replicator {
2770
2931
  }
2771
2932
  }
2772
2933
 
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
2934
  function removeHotswap(block) {
2784
2935
  if (block.hotswap === null) return false
2785
2936
  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.0",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {