Skip to main content

hashgraph_like_consensus/
scope_config.rs

1//! Scope-level configuration for consensus defaults.
2//!
3//! A [`ScopeConfig`] holds per-scope defaults (network type, threshold, timeout,
4//! liveness criteria) that every proposal in the scope inherits unless overridden.
5//! Use [`ScopeConfigBuilder`] (via [`ConsensusService::scope()`](crate::service::ConsensusService::scope))
6//! to create or update configurations.
7
8use std::time::Duration;
9
10use crate::error::ConsensusError;
11use crate::utils::{validate_threshold, validate_timeout};
12
13pub(crate) const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60);
14
15/// Network type determines how rounds and votes are handled.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum NetworkType {
18    /// Gossipsub network: 2 rounds, multiple votes can be in round 2
19    Gossipsub,
20    /// P2P network: dynamically calculated max rounds (default is ceil(2n/3)),
21    /// each vote increments the round number
22    P2P,
23}
24
25/// Scope-level configuration that applies to all proposals in a scope.
26///
27/// This provides default settings for proposals created in a scope.
28/// Individual proposals can override these defaults if needed.
29#[derive(Debug, Clone)]
30pub struct ScopeConfig {
31    /// Network type: P2P or Gossipsub
32    pub network_type: NetworkType,
33    /// Default consensus threshold (e.g., 2/3 = 0.667)
34    pub default_consensus_threshold: f64,
35    /// Default timeout for proposals in this scope
36    pub default_timeout: Duration,
37    /// Default liveness criteria (how silent peers are counted)
38    pub default_liveness_criteria_yes: bool,
39    /// Optional: Max rounds override (if None, uses network_type defaults)
40    pub max_rounds_override: Option<u32>,
41}
42
43impl Default for ScopeConfig {
44    fn default() -> Self {
45        Self {
46            network_type: NetworkType::Gossipsub,
47            default_consensus_threshold: 2.0 / 3.0,
48            default_timeout: DEFAULT_TIMEOUT,
49            default_liveness_criteria_yes: true,
50            max_rounds_override: None,
51        }
52    }
53}
54
55impl ScopeConfig {
56    /// Validate the configuration
57    pub fn validate(&self) -> Result<(), ConsensusError> {
58        validate_threshold(self.default_consensus_threshold)?;
59        validate_timeout(self.default_timeout)?;
60        // Allow max_rounds_override = Some(0) only for P2P networks (triggers dynamic calculation)
61        // For Gossipsub networks, max_rounds_override must be greater than 0
62        if let Some(max_rounds) = self.max_rounds_override
63            && max_rounds == 0
64            && self.network_type == NetworkType::Gossipsub
65        {
66            return Err(ConsensusError::InvalidMaxRounds);
67        }
68        Ok(())
69    }
70}
71
72impl From<NetworkType> for ScopeConfig {
73    fn from(network_type: NetworkType) -> Self {
74        match network_type {
75            NetworkType::Gossipsub => Self {
76                network_type: NetworkType::Gossipsub,
77                default_consensus_threshold: 2.0 / 3.0,
78                default_timeout: DEFAULT_TIMEOUT,
79                default_liveness_criteria_yes: true,
80                max_rounds_override: None,
81            },
82            NetworkType::P2P => Self {
83                network_type: NetworkType::P2P,
84                default_consensus_threshold: 2.0 / 3.0,
85                default_timeout: DEFAULT_TIMEOUT,
86                default_liveness_criteria_yes: true,
87                max_rounds_override: None,
88            },
89        }
90    }
91}
92
93pub(crate) struct ScopeConfigBuilder {
94    config: ScopeConfig,
95}
96
97impl ScopeConfigBuilder {
98    pub(crate) fn new() -> Self {
99        Self {
100            config: ScopeConfig::default(),
101        }
102    }
103
104    /// Set network type (P2P or Gossipsub)
105    pub fn with_network_type(mut self, network_type: NetworkType) -> Self {
106        self.config.network_type = network_type;
107        self
108    }
109
110    /// Set consensus threshold (0.0 to 1.0)
111    pub fn with_threshold(mut self, threshold: f64) -> Self {
112        self.config.default_consensus_threshold = threshold;
113        self
114    }
115
116    /// Set default timeout for proposals (in seconds)
117    pub fn with_timeout(mut self, timeout: Duration) -> Self {
118        self.config.default_timeout = timeout;
119        self
120    }
121
122    /// Set liveness criteria (how silent peers are counted)
123    pub fn with_liveness_criteria(mut self, liveness_criteria_yes: bool) -> Self {
124        self.config.default_liveness_criteria_yes = liveness_criteria_yes;
125        self
126    }
127
128    /// Override max rounds (if None, uses network_type defaults)
129    pub fn with_max_rounds(mut self, max_rounds: Option<u32>) -> Self {
130        self.config.max_rounds_override = max_rounds;
131        self
132    }
133
134    /// Start builder from an existing ScopeConfig (useful for partial updates)
135    pub fn from_existing(config: ScopeConfig) -> Self {
136        Self { config }
137    }
138
139    /// Use P2P preset with common defaults
140    pub fn p2p_preset(mut self) -> Self {
141        self.config.network_type = NetworkType::P2P;
142        self.config.default_consensus_threshold = 2.0 / 3.0;
143        self.config.default_timeout = DEFAULT_TIMEOUT;
144        self.config.default_liveness_criteria_yes = true;
145        self.config.max_rounds_override = None;
146        self
147    }
148
149    /// Use Gossipsub preset with common defaults
150    pub fn gossipsub_preset(mut self) -> Self {
151        self.config.network_type = NetworkType::Gossipsub;
152        self.config.default_consensus_threshold = 2.0 / 3.0;
153        self.config.default_timeout = DEFAULT_TIMEOUT;
154        self.config.default_liveness_criteria_yes = true;
155        self.config.max_rounds_override = None;
156        self
157    }
158
159    /// Use strict consensus (higher threshold = 0.9)
160    pub fn strict_consensus(mut self) -> Self {
161        self.config.default_consensus_threshold = 0.9;
162        self
163    }
164
165    /// Use fast consensus (lower threshold = 0.6, shorter timeout = 30s)
166    pub fn fast_consensus(mut self) -> Self {
167        self.config.default_consensus_threshold = 0.6;
168        self.config.default_timeout = Duration::from_secs(30);
169        self
170    }
171
172    /// Start with network-specific defaults
173    pub fn with_network_defaults(mut self, network_type: NetworkType) -> Self {
174        match network_type {
175            NetworkType::P2P => {
176                self.config.network_type = NetworkType::P2P;
177                self.config.default_consensus_threshold = 2.0 / 3.0;
178                self.config.default_timeout = DEFAULT_TIMEOUT;
179            }
180            NetworkType::Gossipsub => {
181                self.config.network_type = NetworkType::Gossipsub;
182                self.config.default_consensus_threshold = 2.0 / 3.0;
183                self.config.default_timeout = DEFAULT_TIMEOUT;
184            }
185        }
186        self
187    }
188
189    /// Validate configuration
190    pub fn validate(&self) -> Result<(), ConsensusError> {
191        self.config.validate()
192    }
193
194    /// Build the final ScopeConfig
195    pub fn build(self) -> Result<ScopeConfig, ConsensusError> {
196        self.validate()?;
197        Ok(self.config)
198    }
199
200    /// Get the current configuration
201    pub fn get_config(&self) -> ScopeConfig {
202        self.config.clone()
203    }
204}
205
206impl Default for ScopeConfigBuilder {
207    fn default() -> Self {
208        Self::new()
209    }
210}