blind-peer 3.0.1 → 3.2.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.
Files changed (2) hide show
  1. package/index.js +168 -6
  2. package/package.json +5 -4
package/index.js CHANGED
@@ -5,6 +5,7 @@ const ReadyResource = require('ready-resource')
5
5
  const Hyperswarm = require('hyperswarm')
6
6
  const ProtomuxRPC = require('protomux-rpc')
7
7
  const c = require('compact-encoding')
8
+ const BlindPeerMuxer = require('blind-peer-muxer')
8
9
  const b4a = require('b4a')
9
10
  const crypto = require('hypercore-crypto')
10
11
  const safetyCatch = require('safety-catch')
@@ -14,8 +15,7 @@ const IdEnc = require('hypercore-id-encoding')
14
15
 
15
16
  const BlindPeerDB = require('./lib/db.js')
16
17
 
17
- const { AddCoreEncoding } = require('blind-peer-encodings')
18
- const { DeleteCoreEncoding } = require('blind-peer-encodings')
18
+ const { AddCoreEncoding, DeleteCoreEncoding } = require('blind-peer-encodings')
19
19
 
20
20
  class CoreTracker {
21
21
  constructor(blindPeer, core) {
@@ -196,7 +196,8 @@ class BlindPeer extends ReadyResource {
196
196
  trustedPubKeys,
197
197
  port,
198
198
  announcingInterval = 100,
199
- wakeupGcTickTime = null
199
+ wakeupGcTickTime = null,
200
+ replicationLagThreshold = 100
200
201
  } = {}
201
202
  ) {
202
203
  super()
@@ -221,12 +222,16 @@ class BlindPeer extends ReadyResource {
221
222
  this.enableGc = enableGc
222
223
  this.lock = new ScopeLock({ debounce: true })
223
224
  this.announcedCores = new Map()
225
+ this.replicationLagThreshold = replicationLagThreshold
224
226
 
225
227
  this.stats = {
226
228
  bytesGcd: 0,
227
229
  coresAdded: 0,
228
230
  activations: 0,
229
- wakeups: 0
231
+ wakeups: 0,
232
+ addCoresRx: 0,
233
+ muxerPaired: 0,
234
+ muxerErrors: 0
230
235
  }
231
236
  }
232
237
 
@@ -424,6 +429,23 @@ class BlindPeer extends ReadyResource {
424
429
 
425
430
  rpc.respond('add-core', AddCoreEncoding, this._onaddcore.bind(this, conn))
426
431
  rpc.respond('delete-core', DeleteCoreEncoding, this._ondeletecore.bind(this, conn))
432
+
433
+ const self = this
434
+ BlindPeerMuxer.pair(conn, function () {
435
+ self.emit('muxer-paired', conn)
436
+ self.stats.muxerPaired++
437
+ new BlindPeerMuxer(conn, {
438
+ async oncores(request) {
439
+ try {
440
+ await self._onaddcores(conn, request)
441
+ } catch (e) {
442
+ self.stats.muxerErrors++
443
+ self.emit('muxer-error', e, conn)
444
+ throw e
445
+ }
446
+ }
447
+ })
448
+ })
427
449
  }
428
450
 
429
451
  async _activateCore(stream, record) {
@@ -467,17 +489,38 @@ class BlindPeer extends ReadyResource {
467
489
  const core = this.store.get({ key })
468
490
  this.announcedCores.set(coreId, core)
469
491
 
492
+ let activeSession = null
493
+
470
494
  core.on('append', () => {
495
+ const replicationLag = core.length - core.contiguousLength
496
+ if (!activeSession.isClient && replicationLag > this.replicationLagThreshold) {
497
+ activeSession.refresh({ server: true, client: true })
498
+ this.emit('core-client-mode-changed', core, true)
499
+ }
500
+
471
501
  this.emit('core-append', core)
472
502
  })
473
503
  core.on('download', () => {
474
- if (core.length === core.contiguousLength) {
504
+ const replicationLag = core.length - core.contiguousLength
505
+ if (replicationLag === 0) {
506
+ if (activeSession.isClient) {
507
+ activeSession.refresh({ server: true, client: false })
508
+ this.emit('core-client-mode-changed', core, false)
509
+ }
510
+
475
511
  this.emit('core-downloaded', core)
476
512
  }
477
513
  })
478
514
 
479
515
  await core.ready()
480
- this.swarm.join(core.discoveryKey, { server: true, client: false })
516
+
517
+ const replicationLag = core.length - core.contiguousLength
518
+ if (replicationLag > this.replicationLagThreshold || core.length === 0) {
519
+ activeSession = this.swarm.join(core.discoveryKey, { server: true, client: true })
520
+ this.emit('core-client-mode-changed', core, true)
521
+ } else {
522
+ activeSession = this.swarm.join(core.discoveryKey, { server: true, client: false })
523
+ }
481
524
 
482
525
  // WARNING: we do not yet handle the case where
483
526
  // data of an announced core is cleared
@@ -527,6 +570,100 @@ class BlindPeer extends ReadyResource {
527
570
  return coreRecord
528
571
  }
529
572
 
573
+ async _onaddcores(stream, request) {
574
+ this.stats.addCoresRx++
575
+ const priority = Math.min(request.priority, 1) // 2 is reserved for trusted peers
576
+ const { cores, referrer } = request
577
+
578
+ if (request.announce !== false && !this._isTrustedPeer(stream.remotePublicKey)) {
579
+ // Note: we can't use the original downgrade-announce event because that assumes a 'record' object
580
+ this.emit('add-cores-downgrade-announce', {
581
+ request,
582
+ remotePublicKey: stream.remotePublicKey
583
+ })
584
+ request.announce = false
585
+ }
586
+
587
+ this.emit('add-cores-received', stream, request)
588
+
589
+ const discKeys = []
590
+ const overview = new Map()
591
+ for (const c of cores) {
592
+ const discoveryKey = crypto.discoveryKey(c.key)
593
+ discKeys.push(discoveryKey)
594
+ const id = IdEnc.normalize(discoveryKey)
595
+ overview.set(id, {
596
+ key: c.key,
597
+ discoveryKey,
598
+ remoteLength: c.length,
599
+ announce: request.announce,
600
+ priority,
601
+ referrer,
602
+ ownLength: 0, // set later
603
+ ownContigLength: 0, // set later
604
+ needsActivation: false // changed later if needed
605
+ })
606
+ }
607
+
608
+ const recordsToAdd = []
609
+ const infos = await this.store.storage.getInfos(discKeys)
610
+ for (let i = 0; i < infos.length; i++) {
611
+ const id = IdEnc.normalize(discKeys[i])
612
+ const storageInfo = infos[i]
613
+ const entry = overview.get(id)
614
+
615
+ // Note: just the null check does not suffice for storageInfo, because we already try
616
+ // loading some keys from other contexts, like when the system core of an autobase is
617
+ // used as a referrer.
618
+ if (
619
+ storageInfo === null ||
620
+ entry.announce ||
621
+ (storageInfo.head === null && entry.remoteLength > 0)
622
+ ) {
623
+ entry.needsActivation = true
624
+ recordsToAdd.push({
625
+ key: entry.key,
626
+ priority: entry.priority,
627
+ announce: entry.announce,
628
+ referrer: entry.referrer
629
+ })
630
+ } else {
631
+ entry.ownLength = storageInfo.head?.length || 0
632
+ entry.ownContigLength = storageInfo.hints?.contiguousLength || 0
633
+ if (entry.ownLength !== entry.remoteLength) entry.needsActivation = true
634
+ if (entry.ownLength > entry.ownContigLength) entry.needsActivation = true
635
+ }
636
+ }
637
+
638
+ // Note: not race condition safe when called with the same cores at a similar time,
639
+ // but it's no problem if we do add the same core twice
640
+ for (const record of recordsToAdd) this.db.addCore(record)
641
+ if (recordsToAdd.length > 0) await this.flush() // flush now as important data
642
+
643
+ if (referrer) {
644
+ const muxer = stream.userData
645
+ const core = this.store.get({ key: referrer })
646
+ await core.ready()
647
+ const discoveryKey = core.discoveryKey
648
+ await core.close()
649
+ await this._onwakeup(discoveryKey, muxer)
650
+ }
651
+
652
+ for (const r of recordsToAdd) this.emit('add-new-core', r, true, stream)
653
+
654
+ const activateProms = []
655
+ for (const entry of overview.values()) {
656
+ if (!entry.needsActivation) continue
657
+ this.stats.coresAdded++
658
+ this.emit('add-core', entry, true, stream)
659
+ activateProms.push(this._activateCore(stream, entry))
660
+ }
661
+ await Promise.all(activateProms)
662
+
663
+ this.emit('add-cores-done', stream, request)
664
+ return null
665
+ }
666
+
530
667
  async _ondeletecore(stream, { key }) {
531
668
  if (!this._isTrustedPeer(stream.remotePublicKey)) {
532
669
  this.emit('delete-blocked', stream, { key })
@@ -657,6 +794,31 @@ class BlindPeer extends ReadyResource {
657
794
  this.set(self.nrAnnouncedCores)
658
795
  }
659
796
  })
797
+
798
+ new promClient.Gauge({
799
+ // eslint-disable-line no-new
800
+ name: 'blind_peer_muxer_errors',
801
+ help: 'The amount of errors on the protomux muxer',
802
+ collect() {
803
+ this.set(self.stats.muxerErrors)
804
+ }
805
+ })
806
+ new promClient.Gauge({
807
+ // eslint-disable-line no-new
808
+ name: 'blind_peer_muxer_paired',
809
+ help: 'The amount of blind-peer-muxer sessions paired',
810
+ collect() {
811
+ this.set(self.stats.muxerPaired)
812
+ }
813
+ })
814
+ new promClient.Gauge({
815
+ // eslint-disable-line no-new
816
+ name: 'blind_peer_add_cores_rx',
817
+ help: 'The amount of add-cores requests received',
818
+ collect() {
819
+ this.set(self.stats.addCoresRx)
820
+ }
821
+ })
660
822
  }
661
823
  }
662
824
 
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "blind-peer",
3
- "version": "3.0.1",
3
+ "version": "3.2.0",
4
4
  "description": "Blind peers help keep hypercores available",
5
5
  "main": "index.js",
6
6
  "dependencies": {
7
7
  "autobase": "^7.0.18",
8
8
  "b4a": "^1.6.7",
9
- "blind-peer-encodings": "^3.1.0",
9
+ "blind-peer-encodings": "^3.1.1",
10
+ "blind-peer-muxer": "^1.0.0",
10
11
  "compact-encoding": "^2.16.0",
11
12
  "corestore": "^7.4.4",
12
13
  "hypercore": "^11.0.12",
@@ -26,13 +27,13 @@
26
27
  "devDependencies": {
27
28
  "bare-events": "^2.8.2",
28
29
  "bare-process": "^4.2.2",
29
- "blind-peering": "^1.13.0",
30
+ "bare-prom-client": "^15.1.3",
31
+ "blind-peering": "^2.0.0",
30
32
  "brittle": "^3.7.0",
31
33
  "debounceify": "^1.1.0",
32
34
  "hyperdht": "^6.20.1",
33
35
  "prettier": "^3.6.2",
34
36
  "prettier-config-holepunch": "^2.0.0",
35
- "bare-prom-client": "^15.1.3",
36
37
  "test-tmp": "^1.3.0",
37
38
  "which-runtime": "^1.3.2"
38
39
  },