libp2p_gossipsub/peer_score/
params.rs1use std::{
22 collections::{HashMap, HashSet},
23 net::IpAddr,
24 time::Duration,
25};
26
27use crate::TopicHash;
28
29const DEFAULT_DECAY_INTERVAL: u64 = 1;
31const DEFAULT_DECAY_TO_ZERO: f64 = 0.1;
33
34pub fn score_parameter_decay(decay: Duration) -> f64 {
37 score_parameter_decay_with_base(
38 decay,
39 Duration::from_secs(DEFAULT_DECAY_INTERVAL),
40 DEFAULT_DECAY_TO_ZERO,
41 )
42}
43
44pub fn score_parameter_decay_with_base(decay: Duration, base: Duration, decay_to_zero: f64) -> f64 {
46 let ticks = decay.as_secs_f64() / base.as_secs_f64();
49 decay_to_zero.powf(1f64 / ticks)
50}
51
52#[derive(Debug, Clone)]
53pub struct PeerScoreThresholds {
54 pub gossip_threshold: f64,
57
58 pub publish_threshold: f64,
61
62 pub graylist_threshold: f64,
66
67 pub accept_px_threshold: f64,
70
71 pub opportunistic_graft_threshold: f64,
74}
75
76impl Default for PeerScoreThresholds {
77 fn default() -> Self {
78 PeerScoreThresholds {
79 gossip_threshold: -10.0,
80 publish_threshold: -50.0,
81 graylist_threshold: -80.0,
82 accept_px_threshold: 10.0,
83 opportunistic_graft_threshold: 20.0,
84 }
85 }
86}
87
88impl PeerScoreThresholds {
89 pub fn validate(&self) -> Result<(), &'static str> {
90 if self.gossip_threshold > 0f64 {
91 return Err("invalid gossip threshold; it must be <= 0");
92 }
93 if self.publish_threshold > 0f64 || self.publish_threshold > self.gossip_threshold {
94 return Err("Invalid publish threshold; it must be <= 0 and <= gossip threshold");
95 }
96 if self.graylist_threshold > 0f64 || self.graylist_threshold > self.publish_threshold {
97 return Err("Invalid graylist threshold; it must be <= 0 and <= publish threshold");
98 }
99 if self.accept_px_threshold < 0f64 {
100 return Err("Invalid accept px threshold; it must be >= 0");
101 }
102 if self.opportunistic_graft_threshold < 0f64 {
103 return Err("Invalid opportunistic grafting threshold; it must be >= 0");
104 }
105 Ok(())
106 }
107}
108
109#[derive(Debug, Clone)]
110pub struct PeerScoreParams {
111 pub topics: HashMap<TopicHash, TopicScoreParams>,
113
114 pub topic_score_cap: f64,
117
118 pub app_specific_weight: f64,
120
121 pub ip_colocation_factor_weight: f64,
131 pub ip_colocation_factor_threshold: f64,
132 pub ip_colocation_factor_whitelist: HashSet<IpAddr>,
133
134 pub behaviour_penalty_weight: f64,
144 pub behaviour_penalty_threshold: f64,
145 pub behaviour_penalty_decay: f64,
146
147 pub decay_interval: Duration,
149
150 pub decay_to_zero: f64,
152
153 pub retain_score: Duration,
155
156 pub slow_peer_weight: f64,
160 pub slow_peer_threshold: f64,
161 pub slow_peer_decay: f64,
162}
163
164impl Default for PeerScoreParams {
165 fn default() -> Self {
166 PeerScoreParams {
167 topics: HashMap::new(),
168 topic_score_cap: 3600.0,
169 app_specific_weight: 10.0,
170 ip_colocation_factor_weight: -5.0,
171 ip_colocation_factor_threshold: 10.0,
172 ip_colocation_factor_whitelist: HashSet::new(),
173 behaviour_penalty_weight: -10.0,
174 behaviour_penalty_threshold: 0.0,
175 behaviour_penalty_decay: 0.2,
176 decay_interval: Duration::from_secs(DEFAULT_DECAY_INTERVAL),
177 decay_to_zero: DEFAULT_DECAY_TO_ZERO,
178 retain_score: Duration::from_secs(3600),
179 slow_peer_weight: -0.2,
180 slow_peer_threshold: 0.0,
181 slow_peer_decay: 0.2,
182 }
183 }
184}
185
186impl PeerScoreParams {
188 pub fn validate(&self) -> Result<(), String> {
189 for (topic, params) in self.topics.iter() {
190 if let Err(e) = params.validate() {
191 return Err(format!("Invalid score parameters for topic {topic}: {e}"));
192 }
193 }
194
195 if self.topic_score_cap < 0f64 {
197 return Err("Invalid topic score cap; must be positive (or 0 for no cap)".into());
198 }
199
200 if self.ip_colocation_factor_weight > 0f64 {
202 return Err(
203 "Invalid ip_colocation_factor_weight; must be negative (or 0 to disable)".into(),
204 );
205 }
206 if self.ip_colocation_factor_weight != 0f64 && self.ip_colocation_factor_threshold < 1f64 {
207 return Err("Invalid ip_colocation_factor_threshold; must be at least 1".into());
208 }
209
210 if self.behaviour_penalty_weight > 0f64 {
212 return Err(
213 "Invalid behaviour_penalty_weight; must be negative (or 0 to disable)".into(),
214 );
215 }
216 if self.behaviour_penalty_weight != 0f64
217 && (self.behaviour_penalty_decay <= 0f64 || self.behaviour_penalty_decay >= 1f64)
218 {
219 return Err("invalid behaviour_penalty_decay; must be between 0 and 1".into());
220 }
221
222 if self.behaviour_penalty_threshold < 0f64 {
223 return Err("invalid behaviour_penalty_threshold; must be >= 0".into());
224 }
225
226 if self.decay_interval < Duration::from_secs(1) {
228 return Err("Invalid decay_interval; must be at least 1s".into());
229 }
230 if self.decay_to_zero <= 0f64 || self.decay_to_zero >= 1f64 {
231 return Err("Invalid decay_to_zero; must be between 0 and 1".into());
232 }
233
234 Ok(())
236 }
237}
238
239#[derive(Debug, Clone)]
240pub struct TopicScoreParams {
241 pub topic_weight: f64,
243
244 pub time_in_mesh_weight: f64,
249 pub time_in_mesh_quantum: Duration,
250 pub time_in_mesh_cap: f64,
251
252 pub first_message_deliveries_weight: f64,
258 pub first_message_deliveries_decay: f64,
259 pub first_message_deliveries_cap: f64,
260
261 pub mesh_message_deliveries_weight: f64,
276 pub mesh_message_deliveries_decay: f64,
277 pub mesh_message_deliveries_cap: f64,
278 pub mesh_message_deliveries_threshold: f64,
279 pub mesh_message_deliveries_window: Duration,
280 pub mesh_message_deliveries_activation: Duration,
281
282 pub mesh_failure_penalty_weight: f64,
287 pub mesh_failure_penalty_decay: f64,
288
289 pub invalid_message_deliveries_weight: f64,
295 pub invalid_message_deliveries_decay: f64,
296}
297
298impl Default for TopicScoreParams {
301 fn default() -> Self {
302 TopicScoreParams {
303 topic_weight: 0.5,
304 time_in_mesh_weight: 1.0,
306 time_in_mesh_quantum: Duration::from_millis(1),
307 time_in_mesh_cap: 3600.0,
308 first_message_deliveries_weight: 1.0,
310 first_message_deliveries_decay: 0.5,
311 first_message_deliveries_cap: 2000.0,
312 mesh_message_deliveries_weight: -1.0,
314 mesh_message_deliveries_decay: 0.5,
315 mesh_message_deliveries_cap: 100.0,
316 mesh_message_deliveries_threshold: 20.0,
317 mesh_message_deliveries_window: Duration::from_millis(10),
318 mesh_message_deliveries_activation: Duration::from_secs(5),
319 mesh_failure_penalty_weight: -1.0,
321 mesh_failure_penalty_decay: 0.5,
322 invalid_message_deliveries_weight: -1.0,
324 invalid_message_deliveries_decay: 0.3,
325 }
326 }
327}
328
329impl TopicScoreParams {
330 pub fn validate(&self) -> Result<(), &'static str> {
331 if self.topic_weight < 0f64 {
333 return Err("invalid topic weight; must be >= 0");
334 }
335
336 if self.time_in_mesh_quantum == Duration::from_secs(0) {
337 return Err("Invalid time_in_mesh_quantum; must be non zero");
338 }
339 if self.time_in_mesh_weight < 0f64 {
340 return Err("Invalid time_in_mesh_weight; must be positive (or 0 to disable)");
341 }
342 if self.time_in_mesh_weight != 0f64 && self.time_in_mesh_cap <= 0f64 {
343 return Err("Invalid time_in_mesh_cap must be positive");
344 }
345
346 if self.first_message_deliveries_weight < 0f64 {
347 return Err(
348 "Invalid first_message_deliveries_weight; must be positive (or 0 to disable)",
349 );
350 }
351 if self.first_message_deliveries_weight != 0f64
352 && (self.first_message_deliveries_decay <= 0f64
353 || self.first_message_deliveries_decay >= 1f64)
354 {
355 return Err("Invalid first_message_deliveries_decay; must be between 0 and 1");
356 }
357 if self.first_message_deliveries_weight != 0f64 && self.first_message_deliveries_cap <= 0f64
358 {
359 return Err("Invalid first_message_deliveries_cap must be positive");
360 }
361
362 if self.mesh_message_deliveries_weight > 0f64 {
363 return Err(
364 "Invalid mesh_message_deliveries_weight; must be negative (or 0 to disable)",
365 );
366 }
367 if self.mesh_message_deliveries_weight != 0f64
368 && (self.mesh_message_deliveries_decay <= 0f64
369 || self.mesh_message_deliveries_decay >= 1f64)
370 {
371 return Err("Invalid mesh_message_deliveries_decay; must be between 0 and 1");
372 }
373 if self.mesh_message_deliveries_weight != 0f64 && self.mesh_message_deliveries_cap <= 0f64 {
374 return Err("Invalid mesh_message_deliveries_cap must be positive");
375 }
376 if self.mesh_message_deliveries_weight != 0f64
377 && self.mesh_message_deliveries_threshold <= 0f64
378 {
379 return Err("Invalid mesh_message_deliveries_threshold; must be positive");
380 }
381 if self.mesh_message_deliveries_weight != 0f64
382 && self.mesh_message_deliveries_activation < Duration::from_secs(1)
383 {
384 return Err("Invalid mesh_message_deliveries_activation; must be at least 1s");
385 }
386
387 if self.mesh_failure_penalty_weight > 0f64 {
389 return Err("Invalid mesh_failure_penalty_weight; must be negative (or 0 to disable)");
390 }
391 if self.mesh_failure_penalty_weight != 0f64
392 && (self.mesh_failure_penalty_decay <= 0f64 || self.mesh_failure_penalty_decay >= 1f64)
393 {
394 return Err("Invalid mesh_failure_penalty_decay; must be between 0 and 1");
395 }
396
397 if self.invalid_message_deliveries_weight > 0f64 {
399 return Err(
400 "Invalid invalid_message_deliveries_weight; must be negative (or 0 to disable)",
401 );
402 }
403 if self.invalid_message_deliveries_decay <= 0f64
404 || self.invalid_message_deliveries_decay >= 1f64
405 {
406 return Err("Invalid invalid_message_deliveries_decay; must be between 0 and 1");
407 }
408 Ok(())
409 }
410}