hypercore 10.4.1 → 10.5.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
@@ -107,7 +107,7 @@ const blockIfFast = await core.get(43, { timeout: 5000 })
107
107
  const blockLocal = await core.get(44, { wait: false })
108
108
  ```
109
109
 
110
- Additional options include
110
+ `options` include:
111
111
 
112
112
  ``` js
113
113
  {
@@ -118,6 +118,10 @@ Additional options include
118
118
  }
119
119
  ```
120
120
 
121
+ #### `const has = await core.has(start, [end])`
122
+
123
+ Check if the core has all blocks between `start` and `end`.
124
+
121
125
  #### `const updated = await core.update()`
122
126
 
123
127
  Wait for the core to try and find a signed update to it's length.
@@ -163,7 +167,7 @@ for await (const data of fullStream) {
163
167
  }
164
168
  ```
165
169
 
166
- Additional options include:
170
+ `options` include:
167
171
 
168
172
  ``` js
169
173
  {
@@ -238,7 +242,7 @@ To cancel downloading a range simply destroy the range instance.
238
242
  range.destroy()
239
243
  ```
240
244
 
241
- #### `const info = await core.info()`
245
+ #### `const info = await core.info([options])`
242
246
 
243
247
  Get information about this core, such as its total size in bytes.
244
248
 
@@ -252,7 +256,21 @@ Info {
252
256
  contiguousLength: 16,
253
257
  byteLength: 742,
254
258
  fork: 0,
255
- padding: 8
259
+ padding: 8,
260
+ storage: {
261
+ oplog: 8192,
262
+ tree: 4096,
263
+ blocks: 4096,
264
+ bitfield: 4096
265
+ }
266
+ }
267
+ ```
268
+
269
+ `options` include:
270
+
271
+ ```js
272
+ {
273
+ storage: false // get storage estimates in bytes, disabled by default
256
274
  }
257
275
  ```
258
276
 
package/index.js CHANGED
@@ -324,7 +324,8 @@ module.exports = class Hypercore extends EventEmitter {
324
324
  crypto: this.crypto,
325
325
  legacy: opts.legacy,
326
326
  auth: opts.auth,
327
- onupdate: this._oncoreupdate.bind(this)
327
+ onupdate: this._oncoreupdate.bind(this),
328
+ onconflict: this._oncoreconflict.bind(this)
328
329
  })
329
330
 
330
331
  if (opts.userData) {
@@ -377,13 +378,13 @@ module.exports = class Hypercore extends EventEmitter {
377
378
  return prev.length !== next.length || prev.fork !== next.fork
378
379
  }
379
380
 
380
- close () {
381
+ close (err) {
381
382
  if (this.closing) return this.closing
382
- this.closing = this._close()
383
+ this.closing = this._close(err || null)
383
384
  return this.closing
384
385
  }
385
386
 
386
- async _close () {
387
+ async _close (err) {
387
388
  await this.opening
388
389
 
389
390
  const i = this.sessions.indexOf(this)
@@ -403,19 +404,23 @@ module.exports = class Hypercore extends EventEmitter {
403
404
 
404
405
  if (this.replicator !== null) {
405
406
  this.replicator.findingPeers -= this._findingPeers
406
- this.replicator.clearRequests(this.activeRequests)
407
+ this.replicator.clearRequests(this.activeRequests, err)
407
408
  }
408
409
 
409
410
  this._findingPeers = 0
410
411
 
411
412
  if (this.sessions.length) {
412
413
  // if this is the last session and we are auto closing, trigger that first to enforce error handling
413
- if (this.sessions.length === 1 && this.autoClose) await this.sessions[0].close()
414
+ if (this.sessions.length === 1 && this.autoClose) await this.sessions[0].close(err)
414
415
  // emit "fake" close as this is a session
415
416
  this.emit('close', false)
416
417
  return
417
418
  }
418
419
 
420
+ if (this.replicator !== null) {
421
+ this.replicator.destroy()
422
+ }
423
+
419
424
  await this.core.close()
420
425
 
421
426
  this.emit('close', true)
@@ -510,6 +515,18 @@ module.exports = class Hypercore extends EventEmitter {
510
515
  }
511
516
  }
512
517
 
518
+ async _oncoreconflict (proof, from) {
519
+ await this.replicator.onconflict(from)
520
+
521
+ for (const s of this.sessions) s.emit('conflict', proof.upgrade.length, proof.fork, proof)
522
+
523
+ const err = new Error('Two conflicting signatures exist for length ' + proof.upgrade.length)
524
+
525
+ const all = []
526
+ for (const s of this.sessions) all.push(s.close(err))
527
+ await Promise.allSettled(all)
528
+ }
529
+
513
530
  _oncoreupdate (status, bitfield, value, from) {
514
531
  if (status !== 0) {
515
532
  const truncatedNonSparse = (status & 0b1000) !== 0
@@ -614,10 +631,10 @@ module.exports = class Hypercore extends EventEmitter {
614
631
  }
615
632
  }
616
633
 
617
- async info () {
634
+ async info (opts) {
618
635
  if (this.opened === false) await this.opening
619
636
 
620
- return Info.from(this)
637
+ return Info.from(this, opts)
621
638
  }
622
639
 
623
640
  async update (opts) {
@@ -666,10 +683,15 @@ module.exports = class Hypercore extends EventEmitter {
666
683
  return req.promise
667
684
  }
668
685
 
669
- async has (index) {
686
+ async has (start, end = start + 1) {
670
687
  if (this.opened === false) await this.opening
671
688
 
672
- return this.core.bitfield.get(index)
689
+ const length = end - start
690
+ if (length <= 0) return false
691
+ if (length === 1) return this.core.bitfield.get(start)
692
+
693
+ const i = this.core.bitfield.firstUnset(start)
694
+ return i === -1 || i >= end
673
695
  }
674
696
 
675
697
  async get (index, opts) {
@@ -804,13 +826,13 @@ module.exports = class Hypercore extends EventEmitter {
804
826
  }
805
827
  }
806
828
 
807
- return await this.core.append(buffers, this.auth, { preappend })
829
+ return this.core.append(buffers, this.auth, { preappend })
808
830
  }
809
831
 
810
832
  async treeHash (length) {
811
833
  if (length === undefined) {
812
834
  await this.ready()
813
- length = this.core.length
835
+ length = this.core.tree.length
814
836
  }
815
837
 
816
838
  const roots = await this.core.tree.getRoots(length)
package/lib/core.js CHANGED
@@ -9,8 +9,9 @@ const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_SIGNATURE } = req
9
9
  const m = require('./messages')
10
10
 
11
11
  module.exports = class Core {
12
- constructor (header, crypto, oplog, tree, blocks, bitfield, auth, legacy, onupdate) {
12
+ constructor (header, crypto, oplog, tree, blocks, bitfield, auth, legacy, onupdate, onconflict) {
13
13
  this.onupdate = onupdate
14
+ this.onconflict = onconflict
14
15
  this.header = header
15
16
  this.crypto = crypto
16
17
  this.oplog = oplog
@@ -158,7 +159,7 @@ module.exports = class Core {
158
159
  }
159
160
  }
160
161
 
161
- return new this(header, crypto, oplog, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop)
162
+ return new this(header, crypto, oplog, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop, opts.onconflict || noop)
162
163
  }
163
164
 
164
165
  _shouldFlush () {
@@ -437,6 +438,27 @@ module.exports = class Core {
437
438
  return verifies[0] !== null
438
439
  }
439
440
 
441
+ async checkConflict (proof, from) {
442
+ if (this.tree.length < proof.upgrade.length || proof.fork !== this.tree.fork) {
443
+ // out of date this proof - ignore for now
444
+ return false
445
+ }
446
+
447
+ const batch = this.tree.verifyFullyRemote(proof)
448
+
449
+ if (!batch.signature || !this._signed(batch, batch.hash())) {
450
+ throw INVALID_SIGNATURE('Proof contains an invalid signature with no input from us')
451
+ }
452
+
453
+ const remoteTreeHash = this.crypto.tree(proof.upgrade.nodes)
454
+ const localTreeHash = this.crypto.tree(await this.tree.getRoots(proof.upgrade.length))
455
+
456
+ if (b4a.equals(localTreeHash, remoteTreeHash)) return false
457
+
458
+ await this.onconflict(proof)
459
+ return true
460
+ }
461
+
440
462
  async verify (proof, from) {
441
463
  // We cannot apply "other forks" atm.
442
464
  // We should probably still try and they are likely super similar for non upgrades
package/lib/errors.js CHANGED
@@ -32,6 +32,18 @@ module.exports = class HypercoreError extends Error {
32
32
  return new HypercoreError(msg, 'INVALID_CAPABILITY', HypercoreError.INVALID_CAPABILITY)
33
33
  }
34
34
 
35
+ static INVALID_CHECKSUM (msg = 'Invalid checksum') {
36
+ return new HypercoreError(msg, 'INVALID_CHECKSUM', HypercoreError.INVALID_CHECKSUM)
37
+ }
38
+
39
+ static INVALID_OPERATION (msg) {
40
+ return new HypercoreError(msg, 'INVALID_OPERATION', HypercoreError.INVALID_OPERATION)
41
+ }
42
+
43
+ static INVALID_PROOF (msg = 'Proof not verifiable') {
44
+ return new HypercoreError(msg, 'INVALID_PROOF', HypercoreError.INVALID_PROOF)
45
+ }
46
+
35
47
  static SNAPSHOT_NOT_AVAILABLE (msg = 'Snapshot is not available') {
36
48
  return new HypercoreError(msg, 'SNAPSHOT_NOT_AVAILABLE', HypercoreError.SNAPSHOT_NOT_AVAILABLE)
37
49
  }
@@ -47,4 +59,12 @@ module.exports = class HypercoreError extends Error {
47
59
  static SESSION_CLOSED (msg = 'Session is closed') {
48
60
  return new HypercoreError(msg, 'SESSION_CLOSED', HypercoreError.SESSION_CLOSED)
49
61
  }
62
+
63
+ static OPLOG_CORRUPT (msg = 'Oplog file appears corrupt or out of date') {
64
+ return new HypercoreError(msg, 'OPLOG_CORRUPT', HypercoreError.OPLOG_CORRUPT)
65
+ }
66
+
67
+ static INVALID_OPLOG_VERSION (msg = 'Invalid header version') {
68
+ return new HypercoreError(msg, 'INVALID_OPLOG_VERSION', HypercoreError.INVALID_OPLOG_VERSION)
69
+ }
50
70
  }
package/lib/info.js CHANGED
@@ -7,9 +7,10 @@ module.exports = class Info {
7
7
  this.byteLength = opts.byteLength || 0
8
8
  this.fork = opts.fork || 0
9
9
  this.padding = opts.padding || 0
10
+ this.storage = opts.storage || null
10
11
  }
11
12
 
12
- static async from (session) {
13
+ static async from (session, opts = {}) {
13
14
  return new Info({
14
15
  key: session.key,
15
16
  discoveryKey: session.discoveryKey,
@@ -17,7 +18,36 @@ module.exports = class Info {
17
18
  contiguousLength: session.contiguousLength,
18
19
  byteLength: session.byteLength,
19
20
  fork: session.fork,
20
- padding: session.padding
21
+ padding: session.padding,
22
+ storage: opts.storage ? await this.storage(session) : null
21
23
  })
22
24
  }
25
+
26
+ static async storage (session) {
27
+ const { oplog, tree, blocks, bitfield } = session.core
28
+ try {
29
+ return {
30
+ oplog: await bytesUsed(oplog.storage),
31
+ tree: await bytesUsed(tree.storage),
32
+ blocks: await bytesUsed(blocks.storage),
33
+ bitfield: await bytesUsed(bitfield.storage)
34
+ }
35
+ } catch {
36
+ return null
37
+ }
38
+
39
+ function bytesUsed (file) {
40
+ return new Promise((resolve, reject) => {
41
+ file.stat((err, st) => {
42
+ if (err) {
43
+ resolve(0) // prob just file not found (TODO, improve)
44
+ } else if (typeof st.blocks !== 'number') {
45
+ reject(new Error('cannot determine bytes used'))
46
+ } else {
47
+ resolve(st.blocks * 512)
48
+ }
49
+ })
50
+ })
51
+ }
52
+ }
23
53
  }
@@ -4,6 +4,7 @@ const c = require('compact-encoding')
4
4
  const Xache = require('xache')
5
5
  const b4a = require('b4a')
6
6
  const caps = require('./caps')
7
+ const { INVALID_PROOF, INVALID_CHECKSUM, INVALID_OPERATION, BAD_ARGUMENT } = require('./errors')
7
8
 
8
9
  const BLANK_HASH = b4a.alloc(32)
9
10
  const OLD_TREE = b4a.from([5, 2, 87, 2, 0, 0, 40, 7, 66, 76, 65, 75, 69, 50, 98])
@@ -26,12 +27,12 @@ class NodeQueue {
26
27
  }
27
28
 
28
29
  if (this.i >= this.nodes.length) {
29
- throw new Error('Expected node ' + index + ', got (nil)')
30
+ throw INVALID_OPERATION('Expected node ' + index + ', got (nil)')
30
31
  }
31
32
 
32
33
  const node = this.nodes[this.i++]
33
34
  if (node.index !== index) {
34
- throw new Error('Expected node ' + index + ', got node ' + node.index)
35
+ throw INVALID_OPERATION('Expected node ' + index + ', got node ' + node.index)
35
36
  }
36
37
 
37
38
  this.length--
@@ -109,7 +110,7 @@ class MerkleTreeBatch {
109
110
  }
110
111
 
111
112
  commit () {
112
- if (!this.commitable()) throw new Error('Tree was modified during batch, refusing to commit')
113
+ if (!this.commitable()) throw INVALID_OPERATION('Tree was modified during batch, refusing to commit')
113
114
 
114
115
  if (this.upgraded) this._commitUpgrade()
115
116
 
@@ -423,7 +424,7 @@ module.exports = class MerkleTree {
423
424
 
424
425
  if (node !== undefined) {
425
426
  if (node.hash === BLANK_HASH) {
426
- if (error) throw new Error('Could not load node: ' + index)
427
+ if (error) throw INVALID_OPERATION('Could not load node: ' + index)
427
428
  return Promise.resolve(null)
428
429
  }
429
430
  return Promise.resolve(node)
@@ -560,7 +561,7 @@ module.exports = class MerkleTree {
560
561
  }
561
562
 
562
563
  if (!verifyUpgrade(proof, unverified, batch)) {
563
- throw new Error('Fork proof not verifiable')
564
+ throw INVALID_PROOF('Fork proof not verifiable')
564
565
  }
565
566
 
566
567
  for (const root of batch.roots) {
@@ -580,6 +581,27 @@ module.exports = class MerkleTree {
580
581
  return batch
581
582
  }
582
583
 
584
+ verifyFullyRemote (proof) {
585
+ // TODO: impl this less hackishly
586
+ const batch = new MerkleTreeBatch(this)
587
+
588
+ batch.fork = proof.fork
589
+ batch.roots = []
590
+ batch.length = 0
591
+ batch.ancestors = 0
592
+ batch.byteLength = 0
593
+
594
+ let unverified = verifyTree(proof, this.crypto, batch.nodes)
595
+
596
+ if (proof.upgrade) {
597
+ if (verifyUpgrade(proof, unverified, batch)) {
598
+ unverified = null
599
+ }
600
+ }
601
+
602
+ return batch
603
+ }
604
+
583
605
  async verify (proof) {
584
606
  const batch = new MerkleTreeBatch(this)
585
607
 
@@ -594,7 +616,7 @@ module.exports = class MerkleTree {
594
616
  if (unverified) {
595
617
  const verified = await this.get(unverified.index)
596
618
  if (!b4a.equals(verified.hash, unverified.hash)) {
597
- throw new Error('Invalid checksum at node ' + unverified.index)
619
+ throw INVALID_CHECKSUM('Invalid checksum at node ' + unverified.index)
598
620
  }
599
621
  }
600
622
 
@@ -613,10 +635,10 @@ module.exports = class MerkleTree {
613
635
  const node = normalizeIndexed(block, hash)
614
636
 
615
637
  if (from >= to || to > head) {
616
- throw new Error('Invalid upgrade')
638
+ throw INVALID_OPERATION('Invalid upgrade')
617
639
  }
618
640
  if (seek && upgrade && node !== null && node.index >= from) {
619
- throw new Error('Cannot both do a seek and block/hash request when upgrading')
641
+ throw INVALID_OPERATION('Cannot both do a seek and block/hash request when upgrading')
620
642
  }
621
643
 
622
644
  let subTree = head
@@ -713,7 +735,7 @@ module.exports = class MerkleTree {
713
735
  async byteRange (index) {
714
736
  const head = 2 * this.length
715
737
  if (((index & 1) === 0 ? index : flat.rightSpan(index)) >= head) {
716
- throw new Error('Index is out of bounds')
738
+ throw BAD_ARGUMENT('Index is out of bounds')
717
739
  }
718
740
  return [await this.byteOffset(index), (await this.get(index)).size]
719
741
  }
@@ -862,7 +884,7 @@ function verifyUpgrade ({ fork, upgrade }, blockRoot, batch) {
862
884
  const node = extra[i++]
863
885
 
864
886
  while (node.index !== ite.index) {
865
- if (ite.factor === 2) throw new Error('Unexpected node: ' + node.index)
887
+ if (ite.factor === 2) throw INVALID_OPERATION('Unexpected node: ' + node.index)
866
888
  ite.leftChild()
867
889
  }
868
890
 
@@ -923,14 +945,14 @@ async function seekTrustedTree (tree, root, bytes) {
923
945
  async function seekUntrustedTree (tree, root, bytes) {
924
946
  const offset = await tree.byteOffset(root)
925
947
 
926
- if (offset > bytes) throw new Error('Invalid seek')
948
+ if (offset > bytes) throw INVALID_OPERATION('Invalid seek')
927
949
  if (offset === bytes) return root
928
950
 
929
951
  bytes -= offset
930
952
 
931
953
  const node = await tree.get(root)
932
954
 
933
- if (node.size <= bytes) throw new Error('Invalid seek')
955
+ if (node.size <= bytes) throw INVALID_OPERATION('Invalid seek')
934
956
 
935
957
  return seekTrustedTree(tree, root, bytes)
936
958
  }
@@ -1061,7 +1083,7 @@ function nodesToRoot (index, nodes, head) {
1061
1083
 
1062
1084
  for (let i = 0; i < nodes; i++) {
1063
1085
  ite.parent()
1064
- if (ite.contains(head)) throw new Error('Nodes is out of bounds')
1086
+ if (ite.contains(head)) throw BAD_ARGUMENT('Nodes is out of bounds')
1065
1087
  }
1066
1088
 
1067
1089
  return ite.index
package/lib/messages.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const c = require('compact-encoding')
2
2
  const b4a = require('b4a')
3
+ const { INVALID_OPLOG_VERSION } = require('./errors')
3
4
 
4
5
  const EMPTY = b4a.alloc(0)
5
6
 
@@ -637,7 +638,7 @@ oplog.header = {
637
638
  const version = c.uint.decode(state)
638
639
 
639
640
  if (version !== 0) {
640
- throw new Error('Invalid header version. Expected 0, got ' + version)
641
+ throw INVALID_OPLOG_VERSION('Invalid header version. Expected 0, got ' + version)
641
642
  }
642
643
 
643
644
  return {
package/lib/oplog.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const cenc = require('compact-encoding')
2
2
  const b4a = require('b4a')
3
3
  const { crc32 } = require('crc-universal')
4
+ const { OPLOG_CORRUPT } = require('./errors')
4
5
 
5
6
  module.exports = class Oplog {
6
7
  constructor (storage, { pageSize = 4096, headerEncoding = cenc.raw, entryEncoding = cenc.raw } = {}) {
@@ -77,7 +78,7 @@ module.exports = class Oplog {
77
78
  this._headers[1] = 0
78
79
 
79
80
  if (buffer.byteLength >= this._entryOffset) {
80
- throw new Error('Oplog file appears corrupt or out of date')
81
+ throw OPLOG_CORRUPT()
81
82
  }
82
83
  return result
83
84
  }
package/lib/replicator.js CHANGED
@@ -250,6 +250,8 @@ class Peer {
250
250
  this.protomux = protomux
251
251
  this.remotePublicKey = this.stream.remotePublicKey
252
252
 
253
+ this.paused = false
254
+
253
255
  this.session = session
254
256
 
255
257
  this.channel = channel
@@ -518,7 +520,32 @@ class Peer {
518
520
  })
519
521
  }
520
522
 
523
+ _checkIfConflict (err) {
524
+ this.paused = true
525
+
526
+ const length = Math.min(this.core.tree.length, this.remoteLength)
527
+ if (length === 0) throw err
528
+
529
+ this.wireRequest.send({
530
+ id: 0, // TODO: use an more explicit id for this eventually...
531
+ fork: this.remoteFork,
532
+ block: null,
533
+ hash: null,
534
+ seek: null,
535
+ upgrade: {
536
+ start: 0,
537
+ length
538
+ }
539
+ })
540
+ }
541
+
521
542
  async ondata (data) {
543
+ // always allow a fork conflict proof to be sent
544
+ if (data.request === 0 && data.upgrade && data.upgrade.start === 0) {
545
+ if (await this.core.checkConflict(data, this)) return
546
+ this.paused = false
547
+ }
548
+
522
549
  const req = data.request > 0 ? this.replicator._inflight.get(data.request) : null
523
550
  const reorg = data.fork > this.core.tree.fork
524
551
 
@@ -542,8 +569,11 @@ class Peer {
542
569
  return
543
570
  }
544
571
  } catch (err) {
572
+ safetyCatch(err)
573
+ // might be a fork, verify
574
+ this._checkIfConflict(err)
545
575
  this.replicator._onnodata(this, req)
546
- throw err
576
+ return
547
577
  } finally {
548
578
  this.dataProcessing--
549
579
  }
@@ -597,6 +627,25 @@ class Peer {
597
627
  this.replicator.updatePeer(this)
598
628
  }
599
629
 
630
+ async _onconflict () {
631
+ this.protomux.cork()
632
+ if (this.remoteLength > 0 && this.core.tree.fork === this.remoteFork) {
633
+ await this.onrequest({
634
+ id: 0,
635
+ fork: this.core.tree.fork,
636
+ block: null,
637
+ hash: null,
638
+ seek: null,
639
+ upgrade: {
640
+ start: 0,
641
+ length: Math.min(this.core.tree.length, this.remoteLength)
642
+ }
643
+ })
644
+ }
645
+ this.channel.close()
646
+ this.protomux.uncork()
647
+ }
648
+
600
649
  _makeRequest (needsUpgrade) {
601
650
  if (needsUpgrade === true && this.replicator._shouldUpgrade(this) === false) {
602
651
  return null
@@ -901,6 +950,15 @@ module.exports = class Replicator {
901
950
  if (this._ranges.length !== 0 || this._seeks.length !== 0) this._updateNonPrimary()
902
951
  }
903
952
 
953
+ // Called externally when a conflict has been detected and verified
954
+ async onconflict (from) {
955
+ const all = []
956
+ for (const peer of this.peers) {
957
+ all.push(peer._onconflict())
958
+ }
959
+ await Promise.allSettled(all)
960
+ }
961
+
904
962
  addUpgrade (session) {
905
963
  if (this._upgrade !== null) {
906
964
  const ref = this._upgrade.attach(session)
@@ -973,10 +1031,10 @@ module.exports = class Replicator {
973
1031
  ref.context.detach(ref, null)
974
1032
  }
975
1033
 
976
- clearRequests (session) {
1034
+ clearRequests (session, err = null) {
977
1035
  while (session.length > 0) {
978
1036
  const ref = session[session.length - 1]
979
- ref.context.detach(ref, null)
1037
+ ref.context.detach(ref, err)
980
1038
  }
981
1039
 
982
1040
  this.updateAll()
@@ -1418,7 +1476,7 @@ module.exports = class Replicator {
1418
1476
  }
1419
1477
 
1420
1478
  _updatePeer (peer) {
1421
- if (peer.inflight >= peer.maxInflight) {
1479
+ if (peer.paused || peer.inflight >= peer.maxInflight) {
1422
1480
  return false
1423
1481
  }
1424
1482
 
@@ -1444,7 +1502,7 @@ module.exports = class Replicator {
1444
1502
  }
1445
1503
 
1446
1504
  _updatePeerNonPrimary (peer) {
1447
- if (peer.inflight >= peer.maxInflight) {
1505
+ if (peer.paused || peer.inflight >= peer.maxInflight) {
1448
1506
  return false
1449
1507
  }
1450
1508
 
@@ -1523,6 +1581,13 @@ module.exports = class Replicator {
1523
1581
  })
1524
1582
  }
1525
1583
 
1584
+ destroy () {
1585
+ for (const peer of this.peers) {
1586
+ peer.protomux.unpair({ protocol: 'hypercore/alpha', id: this.discoveryKey })
1587
+ peer.channel.close()
1588
+ }
1589
+ }
1590
+
1526
1591
  _makePeer (protomux, session) {
1527
1592
  if (protomux.opened({ protocol: 'hypercore/alpha', id: this.discoveryKey })) return onnochannel()
1528
1593
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.4.1",
3
+ "version": "10.5.1",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -35,7 +35,7 @@
35
35
  "dependencies": {
36
36
  "@hyperswarm/secret-stream": "^6.0.0",
37
37
  "b4a": "^1.1.0",
38
- "big-sparse-array": "^1.0.2",
38
+ "big-sparse-array": "^1.0.3",
39
39
  "compact-encoding": "^2.11.0",
40
40
  "crc-universal": "^1.0.2",
41
41
  "events": "^3.3.0",
@@ -55,7 +55,7 @@
55
55
  "devDependencies": {
56
56
  "brittle": "^3.0.0",
57
57
  "hyperswarm": "^4.3.0",
58
- "random-access-memory": "^6.0.0",
58
+ "random-access-memory": "^6.1.0",
59
59
  "random-access-memory-overlay": "^3.0.0",
60
60
  "standard": "^17.0.0",
61
61
  "tmp-promise": "^3.0.2"