blind-peer 3.4.0 → 3.5.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/index.js +87 -4
- package/lib/top-k.js +94 -0
- package/package.json +15 -7
package/index.js
CHANGED
|
@@ -12,14 +12,21 @@ const safetyCatch = require('safety-catch')
|
|
|
12
12
|
const Wakeup = require('protomux-wakeup')
|
|
13
13
|
const ScopeLock = require('scope-lock')
|
|
14
14
|
const IdEnc = require('hypercore-id-encoding')
|
|
15
|
+
const ProtomuxRpcClientPool = require('protomux-rpc-client-pool')
|
|
16
|
+
const ProtomuxRpcClient = require('protomux-rpc-client')
|
|
17
|
+
const {
|
|
18
|
+
AddCoreEncoding,
|
|
19
|
+
DeleteCoreEncoding,
|
|
20
|
+
RouterResolvePeersRequest,
|
|
21
|
+
RouterResolvePeersResponse
|
|
22
|
+
} = require('blind-peer-encodings')
|
|
15
23
|
|
|
16
24
|
const BlindPeerDB = require('./lib/db.js')
|
|
25
|
+
const TopKWindow = require('./lib/top-k.js')
|
|
17
26
|
|
|
18
27
|
// Enable Small wants in Hypercore. Must be before anywhere that uses Hypercore
|
|
19
28
|
Hypercore.enable(Hypercore.SMALL_WANTS)
|
|
20
29
|
|
|
21
|
-
const { AddCoreEncoding, DeleteCoreEncoding } = require('blind-peer-encodings')
|
|
22
|
-
|
|
23
30
|
class CoreTracker {
|
|
24
31
|
constructor(blindPeer, core) {
|
|
25
32
|
this.blindPeer = blindPeer
|
|
@@ -197,10 +204,13 @@ class BlindPeer extends ReadyResource {
|
|
|
197
204
|
maxBytes = 100_000_000_000,
|
|
198
205
|
enableGc = true,
|
|
199
206
|
trustedPubKeys,
|
|
207
|
+
routerKey,
|
|
208
|
+
routerPoolOpts,
|
|
200
209
|
port,
|
|
201
210
|
announcingInterval = 100,
|
|
202
211
|
wakeupGcTickTime = null,
|
|
203
|
-
replicationLagThreshold = 100
|
|
212
|
+
replicationLagThreshold = 100,
|
|
213
|
+
topK = {}
|
|
204
214
|
} = {}
|
|
205
215
|
) {
|
|
206
216
|
super()
|
|
@@ -227,6 +237,10 @@ class BlindPeer extends ReadyResource {
|
|
|
227
237
|
this.announcedCores = new Map()
|
|
228
238
|
this.replicationLagThreshold = replicationLagThreshold
|
|
229
239
|
|
|
240
|
+
this.routerKey = routerKey || null
|
|
241
|
+
this.routerPoolOpts = routerPoolOpts || {}
|
|
242
|
+
this.routerPool = null
|
|
243
|
+
|
|
230
244
|
this.stats = {
|
|
231
245
|
bytesGcd: 0,
|
|
232
246
|
coresAdded: 0,
|
|
@@ -236,6 +250,17 @@ class BlindPeer extends ReadyResource {
|
|
|
236
250
|
muxerPaired: 0,
|
|
237
251
|
muxerErrors: 0
|
|
238
252
|
}
|
|
253
|
+
|
|
254
|
+
const {
|
|
255
|
+
bucketCount = 6,
|
|
256
|
+
bucketTime = 10_000,
|
|
257
|
+
k = 5,
|
|
258
|
+
peerThreshold = 100,
|
|
259
|
+
referrerThreshold = 100
|
|
260
|
+
} = topK
|
|
261
|
+
|
|
262
|
+
this.topKByPeer = new TopKWindow(bucketCount, bucketTime, k, peerThreshold)
|
|
263
|
+
this.topKByReferrer = new TopKWindow(bucketCount, bucketTime, k, referrerThreshold)
|
|
239
264
|
}
|
|
240
265
|
|
|
241
266
|
get encryptionPublicKey() {
|
|
@@ -285,6 +310,14 @@ class BlindPeer extends ReadyResource {
|
|
|
285
310
|
}
|
|
286
311
|
this.swarm.on('connection', this._onconnection.bind(this))
|
|
287
312
|
|
|
313
|
+
if (this.routerKey) {
|
|
314
|
+
const rpcClient = new ProtomuxRpcClient(this.swarm.dht)
|
|
315
|
+
this.routerPool = new ProtomuxRpcClientPool([this.routerKey], rpcClient, this.routerPoolOpts)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
await this.topKByPeer.ready()
|
|
319
|
+
await this.topKByReferrer.ready()
|
|
320
|
+
|
|
288
321
|
this._announceCores().catch(safetyCatch) // announcing cores asynchronously
|
|
289
322
|
this.flushInterval = setInterval(this.flush.bind(this), 10_000)
|
|
290
323
|
}
|
|
@@ -473,6 +506,23 @@ class BlindPeer extends ReadyResource {
|
|
|
473
506
|
stream.on('close', () => core.close().catch(safetyCatch))
|
|
474
507
|
}
|
|
475
508
|
|
|
509
|
+
async _resolvePeers(key) {
|
|
510
|
+
if (!this.routerPool) return
|
|
511
|
+
|
|
512
|
+
const result = await this.routerPool.makeRequest(
|
|
513
|
+
'resolve-peers',
|
|
514
|
+
{ key },
|
|
515
|
+
{
|
|
516
|
+
requestEncoding: RouterResolvePeersRequest,
|
|
517
|
+
responseEncoding: RouterResolvePeersResponse
|
|
518
|
+
}
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
this.emit('resolve-peers', { key, result })
|
|
522
|
+
|
|
523
|
+
return result
|
|
524
|
+
}
|
|
525
|
+
|
|
476
526
|
async _announceCores() {
|
|
477
527
|
for await (const record of this.db.createAnnouncingCoresStream()) {
|
|
478
528
|
if (this.closing) return
|
|
@@ -575,8 +625,14 @@ class BlindPeer extends ReadyResource {
|
|
|
575
625
|
|
|
576
626
|
async _onaddcores(stream, request) {
|
|
577
627
|
this.stats.addCoresRx++
|
|
578
|
-
|
|
628
|
+
|
|
579
629
|
const { cores, referrer } = request
|
|
630
|
+
if (referrer) {
|
|
631
|
+
this.topKByReferrer.hit(IdEnc.normalize(referrer))
|
|
632
|
+
}
|
|
633
|
+
this.topKByPeer.hit(IdEnc.normalize(stream.remotePublicKey))
|
|
634
|
+
|
|
635
|
+
const priority = Math.min(request.priority, 1) // 2 is reserved for trusted peers
|
|
580
636
|
|
|
581
637
|
if (request.announce !== false && !this._isTrustedPeer(stream.remotePublicKey)) {
|
|
582
638
|
// Note: we can't use the original downgrade-announce event because that assumes a 'record' object
|
|
@@ -650,6 +706,13 @@ class BlindPeer extends ReadyResource {
|
|
|
650
706
|
const discoveryKey = core.discoveryKey
|
|
651
707
|
await core.close()
|
|
652
708
|
await this._onwakeup(discoveryKey, muxer)
|
|
709
|
+
|
|
710
|
+
// TODO: will process the result in V2
|
|
711
|
+
// TODO: handle no referrer
|
|
712
|
+
this._resolvePeers(referrer).catch((err) => {
|
|
713
|
+
safetyCatch(err)
|
|
714
|
+
this.emit('resolve-peers-error', { key: referrer, error: err })
|
|
715
|
+
})
|
|
653
716
|
}
|
|
654
717
|
|
|
655
718
|
for (const r of recordsToAdd) this.emit('add-new-core', r, true, stream)
|
|
@@ -713,7 +776,13 @@ class BlindPeer extends ReadyResource {
|
|
|
713
776
|
}
|
|
714
777
|
|
|
715
778
|
async _close() {
|
|
779
|
+
if (this.routerPool) {
|
|
780
|
+
await this.routerPool.destroy()
|
|
781
|
+
await this.routerPool.statelessRpc.close()
|
|
782
|
+
}
|
|
716
783
|
clearInterval(this.flushInterval)
|
|
784
|
+
await this.topKByPeer.close()
|
|
785
|
+
await this.topKByReferrer.close()
|
|
717
786
|
if (this.ownsWakeup) this.wakeup.destroy()
|
|
718
787
|
if (this.ownsSwarm) await this.swarm.destroy()
|
|
719
788
|
await this.flush()
|
|
@@ -822,6 +891,20 @@ class BlindPeer extends ReadyResource {
|
|
|
822
891
|
this.set(self.stats.addCoresRx)
|
|
823
892
|
}
|
|
824
893
|
})
|
|
894
|
+
new promClient.Gauge({
|
|
895
|
+
name: 'blind_peer_add_cores_top5_by_remote_key',
|
|
896
|
+
help: 'The total number of requests from the top 5 peers in the last minute',
|
|
897
|
+
collect() {
|
|
898
|
+
this.set(self.topKByPeer.topKSum())
|
|
899
|
+
}
|
|
900
|
+
})
|
|
901
|
+
new promClient.Gauge({
|
|
902
|
+
name: 'blind_peer_add_cores_top5_by_referrer',
|
|
903
|
+
help: 'The total number of requests from the top 5 referrers in the last minute',
|
|
904
|
+
collect() {
|
|
905
|
+
this.set(self.topKByReferrer.topKSum())
|
|
906
|
+
}
|
|
907
|
+
})
|
|
825
908
|
if (self.rocks.stats) {
|
|
826
909
|
new promClient.Gauge({
|
|
827
910
|
// eslint-disable-line no-new
|
package/lib/top-k.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const ReadyResource = require('ready-resource')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tracks the most frequent keys within a rolling time window.
|
|
5
|
+
*/
|
|
6
|
+
class TopKWindow extends ReadyResource {
|
|
7
|
+
/**
|
|
8
|
+
* @param {number} [bucketCount=6]
|
|
9
|
+
* @param {number} [bucketTime=10_000]
|
|
10
|
+
* @param {number} [k=5]
|
|
11
|
+
* @param {number | null} [spikeThreshold=null]
|
|
12
|
+
*/
|
|
13
|
+
constructor(bucketCount = 6, bucketTime = 10_000, k = 5, spikeThreshold = null) {
|
|
14
|
+
super()
|
|
15
|
+
|
|
16
|
+
this.bucketCount = bucketCount
|
|
17
|
+
this.bucketTime = bucketTime
|
|
18
|
+
this.k = k
|
|
19
|
+
this.spikeThreshold = spikeThreshold
|
|
20
|
+
|
|
21
|
+
/** @type {Map<string, number>[]} */
|
|
22
|
+
this._buckets = Array.from({ length: bucketCount }, () => new Map())
|
|
23
|
+
this._index = 0
|
|
24
|
+
this.topK = []
|
|
25
|
+
this._timer = null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} key
|
|
30
|
+
*/
|
|
31
|
+
hit(key) {
|
|
32
|
+
const bucket = this._buckets[this._index]
|
|
33
|
+
bucket.set(key, (bucket.get(key) || 0) + 1)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @returns {number}
|
|
38
|
+
*/
|
|
39
|
+
topKSum() {
|
|
40
|
+
let sum = 0
|
|
41
|
+
for (const { count } of this.topK) {
|
|
42
|
+
sum += count
|
|
43
|
+
}
|
|
44
|
+
return sum
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async _open() {
|
|
48
|
+
this._timer = setInterval(this._rotate.bind(this), this.bucketTime)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async _close() {
|
|
52
|
+
if (this._timer !== null) {
|
|
53
|
+
clearInterval(this._timer)
|
|
54
|
+
this._timer = null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.topK = []
|
|
58
|
+
for (const bucket of this._buckets) {
|
|
59
|
+
bucket.clear()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_rotate() {
|
|
64
|
+
// compute the top-k and cache
|
|
65
|
+
const totals = new Map()
|
|
66
|
+
for (const bucket of this._buckets) {
|
|
67
|
+
for (const [key, count] of bucket) {
|
|
68
|
+
totals.set(key, (totals.get(key) || 0) + count)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.topK = [...totals.entries()]
|
|
73
|
+
.sort((a, b) => b[1] - a[1])
|
|
74
|
+
.slice(0, this.k)
|
|
75
|
+
.map(([key, count]) => ({ key, count }))
|
|
76
|
+
|
|
77
|
+
if (this.spikeThreshold !== null) {
|
|
78
|
+
// Deliberately emit only for cached top-k entries to keep spike volume bounded.
|
|
79
|
+
for (const { key, count } of this.topK) {
|
|
80
|
+
if (count >= this.spikeThreshold) {
|
|
81
|
+
this.emit('spike', key, count)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// rotate current bucket and clear the old bucket
|
|
87
|
+
this._index = (this._index + 1) % this.bucketCount
|
|
88
|
+
this._buckets[this._index].clear()
|
|
89
|
+
|
|
90
|
+
this.emit('rotated')
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = TopKWindow
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blind-peer",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.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.
|
|
9
|
+
"blind-peer-encodings": "^3.2.0",
|
|
10
10
|
"blind-peer-muxer": "^1.0.0",
|
|
11
11
|
"compact-encoding": "^2.16.0",
|
|
12
12
|
"corestore": "^7.4.4",
|
|
@@ -14,9 +14,12 @@
|
|
|
14
14
|
"hypercore-crypto": "^3.5.0",
|
|
15
15
|
"hypercore-id-encoding": "^1.3.0",
|
|
16
16
|
"hyperdb": "^5.0.0",
|
|
17
|
+
"hyperdht": "^6.29.0",
|
|
17
18
|
"hyperschema": "^1.10.3",
|
|
18
19
|
"hyperswarm": "^4.13.1",
|
|
19
20
|
"protomux-rpc": "^1.7.1",
|
|
21
|
+
"protomux-rpc-client": "^2.1.0",
|
|
22
|
+
"protomux-rpc-client-pool": "^2.1.0",
|
|
20
23
|
"protomux-wakeup": "^2.9.0",
|
|
21
24
|
"ready-resource": "^1.1.2",
|
|
22
25
|
"repl-swarm": "^2.3.0",
|
|
@@ -28,14 +31,15 @@
|
|
|
28
31
|
"bare-events": "^2.8.2",
|
|
29
32
|
"bare-process": "^4.2.2",
|
|
30
33
|
"bare-prom-client": "^15.1.3",
|
|
31
|
-
"blind-
|
|
34
|
+
"blind-peer-router": "^0.2.2",
|
|
35
|
+
"blind-peering": "^2.0.1",
|
|
32
36
|
"brittle": "^3.7.0",
|
|
33
37
|
"debounceify": "^1.1.0",
|
|
34
|
-
"
|
|
38
|
+
"lunte": "^1.6.0",
|
|
35
39
|
"prettier": "^3.6.2",
|
|
36
40
|
"prettier-config-holepunch": "^2.0.0",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
41
|
+
"protomux-rpc-router": "^1.2.0",
|
|
42
|
+
"test-tmp": "^1.3.0"
|
|
39
43
|
},
|
|
40
44
|
"files": [
|
|
41
45
|
"index.js",
|
|
@@ -44,7 +48,7 @@
|
|
|
44
48
|
"scripts": {
|
|
45
49
|
"format": "prettier --write .",
|
|
46
50
|
"format:check": "prettier --check .",
|
|
47
|
-
"test": "npm run format:check && brittle test/*.js",
|
|
51
|
+
"test": "npm run format:check && lunte && brittle test/*.js",
|
|
48
52
|
"test:bare": "bare test/test.js"
|
|
49
53
|
},
|
|
50
54
|
"repository": {
|
|
@@ -61,6 +65,10 @@
|
|
|
61
65
|
"events": {
|
|
62
66
|
"bare": "bare-events",
|
|
63
67
|
"default": "events"
|
|
68
|
+
},
|
|
69
|
+
"prom-client": {
|
|
70
|
+
"bare": "bare-prom-client",
|
|
71
|
+
"default": "prom-client"
|
|
64
72
|
}
|
|
65
73
|
}
|
|
66
74
|
}
|