@libp2p/gossipsub 14.1.1-6059227cb
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 +85 -0
- package/dist/index.min.js +19 -0
- package/dist/index.min.js.map +7 -0
- package/dist/src/config.d.ts +32 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +2 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/constants.d.ts +213 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +217 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +15 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/gossipsub.d.ts +419 -0
- package/dist/src/gossipsub.d.ts.map +1 -0
- package/dist/src/gossipsub.js +2520 -0
- package/dist/src/gossipsub.js.map +1 -0
- package/dist/src/index.d.ts +344 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +43 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/message/decodeRpc.d.ts +11 -0
- package/dist/src/message/decodeRpc.d.ts.map +1 -0
- package/dist/src/message/decodeRpc.js +10 -0
- package/dist/src/message/decodeRpc.js.map +1 -0
- package/dist/src/message/index.d.ts +2 -0
- package/dist/src/message/index.d.ts.map +1 -0
- package/dist/src/message/index.js +2 -0
- package/dist/src/message/index.js.map +1 -0
- package/dist/src/message/rpc.d.ts +99 -0
- package/dist/src/message/rpc.d.ts.map +1 -0
- package/dist/src/message/rpc.js +663 -0
- package/dist/src/message/rpc.js.map +1 -0
- package/dist/src/message-cache.d.ts +80 -0
- package/dist/src/message-cache.d.ts.map +1 -0
- package/dist/src/message-cache.js +144 -0
- package/dist/src/message-cache.js.map +1 -0
- package/dist/src/metrics.d.ts +467 -0
- package/dist/src/metrics.d.ts.map +1 -0
- package/dist/src/metrics.js +896 -0
- package/dist/src/metrics.js.map +1 -0
- package/dist/src/score/compute-score.d.ts +4 -0
- package/dist/src/score/compute-score.d.ts.map +1 -0
- package/dist/src/score/compute-score.js +75 -0
- package/dist/src/score/compute-score.js.map +1 -0
- package/dist/src/score/index.d.ts +4 -0
- package/dist/src/score/index.d.ts.map +1 -0
- package/dist/src/score/index.js +4 -0
- package/dist/src/score/index.js.map +1 -0
- package/dist/src/score/message-deliveries.d.ts +45 -0
- package/dist/src/score/message-deliveries.d.ts.map +1 -0
- package/dist/src/score/message-deliveries.js +75 -0
- package/dist/src/score/message-deliveries.js.map +1 -0
- package/dist/src/score/peer-score-params.d.ts +125 -0
- package/dist/src/score/peer-score-params.d.ts.map +1 -0
- package/dist/src/score/peer-score-params.js +159 -0
- package/dist/src/score/peer-score-params.js.map +1 -0
- package/dist/src/score/peer-score-thresholds.d.ts +31 -0
- package/dist/src/score/peer-score-thresholds.d.ts.map +1 -0
- package/dist/src/score/peer-score-thresholds.js +32 -0
- package/dist/src/score/peer-score-thresholds.js.map +1 -0
- package/dist/src/score/peer-score.d.ts +119 -0
- package/dist/src/score/peer-score.d.ts.map +1 -0
- package/dist/src/score/peer-score.js +459 -0
- package/dist/src/score/peer-score.js.map +1 -0
- package/dist/src/score/peer-stats.d.ts +32 -0
- package/dist/src/score/peer-stats.d.ts.map +1 -0
- package/dist/src/score/peer-stats.js +2 -0
- package/dist/src/score/peer-stats.js.map +1 -0
- package/dist/src/score/scoreMetrics.d.ts +23 -0
- package/dist/src/score/scoreMetrics.d.ts.map +1 -0
- package/dist/src/score/scoreMetrics.js +155 -0
- package/dist/src/score/scoreMetrics.js.map +1 -0
- package/dist/src/stream.d.ts +30 -0
- package/dist/src/stream.d.ts.map +1 -0
- package/dist/src/stream.js +55 -0
- package/dist/src/stream.js.map +1 -0
- package/dist/src/tracer.d.ts +53 -0
- package/dist/src/tracer.d.ts.map +1 -0
- package/dist/src/tracer.js +155 -0
- package/dist/src/tracer.js.map +1 -0
- package/dist/src/types.d.ts +148 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +90 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/buildRawMessage.d.ts +20 -0
- package/dist/src/utils/buildRawMessage.d.ts.map +1 -0
- package/dist/src/utils/buildRawMessage.js +151 -0
- package/dist/src/utils/buildRawMessage.js.map +1 -0
- package/dist/src/utils/create-gossip-rpc.d.ts +7 -0
- package/dist/src/utils/create-gossip-rpc.d.ts.map +1 -0
- package/dist/src/utils/create-gossip-rpc.js +31 -0
- package/dist/src/utils/create-gossip-rpc.js.map +1 -0
- package/dist/src/utils/index.d.ts +4 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +4 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/messageIdToString.d.ts +5 -0
- package/dist/src/utils/messageIdToString.d.ts.map +1 -0
- package/dist/src/utils/messageIdToString.js +8 -0
- package/dist/src/utils/messageIdToString.js.map +1 -0
- package/dist/src/utils/msgIdFn.d.ts +10 -0
- package/dist/src/utils/msgIdFn.d.ts.map +1 -0
- package/dist/src/utils/msgIdFn.js +23 -0
- package/dist/src/utils/msgIdFn.js.map +1 -0
- package/dist/src/utils/multiaddr.d.ts +3 -0
- package/dist/src/utils/multiaddr.d.ts.map +1 -0
- package/dist/src/utils/multiaddr.js +15 -0
- package/dist/src/utils/multiaddr.js.map +1 -0
- package/dist/src/utils/publishConfig.d.ts +8 -0
- package/dist/src/utils/publishConfig.d.ts.map +1 -0
- package/dist/src/utils/publishConfig.js +25 -0
- package/dist/src/utils/publishConfig.js.map +1 -0
- package/dist/src/utils/set.d.ts +14 -0
- package/dist/src/utils/set.d.ts.map +1 -0
- package/dist/src/utils/set.js +41 -0
- package/dist/src/utils/set.js.map +1 -0
- package/dist/src/utils/shuffle.d.ts +7 -0
- package/dist/src/utils/shuffle.d.ts.map +1 -0
- package/dist/src/utils/shuffle.js +21 -0
- package/dist/src/utils/shuffle.js.map +1 -0
- package/dist/src/utils/time-cache.d.ts +22 -0
- package/dist/src/utils/time-cache.d.ts.map +1 -0
- package/dist/src/utils/time-cache.js +54 -0
- package/dist/src/utils/time-cache.js.map +1 -0
- package/package.json +142 -0
- package/src/config.ts +31 -0
- package/src/constants.ts +261 -0
- package/src/errors.ts +17 -0
- package/src/gossipsub.ts +3061 -0
- package/src/index.ts +404 -0
- package/src/message/decodeRpc.ts +19 -0
- package/src/message/index.ts +1 -0
- package/src/message/rpc.proto +58 -0
- package/src/message/rpc.ts +848 -0
- package/src/message-cache.ts +196 -0
- package/src/metrics.ts +1014 -0
- package/src/score/compute-score.ts +98 -0
- package/src/score/index.ts +3 -0
- package/src/score/message-deliveries.ts +95 -0
- package/src/score/peer-score-params.ts +316 -0
- package/src/score/peer-score-thresholds.ts +70 -0
- package/src/score/peer-score.ts +565 -0
- package/src/score/peer-stats.ts +33 -0
- package/src/score/scoreMetrics.ts +215 -0
- package/src/stream.ts +79 -0
- package/src/tracer.ts +177 -0
- package/src/types.ts +178 -0
- package/src/utils/buildRawMessage.ts +174 -0
- package/src/utils/create-gossip-rpc.ts +34 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/messageIdToString.ts +8 -0
- package/src/utils/msgIdFn.ts +24 -0
- package/src/utils/multiaddr.ts +19 -0
- package/src/utils/publishConfig.ts +33 -0
- package/src/utils/set.ts +43 -0
- package/src/utils/shuffle.ts +21 -0
- package/src/utils/time-cache.ts +71 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { PeerScoreParams } from './peer-score-params.js'
|
|
2
|
+
import type { PeerStats } from './peer-stats.js'
|
|
3
|
+
|
|
4
|
+
export function computeScore (
|
|
5
|
+
peer: string,
|
|
6
|
+
pstats: PeerStats,
|
|
7
|
+
params: PeerScoreParams,
|
|
8
|
+
peerIPs: Map<string, Set<string>>
|
|
9
|
+
): number {
|
|
10
|
+
let score = 0
|
|
11
|
+
|
|
12
|
+
// topic stores
|
|
13
|
+
Object.entries(pstats.topics).forEach(([topic, tstats]) => {
|
|
14
|
+
// the topic parameters
|
|
15
|
+
const topicParams = params.topics[topic]
|
|
16
|
+
if (topicParams === undefined) {
|
|
17
|
+
// we are not scoring this topic
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let topicScore = 0
|
|
22
|
+
|
|
23
|
+
// P1: time in Mesh
|
|
24
|
+
if (tstats.inMesh) {
|
|
25
|
+
let p1 = tstats.meshTime / topicParams.timeInMeshQuantum
|
|
26
|
+
if (p1 > topicParams.timeInMeshCap) {
|
|
27
|
+
p1 = topicParams.timeInMeshCap
|
|
28
|
+
}
|
|
29
|
+
topicScore += p1 * topicParams.timeInMeshWeight
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// P2: first message deliveries
|
|
33
|
+
let p2 = tstats.firstMessageDeliveries
|
|
34
|
+
if (p2 > topicParams.firstMessageDeliveriesCap) {
|
|
35
|
+
p2 = topicParams.firstMessageDeliveriesCap
|
|
36
|
+
}
|
|
37
|
+
topicScore += p2 * topicParams.firstMessageDeliveriesWeight
|
|
38
|
+
|
|
39
|
+
// P3: mesh message deliveries
|
|
40
|
+
if (
|
|
41
|
+
tstats.meshMessageDeliveriesActive &&
|
|
42
|
+
tstats.meshMessageDeliveries < topicParams.meshMessageDeliveriesThreshold
|
|
43
|
+
) {
|
|
44
|
+
const deficit = topicParams.meshMessageDeliveriesThreshold - tstats.meshMessageDeliveries
|
|
45
|
+
const p3 = deficit * deficit
|
|
46
|
+
topicScore += p3 * topicParams.meshMessageDeliveriesWeight
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// P3b:
|
|
50
|
+
// NOTE: the weight of P3b is negative (validated in validateTopicScoreParams) so this detracts
|
|
51
|
+
const p3b = tstats.meshFailurePenalty
|
|
52
|
+
topicScore += p3b * topicParams.meshFailurePenaltyWeight
|
|
53
|
+
|
|
54
|
+
// P4: invalid messages
|
|
55
|
+
// NOTE: the weight of P4 is negative (validated in validateTopicScoreParams) so this detracts
|
|
56
|
+
const p4 = tstats.invalidMessageDeliveries * tstats.invalidMessageDeliveries
|
|
57
|
+
topicScore += p4 * topicParams.invalidMessageDeliveriesWeight
|
|
58
|
+
|
|
59
|
+
// update score, mixing with topic weight
|
|
60
|
+
score += topicScore * topicParams.topicWeight
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// apply the topic score cap, if any
|
|
64
|
+
if (params.topicScoreCap > 0 && score > params.topicScoreCap) {
|
|
65
|
+
score = params.topicScoreCap
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// P5: application-specific score
|
|
69
|
+
const p5 = params.appSpecificScore(peer)
|
|
70
|
+
score += p5 * params.appSpecificWeight
|
|
71
|
+
|
|
72
|
+
// P6: IP colocation factor
|
|
73
|
+
pstats.knownIPs.forEach((ip) => {
|
|
74
|
+
if (params.IPColocationFactorWhitelist.has(ip)) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// P6 has a cliff (IPColocationFactorThreshold)
|
|
79
|
+
// It's only applied if at least that many peers are connected to us from that source IP addr.
|
|
80
|
+
// It is quadratic, and the weight is negative (validated in validatePeerScoreParams)
|
|
81
|
+
const peersInIP = peerIPs.get(ip)
|
|
82
|
+
const numPeersInIP = (peersInIP != null) ? peersInIP.size : 0
|
|
83
|
+
if (numPeersInIP > params.IPColocationFactorThreshold) {
|
|
84
|
+
const surplus = numPeersInIP - params.IPColocationFactorThreshold
|
|
85
|
+
const p6 = surplus * surplus
|
|
86
|
+
score += p6 * params.IPColocationFactorWeight
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// P7: behavioural pattern penalty
|
|
91
|
+
if (pstats.behaviourPenalty > params.behaviourPenaltyThreshold) {
|
|
92
|
+
const excess = pstats.behaviourPenalty - params.behaviourPenaltyThreshold
|
|
93
|
+
const p7 = excess * excess
|
|
94
|
+
score += p7 * params.behaviourPenaltyWeight
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return score
|
|
98
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import Denque from 'denque'
|
|
2
|
+
import { TimeCacheDuration } from '../constants.js'
|
|
3
|
+
|
|
4
|
+
export enum DeliveryRecordStatus {
|
|
5
|
+
/**
|
|
6
|
+
* we don't know (yet) if the message is valid
|
|
7
|
+
*/
|
|
8
|
+
unknown,
|
|
9
|
+
/**
|
|
10
|
+
* we know the message is valid
|
|
11
|
+
*/
|
|
12
|
+
valid,
|
|
13
|
+
/**
|
|
14
|
+
* we know the message is invalid
|
|
15
|
+
*/
|
|
16
|
+
invalid,
|
|
17
|
+
/**
|
|
18
|
+
* we were instructed by the validator to ignore the message
|
|
19
|
+
*/
|
|
20
|
+
ignored
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DeliveryRecord {
|
|
24
|
+
status: DeliveryRecordStatus
|
|
25
|
+
firstSeenTsMs: number
|
|
26
|
+
validated: number
|
|
27
|
+
peers: Set<string>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface DeliveryQueueEntry {
|
|
31
|
+
msgId: string
|
|
32
|
+
expire: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Map of canonical message ID to DeliveryRecord
|
|
37
|
+
*
|
|
38
|
+
* Maintains an internal queue for efficient gc of old messages
|
|
39
|
+
*/
|
|
40
|
+
export class MessageDeliveries {
|
|
41
|
+
private readonly records: Map<string, DeliveryRecord>
|
|
42
|
+
public queue: Denque<DeliveryQueueEntry>
|
|
43
|
+
|
|
44
|
+
constructor () {
|
|
45
|
+
this.records = new Map()
|
|
46
|
+
this.queue = new Denque()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getRecord (msgIdStr: string): DeliveryRecord | undefined {
|
|
50
|
+
return this.records.get(msgIdStr)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
ensureRecord (msgIdStr: string): DeliveryRecord {
|
|
54
|
+
let drec = this.records.get(msgIdStr)
|
|
55
|
+
if (drec != null) {
|
|
56
|
+
return drec
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// record doesn't exist yet
|
|
60
|
+
// create record
|
|
61
|
+
drec = {
|
|
62
|
+
status: DeliveryRecordStatus.unknown,
|
|
63
|
+
firstSeenTsMs: Date.now(),
|
|
64
|
+
validated: 0,
|
|
65
|
+
peers: new Set()
|
|
66
|
+
}
|
|
67
|
+
this.records.set(msgIdStr, drec)
|
|
68
|
+
|
|
69
|
+
// and add msgId to the queue
|
|
70
|
+
const entry: DeliveryQueueEntry = {
|
|
71
|
+
msgId: msgIdStr,
|
|
72
|
+
expire: Date.now() + TimeCacheDuration
|
|
73
|
+
}
|
|
74
|
+
this.queue.push(entry)
|
|
75
|
+
|
|
76
|
+
return drec
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
gc (): void {
|
|
80
|
+
const now = Date.now()
|
|
81
|
+
// queue is sorted by expiry time
|
|
82
|
+
// remove expired messages, remove from queue until first un-expired message found
|
|
83
|
+
let head = this.queue.peekFront()
|
|
84
|
+
while ((head != null) && head.expire < now) {
|
|
85
|
+
this.records.delete(head.msgId)
|
|
86
|
+
this.queue.shift()
|
|
87
|
+
head = this.queue.peekFront()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
clear (): void {
|
|
92
|
+
this.records.clear()
|
|
93
|
+
this.queue.clear()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { InvalidPeerScoreParamsError } from '../errors.js'
|
|
2
|
+
|
|
3
|
+
// This file defines PeerScoreParams and TopicScoreParams interfaces
|
|
4
|
+
// as well as constructors, default constructors, and validation functions
|
|
5
|
+
// for these interfaces
|
|
6
|
+
|
|
7
|
+
export interface PeerScoreParams {
|
|
8
|
+
/**
|
|
9
|
+
* Score parameters per topic.
|
|
10
|
+
*/
|
|
11
|
+
topics: Record<string, TopicScoreParams>
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Aggregate topic score cap; this limits the total contribution of topics towards a positive
|
|
15
|
+
* score. It must be positive (or 0 for no cap).
|
|
16
|
+
*/
|
|
17
|
+
topicScoreCap: number
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* P5: Application-specific peer scoring
|
|
21
|
+
*/
|
|
22
|
+
appSpecificScore(p: string): number
|
|
23
|
+
appSpecificWeight: number
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* P6: IP-colocation factor.
|
|
27
|
+
* The parameter has an associated counter which counts the number of peers with the same IP.
|
|
28
|
+
* If the number of peers in the same IP exceeds IPColocationFactorThreshold, then the value
|
|
29
|
+
* is the square of the difference, ie (PeersInSameIP - IPColocationThreshold)^2.
|
|
30
|
+
* If the number of peers in the same IP is less than the threshold, then the value is 0.
|
|
31
|
+
* The weight of the parameter MUST be negative, unless you want to disable for testing.
|
|
32
|
+
* Note: In order to simulate many IPs in a managable manner when testing, you can set the weight to 0
|
|
33
|
+
* thus disabling the IP colocation penalty.
|
|
34
|
+
*/
|
|
35
|
+
IPColocationFactorWeight: number
|
|
36
|
+
IPColocationFactorThreshold: number
|
|
37
|
+
IPColocationFactorWhitelist: Set<string>
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* P7: behavioural pattern penalties.
|
|
41
|
+
* This parameter has an associated counter which tracks misbehaviour as detected by the
|
|
42
|
+
* router. The router currently applies penalties for the following behaviors:
|
|
43
|
+
* - attempting to re-graft before the prune backoff time has elapsed.
|
|
44
|
+
* - not following up in IWANT requests for messages advertised with IHAVE.
|
|
45
|
+
*
|
|
46
|
+
* The value of the parameter is the square of the counter, which decays with BehaviourPenaltyDecay.
|
|
47
|
+
* The weight of the parameter MUST be negative (or zero to disable).
|
|
48
|
+
*/
|
|
49
|
+
behaviourPenaltyWeight: number
|
|
50
|
+
behaviourPenaltyThreshold: number
|
|
51
|
+
behaviourPenaltyDecay: number
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* the decay interval for parameter counters.
|
|
55
|
+
*/
|
|
56
|
+
decayInterval: number
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* counter value below which it is considered 0.
|
|
60
|
+
*/
|
|
61
|
+
decayToZero: number
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* time to remember counters for a disconnected peer.
|
|
65
|
+
*/
|
|
66
|
+
retainScore: number
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface TopicScoreParams {
|
|
70
|
+
/**
|
|
71
|
+
* The weight of the topic.
|
|
72
|
+
*/
|
|
73
|
+
topicWeight: number
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* P1: time in the mesh
|
|
77
|
+
* This is the time the peer has ben grafted in the mesh.
|
|
78
|
+
* The value of the parameter is the time/TimeInMeshQuantum, capped by TimeInMeshCap
|
|
79
|
+
* The weight of the parameter MUST be positive (or zero to disable).
|
|
80
|
+
*/
|
|
81
|
+
timeInMeshWeight: number
|
|
82
|
+
timeInMeshQuantum: number
|
|
83
|
+
timeInMeshCap: number
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* P2: first message deliveries
|
|
87
|
+
* This is the number of message deliveries in the topic.
|
|
88
|
+
* The value of the parameter is a counter, decaying with FirstMessageDeliveriesDecay, and capped
|
|
89
|
+
* by FirstMessageDeliveriesCap.
|
|
90
|
+
* The weight of the parameter MUST be positive (or zero to disable).
|
|
91
|
+
*/
|
|
92
|
+
firstMessageDeliveriesWeight: number
|
|
93
|
+
firstMessageDeliveriesDecay: number
|
|
94
|
+
firstMessageDeliveriesCap: number
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* P3: mesh message deliveries
|
|
98
|
+
* This is the number of message deliveries in the mesh, within the MeshMessageDeliveriesWindow of
|
|
99
|
+
* message validation; deliveries during validation also count and are retroactively applied
|
|
100
|
+
* when validation succeeds.
|
|
101
|
+
* This window accounts for the minimum time before a hostile mesh peer trying to game the score
|
|
102
|
+
* could replay back a valid message we just sent them.
|
|
103
|
+
* It effectively tracks first and near-first deliveries, ie a message seen from a mesh peer
|
|
104
|
+
* before we have forwarded it to them.
|
|
105
|
+
* The parameter has an associated counter, decaying with MeshMessageDeliveriesDecay.
|
|
106
|
+
* If the counter exceeds the threshold, its value is 0.
|
|
107
|
+
* If the counter is below the MeshMessageDeliveriesThreshold, the value is the square of
|
|
108
|
+
* the deficit, ie (MessageDeliveriesThreshold - counter)^2
|
|
109
|
+
* The penalty is only activated after MeshMessageDeliveriesActivation time in the mesh.
|
|
110
|
+
* The weight of the parameter MUST be negative (or zero to disable).
|
|
111
|
+
*/
|
|
112
|
+
meshMessageDeliveriesWeight: number
|
|
113
|
+
meshMessageDeliveriesDecay: number
|
|
114
|
+
meshMessageDeliveriesCap: number
|
|
115
|
+
meshMessageDeliveriesThreshold: number
|
|
116
|
+
meshMessageDeliveriesWindow: number
|
|
117
|
+
meshMessageDeliveriesActivation: number
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* P3b: sticky mesh propagation failures
|
|
121
|
+
* This is a sticky penalty that applies when a peer gets pruned from the mesh with an active
|
|
122
|
+
* mesh message delivery penalty.
|
|
123
|
+
* The weight of the parameter MUST be negative (or zero to disable)
|
|
124
|
+
*/
|
|
125
|
+
meshFailurePenaltyWeight: number
|
|
126
|
+
meshFailurePenaltyDecay: number
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* P4: invalid messages
|
|
130
|
+
* This is the number of invalid messages in the topic.
|
|
131
|
+
* The value of the parameter is the square of the counter, decaying with
|
|
132
|
+
* InvalidMessageDeliveriesDecay.
|
|
133
|
+
* The weight of the parameter MUST be negative (or zero to disable).
|
|
134
|
+
*/
|
|
135
|
+
invalidMessageDeliveriesWeight: number
|
|
136
|
+
invalidMessageDeliveriesDecay: number
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const defaultPeerScoreParams: PeerScoreParams = {
|
|
140
|
+
topics: {},
|
|
141
|
+
topicScoreCap: 10.0,
|
|
142
|
+
appSpecificScore: () => 0.0,
|
|
143
|
+
appSpecificWeight: 10.0,
|
|
144
|
+
IPColocationFactorWeight: -5.0,
|
|
145
|
+
IPColocationFactorThreshold: 10.0,
|
|
146
|
+
IPColocationFactorWhitelist: new Set(),
|
|
147
|
+
behaviourPenaltyWeight: -10.0,
|
|
148
|
+
behaviourPenaltyThreshold: 0.0,
|
|
149
|
+
behaviourPenaltyDecay: 0.2,
|
|
150
|
+
decayInterval: 1000.0,
|
|
151
|
+
decayToZero: 0.1,
|
|
152
|
+
retainScore: 3600 * 1000
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const defaultTopicScoreParams: TopicScoreParams = {
|
|
156
|
+
topicWeight: 0.5,
|
|
157
|
+
timeInMeshWeight: 1,
|
|
158
|
+
timeInMeshQuantum: 1,
|
|
159
|
+
timeInMeshCap: 3600,
|
|
160
|
+
|
|
161
|
+
firstMessageDeliveriesWeight: 1,
|
|
162
|
+
firstMessageDeliveriesDecay: 0.5,
|
|
163
|
+
firstMessageDeliveriesCap: 2000,
|
|
164
|
+
|
|
165
|
+
meshMessageDeliveriesWeight: -1,
|
|
166
|
+
meshMessageDeliveriesDecay: 0.5,
|
|
167
|
+
meshMessageDeliveriesCap: 100,
|
|
168
|
+
meshMessageDeliveriesThreshold: 20,
|
|
169
|
+
meshMessageDeliveriesWindow: 10,
|
|
170
|
+
meshMessageDeliveriesActivation: 5000,
|
|
171
|
+
|
|
172
|
+
meshFailurePenaltyWeight: -1,
|
|
173
|
+
meshFailurePenaltyDecay: 0.5,
|
|
174
|
+
|
|
175
|
+
invalidMessageDeliveriesWeight: -1,
|
|
176
|
+
invalidMessageDeliveriesDecay: 0.3
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function createPeerScoreParams (p: Partial<PeerScoreParams> = {}): PeerScoreParams {
|
|
180
|
+
return {
|
|
181
|
+
...defaultPeerScoreParams,
|
|
182
|
+
...p,
|
|
183
|
+
topics: (p.topics != null)
|
|
184
|
+
? Object.entries(p.topics).reduce<Record<string, TopicScoreParams>>((topics, [topic, topicScoreParams]) => {
|
|
185
|
+
topics[topic] = createTopicScoreParams(topicScoreParams)
|
|
186
|
+
return topics
|
|
187
|
+
}, {})
|
|
188
|
+
: {}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function createTopicScoreParams (p: Partial<TopicScoreParams> = {}): TopicScoreParams {
|
|
193
|
+
return {
|
|
194
|
+
...defaultTopicScoreParams,
|
|
195
|
+
...p
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// peer score parameter validation
|
|
200
|
+
export function validatePeerScoreParams (p: PeerScoreParams): void {
|
|
201
|
+
for (const [topic, params] of Object.entries(p.topics)) {
|
|
202
|
+
try {
|
|
203
|
+
validateTopicScoreParams(params)
|
|
204
|
+
} catch (e) {
|
|
205
|
+
throw new InvalidPeerScoreParamsError(`invalid score parameters for topic ${topic}: ${(e as Error).message}`)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// check that the topic score is 0 or something positive
|
|
210
|
+
if (p.topicScoreCap < 0) {
|
|
211
|
+
throw new InvalidPeerScoreParamsError('invalid topic score cap; must be positive (or 0 for no cap)')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// check that we have an app specific score; the weight can be anything (but expected positive)
|
|
215
|
+
if (p.appSpecificScore === null || p.appSpecificScore === undefined) {
|
|
216
|
+
throw new InvalidPeerScoreParamsError('missing application specific score function')
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// check the IP colocation factor
|
|
220
|
+
if (p.IPColocationFactorWeight > 0) {
|
|
221
|
+
throw new InvalidPeerScoreParamsError('invalid IPColocationFactorWeight; must be negative (or 0 to disable)')
|
|
222
|
+
}
|
|
223
|
+
if (p.IPColocationFactorWeight !== 0 && p.IPColocationFactorThreshold < 1) {
|
|
224
|
+
throw new InvalidPeerScoreParamsError('invalid IPColocationFactorThreshold; must be at least 1')
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// check the behaviour penalty
|
|
228
|
+
if (p.behaviourPenaltyWeight > 0) {
|
|
229
|
+
throw new InvalidPeerScoreParamsError('invalid BehaviourPenaltyWeight; must be negative (or 0 to disable)')
|
|
230
|
+
}
|
|
231
|
+
if (p.behaviourPenaltyWeight !== 0 && (p.behaviourPenaltyDecay <= 0 || p.behaviourPenaltyDecay >= 1)) {
|
|
232
|
+
throw new InvalidPeerScoreParamsError('invalid BehaviourPenaltyDecay; must be between 0 and 1')
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// check the decay parameters
|
|
236
|
+
if (p.decayInterval < 1000) {
|
|
237
|
+
throw new InvalidPeerScoreParamsError('invalid DecayInterval; must be at least 1s')
|
|
238
|
+
}
|
|
239
|
+
if (p.decayToZero <= 0 || p.decayToZero >= 1) {
|
|
240
|
+
throw new InvalidPeerScoreParamsError('invalid DecayToZero; must be between 0 and 1')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// no need to check the score retention; a value of 0 means that we don't retain scores
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// eslint-disable-next-line complexity
|
|
247
|
+
export function validateTopicScoreParams (p: TopicScoreParams): void {
|
|
248
|
+
// make sure we have a sane topic weight
|
|
249
|
+
if (p.topicWeight < 0) {
|
|
250
|
+
throw new InvalidPeerScoreParamsError('invalid topic weight; must be >= 0')
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// check P1
|
|
254
|
+
if (p.timeInMeshQuantum === 0) {
|
|
255
|
+
throw new InvalidPeerScoreParamsError('invalid TimeInMeshQuantum; must be non zero')
|
|
256
|
+
}
|
|
257
|
+
if (p.timeInMeshWeight < 0) {
|
|
258
|
+
throw new InvalidPeerScoreParamsError('invalid TimeInMeshWeight; must be positive (or 0 to disable)')
|
|
259
|
+
}
|
|
260
|
+
if (p.timeInMeshWeight !== 0 && p.timeInMeshQuantum <= 0) {
|
|
261
|
+
throw new InvalidPeerScoreParamsError('invalid TimeInMeshQuantum; must be positive')
|
|
262
|
+
}
|
|
263
|
+
if (p.timeInMeshWeight !== 0 && p.timeInMeshCap <= 0) {
|
|
264
|
+
throw new InvalidPeerScoreParamsError('invalid TimeInMeshCap; must be positive')
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// check P2
|
|
268
|
+
if (p.firstMessageDeliveriesWeight < 0) {
|
|
269
|
+
throw new InvalidPeerScoreParamsError('invallid FirstMessageDeliveriesWeight; must be positive (or 0 to disable)')
|
|
270
|
+
}
|
|
271
|
+
if (
|
|
272
|
+
p.firstMessageDeliveriesWeight !== 0 &&
|
|
273
|
+
(p.firstMessageDeliveriesDecay <= 0 || p.firstMessageDeliveriesDecay >= 1)
|
|
274
|
+
) {
|
|
275
|
+
throw new InvalidPeerScoreParamsError('invalid FirstMessageDeliveriesDecay; must be between 0 and 1')
|
|
276
|
+
}
|
|
277
|
+
if (p.firstMessageDeliveriesWeight !== 0 && p.firstMessageDeliveriesCap <= 0) {
|
|
278
|
+
throw new InvalidPeerScoreParamsError('invalid FirstMessageDeliveriesCap; must be positive')
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// check P3
|
|
282
|
+
if (p.meshMessageDeliveriesWeight > 0) {
|
|
283
|
+
throw new InvalidPeerScoreParamsError('invalid MeshMessageDeliveriesWeight; must be negative (or 0 to disable)')
|
|
284
|
+
}
|
|
285
|
+
if (p.meshMessageDeliveriesWeight !== 0 && (p.meshMessageDeliveriesDecay <= 0 || p.meshMessageDeliveriesDecay >= 1)) {
|
|
286
|
+
throw new InvalidPeerScoreParamsError('invalid MeshMessageDeliveriesDecay; must be between 0 and 1')
|
|
287
|
+
}
|
|
288
|
+
if (p.meshMessageDeliveriesWeight !== 0 && p.meshMessageDeliveriesCap <= 0) {
|
|
289
|
+
throw new InvalidPeerScoreParamsError('invalid MeshMessageDeliveriesCap; must be positive')
|
|
290
|
+
}
|
|
291
|
+
if (p.meshMessageDeliveriesWeight !== 0 && p.meshMessageDeliveriesThreshold <= 0) {
|
|
292
|
+
throw new InvalidPeerScoreParamsError('invalid MeshMessageDeliveriesThreshold; must be positive')
|
|
293
|
+
}
|
|
294
|
+
if (p.meshMessageDeliveriesWindow < 0) {
|
|
295
|
+
throw new InvalidPeerScoreParamsError('invalid MeshMessageDeliveriesWindow; must be non-negative')
|
|
296
|
+
}
|
|
297
|
+
if (p.meshMessageDeliveriesWeight !== 0 && p.meshMessageDeliveriesActivation < 1000) {
|
|
298
|
+
throw new InvalidPeerScoreParamsError('invalid MeshMessageDeliveriesActivation; must be at least 1s')
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// check P3b
|
|
302
|
+
if (p.meshFailurePenaltyWeight > 0) {
|
|
303
|
+
throw new InvalidPeerScoreParamsError('invalid MeshFailurePenaltyWeight; must be negative (or 0 to disable)')
|
|
304
|
+
}
|
|
305
|
+
if (p.meshFailurePenaltyWeight !== 0 && (p.meshFailurePenaltyDecay <= 0 || p.meshFailurePenaltyDecay >= 1)) {
|
|
306
|
+
throw new InvalidPeerScoreParamsError('invalid MeshFailurePenaltyDecay; must be between 0 and 1')
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// check P4
|
|
310
|
+
if (p.invalidMessageDeliveriesWeight > 0) {
|
|
311
|
+
throw new InvalidPeerScoreParamsError('invalid InvalidMessageDeliveriesWeight; must be negative (or 0 to disable)')
|
|
312
|
+
}
|
|
313
|
+
if (p.invalidMessageDeliveriesDecay <= 0 || p.invalidMessageDeliveriesDecay >= 1) {
|
|
314
|
+
throw new InvalidPeerScoreParamsError('invalid InvalidMessageDeliveriesDecay; must be between 0 and 1')
|
|
315
|
+
}
|
|
316
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { InvalidPeerScoreThresholdsError } from '../errors.js'
|
|
2
|
+
|
|
3
|
+
// This file defines PeerScoreThresholds interface
|
|
4
|
+
// as well as a constructor, default constructor, and validation function
|
|
5
|
+
// for this interface
|
|
6
|
+
|
|
7
|
+
export interface PeerScoreThresholds {
|
|
8
|
+
/**
|
|
9
|
+
* gossipThreshold is the score threshold below which gossip propagation is supressed;
|
|
10
|
+
* should be negative.
|
|
11
|
+
*/
|
|
12
|
+
gossipThreshold: number
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* publishThreshold is the score threshold below which we shouldn't publish when using flood
|
|
16
|
+
* publishing (also applies to fanout and floodsub peers); should be negative and <= GossipThreshold.
|
|
17
|
+
*/
|
|
18
|
+
publishThreshold: number
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* graylistThreshold is the score threshold below which message processing is supressed altogether,
|
|
22
|
+
* implementing an effective graylist according to peer score; should be negative and <= PublisThreshold.
|
|
23
|
+
*/
|
|
24
|
+
graylistThreshold: number
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* acceptPXThreshold is the score threshold below which PX will be ignored; this should be positive
|
|
28
|
+
* and limited to scores attainable by bootstrappers and other trusted nodes.
|
|
29
|
+
*/
|
|
30
|
+
acceptPXThreshold: number
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* opportunisticGraftThreshold is the median mesh score threshold before triggering opportunistic
|
|
34
|
+
* grafting; this should have a small positive value.
|
|
35
|
+
*/
|
|
36
|
+
opportunisticGraftThreshold: number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const defaultPeerScoreThresholds: PeerScoreThresholds = {
|
|
40
|
+
gossipThreshold: -10,
|
|
41
|
+
publishThreshold: -50,
|
|
42
|
+
graylistThreshold: -80,
|
|
43
|
+
acceptPXThreshold: 10,
|
|
44
|
+
opportunisticGraftThreshold: 20
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createPeerScoreThresholds (p: Partial<PeerScoreThresholds> = {}): PeerScoreThresholds {
|
|
48
|
+
return {
|
|
49
|
+
...defaultPeerScoreThresholds,
|
|
50
|
+
...p
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function validatePeerScoreThresholds (p: PeerScoreThresholds): void {
|
|
55
|
+
if (p.gossipThreshold > 0) {
|
|
56
|
+
throw new InvalidPeerScoreThresholdsError('invalid gossip threshold; it must be <= 0')
|
|
57
|
+
}
|
|
58
|
+
if (p.publishThreshold > 0 || p.publishThreshold > p.gossipThreshold) {
|
|
59
|
+
throw new InvalidPeerScoreThresholdsError('invalid publish threshold; it must be <= 0 and <= gossip threshold')
|
|
60
|
+
}
|
|
61
|
+
if (p.graylistThreshold > 0 || p.graylistThreshold > p.publishThreshold) {
|
|
62
|
+
throw new InvalidPeerScoreThresholdsError('invalid graylist threshold; it must be <= 0 and <= publish threshold')
|
|
63
|
+
}
|
|
64
|
+
if (p.acceptPXThreshold < 0) {
|
|
65
|
+
throw new InvalidPeerScoreThresholdsError('invalid accept PX threshold; it must be >= 0')
|
|
66
|
+
}
|
|
67
|
+
if (p.opportunisticGraftThreshold < 0) {
|
|
68
|
+
throw new InvalidPeerScoreThresholdsError('invalid opportunistic grafting threshold; it must be >= 0')
|
|
69
|
+
}
|
|
70
|
+
}
|