guts_consensus/
validator.rs1use crate::error::{ConsensusError, Result};
7use crate::transaction::SerializablePublicKey;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::net::SocketAddr;
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
14pub struct Validator {
15 pub pubkey: SerializablePublicKey,
17
18 pub name: String,
20
21 pub weight: u64,
23
24 pub addr: SocketAddr,
26
27 pub joined_epoch: u64,
29
30 pub active: bool,
32}
33
34impl Validator {
35 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 pub fn with_joined_epoch(mut self, epoch: u64) -> Self {
54 self.joined_epoch = epoch;
55 self
56 }
57
58 pub fn with_active(mut self, active: bool) -> Self {
60 self.active = active;
61 self
62 }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct ValidatorConfig {
68 pub min_validators: usize,
70
71 pub max_validators: usize,
73
74 pub quorum_threshold: f64,
77
78 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#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct ValidatorSet {
96 validators: Vec<Validator>,
98
99 epoch: u64,
101
102 config: ValidatorConfig,
104
105 #[serde(skip)]
107 index: HashMap<String, usize>,
108}
109
110impl ValidatorSet {
111 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 pub fn genesis(validators: Vec<Validator>) -> Result<Self> {
142 Self::new(validators, 0, ValidatorConfig::default())
143 }
144
145 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 pub fn epoch(&self) -> u64 {
155 self.epoch
156 }
157
158 pub fn validators(&self) -> &[Validator] {
160 &self.validators
161 }
162
163 pub fn active_validators(&self) -> Vec<&Validator> {
165 self.validators.iter().filter(|v| v.active).collect()
166 }
167
168 pub fn len(&self) -> usize {
170 self.validators.len()
171 }
172
173 pub fn is_empty(&self) -> bool {
175 self.validators.is_empty()
176 }
177
178 pub fn active_count(&self) -> usize {
180 self.validators.iter().filter(|v| v.active).count()
181 }
182
183 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 pub fn is_validator(&self, pubkey: &SerializablePublicKey) -> bool {
192 self.index.contains_key(&pubkey.0)
193 }
194
195 pub fn is_active_validator(&self, pubkey: &SerializablePublicKey) -> bool {
197 self.get(pubkey).map(|v| v.active).unwrap_or(false)
198 }
199
200 pub fn total_weight(&self) -> u64 {
202 self.validators.iter().map(|v| v.weight).sum()
203 }
204
205 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 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 pub fn max_byzantine_weight(&self) -> u64 {
223 let total = self.active_weight();
224 total / 3
225 }
226
227 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 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 pub fn block_time_ms(&self) -> u64 {
248 self.config.block_time_ms
249 }
250
251 pub fn config(&self) -> &ValidatorConfig {
253 &self.config
254 }
255
256 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 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 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); let set = ValidatorSet::genesis(validators).unwrap();
341
342 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 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 assert!(set.has_quorum(&pubkeys[0..3]));
368
369 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}