guts_consensus/
validator.rs

1//! Validator set management.
2//!
3//! Validators are the nodes that participate in consensus. They propose blocks,
4//! vote on proposals, and earn the right to finalize blocks.
5
6use crate::error::{ConsensusError, Result};
7use crate::transaction::SerializablePublicKey;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::net::SocketAddr;
11
12/// A validator in the consensus network.
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
14pub struct Validator {
15    /// Validator's public key (identity).
16    pub pubkey: SerializablePublicKey,
17
18    /// Human-readable name.
19    pub name: String,
20
21    /// Voting weight.
22    pub weight: u64,
23
24    /// Network address for P2P communication.
25    pub addr: SocketAddr,
26
27    /// Epoch when the validator joined.
28    pub joined_epoch: u64,
29
30    /// Whether the validator is active (participating in consensus).
31    pub active: bool,
32}
33
34impl Validator {
35    /// Creates a new validator.
36    pub fn new(
37        pubkey: SerializablePublicKey,
38        name: impl Into<String>,
39        weight: u64,
40        addr: SocketAddr,
41    ) -> Self {
42        Self {
43            pubkey,
44            name: name.into(),
45            weight,
46            addr,
47            joined_epoch: 0,
48            active: true,
49        }
50    }
51
52    /// Sets the epoch when the validator joined.
53    pub fn with_joined_epoch(mut self, epoch: u64) -> Self {
54        self.joined_epoch = epoch;
55        self
56    }
57
58    /// Sets the active status.
59    pub fn with_active(mut self, active: bool) -> Self {
60        self.active = active;
61        self
62    }
63}
64
65/// Configuration for validator set behavior.
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct ValidatorConfig {
68    /// Minimum number of validators for network operation.
69    pub min_validators: usize,
70
71    /// Maximum number of validators.
72    pub max_validators: usize,
73
74    /// Quorum threshold (fraction of weight required for consensus).
75    /// For BFT, this is typically 2/3.
76    pub quorum_threshold: f64,
77
78    /// Target block time in milliseconds.
79    pub block_time_ms: u64,
80}
81
82impl Default for ValidatorConfig {
83    fn default() -> Self {
84        Self {
85            min_validators: 4,
86            max_validators: 100,
87            quorum_threshold: 2.0 / 3.0,
88            block_time_ms: 2000,
89        }
90    }
91}
92
93/// The validator set for a given epoch.
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct ValidatorSet {
96    /// Current validators.
97    validators: Vec<Validator>,
98
99    /// Epoch number (changes when validator set changes).
100    epoch: u64,
101
102    /// Configuration.
103    config: ValidatorConfig,
104
105    /// Index for fast lookup by public key (hex string).
106    #[serde(skip)]
107    index: HashMap<String, usize>,
108}
109
110impl ValidatorSet {
111    /// Creates a new validator set.
112    pub fn new(validators: Vec<Validator>, epoch: u64, config: ValidatorConfig) -> Result<Self> {
113        if validators.len() < config.min_validators {
114            return Err(ConsensusError::InvalidGenesis(format!(
115                "need at least {} validators, got {}",
116                config.min_validators,
117                validators.len()
118            )));
119        }
120
121        if validators.len() > config.max_validators {
122            return Err(ConsensusError::InvalidGenesis(format!(
123                "too many validators: {} > {}",
124                validators.len(),
125                config.max_validators
126            )));
127        }
128
129        let mut set = Self {
130            validators,
131            epoch,
132            config,
133            index: HashMap::new(),
134        };
135
136        set.rebuild_index();
137        Ok(set)
138    }
139
140    /// Creates a genesis validator set.
141    pub fn genesis(validators: Vec<Validator>) -> Result<Self> {
142        Self::new(validators, 0, ValidatorConfig::default())
143    }
144
145    /// Rebuilds the lookup index.
146    fn rebuild_index(&mut self) {
147        self.index.clear();
148        for (i, v) in self.validators.iter().enumerate() {
149            self.index.insert(v.pubkey.0.clone(), i);
150        }
151    }
152
153    /// Returns the current epoch.
154    pub fn epoch(&self) -> u64 {
155        self.epoch
156    }
157
158    /// Returns all validators.
159    pub fn validators(&self) -> &[Validator] {
160        &self.validators
161    }
162
163    /// Returns all active validators.
164    pub fn active_validators(&self) -> Vec<&Validator> {
165        self.validators.iter().filter(|v| v.active).collect()
166    }
167
168    /// Returns the number of validators.
169    pub fn len(&self) -> usize {
170        self.validators.len()
171    }
172
173    /// Returns true if there are no validators.
174    pub fn is_empty(&self) -> bool {
175        self.validators.is_empty()
176    }
177
178    /// Returns the number of active validators.
179    pub fn active_count(&self) -> usize {
180        self.validators.iter().filter(|v| v.active).count()
181    }
182
183    /// Gets a validator by public key.
184    pub fn get(&self, pubkey: &SerializablePublicKey) -> Option<&Validator> {
185        self.index
186            .get(&pubkey.0)
187            .and_then(|&i| self.validators.get(i))
188    }
189
190    /// Checks if a public key belongs to a validator.
191    pub fn is_validator(&self, pubkey: &SerializablePublicKey) -> bool {
192        self.index.contains_key(&pubkey.0)
193    }
194
195    /// Checks if a public key belongs to an active validator.
196    pub fn is_active_validator(&self, pubkey: &SerializablePublicKey) -> bool {
197        self.get(pubkey).map(|v| v.active).unwrap_or(false)
198    }
199
200    /// Returns the total voting weight.
201    pub fn total_weight(&self) -> u64 {
202        self.validators.iter().map(|v| v.weight).sum()
203    }
204
205    /// Returns the total active voting weight.
206    pub fn active_weight(&self) -> u64 {
207        self.validators
208            .iter()
209            .filter(|v| v.active)
210            .map(|v| v.weight)
211            .sum()
212    }
213
214    /// Returns the quorum weight required for consensus.
215    pub fn quorum_weight(&self) -> u64 {
216        let total = self.active_weight();
217        ((total as f64) * self.config.quorum_threshold).ceil() as u64
218    }
219
220    /// Returns the maximum Byzantine weight that can be tolerated.
221    /// For f < n/3, this is floor((n-1)/3).
222    pub fn max_byzantine_weight(&self) -> u64 {
223        let total = self.active_weight();
224        total / 3
225    }
226
227    /// Gets the leader for a given view using round-robin selection.
228    pub fn leader_for_view(&self, view: u64) -> Option<&Validator> {
229        let active: Vec<_> = self.validators.iter().filter(|v| v.active).collect();
230        if active.is_empty() {
231            return None;
232        }
233        let idx = (view as usize) % active.len();
234        Some(active[idx])
235    }
236
237    /// Gets the leader index for a given view.
238    pub fn leader_index_for_view(&self, view: u64) -> Option<usize> {
239        let active_count = self.active_count();
240        if active_count == 0 {
241            return None;
242        }
243        Some((view as usize) % active_count)
244    }
245
246    /// Returns the target block time.
247    pub fn block_time_ms(&self) -> u64 {
248        self.config.block_time_ms
249    }
250
251    /// Returns the configuration.
252    pub fn config(&self) -> &ValidatorConfig {
253        &self.config
254    }
255
256    /// Checks if a set of signers meets quorum.
257    pub fn has_quorum(&self, signers: &[SerializablePublicKey]) -> bool {
258        let signed_weight: u64 = signers
259            .iter()
260            .filter_map(|pk| self.get(pk))
261            .filter(|v| v.active)
262            .map(|v| v.weight)
263            .sum();
264
265        signed_weight >= self.quorum_weight()
266    }
267
268    /// Gets the network addresses of all active validators.
269    pub fn active_addresses(&self) -> Vec<SocketAddr> {
270        self.validators
271            .iter()
272            .filter(|v| v.active)
273            .map(|v| v.addr)
274            .collect()
275    }
276
277    /// Gets the public keys of all active validators.
278    pub fn active_pubkeys(&self) -> Vec<SerializablePublicKey> {
279        self.validators
280            .iter()
281            .filter(|v| v.active)
282            .map(|v| v.pubkey.clone())
283            .collect()
284    }
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290    use commonware_cryptography::{ed25519, PrivateKeyExt, Signer};
291
292    fn test_validators(count: usize) -> Vec<Validator> {
293        (0..count as u64)
294            .map(|i| {
295                let key = ed25519::PrivateKey::from_seed(i);
296                Validator::new(
297                    SerializablePublicKey::from_pubkey(&key.public_key()),
298                    format!("validator-{}", i),
299                    100,
300                    format!("127.0.0.1:{}", 9000 + i).parse().unwrap(),
301                )
302            })
303            .collect()
304    }
305
306    #[test]
307    fn test_validator_set_genesis() {
308        let validators = test_validators(4);
309        let set = ValidatorSet::genesis(validators).unwrap();
310
311        assert_eq!(set.epoch(), 0);
312        assert_eq!(set.len(), 4);
313        assert_eq!(set.active_count(), 4);
314    }
315
316    #[test]
317    fn test_validator_set_too_few() {
318        let validators = test_validators(2);
319        let result = ValidatorSet::genesis(validators);
320
321        assert!(matches!(result, Err(ConsensusError::InvalidGenesis(_))));
322    }
323
324    #[test]
325    fn test_validator_lookup() {
326        let validators = test_validators(4);
327        let pubkey = validators[2].pubkey.clone();
328        let set = ValidatorSet::genesis(validators).unwrap();
329
330        assert!(set.is_validator(&pubkey));
331        assert!(set.is_active_validator(&pubkey));
332
333        let v = set.get(&pubkey).unwrap();
334        assert_eq!(v.name, "validator-2");
335    }
336
337    #[test]
338    fn test_quorum_weight() {
339        let validators = test_validators(4); // 4 * 100 = 400 total weight
340        let set = ValidatorSet::genesis(validators).unwrap();
341
342        // 2/3 of 400 = 266.67, ceil = 267
343        assert_eq!(set.quorum_weight(), 267);
344    }
345
346    #[test]
347    fn test_leader_rotation() {
348        let validators = test_validators(4);
349        let set = ValidatorSet::genesis(validators).unwrap();
350
351        let leader0 = set.leader_for_view(0).unwrap();
352        let leader1 = set.leader_for_view(1).unwrap();
353        let leader4 = set.leader_for_view(4).unwrap();
354
355        // View 4 should wrap around to validator 0
356        assert_eq!(leader0.pubkey, leader4.pubkey);
357        assert_ne!(leader0.pubkey, leader1.pubkey);
358    }
359
360    #[test]
361    fn test_has_quorum() {
362        let validators = test_validators(4);
363        let pubkeys: Vec<_> = validators.iter().map(|v| v.pubkey.clone()).collect();
364        let set = ValidatorSet::genesis(validators).unwrap();
365
366        // 3 of 4 validators (300/400 = 75% > 66.7%)
367        assert!(set.has_quorum(&pubkeys[0..3]));
368
369        // 2 of 4 validators (200/400 = 50% < 66.7%)
370        assert!(!set.has_quorum(&pubkeys[0..2]));
371    }
372
373    #[test]
374    fn test_active_addresses() {
375        let validators = test_validators(4);
376        let set = ValidatorSet::genesis(validators).unwrap();
377
378        let addrs = set.active_addresses();
379        assert_eq!(addrs.len(), 4);
380    }
381}