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 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    /// Set all configuration at once from a ScopeConfig
135    pub fn with_config(mut self, config: ScopeConfig) -> Self {
136        self.config = config;
137        self
138    }
139
140    /// Start builder from an existing ScopeConfig (useful for partial updates)
141    pub fn from_existing(config: ScopeConfig) -> Self {
142        Self { config }
143    }
144
145    /// Use P2P preset with common defaults
146    pub fn p2p_preset(mut self) -> Self {
147        self.config.network_type = NetworkType::P2P;
148        self.config.default_consensus_threshold = 2.0 / 3.0;
149        self.config.default_timeout = DEFAULT_TIMEOUT;
150        self.config.default_liveness_criteria_yes = true;
151        self.config.max_rounds_override = None;
152        self
153    }
154
155    /// Use Gossipsub preset with common defaults
156    pub fn gossipsub_preset(mut self) -> Self {
157        self.config.network_type = NetworkType::Gossipsub;
158        self.config.default_consensus_threshold = 2.0 / 3.0;
159        self.config.default_timeout = DEFAULT_TIMEOUT;
160        self.config.default_liveness_criteria_yes = true;
161        self.config.max_rounds_override = None;
162        self
163    }
164
165    /// Use strict consensus (higher threshold = 0.9)
166    pub fn strict_consensus(mut self) -> Self {
167        self.config.default_consensus_threshold = 0.9;
168        self
169    }
170
171    /// Use fast consensus (lower threshold = 0.6, shorter timeout = 30s)
172    pub fn fast_consensus(mut self) -> Self {
173        self.config.default_consensus_threshold = 0.6;
174        self.config.default_timeout = Duration::from_secs(30);
175        self
176    }
177
178    /// Start with network-specific defaults
179    pub fn with_network_defaults(mut self, network_type: NetworkType) -> Self {
180        match network_type {
181            NetworkType::P2P => {
182                self.config.network_type = NetworkType::P2P;
183                self.config.default_consensus_threshold = 2.0 / 3.0;
184                self.config.default_timeout = DEFAULT_TIMEOUT;
185            }
186            NetworkType::Gossipsub => {
187                self.config.network_type = NetworkType::Gossipsub;
188                self.config.default_consensus_threshold = 2.0 / 3.0;
189                self.config.default_timeout = DEFAULT_TIMEOUT;
190            }
191        }
192        self
193    }
194
195    /// Validate configuration
196    pub fn validate(&self) -> Result<(), ConsensusError> {
197        self.config.validate()
198    }
199
200    /// Build the final ScopeConfig
201    pub fn build(self) -> Result<ScopeConfig, ConsensusError> {
202        self.validate()?;
203        Ok(self.config)
204    }
205
206    /// Get the current configuration
207    pub fn get_config(&self) -> ScopeConfig {
208        self.config.clone()
209    }
210}
211
212impl Default for ScopeConfigBuilder {
213    fn default() -> Self {
214        Self::new()
215    }
216}