1use std::{
5 fmt::{Display, Formatter},
6 ops::{Index, IndexMut},
7};
8
9use mysten_network::Multiaddr;
10use serde::{Deserialize, Serialize};
11
12use crate::{AuthorityName, NetworkPublicKey, ProtocolPublicKey};
13
14pub type Epoch = u64;
16
17pub type Stake = u64;
21
22#[derive(Clone, Debug, Serialize, Deserialize)]
25pub struct Committee {
26 epoch: Epoch,
28 authorities: Vec<Authority>,
30 total_stake: Stake,
32
33 quorum_threshold: Stake,
35 certification_threshold: Stake,
36 validity_threshold: Stake,
37}
38
39impl Committee {
40 pub fn new(epoch: Epoch, authorities: Vec<Authority>) -> Self {
41 assert!(!authorities.is_empty(), "Committee cannot be empty!");
42 assert!(
43 authorities.len() < u32::MAX as usize,
44 "Too many authorities ({})!",
45 authorities.len()
46 );
47
48 let total_stake: Stake = authorities.iter().map(|a| a.stake).sum();
49 assert_ne!(total_stake, 0, "Total stake cannot be zero!");
50
51 let fault_tolerance = (total_stake - 1) / 3;
53 let quorum_threshold = total_stake - fault_tolerance;
54 let validity_threshold = fault_tolerance + 1;
55 assert!(
56 2 * quorum_threshold - fault_tolerance > total_stake,
57 "Quorum must intersect under maxim equivocations! Quorum: {quorum_threshold}, Fault tolerance: {fault_tolerance}, Total: {total_stake}"
58 );
59
60 Self {
61 epoch,
62 authorities,
63 total_stake,
64 quorum_threshold,
65 validity_threshold,
66
67 certification_threshold: quorum_threshold,
69 }
70 }
71
72 #[allow(clippy::int_plus_one)] pub fn new_v3(
88 epoch: Epoch,
89 authorities: Vec<Authority>,
90 malicious_stake: Stake,
91 crash_stake: Stake,
92 ) -> Self {
93 assert!(!authorities.is_empty(), "Committee cannot be empty!");
94 assert!(
95 authorities.len() < u32::MAX as usize,
96 "Too many authorities ({})!",
97 authorities.len()
98 );
99
100 let actual_total_stake: Stake = authorities.iter().map(|a| a.stake).sum();
101 assert_ne!(actual_total_stake, 0, "Total stake cannot be zero!");
102
103 let base_stake = 5 * malicious_stake + 3 * crash_stake;
105 let (f, c) = if base_stake > 0 {
106 let scale = |nominal: Stake| -> Stake {
109 nominal
110 .checked_mul(actual_total_stake - 1)
111 .unwrap_or_else(|| panic!("Overflowed: {} {}", nominal, actual_total_stake - 1))
112 .checked_div(base_stake)
113 .unwrap_or_else(|| panic!("Division error: {} {}", nominal, base_stake))
114 };
115 (scale(malicious_stake), scale(crash_stake))
116 } else {
117 (0, 0)
119 };
120
121 let validity_threshold = f + 1;
122 let certification_threshold = 2 * f + c + 1;
123 let quorum_threshold = actual_total_stake - f - c;
124
125 assert!(
127 certification_threshold + quorum_threshold >= actual_total_stake + f + 1,
128 "Stake-safety invariant violated: \
129 committed_cert ({certification_threshold}) + \
130 quorum ({quorum_threshold}) < \
131 actual_total_stake ({actual_total_stake}) + f ({f}) + 1"
132 );
133
134 assert!(
136 quorum_threshold * 2 >= actual_total_stake + f + certification_threshold,
137 "Stake-safety invariant violated: \
138 quorum_threshold ({quorum_threshold}) * 2 < \
139 actual_total_stake ({actual_total_stake}) + f ({f}) + \
140 certification_threshold ({certification_threshold})"
141 );
142
143 Self {
144 epoch,
145 total_stake: actual_total_stake,
146 quorum_threshold,
147 certification_threshold,
148 validity_threshold,
149 authorities,
150 }
151 }
152
153 pub fn epoch(&self) -> Epoch {
157 self.epoch
158 }
159
160 pub fn total_stake(&self) -> Stake {
161 self.total_stake
162 }
163
164 pub fn quorum_threshold(&self) -> Stake {
165 self.quorum_threshold
166 }
167
168 pub fn certification_threshold(&self) -> Stake {
169 self.certification_threshold
170 }
171
172 pub fn validity_threshold(&self) -> Stake {
173 self.validity_threshold
174 }
175
176 pub fn stake(&self, authority_index: AuthorityIndex) -> Stake {
177 self.authorities[authority_index].stake
178 }
179
180 pub fn authority(&self, authority_index: AuthorityIndex) -> &Authority {
181 &self.authorities[authority_index]
182 }
183
184 pub fn authorities(&self) -> impl Iterator<Item = (AuthorityIndex, &Authority)> {
185 self.authorities
186 .iter()
187 .enumerate()
188 .map(|(i, a)| (AuthorityIndex(i as u32), a))
189 }
190
191 pub fn authorities_slice(&self) -> &[Authority] {
195 &self.authorities
196 }
197
198 pub fn reached_quorum(&self, stake: Stake) -> bool {
203 stake >= self.quorum_threshold()
204 }
205
206 pub fn reached_validity(&self, stake: Stake) -> bool {
208 stake >= self.validity_threshold()
209 }
210
211 pub fn to_authority_index(&self, index: usize) -> Option<AuthorityIndex> {
214 if index < self.authorities.len() {
215 Some(AuthorityIndex(index as u32))
216 } else {
217 None
218 }
219 }
220
221 pub fn is_valid_index(&self, index: AuthorityIndex) -> bool {
223 index.value() < self.size()
224 }
225
226 pub fn size(&self) -> usize {
228 self.authorities.len()
229 }
230}
231
232#[derive(Clone, Debug, Serialize, Deserialize)]
237pub struct Authority {
238 pub stake: Stake,
240 pub address: Multiaddr,
242 pub hostname: String,
244 pub authority_name: AuthorityName,
246 pub protocol_key: ProtocolPublicKey,
248 pub network_key: NetworkPublicKey,
250}
251
252#[derive(
259 Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug, Default, Hash, Serialize, Deserialize,
260)]
261pub struct AuthorityIndex(u32);
262
263impl AuthorityIndex {
264 pub const ZERO: Self = Self(0);
266
267 pub const MIN: Self = Self::ZERO;
269 pub const MAX: Self = Self(u32::MAX);
270
271 pub fn value(&self) -> usize {
272 self.0 as usize
273 }
274
275 pub fn value_u32(&self) -> u32 {
276 self.0
277 }
278}
279
280impl AuthorityIndex {
281 pub fn new_for_test(index: u32) -> Self {
282 Self(index)
283 }
284}
285
286impl Display for AuthorityIndex {
287 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
288 write!(f, "[{}]", self.value())
289 }
290}
291
292impl<T, const N: usize> Index<AuthorityIndex> for [T; N] {
293 type Output = T;
294
295 fn index(&self, index: AuthorityIndex) -> &Self::Output {
296 self.get(index.value()).unwrap()
297 }
298}
299
300impl<T> Index<AuthorityIndex> for Vec<T> {
301 type Output = T;
302
303 fn index(&self, index: AuthorityIndex) -> &Self::Output {
304 self.get(index.value()).unwrap()
305 }
306}
307
308impl<T, const N: usize> IndexMut<AuthorityIndex> for [T; N] {
309 fn index_mut(&mut self, index: AuthorityIndex) -> &mut Self::Output {
310 self.get_mut(index.value()).unwrap()
311 }
312}
313
314impl<T> IndexMut<AuthorityIndex> for Vec<T> {
315 fn index_mut(&mut self, index: AuthorityIndex) -> &mut Self::Output {
316 self.get_mut(index.value()).unwrap()
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use crate::{local_committee_and_keys, local_committee_and_keys_with_test_options};
324
325 #[test]
326 fn committee_basic() {
327 let epoch = 100;
329 let num_of_authorities = 10;
330 let authority_stakes = (1..=num_of_authorities).map(|s| s as Stake).collect();
331 let (committee, _) = local_committee_and_keys(epoch, authority_stakes);
332
333 assert_eq!(committee.size(), num_of_authorities);
335 for (i, authority) in committee.authorities() {
336 assert_eq!((i.value() + 1) as Stake, authority.stake);
337 }
338
339 assert_eq!(committee.total_stake(), 55);
341 assert_eq!(committee.quorum_threshold(), 37);
342 assert_eq!(committee.validity_threshold(), 19);
343 }
344
345 #[test]
346 fn committee_thresholds_across_sizes() {
347 struct Case {
348 n: usize,
349 stake: Stake,
350 total: Stake,
351 quorum: Stake,
352 validity: Stake,
353 }
354 let cases = [
355 Case {
356 n: 11,
357 stake: 1,
358 total: 11,
359 quorum: 8,
360 validity: 4,
361 },
362 Case {
363 n: 12,
364 stake: 10,
365 total: 120,
366 quorum: 81,
367 validity: 40,
368 },
369 ];
370
371 for case in cases {
372 let stakes = vec![case.stake; case.n];
373 let (committee, _) = local_committee_and_keys(100, stakes);
374 assert_eq!(committee.total_stake(), case.total);
375 assert_eq!(committee.quorum_threshold(), case.quorum);
376 assert_eq!(committee.validity_threshold(), case.validity);
377 }
378 }
379
380 fn create_committee_with_total_stake(num_authorities: usize, total_stake: Stake) -> Committee {
381 assert!(num_authorities > 0);
384 let per = total_stake / num_authorities as Stake;
385 let mut stakes = vec![per; num_authorities];
386 *stakes.last_mut().unwrap() = total_stake - per * (num_authorities as Stake - 1);
387 let (committee, _) = local_committee_and_keys_with_test_options(0, stakes, false);
388 assert_eq!(committee.total_stake(), total_stake);
389 committee
390 }
391
392 #[test]
393 fn committee_v3_thresholds_across_actual_stakes() {
394 struct Case {
405 name: &'static str,
406 num_authorities: usize,
407 actual: Stake,
408 malicious: Stake,
409 crash: Stake,
410 validity: Stake,
411 cert: Stake,
412 quorum: Stake,
413 }
414 let cases = [
415 Case {
417 name: "no scaling",
418 num_authorities: 4,
419 actual: 10_001,
420 malicious: 1_250,
421 crash: 1_250,
422 validity: 1_251,
423 cert: 3_751,
424 quorum: 7_501,
425 },
426 Case {
429 name: "tight boundary",
430 num_authorities: 7,
431 actual: 10_002,
432 malicious: 1_250,
433 crash: 1_250,
434 validity: 1_251,
435 cert: 3_751,
436 quorum: 7_502,
437 },
438 Case {
440 name: "scale with remainder",
441 num_authorities: 10,
442 actual: 15_000,
443 malicious: 1_250,
444 crash: 1_250,
445 validity: 1_875,
446 cert: 5_623,
447 quorum: 11_252,
448 },
449 Case {
452 name: "aggressive scaling",
453 num_authorities: 5,
454 actual: 20_002,
455 malicious: 1,
456 crash: 1,
457 validity: 2_501,
458 cert: 7_501,
459 quorum: 15_002,
460 },
461 Case {
463 name: "crash-only (f=0)",
464 num_authorities: 4,
465 actual: 10_000,
466 malicious: 0,
467 crash: 1_000,
468 validity: 1,
469 cert: 3_334,
470 quorum: 6_667,
471 },
472 Case {
475 name: "byzantine-only (c=0)",
476 num_authorities: 6,
477 actual: 10_000,
478 malicious: 1_000,
479 crash: 0,
480 validity: 2_000,
481 cert: 3_999,
482 quorum: 8_001,
483 },
484 ];
485
486 for case in cases {
487 let seed = create_committee_with_total_stake(case.num_authorities, case.actual);
488 let committee = Committee::new_v3(
489 seed.epoch(),
490 seed.authorities_slice().to_vec(),
491 case.malicious,
492 case.crash,
493 );
494 assert_eq!(committee.size(), case.num_authorities, "{}", case.name);
495 assert_eq!(committee.total_stake(), case.actual, "{}", case.name);
496 assert_eq!(
497 committee.validity_threshold(),
498 case.validity,
499 "{}",
500 case.name
501 );
502 assert_eq!(
503 committee.certification_threshold(),
504 case.cert,
505 "{}",
506 case.name
507 );
508 assert_eq!(committee.quorum_threshold(), case.quorum, "{}", case.name);
509 }
510 }
511
512 #[test]
513 fn committee_v3_no_fault_budget_single_authority() {
514 let (seed, _) = local_committee_and_keys_with_test_options(0, vec![100 as Stake], false);
516 let committee = Committee::new_v3(seed.epoch(), seed.authorities_slice().to_vec(), 0, 0);
517 assert_eq!(committee.validity_threshold(), 1);
518 assert_eq!(committee.certification_threshold(), 1);
519 assert_eq!(committee.quorum_threshold(), 100);
520 }
521
522 #[test]
523 #[should_panic(expected = "Total stake cannot be zero!")]
524 fn committee_v3_zero_actual_stake_panics() {
525 let zero_stake_authorities: Vec<Authority> = {
526 let (seed, _) =
527 local_committee_and_keys_with_test_options(0, vec![1 as Stake; 4], false);
528 seed.authorities_slice()
529 .iter()
530 .map(|a| Authority {
531 stake: 0,
532 ..a.clone()
533 })
534 .collect()
535 };
536 Committee::new_v3(0, zero_stake_authorities, 1_250, 1_250);
537 }
538}