hypercore 10.38.2 → 11.0.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/lib/replicator.js CHANGED
@@ -51,6 +51,7 @@ const PRIORITY = {
51
51
  class Attachable {
52
52
  constructor () {
53
53
  this.resolved = false
54
+ this.processing = false
54
55
  this.refs = []
55
56
  }
56
57
 
@@ -101,7 +102,12 @@ class Attachable {
101
102
  }
102
103
 
103
104
  gc () {
104
- if (this.refs.length === 0) this._unref()
105
+ if (this.refs.length === 0 && !this.processing) this._unref()
106
+ }
107
+
108
+ processed () {
109
+ this.processing = false
110
+ this.gc()
105
111
  }
106
112
 
107
113
  _cancel (r, err) {
@@ -333,8 +339,28 @@ class RoundtripQueue {
333
339
  }
334
340
  }
335
341
 
342
+ class ProofRequest {
343
+ constructor (msg, proof, block, manifest) {
344
+ this.msg = msg
345
+ this.proof = proof
346
+ this.block = block
347
+ this.manifest = manifest
348
+ }
349
+
350
+ async fulfill () {
351
+ if (this.proof === null) return null
352
+
353
+ const proof = await this.proof.settle()
354
+
355
+ if (this.manifest) proof.manifest = this.manifest
356
+ if (this.block) proof.block.value = await this.block
357
+
358
+ return proof
359
+ }
360
+ }
361
+
336
362
  class Peer {
337
- constructor (replicator, protomux, channel, useSession, inflightRange) {
363
+ constructor (replicator, protomux, channel, inflightRange) {
338
364
  this.core = replicator.core
339
365
  this.replicator = replicator
340
366
  this.stream = protomux.stream
@@ -346,8 +372,6 @@ class Peer {
346
372
  this.paused = false
347
373
  this.removed = false
348
374
 
349
- this.useSession = useSession
350
-
351
375
  this.channel = channel
352
376
  this.channel.userData = this
353
377
 
@@ -418,6 +442,7 @@ class Peer {
418
442
  this.lastExtensionRecv = ''
419
443
 
420
444
  replicator._ifAvailable++
445
+ replicator._active++
421
446
  }
422
447
 
423
448
  get remoteContiguousLength () {
@@ -490,16 +515,16 @@ class Peer {
490
515
  return
491
516
  }
492
517
 
493
- if (this.core.tree.fork !== this.remoteFork) {
518
+ if (this.core.state.fork !== this.remoteFork) {
494
519
  this.canUpgrade = false
495
520
  }
496
521
 
497
522
  this.needsSync = false
498
523
 
499
524
  this.wireSync.send({
500
- fork: this.core.tree.fork,
501
- length: this.core.tree.length,
502
- remoteLength: this.core.tree.fork === this.remoteFork ? this.remoteLength : 0,
525
+ fork: this.core.state.fork,
526
+ length: this.core.state.length,
527
+ remoteLength: this.core.state.fork === this.remoteFork ? this.remoteLength : 0,
503
528
  canUpgrade: this.canUpgrade,
504
529
  uploading: true,
505
530
  downloading: this.replicator.isDownloading(),
@@ -509,7 +534,7 @@ class Peer {
509
534
  }
510
535
 
511
536
  onopen ({ seeks, capability }) {
512
- const expected = caps.replicate(this.stream.isInitiator === false, this.replicator.key, this.stream.handshakeHash)
537
+ const expected = caps.replicate(this.stream.isInitiator === false, this.core.key, this.stream.handshakeHash)
513
538
 
514
539
  if (b4a.equals(capability, expected) !== true) { // TODO: change this to a rejection instead, less leakage
515
540
  throw INVALID_CAPABILITY('Remote sent an invalid replication capability')
@@ -523,11 +548,11 @@ class Peer {
523
548
 
524
549
  this.sendSync()
525
550
 
526
- const contig = Math.min(this.core.tree.length, this.core.header.hints.contiguousLength)
551
+ const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)
527
552
  if (contig > 0) {
528
553
  this.broadcastRange(0, contig, false)
529
554
 
530
- if (contig === this.core.tree.length) {
555
+ if (contig === this.core.state.length) {
531
556
  this.broadcastedNonSparse = true
532
557
  }
533
558
  }
@@ -536,6 +561,8 @@ class Peer {
536
561
  this.replicator._addPeer(this)
537
562
 
538
563
  this.protomux.uncork()
564
+
565
+ this.core.checkIfIdle()
539
566
  }
540
567
 
541
568
  onclose (isRemote) {
@@ -546,10 +573,6 @@ class Peer {
546
573
  this.remoteUploading === true && this.replicator.downloading === true
547
574
 
548
575
  if (this.remoteOpened === false) {
549
- if (this.useSession) {
550
- this.replicator._peerSessions--
551
- this.replicator._closeSessionMaybe()
552
- }
553
576
  this.replicator._ifAvailable--
554
577
  this.replicator.updateAll()
555
578
  return
@@ -567,12 +590,7 @@ class Peer {
567
590
  this.replicator._removePeer(this)
568
591
 
569
592
  if (reopen) {
570
- this.replicator._makePeer(this.protomux, this.useSession)
571
- }
572
-
573
- if (this.useSession) {
574
- this.replicator._peerSessions--
575
- this.replicator._closeSessionMaybe()
593
+ this.replicator._makePeer(this.protomux)
576
594
  }
577
595
  }
578
596
 
@@ -588,7 +606,7 @@ class Peer {
588
606
 
589
607
  async onsync ({ fork, length, remoteLength, canUpgrade, uploading, downloading, hasManifest }) {
590
608
  const lengthChanged = length !== this.remoteLength
591
- const sameFork = fork === this.core.tree.fork
609
+ const sameFork = fork === this.core.state.fork
592
610
 
593
611
  this.remoteSynced = true
594
612
  this.remoteFork = fork
@@ -605,7 +623,7 @@ class Peer {
605
623
 
606
624
  this.replicator._updateFork(this)
607
625
 
608
- if (this.remoteLength > this.core.tree.length && this.lengthAcked === this.core.tree.length) {
626
+ if (this.remoteLength > this.core.state.length && this.lengthAcked === this.core.state.length) {
609
627
  if (this.replicator._addUpgradeMaybe() !== null) this._update()
610
628
  }
611
629
 
@@ -613,13 +631,13 @@ class Peer {
613
631
  ? this.canUpgrade && sameFork
614
632
  : await this._canUpgrade(length, fork)
615
633
 
616
- if (length === this.remoteLength && fork === this.core.tree.fork) {
634
+ if (length === this.remoteLength && fork === this.core.state.fork) {
617
635
  this.canUpgrade = upgrade
618
636
  }
619
637
 
620
638
  if (--this.syncsProcessing !== 0) return // ie not latest
621
639
 
622
- if (this.needsSync === true || (this.core.tree.fork === this.remoteFork && this.core.tree.length > this.remoteLength)) {
640
+ if (this.needsSync === true || (this.core.state.fork === this.remoteFork && this.core.state.length > this.remoteLength)) {
623
641
  this.signalUpgrade()
624
642
  }
625
643
 
@@ -627,18 +645,18 @@ class Peer {
627
645
  }
628
646
 
629
647
  _shouldUpdateCanUpgrade () {
630
- return this.core.tree.fork === this.remoteFork &&
631
- this.core.tree.length > this.remoteLength &&
648
+ return this.core.state.fork === this.remoteFork &&
649
+ this.core.state.length > this.remoteLength &&
632
650
  this.canUpgrade === false &&
633
651
  this.syncsProcessing === 0
634
652
  }
635
653
 
636
654
  async _updateCanUpgradeAndSync () {
637
- const { length, fork } = this.core.tree
655
+ const { length, fork } = this.core.state
638
656
 
639
657
  const canUpgrade = await this._canUpgrade(this.remoteLength, this.remoteFork)
640
658
 
641
- if (this.syncsProcessing > 0 || length !== this.core.tree.length || fork !== this.core.tree.fork) {
659
+ if (this.syncsProcessing > 0 || length !== this.core.state.length || fork !== this.core.state.fork) {
642
660
  return
643
661
  }
644
662
  if (canUpgrade === this.canUpgrade) {
@@ -651,16 +669,16 @@ class Peer {
651
669
 
652
670
  // Safe to call in the background - never fails
653
671
  async _canUpgrade (remoteLength, remoteFork) {
654
- if (remoteFork !== this.core.tree.fork) return false
672
+ if (remoteFork !== this.core.state.fork) return false
655
673
 
656
674
  if (remoteLength === 0) return true
657
- if (remoteLength >= this.core.tree.length) return false
675
+ if (remoteLength >= this.core.state.length) return false
658
676
 
659
677
  try {
660
678
  // Rely on caching to make sure this is cheap...
661
679
  const canUpgrade = await this.core.tree.upgradeable(remoteLength)
662
680
 
663
- if (remoteFork !== this.core.tree.fork) return false
681
+ if (remoteFork !== this.core.state.fork) return false
664
682
 
665
683
  return canUpgrade
666
684
  } catch {
@@ -668,24 +686,24 @@ class Peer {
668
686
  }
669
687
  }
670
688
 
671
- async _getProof (msg) {
672
- const proof = await this.core.tree.proof(msg)
689
+ async _getProof (batch, msg) {
690
+ let block = null
673
691
 
674
- if (proof.block) {
692
+ if (msg.block) {
675
693
  const index = msg.block.index
676
694
 
677
- if (msg.fork !== this.core.tree.fork || !this.core.bitfield.get(index)) {
678
- return null
695
+ if (msg.fork !== this.core.state.fork || !this.core.bitfield.get(index)) {
696
+ return new ProofRequest(msg, null, null, null)
679
697
  }
680
698
 
681
- proof.block.value = await this.core.blocks.get(index)
699
+ block = this.core.blocks.get(batch, index)
682
700
  }
683
701
 
684
- if (msg.manifest && !this.core.compat) {
685
- proof.manifest = this.core.header.manifest
686
- }
702
+ const manifest = (msg.manifest && !this.core.compat) ? this.core.header.manifest : null
703
+ const treeBatch = this.core.state.createTreeBatch()
704
+ const proof = await this.core.tree.proof(batch, treeBatch, msg)
687
705
 
688
- return proof
706
+ return new ProofRequest(msg, proof, block, manifest)
689
707
  }
690
708
 
691
709
  async onrequest (msg) {
@@ -703,6 +721,8 @@ class Peer {
703
721
  return
704
722
  }
705
723
 
724
+ if (this.replicator.destroyed) return
725
+
706
726
  await this._handleRequest(msg)
707
727
  }
708
728
 
@@ -720,7 +740,7 @@ class Peer {
720
740
  }
721
741
 
722
742
  async _handleRequests () {
723
- if (this.receiverBusy) return
743
+ if (this.receiverBusy || this.replicator.destroyed) return
724
744
  this.receiverBusy = true
725
745
  this.protomux.cork()
726
746
 
@@ -734,45 +754,48 @@ class Peer {
734
754
  }
735
755
 
736
756
  async _handleRequest (msg) {
737
- let proof = null
757
+ const batch = this.core.storage.read()
738
758
 
739
759
  // TODO: could still be answerable if (index, fork) is an ancestor of the current fork
740
- if (msg.fork === this.core.tree.fork) {
741
- try {
742
- proof = await this._getProof(msg)
743
- } catch (err) {
744
- safetyCatch(err)
745
- if (msg.fork === this.core.tree.fork && isCriticalError(err)) throw err
746
- }
747
- }
760
+ const req = msg.fork === this.core.state.fork
761
+ ? await this._getProof(batch, msg)
762
+ : new ProofRequest(msg, null, null, null)
763
+
764
+ batch.tryFlush()
765
+
766
+ await this._fulfillRequest(req)
767
+ }
768
+
769
+ async _fulfillRequest (req) {
770
+ const proof = await req.fulfill()
748
771
 
749
772
  // if cancelled do not reply
750
- if (this.remoteRequests.get(msg.id) !== msg) {
773
+ if (this.remoteRequests.get(req.msg.id) !== req.msg) {
751
774
  return
752
775
  }
753
776
 
754
777
  // sync from now on, so safe to delete from the map
755
- this.remoteRequests.delete(msg.id)
778
+ this.remoteRequests.delete(req.msg.id)
756
779
 
757
780
  if (proof === null) {
758
- if (msg.manifest && this.core.header.manifest) {
781
+ if (req.msg.manifest && this.core.header.manifest) {
759
782
  const manifest = this.core.header.manifest
760
- this.wireData.send({ request: msg.id, fork: this.core.tree.fork, block: null, hash: null, seek: null, upgrade: null, manifest })
783
+ this.wireData.send({ request: req.msg.id, fork: this.core.state.fork, block: null, hash: null, seek: null, upgrade: null, manifest })
761
784
  incrementTx(this.stats.wireData, this.replicator.stats.wireData)
762
785
  return
763
786
  }
764
787
 
765
- this.wireNoData.send({ request: msg.id })
788
+ this.wireNoData.send({ request: req.msg.id })
766
789
  return
767
790
  }
768
791
 
769
792
  if (proof.block !== null) {
770
- this.replicator.onupload(proof.block.index, proof.block.value, this)
793
+ this.replicator._onupload(proof.block.index, proof.block.value.byteLength, this)
771
794
  }
772
795
 
773
796
  this.wireData.send({
774
- request: msg.id,
775
- fork: msg.fork,
797
+ request: req.msg.id,
798
+ fork: req.msg.fork,
776
799
  block: proof.block,
777
800
  hash: proof.hash,
778
801
  seek: proof.seek,
@@ -803,7 +826,7 @@ class Peer {
803
826
  _checkIfConflict () {
804
827
  this.paused = true
805
828
 
806
- const length = Math.min(this.core.tree.length, this.remoteLength)
829
+ const length = Math.min(this.core.state.length, this.remoteLength)
807
830
  if (length === 0) return // pause and ignore
808
831
 
809
832
  this.wireRequest.send({
@@ -829,7 +852,7 @@ class Peer {
829
852
  }
830
853
 
831
854
  const req = data.request > 0 ? this.replicator._inflight.get(data.request) : null
832
- const reorg = data.fork > this.core.tree.fork
855
+ const reorg = data.fork > this.core.state.fork
833
856
 
834
857
  // no push atm, TODO: check if this satisfies another pending request
835
858
  // allow reorg pushes tho as those are not written to storage so we'll take all the help we can get
@@ -847,11 +870,12 @@ class Peer {
847
870
  if (isBlockRequest(req)) this.replicator._unmarkInflight(req.block.index)
848
871
 
849
872
  this.paused = true
850
- this.replicator.oninvalid(err, req, data, this)
873
+ this.replicator._oninvalid(err, req, data, this)
851
874
  return
852
875
  }
853
876
 
854
877
  this.dataProcessing++
878
+ if (isBlockRequest(req)) this.replicator._markProcessing(req.block.index)
855
879
 
856
880
  try {
857
881
  if (!matchingRequest(req, data) || !(await this.core.verify(data, this))) {
@@ -877,9 +901,10 @@ class Peer {
877
901
  }
878
902
 
879
903
  this.replicator._onnodata(this, req)
880
- this.replicator.oninvalid(err, req, data, this)
904
+ this.replicator._oninvalid(err, req, data, this)
881
905
  return
882
906
  } finally {
907
+ if (isBlockRequest(req)) this.replicator._markProcessed(req.block.index)
883
908
  this.dataProcessing--
884
909
  }
885
910
 
@@ -936,11 +961,10 @@ class Peer {
936
961
  return
937
962
  }
938
963
 
939
- const contig = Math.min(this.core.tree.length, this.core.header.hints.contiguousLength)
964
+ const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)
940
965
 
941
966
  if (start + length < contig) {
942
- const delta = contig - start
943
- this.missingBlocks.setRange(start, delta, false)
967
+ this.missingBlocks.setRange(start, contig, false)
944
968
  return
945
969
  }
946
970
 
@@ -950,7 +974,7 @@ class Peer {
950
974
  length += rem
951
975
  }
952
976
 
953
- const end = start + Math.min(length, this.core.tree.length)
977
+ const end = start + Math.min(length, this.core.state.length)
954
978
  while (start < end) {
955
979
  const local = bitfield.getBitfield(start)
956
980
 
@@ -1009,12 +1033,12 @@ class Peer {
1009
1033
  this.missingBlocks.set(start, has && !bitfield.get(start))
1010
1034
  } else {
1011
1035
  const rangeStart = this.remoteBitfield.findFirst(!has, start)
1012
- const rangeLength = length - (rangeStart - start)
1036
+ const rangeEnd = length + start
1013
1037
 
1014
- if (rangeLength > 0) {
1015
- this.remoteBitfield.setRange(rangeStart, rangeLength, has)
1016
- this.missingBlocks.setRange(rangeStart, rangeLength, has)
1017
- if (has) this._clearLocalRange(rangeStart, rangeLength)
1038
+ if (rangeStart !== -1 && rangeStart < rangeEnd) {
1039
+ this.remoteBitfield.setRange(rangeStart, rangeEnd, has)
1040
+ this.missingBlocks.setRange(rangeStart, rangeEnd, has)
1041
+ if (has) this._clearLocalRange(rangeStart, rangeEnd - rangeStart)
1018
1042
  }
1019
1043
  }
1020
1044
 
@@ -1033,16 +1057,16 @@ class Peer {
1033
1057
 
1034
1058
  async _onconflict () {
1035
1059
  this.protomux.cork()
1036
- if (this.remoteLength > 0 && this.core.tree.fork === this.remoteFork) {
1060
+ if (this.remoteLength > 0 && this.core.state.fork === this.remoteFork) {
1037
1061
  await this.onrequest({
1038
1062
  id: 0,
1039
- fork: this.core.tree.fork,
1063
+ fork: this.core.state.fork,
1040
1064
  block: null,
1041
1065
  hash: null,
1042
1066
  seek: null,
1043
1067
  upgrade: {
1044
1068
  start: 0,
1045
- length: Math.min(this.core.tree.length, this.remoteLength)
1069
+ length: Math.min(this.core.state.length, this.remoteLength)
1046
1070
  }
1047
1071
  })
1048
1072
  }
@@ -1074,7 +1098,7 @@ class Peer {
1074
1098
  seek: null,
1075
1099
  upgrade: needsUpgrade === false
1076
1100
  ? null
1077
- : { start: this.core.tree.length, length: this.remoteLength - this.core.tree.length },
1101
+ : { start: this.core.state.length, length: this.remoteLength - this.core.state.length },
1078
1102
  // remote manifest check can be removed eventually...
1079
1103
  manifest: this.core.header.manifest === null && this.remoteHasManifest === true,
1080
1104
  priority
@@ -1099,7 +1123,7 @@ class Peer {
1099
1123
  // if replicator is updating the seeks etc, bail and wait for it to drain
1100
1124
  if (this.replicator._updatesPending > 0) return false
1101
1125
 
1102
- const { length, fork } = this.core.tree
1126
+ const { length, fork } = this.core.state
1103
1127
 
1104
1128
  if (fork !== this.remoteFork) return false
1105
1129
 
@@ -1156,7 +1180,7 @@ class Peer {
1156
1180
  }
1157
1181
 
1158
1182
  _hasTreeParent (index) {
1159
- if (this.remoteLength >= this.core.tree.length) return true
1183
+ if (this.remoteLength >= this.core.state.length) return true
1160
1184
 
1161
1185
  const ite = flatTree.iterator(index * 2)
1162
1186
 
@@ -1170,7 +1194,7 @@ class Peer {
1170
1194
  length = left + span
1171
1195
 
1172
1196
  // if larger than local AND larger than remote - they share the root so its ok
1173
- if (length > this.core.tree.length) {
1197
+ if (length > this.core.state.length) {
1174
1198
  if (length > this.remoteLength) return true
1175
1199
  break
1176
1200
  }
@@ -1201,7 +1225,7 @@ class Peer {
1201
1225
  }
1202
1226
 
1203
1227
  _requestBlock (b) {
1204
- const { length, fork } = this.core.tree
1228
+ const { length, fork } = this.core.state
1205
1229
 
1206
1230
  if (this._remoteHasBlock(b.index) === false || fork !== this.remoteFork) {
1207
1231
  this._maybeWant(b.index)
@@ -1257,7 +1281,7 @@ class Peer {
1257
1281
  }
1258
1282
 
1259
1283
  _requestRange (r) {
1260
- const { length, fork } = this.core.tree
1284
+ const { length, fork } = this.core.state
1261
1285
 
1262
1286
  if (r.blocks) {
1263
1287
  let min = -1
@@ -1275,7 +1299,7 @@ class Peer {
1275
1299
  return false
1276
1300
  }
1277
1301
 
1278
- const end = Math.min(this.core.tree.length, Math.min(r.end === -1 ? this.remoteLength : r.end, this.remoteLength))
1302
+ const end = Math.min(this.core.state.length, Math.min(r.end === -1 ? this.remoteLength : r.end, this.remoteLength))
1279
1303
  if (end <= r.start || fork !== this.remoteFork) return false
1280
1304
 
1281
1305
  const len = end - r.start
@@ -1368,7 +1392,7 @@ class Peer {
1368
1392
  }
1369
1393
 
1370
1394
  async _send (req) {
1371
- const fork = this.core.tree.fork
1395
+ const fork = this.core.state.fork
1372
1396
 
1373
1397
  this.inflight++
1374
1398
  this.replicator._inflight.add(req)
@@ -1380,11 +1404,11 @@ class Peer {
1380
1404
 
1381
1405
  try {
1382
1406
  if (req.block !== null && req.fork === fork) {
1383
- req.block.nodes = await this.core.tree.missingNodes(2 * req.block.index)
1407
+ req.block.nodes = await this.core.tree.missingNodes(2 * req.block.index, this.core.state.length)
1384
1408
  if (req.priority === PRIORITY.CANCELLED) return
1385
1409
  }
1386
1410
  if (req.hash !== null && req.fork === fork && req.hash.nodes === 0) {
1387
- req.hash.nodes = await this.core.tree.missingNodes(req.hash.index)
1411
+ req.hash.nodes = await this.core.tree.missingNodes(req.hash.index, this.core.state.length)
1388
1412
  if (req.priority === PRIORITY.CANCELLED) return
1389
1413
 
1390
1414
  // nodes === 0, we already have it, bail
@@ -1407,23 +1431,15 @@ class Peer {
1407
1431
  module.exports = class Replicator {
1408
1432
  static Peer = Peer // hack to be able to access Peer from outside this module
1409
1433
 
1410
- constructor (core, key, {
1434
+ constructor (core, {
1411
1435
  notDownloadingLinger = NOT_DOWNLOADING_SLACK,
1412
1436
  eagerUpgrade = true,
1413
1437
  allowFork = true,
1414
- inflightRange = null,
1415
- onpeerupdate = noop,
1416
- onupload = noop,
1417
- oninvalid = noop
1438
+ inflightRange = null
1418
1439
  } = {}) {
1419
- this.key = key
1420
- this.discoveryKey = core.crypto.discoveryKey(key)
1421
1440
  this.core = core
1422
1441
  this.eagerUpgrade = eagerUpgrade
1423
1442
  this.allowFork = allowFork
1424
- this.onpeerupdate = onpeerupdate
1425
- this.onupload = onupload
1426
- this.oninvalid = oninvalid
1427
1443
  this.ondownloading = null // optional external hook for monitoring downloading status
1428
1444
  this.peers = []
1429
1445
  this.findingPeers = 0 // updateable from the outside
@@ -1461,12 +1477,11 @@ module.exports = class Replicator {
1461
1477
  this._ranges = []
1462
1478
 
1463
1479
  this._hadPeers = false
1480
+ this._active = 0
1464
1481
  this._ifAvailable = 0
1465
1482
  this._updatesPending = 0
1466
1483
  this._applyingReorg = null
1467
1484
  this._manifestPeer = null
1468
- this._hasSession = false
1469
- this._peerSessions = 0
1470
1485
  this._notDownloadingLinger = notDownloadingLinger
1471
1486
  this._downloadingTimer = null
1472
1487
 
@@ -1510,7 +1525,7 @@ module.exports = class Replicator {
1510
1525
  if (downloading) { // restart channel if needed...
1511
1526
  for (const protomux of this._attached) {
1512
1527
  if (!protomux.stream.handshakeHash) continue
1513
- if (protomux.opened({ protocol: 'hypercore/alpha', id: this.discoveryKey })) continue
1528
+ if (protomux.opened({ protocol: 'hypercore/alpha', id: this.core.discoveryKey })) continue
1514
1529
  this._makePeer(protomux, true)
1515
1530
  }
1516
1531
  } else {
@@ -1557,7 +1572,9 @@ module.exports = class Replicator {
1557
1572
  for (const peer of this.peers) peer.signalUpgrade()
1558
1573
  if (this._blocks.isEmpty() === false) this._resolveBlocksLocally()
1559
1574
  if (this._upgrade !== null) this._resolveUpgradeRequest(null)
1560
- if (this._ranges.length !== 0 || this._seeks.length !== 0) this._updateNonPrimary(true)
1575
+ if (!this._blocks.isEmpty() || this._ranges.length !== 0 || this._seeks.length !== 0) {
1576
+ this._updateNonPrimary(true)
1577
+ }
1561
1578
  }
1562
1579
 
1563
1580
  // Called externally when a conflict has been detected and verified
@@ -1689,13 +1706,13 @@ module.exports = class Replicator {
1689
1706
 
1690
1707
  if (peer.remoteSynced === false) return
1691
1708
 
1692
- if (this.core.tree.length === 0 && peer.remoteLength > 0) return
1709
+ if (this.core.state.length === 0 && peer.remoteLength > 0) return
1693
1710
 
1694
1711
  if (peer.remoteLength <= this._upgrade.length || peer.remoteFork !== this._upgrade.fork) continue
1695
1712
 
1696
1713
  if (peer.syncsProcessing > 0) return
1697
1714
 
1698
- if (peer.lengthAcked !== this.core.tree.length && peer.remoteFork === this.core.tree.fork) return
1715
+ if (peer.lengthAcked !== this.core.state.length && peer.remoteFork === this.core.state.fork) return
1699
1716
  if (peer.remoteCanUpgrade === true) return
1700
1717
  }
1701
1718
 
@@ -1725,7 +1742,7 @@ module.exports = class Replicator {
1725
1742
  if (this._upgrade !== null) return this._upgrade
1726
1743
 
1727
1744
  // TODO: needs a reorg: true/false flag to indicate if the user requested a reorg
1728
- this._upgrade = new UpgradeRequest(this, this.core.tree.fork, this.core.tree.length)
1745
+ this._upgrade = new UpgradeRequest(this, this.core.state.fork, this.core.state.length)
1729
1746
 
1730
1747
  return this._upgrade
1731
1748
  }
@@ -1763,19 +1780,19 @@ module.exports = class Replicator {
1763
1780
  _shouldUpgrade (peer) {
1764
1781
  if (this._upgrade !== null && this._upgrade.inflight.length > 0) return false
1765
1782
  return peer.remoteCanUpgrade === true &&
1766
- peer.remoteLength > this.core.tree.length &&
1767
- peer.lengthAcked === this.core.tree.length
1783
+ peer.remoteLength > this.core.state.length &&
1784
+ peer.lengthAcked === this.core.state.length
1768
1785
  }
1769
1786
 
1770
1787
  _autoUpgrade (peer) {
1771
- return this._upgrade !== null && peer.remoteFork === this.core.tree.fork && this._shouldUpgrade(peer)
1788
+ return this._upgrade !== null && peer.remoteFork === this.core.state.fork && this._shouldUpgrade(peer)
1772
1789
  }
1773
1790
 
1774
1791
  _addPeer (peer) {
1775
1792
  this._hadPeers = true
1776
1793
  this.peers.push(peer)
1777
1794
  this.updatePeer(peer)
1778
- this.onpeerupdate(true, peer)
1795
+ this._onpeerupdate(true, peer)
1779
1796
  }
1780
1797
 
1781
1798
  _requestDone (id, roundtrip) {
@@ -1795,9 +1812,7 @@ module.exports = class Replicator {
1795
1812
  this._clearRequest(peer, req)
1796
1813
  }
1797
1814
 
1798
- if (peer.useSession) this._closeSessionMaybe()
1799
-
1800
- this.onpeerupdate(false, peer)
1815
+ this._onpeerupdate(false, peer)
1801
1816
  this.updateAll()
1802
1817
  }
1803
1818
 
@@ -1817,22 +1832,19 @@ module.exports = class Replicator {
1817
1832
  async _resolveBlocksLocally () {
1818
1833
  // TODO: check if fork compat etc. Requires that we pass down truncation info
1819
1834
 
1820
- let clear = null
1835
+ const clear = []
1836
+ const blocks = []
1821
1837
 
1838
+ const reader = this.core.storage.read()
1822
1839
  for (const b of this._blocks) {
1823
1840
  if (this.core.bitfield.get(b.index) === false) continue
1824
-
1825
- try {
1826
- b.resolve(await this.core.blocks.get(b.index))
1827
- } catch (err) {
1828
- b.reject(err)
1829
- }
1830
-
1831
- if (clear === null) clear = []
1832
- clear.push(b)
1841
+ blocks.push(this._resolveLocalBlock(b, reader, clear))
1833
1842
  }
1843
+ reader.tryFlush()
1834
1844
 
1835
- if (clear === null) return
1845
+ await Promise.all(blocks)
1846
+
1847
+ if (!clear.length) return
1836
1848
 
1837
1849
  // Currently the block tracker does not support deletes during iteration, so we make
1838
1850
  // sure to clear them afterwards.
@@ -1842,6 +1854,17 @@ module.exports = class Replicator {
1842
1854
  }
1843
1855
  }
1844
1856
 
1857
+ async _resolveLocalBlock (b, reader, resolved) {
1858
+ try {
1859
+ b.resolve(await this.core.blocks.get(reader, b.index))
1860
+ } catch (err) {
1861
+ b.reject(err)
1862
+ return
1863
+ }
1864
+
1865
+ resolved.push(b)
1866
+ }
1867
+
1845
1868
  _resolveBlockRequest (tracker, index, value, req) {
1846
1869
  const b = tracker.remove(index)
1847
1870
  if (b === null) return false
@@ -1865,7 +1888,7 @@ module.exports = class Replicator {
1865
1888
  _resolveUpgradeRequest (req) {
1866
1889
  if (req !== null) removeInflight(this._upgrade.inflight, req)
1867
1890
 
1868
- if (this.core.tree.length === this._upgrade.length && this.core.tree.fork === this._upgrade.fork) return false
1891
+ if (this.core.state.length === this._upgrade.length && this.core.state.fork === this._upgrade.fork) return false
1869
1892
 
1870
1893
  const u = this._upgrade
1871
1894
  this._upgrade = null
@@ -2035,6 +2058,25 @@ module.exports = class Replicator {
2035
2058
  }
2036
2059
  }
2037
2060
 
2061
+ _markProcessing (index) {
2062
+ const b = this._blocks.get(index)
2063
+ if (b) {
2064
+ b.processing = true
2065
+ return
2066
+ }
2067
+
2068
+ const h = this._hashes.get(index)
2069
+ if (h) h.processing = true
2070
+ }
2071
+
2072
+ _markProcessed (index) {
2073
+ const b = this._blocks.get(index)
2074
+ if (b) return b.processed()
2075
+
2076
+ const h = this._hashes.get(index)
2077
+ if (h) h.processed()
2078
+ }
2079
+
2038
2080
  _markInflight (index) {
2039
2081
  if (this.core.skipBitfield !== null) this.core.skipBitfield.set(index, true)
2040
2082
  for (const peer of this.peers) peer._markInflight(index)
@@ -2048,6 +2090,7 @@ module.exports = class Replicator {
2048
2090
  _ondata (peer, req, data) {
2049
2091
  if (data.block !== null) {
2050
2092
  this._resolveBlockRequest(this._blocks, data.block.index, data.block.value, req)
2093
+ this._ondownload(data.block.index, data.block.value.byteLength, peer)
2051
2094
  }
2052
2095
 
2053
2096
  if (data.hash !== null && (data.hash.index & 1) === 0) {
@@ -2075,9 +2118,9 @@ module.exports = class Replicator {
2075
2118
  }
2076
2119
 
2077
2120
  _onwant (peer, start, length) {
2078
- const contig = Math.min(this.core.tree.length, this.core.header.hints.contiguousLength)
2121
+ const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)
2079
2122
 
2080
- if (start + length < contig || (this.core.tree.length === contig)) {
2123
+ if (start + length < contig || (this.core.state.length === contig)) {
2081
2124
  peer.wireRange.send({
2082
2125
  drop: false,
2083
2126
  start: 0,
@@ -2087,7 +2130,7 @@ module.exports = class Replicator {
2087
2130
  return
2088
2131
  }
2089
2132
 
2090
- length = Math.min(length, this.core.tree.length - start)
2133
+ length = Math.min(length, this.core.state.length - start)
2091
2134
 
2092
2135
  peer.protomux.cork()
2093
2136
 
@@ -2173,7 +2216,7 @@ module.exports = class Replicator {
2173
2216
  }
2174
2217
 
2175
2218
  _updateFork (peer) {
2176
- if (this._applyingReorg !== null || this.allowFork === false || peer.remoteFork <= this.core.tree.fork) {
2219
+ if (this._applyingReorg !== null || this.allowFork === false || peer.remoteFork <= this.core.state.fork) {
2177
2220
  return false
2178
2221
  }
2179
2222
 
@@ -2304,67 +2347,62 @@ module.exports = class Replicator {
2304
2347
  this._maybeResolveIfAvailableRanges()
2305
2348
  }
2306
2349
 
2307
- _closeSessionMaybe () {
2308
- if (this._hasSession && this._peerSessions === 0) {
2309
- this._hasSession = false
2310
- this.core.active--
2311
- }
2312
-
2313
- // we were the last active ref, so lets shut things down
2314
- if (this.core.active === 0 && this.core.sessions.length === 0) {
2315
- this.destroy()
2316
- this.core.close().catch(safetyCatch)
2317
- return
2318
- }
2319
-
2320
- // in case one session is still alive but its been marked for auto close also kill it
2321
- if (this.core.sessions.length === 1 && this.core.active === 1 && this.core.sessions[0].autoClose) {
2322
- this.core.sessions[0].close().catch(safetyCatch)
2323
- }
2350
+ onpeerdestroy () {
2351
+ if (--this._active === 0) this.core.checkIfIdle()
2324
2352
  }
2325
2353
 
2326
2354
  attached (protomux) {
2327
2355
  return this._attached.has(protomux)
2328
2356
  }
2329
2357
 
2330
- ensureSession () {
2331
- if (this._hasSession) return
2332
- this._hasSession = true
2333
- this.core.active++
2334
- }
2335
-
2336
- attachTo (protomux, useSession) {
2358
+ attachTo (protomux) {
2337
2359
  if (this.core.closed) return
2338
- if (useSession) this.ensureSession()
2339
2360
 
2340
- const makePeer = this._makePeer.bind(this, protomux, useSession)
2361
+ const makePeer = this._makePeer.bind(this, protomux)
2341
2362
 
2342
2363
  this._attached.add(protomux)
2343
- protomux.pair({ protocol: 'hypercore/alpha', id: this.discoveryKey }, makePeer)
2364
+ protomux.pair({ protocol: 'hypercore/alpha', id: this.core.discoveryKey }, makePeer)
2344
2365
  protomux.stream.setMaxListeners(0)
2345
2366
  protomux.stream.on('close', this._onstreamclose)
2346
2367
 
2347
- if (useSession) this._peerSessions++
2348
2368
  this._ifAvailable++
2369
+ this._active++
2349
2370
 
2350
2371
  protomux.stream.opened.then((opened) => {
2351
- if (useSession) this._peerSessions--
2352
2372
  this._ifAvailable--
2373
+ this._active--
2353
2374
 
2354
2375
  if (opened && !this.destroyed) makePeer()
2355
- else if (useSession) this._closeSessionMaybe()
2356
2376
  this._checkUpgradeIfAvailable()
2377
+
2378
+ this.core.checkIfIdle()
2357
2379
  })
2358
2380
  }
2359
2381
 
2360
2382
  detachFrom (protomux) {
2361
2383
  if (this._attached.delete(protomux)) {
2362
2384
  protomux.stream.removeListener('close', this._onstreamclose)
2363
- protomux.unpair({ protocol: 'hypercore/alpha', id: this.discoveryKey })
2385
+ protomux.unpair({ protocol: 'hypercore/alpha', id: this.core.discoveryKey })
2386
+ }
2387
+ }
2388
+
2389
+ idle () {
2390
+ return this.peers.length === 0 && this._active === 0
2391
+ }
2392
+
2393
+ close () {
2394
+ const waiting = []
2395
+
2396
+ for (const peer of this.peers) {
2397
+ waiting.push(peer.channel.fullyClosed())
2364
2398
  }
2399
+
2400
+ this.destroy()
2401
+ return Promise.all(waiting)
2365
2402
  }
2366
2403
 
2367
2404
  destroy () {
2405
+ if (this.destroyed) return
2368
2406
  this.destroyed = true
2369
2407
 
2370
2408
  if (this._downloadingTimer) {
@@ -2372,31 +2410,26 @@ module.exports = class Replicator {
2372
2410
  this._downloadingTimer = null
2373
2411
  }
2374
2412
 
2375
- const waiting = []
2376
-
2377
2413
  while (this.peers.length) {
2378
2414
  const peer = this.peers[this.peers.length - 1]
2379
2415
  this.detachFrom(peer.protomux)
2380
2416
  peer.channel.close() // peer is removed from array in onclose
2381
- waiting.push(peer.channel.fullyClosed())
2382
2417
  }
2383
2418
 
2384
2419
  for (const protomux of this._attached) {
2385
2420
  this.detachFrom(protomux)
2386
2421
  }
2387
-
2388
- return Promise.all(waiting)
2389
2422
  }
2390
2423
 
2391
- _makePeer (protomux, useSession) {
2424
+ _makePeer (protomux) {
2392
2425
  const replicator = this
2393
- if (protomux.opened({ protocol: 'hypercore/alpha', id: this.discoveryKey })) return onnochannel()
2426
+ if (protomux.opened({ protocol: 'hypercore/alpha', id: this.core.discoveryKey })) return onnochannel()
2394
2427
 
2395
2428
  const channel = protomux.createChannel({
2396
2429
  userData: null,
2397
2430
  protocol: 'hypercore/alpha',
2398
2431
  aliases: ['hypercore'],
2399
- id: this.discoveryKey,
2432
+ id: this.core.discoveryKey,
2400
2433
  handshake: m.wire.handshake,
2401
2434
  messages: [
2402
2435
  { encoding: m.wire.sync, onmessage: onwiresync },
@@ -2412,32 +2445,67 @@ module.exports = class Replicator {
2412
2445
  ],
2413
2446
  onopen: onwireopen,
2414
2447
  onclose: onwireclose,
2415
- ondrain: onwiredrain
2448
+ ondrain: onwiredrain,
2449
+ ondestroy: onwiredestroy
2416
2450
  })
2417
2451
 
2418
2452
  if (channel === null) return onnochannel()
2419
2453
 
2420
- const peer = new Peer(replicator, protomux, channel, useSession, this.inflightRange)
2454
+ const peer = new Peer(replicator, protomux, channel, this.inflightRange)
2421
2455
  const stream = protomux.stream
2422
2456
 
2423
- if (useSession) {
2424
- // session may have been unref'd underneath us
2425
- replicator.ensureSession()
2426
- replicator._peerSessions++
2427
- }
2428
-
2429
2457
  peer.channel.open({
2430
2458
  seeks: true,
2431
- capability: caps.replicate(stream.isInitiator, this.key, stream.handshakeHash)
2459
+ capability: caps.replicate(stream.isInitiator, this.core.key, stream.handshakeHash)
2432
2460
  })
2433
2461
 
2434
2462
  return true
2435
2463
 
2436
2464
  function onnochannel () {
2437
- if (useSession) replicator._closeSessionMaybe()
2438
2465
  return false
2439
2466
  }
2440
2467
  }
2468
+
2469
+ _onpeerupdate (added, peer) {
2470
+ const name = added ? 'peer-add' : 'peer-remove'
2471
+ const sessions = this.core.monitors
2472
+
2473
+ for (let i = sessions.length - 1; i >= 0; i--) {
2474
+ sessions[i].emit(name, peer)
2475
+
2476
+ if (added) {
2477
+ for (const ext of sessions[i].extensions.values()) {
2478
+ peer.extensions.set(ext.name, ext)
2479
+ }
2480
+ }
2481
+ }
2482
+ }
2483
+
2484
+ _ondownload (index, byteLength, from) {
2485
+ const sessions = this.core.monitors
2486
+
2487
+ for (let i = sessions.length - 1; i >= 0; i--) {
2488
+ const s = sessions[i]
2489
+ s.emit('download', index, byteLength - s.padding, from)
2490
+ }
2491
+ }
2492
+
2493
+ _onupload (index, byteLength, from) {
2494
+ const sessions = this.core.monitors
2495
+
2496
+ for (let i = sessions.length - 1; i >= 0; i--) {
2497
+ const s = sessions[i]
2498
+ s.emit('upload', index, byteLength - s.padding, from)
2499
+ }
2500
+ }
2501
+
2502
+ _oninvalid (err, req, res, from) {
2503
+ const sessions = this.core.monitors
2504
+
2505
+ for (let i = 0; i < sessions.length; i++) {
2506
+ sessions[i].emit('verification-error', err, req, res, from)
2507
+ }
2508
+ }
2441
2509
  }
2442
2510
 
2443
2511
  function matchingRequest (req, data) {
@@ -2462,8 +2530,6 @@ function removeInflight (inf, req) {
2462
2530
  return true
2463
2531
  }
2464
2532
 
2465
- function noop () {}
2466
-
2467
2533
  function toLength (start, end) {
2468
2534
  return end === -1 ? -1 : (end < start ? 0 : end - start)
2469
2535
  }
@@ -2512,6 +2578,10 @@ function onwireclose (isRemote, c) {
2512
2578
  return c.userData.onclose(isRemote)
2513
2579
  }
2514
2580
 
2581
+ function onwiredestroy (c) {
2582
+ c.userData.replicator.onpeerdestroy()
2583
+ }
2584
+
2515
2585
  function onwiredrain (c) {
2516
2586
  return c.userData.ondrain()
2517
2587
  }