cs_mwc_libp2p_gossipsub/peer_score/
params.rs1use crate::TopicHash;
22use std::collections::{HashMap, HashSet};
23use std::net::IpAddr;
24use std::time::Duration;
25
26const DEFAULT_DECAY_INTERVAL: u64 = 1;
28const DEFAULT_DECAY_TO_ZERO: f64 = 0.1;
30
31pub fn score_parameter_decay(decay: Duration) -> f64 {
34 score_parameter_decay_with_base(
35 decay,
36 Duration::from_secs(DEFAULT_DECAY_INTERVAL),
37 DEFAULT_DECAY_TO_ZERO,
38 )
39}
40
41pub fn score_parameter_decay_with_base(decay: Duration, base: Duration, decay_to_zero: f64) -> f64 {
43 let ticks = decay.as_secs_f64() / base.as_secs_f64();
46 decay_to_zero.powf(1f64 / ticks)
47}
48
49#[derive(Debug, Clone)]
50pub struct PeerScoreThresholds {
51 pub gossip_threshold: f64,
54
55 pub publish_threshold: f64,
58
59 pub graylist_threshold: f64,
63
64 pub accept_px_threshold: f64,
67
68 pub opportunistic_graft_threshold: f64,
71}
72
73impl Default for PeerScoreThresholds {
74 fn default() -> Self {
75 PeerScoreThresholds {
76 gossip_threshold: -10.0,
77 publish_threshold: -50.0,
78 graylist_threshold: -80.0,
79 accept_px_threshold: 10.0,
80 opportunistic_graft_threshold: 20.0,
81 }
82 }
83}
84
85impl PeerScoreThresholds {
86 pub fn validate(&self) -> Result<(), &'static str> {
87 if self.gossip_threshold > 0f64 {
88 return Err("invalid gossip threshold; it must be <= 0");
89 }
90 if self.publish_threshold > 0f64 || self.publish_threshold > self.gossip_threshold {
91 return Err("Invalid publish threshold; it must be <= 0 and <= gossip threshold");
92 }
93 if self.graylist_threshold > 0f64 || self.graylist_threshold > self.publish_threshold {
94 return Err("Invalid graylist threshold; it must be <= 0 and <= publish threshold");
95 }
96 if self.accept_px_threshold < 0f64 {
97 return Err("Invalid accept px threshold; it must be >= 0");
98 }
99 if self.opportunistic_graft_threshold < 0f64 {
100 return Err("Invalid opportunistic grafting threshold; it must be >= 0");
101 }
102 Ok(())
103 }
104}
105
106#[derive(Debug, Clone)]
107pub struct PeerScoreParams {
108 pub topics: HashMap<TopicHash, TopicScoreParams>,
110
111 pub topic_score_cap: f64,
114
115 pub app_specific_weight: f64,
117
118 pub ip_colocation_factor_weight: f64,
127 pub ip_colocation_factor_threshold: f64,
128 pub ip_colocation_factor_whitelist: HashSet<IpAddr>,
129
130 pub behaviour_penalty_weight: f64,
140 pub behaviour_penalty_threshold: f64,
141 pub behaviour_penalty_decay: f64,
142
143 pub decay_interval: Duration,
145
146 pub decay_to_zero: f64,
148
149 pub retain_score: Duration,
151}
152
153impl Default for PeerScoreParams {
154 fn default() -> Self {
155 PeerScoreParams {
156 topics: HashMap::new(),
157 topic_score_cap: 3600.0,
158 app_specific_weight: 10.0,
159 ip_colocation_factor_weight: -5.0,
160 ip_colocation_factor_threshold: 10.0,
161 ip_colocation_factor_whitelist: HashSet::new(),
162 behaviour_penalty_weight: -10.0,
163 behaviour_penalty_threshold: 0.0,
164 behaviour_penalty_decay: 0.2,
165 decay_interval: Duration::from_secs(DEFAULT_DECAY_INTERVAL),
166 decay_to_zero: DEFAULT_DECAY_TO_ZERO,
167 retain_score: Duration::from_secs(3600),
168 }
169 }
170}
171
172impl PeerScoreParams {
174 pub fn validate(&self) -> Result<(), String> {
175 for (topic, params) in self.topics.iter() {
176 if let Err(e) = params.validate() {
177 return Err(format!(
178 "Invalid score parameters for topic {}: {}",
179 topic, e
180 ));
181 }
182 }
183
184 if self.topic_score_cap < 0f64 {
186 return Err("Invalid topic score cap; must be positive (or 0 for no cap)".into());
187 }
188
189 if self.ip_colocation_factor_weight > 0f64 {
191 return Err(
192 "Invalid ip_colocation_factor_weight; must be negative (or 0 to disable)".into(),
193 );
194 }
195 if self.ip_colocation_factor_weight != 0f64 && self.ip_colocation_factor_threshold < 1f64 {
196 return Err("Invalid ip_colocation_factor_threshold; must be at least 1".into());
197 }
198
199 if self.behaviour_penalty_weight > 0f64 {
201 return Err(
202 "Invalid behaviour_penalty_weight; must be negative (or 0 to disable)".into(),
203 );
204 }
205 if self.behaviour_penalty_weight != 0f64
206 && (self.behaviour_penalty_decay <= 0f64 || self.behaviour_penalty_decay >= 1f64)
207 {
208 return Err("invalid behaviour_penalty_decay; must be between 0 and 1".into());
209 }
210
211 if self.behaviour_penalty_threshold < 0f64 {
212 return Err("invalid behaviour_penalty_threshold; must be >= 0".into());
213 }
214
215 if self.decay_interval < Duration::from_secs(1) {
217 return Err("Invalid decay_interval; must be at least 1s".into());
218 }
219 if self.decay_to_zero <= 0f64 || self.decay_to_zero >= 1f64 {
220 return Err("Invalid decay_to_zero; must be between 0 and 1".into());
221 }
222
223 Ok(())
225 }
226}
227
228#[derive(Debug, Clone)]
229pub struct TopicScoreParams {
230 pub topic_weight: f64,
232
233 pub time_in_mesh_weight: f64,
238 pub time_in_mesh_quantum: Duration,
239 pub time_in_mesh_cap: f64,
240
241 pub first_message_deliveries_weight: f64,
247 pub first_message_deliveries_decay: f64,
248 pub first_message_deliveries_cap: f64,
249
250 pub mesh_message_deliveries_weight: f64,
265 pub mesh_message_deliveries_decay: f64,
266 pub mesh_message_deliveries_cap: f64,
267 pub mesh_message_deliveries_threshold: f64,
268 pub mesh_message_deliveries_window: Duration,
269 pub mesh_message_deliveries_activation: Duration,
270
271 pub mesh_failure_penalty_weight: f64,
276 pub mesh_failure_penalty_decay: f64,
277
278 pub invalid_message_deliveries_weight: f64,
284 pub invalid_message_deliveries_decay: f64,
285}
286
287impl Default for TopicScoreParams {
290 fn default() -> Self {
291 TopicScoreParams {
292 topic_weight: 0.5,
293 time_in_mesh_weight: 1.0,
295 time_in_mesh_quantum: Duration::from_millis(1),
296 time_in_mesh_cap: 3600.0,
297 first_message_deliveries_weight: 1.0,
299 first_message_deliveries_decay: 0.5,
300 first_message_deliveries_cap: 2000.0,
301 mesh_message_deliveries_weight: -1.0,
303 mesh_message_deliveries_decay: 0.5,
304 mesh_message_deliveries_cap: 100.0,
305 mesh_message_deliveries_threshold: 20.0,
306 mesh_message_deliveries_window: Duration::from_millis(10),
307 mesh_message_deliveries_activation: Duration::from_secs(5),
308 mesh_failure_penalty_weight: -1.0,
310 mesh_failure_penalty_decay: 0.5,
311 invalid_message_deliveries_weight: -1.0,
313 invalid_message_deliveries_decay: 0.3,
314 }
315 }
316}
317
318impl TopicScoreParams {
319 pub fn validate(&self) -> Result<(), &'static str> {
320 if self.topic_weight < 0f64 {
322 return Err("invalid topic weight; must be >= 0");
323 }
324
325 if self.time_in_mesh_quantum == Duration::from_secs(0) {
326 return Err("Invalid time_in_mesh_quantum; must be non zero");
327 }
328 if self.time_in_mesh_weight < 0f64 {
329 return Err("Invalid time_in_mesh_weight; must be positive (or 0 to disable)");
330 }
331 if self.time_in_mesh_weight != 0f64 && self.time_in_mesh_cap <= 0f64 {
332 return Err("Invalid time_in_mesh_cap must be positive");
333 }
334
335 if self.first_message_deliveries_weight < 0f64 {
336 return Err(
337 "Invalid first_message_deliveries_weight; must be positive (or 0 to disable)",
338 );
339 }
340 if self.first_message_deliveries_weight != 0f64
341 && (self.first_message_deliveries_decay <= 0f64
342 || self.first_message_deliveries_decay >= 1f64)
343 {
344 return Err("Invalid first_message_deliveries_decay; must be between 0 and 1");
345 }
346 if self.first_message_deliveries_weight != 0f64 && self.first_message_deliveries_cap <= 0f64
347 {
348 return Err("Invalid first_message_deliveries_cap must be positive");
349 }
350
351 if self.mesh_message_deliveries_weight > 0f64 {
352 return Err(
353 "Invalid mesh_message_deliveries_weight; must be negative (or 0 to disable)",
354 );
355 }
356 if self.mesh_message_deliveries_weight != 0f64
357 && (self.mesh_message_deliveries_decay <= 0f64
358 || self.mesh_message_deliveries_decay >= 1f64)
359 {
360 return Err("Invalid mesh_message_deliveries_decay; must be between 0 and 1");
361 }
362 if self.mesh_message_deliveries_weight != 0f64 && self.mesh_message_deliveries_cap <= 0f64 {
363 return Err("Invalid mesh_message_deliveries_cap must be positive");
364 }
365 if self.mesh_message_deliveries_weight != 0f64
366 && self.mesh_message_deliveries_threshold <= 0f64
367 {
368 return Err("Invalid mesh_message_deliveries_threshold; must be positive");
369 }
370 if self.mesh_message_deliveries_weight != 0f64
371 && self.mesh_message_deliveries_activation < Duration::from_secs(1)
372 {
373 return Err("Invalid mesh_message_deliveries_activation; must be at least 1s");
374 }
375
376 if self.mesh_failure_penalty_weight > 0f64 {
378 return Err("Invalid mesh_failure_penalty_weight; must be negative (or 0 to disable)");
379 }
380 if self.mesh_failure_penalty_weight != 0f64
381 && (self.mesh_failure_penalty_decay <= 0f64 || self.mesh_failure_penalty_decay >= 1f64)
382 {
383 return Err("Invalid mesh_failure_penalty_decay; must be between 0 and 1");
384 }
385
386 if self.invalid_message_deliveries_weight > 0f64 {
388 return Err(
389 "Invalid invalid_message_deliveries_weight; must be negative (or 0 to disable)",
390 );
391 }
392 if self.invalid_message_deliveries_decay <= 0f64
393 || self.invalid_message_deliveries_decay >= 1f64
394 {
395 return Err("Invalid invalid_message_deliveries_decay; must be between 0 and 1");
396 }
397 Ok(())
398 }
399}