use std::{
collections::{HashMap, HashSet},
net::IpAddr,
time::Duration,
};
use crate::TopicHash;
const DEFAULT_DECAY_INTERVAL: u64 = 1;
const DEFAULT_DECAY_TO_ZERO: f64 = 0.1;
pub fn score_parameter_decay(decay: Duration) -> f64 {
score_parameter_decay_with_base(
decay,
Duration::from_secs(DEFAULT_DECAY_INTERVAL),
DEFAULT_DECAY_TO_ZERO,
)
}
pub fn score_parameter_decay_with_base(decay: Duration, base: Duration, decay_to_zero: f64) -> f64 {
let ticks = decay.as_secs_f64() / base.as_secs_f64();
decay_to_zero.powf(1f64 / ticks)
}
#[derive(Debug, Clone)]
pub struct PeerScoreThresholds {
pub gossip_threshold: f64,
pub publish_threshold: f64,
pub graylist_threshold: f64,
pub accept_px_threshold: f64,
pub opportunistic_graft_threshold: f64,
}
impl Default for PeerScoreThresholds {
fn default() -> Self {
PeerScoreThresholds {
gossip_threshold: -10.0,
publish_threshold: -50.0,
graylist_threshold: -80.0,
accept_px_threshold: 10.0,
opportunistic_graft_threshold: 20.0,
}
}
}
impl PeerScoreThresholds {
pub fn validate(&self) -> Result<(), &'static str> {
if self.gossip_threshold > 0f64 {
return Err("invalid gossip threshold; it must be <= 0");
}
if self.publish_threshold > 0f64 || self.publish_threshold > self.gossip_threshold {
return Err("Invalid publish threshold; it must be <= 0 and <= gossip threshold");
}
if self.graylist_threshold > 0f64 || self.graylist_threshold > self.publish_threshold {
return Err("Invalid graylist threshold; it must be <= 0 and <= publish threshold");
}
if self.accept_px_threshold < 0f64 {
return Err("Invalid accept px threshold; it must be >= 0");
}
if self.opportunistic_graft_threshold < 0f64 {
return Err("Invalid opportunistic grafting threshold; it must be >= 0");
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct PeerScoreParams {
pub topics: HashMap<TopicHash, TopicScoreParams>,
pub topic_score_cap: f64,
pub app_specific_weight: f64,
pub ip_colocation_factor_weight: f64,
pub ip_colocation_factor_threshold: f64,
pub ip_colocation_factor_whitelist: HashSet<IpAddr>,
pub behaviour_penalty_weight: f64,
pub behaviour_penalty_threshold: f64,
pub behaviour_penalty_decay: f64,
pub decay_interval: Duration,
pub decay_to_zero: f64,
pub retain_score: Duration,
pub slow_peer_weight: f64,
pub slow_peer_threshold: f64,
pub slow_peer_decay: f64,
}
impl Default for PeerScoreParams {
fn default() -> Self {
PeerScoreParams {
topics: HashMap::new(),
topic_score_cap: 3600.0,
app_specific_weight: 10.0,
ip_colocation_factor_weight: -5.0,
ip_colocation_factor_threshold: 10.0,
ip_colocation_factor_whitelist: HashSet::new(),
behaviour_penalty_weight: -10.0,
behaviour_penalty_threshold: 0.0,
behaviour_penalty_decay: 0.2,
decay_interval: Duration::from_secs(DEFAULT_DECAY_INTERVAL),
decay_to_zero: DEFAULT_DECAY_TO_ZERO,
retain_score: Duration::from_secs(3600),
slow_peer_weight: -0.2,
slow_peer_threshold: 0.0,
slow_peer_decay: 0.2,
}
}
}
impl PeerScoreParams {
pub fn validate(&self) -> Result<(), String> {
for (topic, params) in self.topics.iter() {
if let Err(e) = params.validate() {
return Err(format!("Invalid score parameters for topic {topic}: {e}"));
}
}
if self.topic_score_cap < 0f64 {
return Err("Invalid topic score cap; must be positive (or 0 for no cap)".into());
}
if self.ip_colocation_factor_weight > 0f64 {
return Err(
"Invalid ip_colocation_factor_weight; must be negative (or 0 to disable)".into(),
);
}
if self.ip_colocation_factor_weight != 0f64 && self.ip_colocation_factor_threshold < 1f64 {
return Err("Invalid ip_colocation_factor_threshold; must be at least 1".into());
}
if self.behaviour_penalty_weight > 0f64 {
return Err(
"Invalid behaviour_penalty_weight; must be negative (or 0 to disable)".into(),
);
}
if self.behaviour_penalty_weight != 0f64
&& (self.behaviour_penalty_decay <= 0f64 || self.behaviour_penalty_decay >= 1f64)
{
return Err("invalid behaviour_penalty_decay; must be between 0 and 1".into());
}
if self.behaviour_penalty_threshold < 0f64 {
return Err("invalid behaviour_penalty_threshold; must be >= 0".into());
}
if self.decay_interval < Duration::from_secs(1) {
return Err("Invalid decay_interval; must be at least 1s".into());
}
if self.decay_to_zero <= 0f64 || self.decay_to_zero >= 1f64 {
return Err("Invalid decay_to_zero; must be between 0 and 1".into());
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct TopicScoreParams {
pub topic_weight: f64,
pub time_in_mesh_weight: f64,
pub time_in_mesh_quantum: Duration,
pub time_in_mesh_cap: f64,
pub first_message_deliveries_weight: f64,
pub first_message_deliveries_decay: f64,
pub first_message_deliveries_cap: f64,
pub mesh_message_deliveries_weight: f64,
pub mesh_message_deliveries_decay: f64,
pub mesh_message_deliveries_cap: f64,
pub mesh_message_deliveries_threshold: f64,
pub mesh_message_deliveries_window: Duration,
pub mesh_message_deliveries_activation: Duration,
pub mesh_failure_penalty_weight: f64,
pub mesh_failure_penalty_decay: f64,
pub invalid_message_deliveries_weight: f64,
pub invalid_message_deliveries_decay: f64,
}
impl Default for TopicScoreParams {
fn default() -> Self {
TopicScoreParams {
topic_weight: 0.5,
time_in_mesh_weight: 1.0,
time_in_mesh_quantum: Duration::from_millis(1),
time_in_mesh_cap: 3600.0,
first_message_deliveries_weight: 1.0,
first_message_deliveries_decay: 0.5,
first_message_deliveries_cap: 2000.0,
mesh_message_deliveries_weight: -1.0,
mesh_message_deliveries_decay: 0.5,
mesh_message_deliveries_cap: 100.0,
mesh_message_deliveries_threshold: 20.0,
mesh_message_deliveries_window: Duration::from_millis(10),
mesh_message_deliveries_activation: Duration::from_secs(5),
mesh_failure_penalty_weight: -1.0,
mesh_failure_penalty_decay: 0.5,
invalid_message_deliveries_weight: -1.0,
invalid_message_deliveries_decay: 0.3,
}
}
}
impl TopicScoreParams {
pub fn validate(&self) -> Result<(), &'static str> {
if self.topic_weight < 0f64 {
return Err("invalid topic weight; must be >= 0");
}
if self.time_in_mesh_quantum == Duration::from_secs(0) {
return Err("Invalid time_in_mesh_quantum; must be non zero");
}
if self.time_in_mesh_weight < 0f64 {
return Err("Invalid time_in_mesh_weight; must be positive (or 0 to disable)");
}
if self.time_in_mesh_weight != 0f64 && self.time_in_mesh_cap <= 0f64 {
return Err("Invalid time_in_mesh_cap must be positive");
}
if self.first_message_deliveries_weight < 0f64 {
return Err(
"Invalid first_message_deliveries_weight; must be positive (or 0 to disable)",
);
}
if self.first_message_deliveries_weight != 0f64
&& (self.first_message_deliveries_decay <= 0f64
|| self.first_message_deliveries_decay >= 1f64)
{
return Err("Invalid first_message_deliveries_decay; must be between 0 and 1");
}
if self.first_message_deliveries_weight != 0f64 && self.first_message_deliveries_cap <= 0f64
{
return Err("Invalid first_message_deliveries_cap must be positive");
}
if self.mesh_message_deliveries_weight > 0f64 {
return Err(
"Invalid mesh_message_deliveries_weight; must be negative (or 0 to disable)",
);
}
if self.mesh_message_deliveries_weight != 0f64
&& (self.mesh_message_deliveries_decay <= 0f64
|| self.mesh_message_deliveries_decay >= 1f64)
{
return Err("Invalid mesh_message_deliveries_decay; must be between 0 and 1");
}
if self.mesh_message_deliveries_weight != 0f64 && self.mesh_message_deliveries_cap <= 0f64 {
return Err("Invalid mesh_message_deliveries_cap must be positive");
}
if self.mesh_message_deliveries_weight != 0f64
&& self.mesh_message_deliveries_threshold <= 0f64
{
return Err("Invalid mesh_message_deliveries_threshold; must be positive");
}
if self.mesh_message_deliveries_weight != 0f64
&& self.mesh_message_deliveries_activation < Duration::from_secs(1)
{
return Err("Invalid mesh_message_deliveries_activation; must be at least 1s");
}
if self.mesh_failure_penalty_weight > 0f64 {
return Err("Invalid mesh_failure_penalty_weight; must be negative (or 0 to disable)");
}
if self.mesh_failure_penalty_weight != 0f64
&& (self.mesh_failure_penalty_decay <= 0f64 || self.mesh_failure_penalty_decay >= 1f64)
{
return Err("Invalid mesh_failure_penalty_decay; must be between 0 and 1");
}
if self.invalid_message_deliveries_weight > 0f64 {
return Err(
"Invalid invalid_message_deliveries_weight; must be negative (or 0 to disable)",
);
}
if self.invalid_message_deliveries_decay <= 0f64
|| self.invalid_message_deliveries_decay >= 1f64
{
return Err("Invalid invalid_message_deliveries_decay; must be between 0 and 1");
}
Ok(())
}
}