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 pub fn new_v3(
87 epoch: Epoch,
88 authorities: Vec<Authority>,
89 malicious_stake: Stake,
90 crash_stake: Stake,
91 ) -> Self {
92 assert!(!authorities.is_empty(), "Committee cannot be empty!");
93 assert!(
94 authorities.len() < u32::MAX as usize,
95 "Too many authorities ({})!",
96 authorities.len()
97 );
98
99 let actual_total_stake: Stake = authorities.iter().map(|a| a.stake).sum();
100 assert_ne!(actual_total_stake, 0, "Total stake cannot be zero!");
101
102 let base_stake = 5 * malicious_stake + 3 * crash_stake;
104 let (f, c) = if base_stake > 0 {
105 let scale = |nominal: Stake| -> Stake {
108 nominal
109 .checked_mul(actual_total_stake - 1)
110 .unwrap_or_else(|| panic!("Overflowed: {} {}", nominal, actual_total_stake - 1))
111 .checked_div(base_stake)
112 .unwrap_or_else(|| panic!("Division error: {} {}", nominal, base_stake))
113 };
114 (scale(malicious_stake), scale(crash_stake))
115 } else {
116 (0, 0)
118 };
119
120 let validity_threshold = f + 1;
121 let certification_threshold = 2 * f + c + 1;
122 let quorum_threshold = actual_total_stake - f - c;
123
124 assert!(
126 certification_threshold + quorum_threshold >= actual_total_stake + f + 1,
127 "Stake-safety invariant violated: \
128 committed_cert ({certification_threshold}) + \
129 quorum ({quorum_threshold}) < \
130 actual_total_stake ({actual_total_stake}) + f ({f}) + 1"
131 );
132
133 assert!(
135 quorum_threshold * 2 >= actual_total_stake + f + certification_threshold,
136 "Stake-safety invariant violated: \
137 quorum_threshold ({quorum_threshold}) * 2 < \
138 actual_total_stake ({actual_total_stake}) + f ({f}) + \
139 certification_threshold ({certification_threshold})"
140 );
141
142 Self {
143 epoch,
144 total_stake: actual_total_stake,
145 quorum_threshold,
146 certification_threshold,
147 validity_threshold,
148 authorities,
149 }
150 }
151
152 pub fn epoch(&self) -> Epoch {
156 self.epoch
157 }
158
159 pub fn total_stake(&self) -> Stake {
160 self.total_stake
161 }
162
163 pub fn quorum_threshold(&self) -> Stake {
164 self.quorum_threshold
165 }
166
167 pub fn certification_threshold(&self) -> Stake {
168 self.certification_threshold
169 }
170
171 pub fn validity_threshold(&self) -> Stake {
172 self.validity_threshold
173 }
174
175 pub fn stake(&self, authority_index: AuthorityIndex) -> Stake {
176 self.authorities[authority_index].stake
177 }
178
179 pub fn authority(&self, authority_index: AuthorityIndex) -> &Authority {
180 &self.authorities[authority_index]
181 }
182
183 pub fn authorities(&self) -> impl Iterator<Item = (AuthorityIndex, &Authority)> {
184 self.authorities
185 .iter()
186 .enumerate()
187 .map(|(i, a)| (AuthorityIndex(i as u32), a))
188 }
189
190 pub fn authorities_slice(&self) -> &[Authority] {
194 &self.authorities
195 }
196
197 pub fn reached_quorum(&self, stake: Stake) -> bool {
202 stake >= self.quorum_threshold()
203 }
204
205 pub fn reached_validity(&self, stake: Stake) -> bool {
207 stake >= self.validity_threshold()
208 }
209
210 pub fn to_authority_index(&self, index: usize) -> Option<AuthorityIndex> {
213 if index < self.authorities.len() {
214 Some(AuthorityIndex(index as u32))
215 } else {
216 None
217 }
218 }
219
220 pub fn is_valid_index(&self, index: AuthorityIndex) -> bool {
222 index.value() < self.size()
223 }
224
225 pub fn size(&self) -> usize {
227 self.authorities.len()
228 }
229}
230
231#[derive(Clone, Debug, Serialize, Deserialize)]
236pub struct Authority {
237 pub stake: Stake,
239 pub address: Multiaddr,
241 pub hostname: String,
243 pub authority_name: AuthorityName,
245 pub protocol_key: ProtocolPublicKey,
247 pub network_key: NetworkPublicKey,
249}
250
251#[derive(
258 Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug, Default, Hash, Serialize, Deserialize,
259)]
260pub struct AuthorityIndex(u32);
261
262impl AuthorityIndex {
263 pub const ZERO: Self = Self(0);
265
266 pub const MIN: Self = Self::ZERO;
268 pub const MAX: Self = Self(u32::MAX);
269
270 pub fn value(&self) -> usize {
271 self.0 as usize
272 }
273}
274
275impl AuthorityIndex {
276 pub fn new_for_test(index: u32) -> Self {
277 Self(index)
278 }
279}
280
281impl Display for AuthorityIndex {
282 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
283 write!(f, "[{}]", self.value())
284 }
285}
286
287impl<T, const N: usize> Index<AuthorityIndex> for [T; N] {
288 type Output = T;
289
290 fn index(&self, index: AuthorityIndex) -> &Self::Output {
291 self.get(index.value()).unwrap()
292 }
293}
294
295impl<T> Index<AuthorityIndex> for Vec<T> {
296 type Output = T;
297
298 fn index(&self, index: AuthorityIndex) -> &Self::Output {
299 self.get(index.value()).unwrap()
300 }
301}
302
303impl<T, const N: usize> IndexMut<AuthorityIndex> for [T; N] {
304 fn index_mut(&mut self, index: AuthorityIndex) -> &mut Self::Output {
305 self.get_mut(index.value()).unwrap()
306 }
307}
308
309impl<T> IndexMut<AuthorityIndex> for Vec<T> {
310 fn index_mut(&mut self, index: AuthorityIndex) -> &mut Self::Output {
311 self.get_mut(index.value()).unwrap()
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use crate::{local_committee_and_keys, local_committee_and_keys_with_test_options};
319
320 #[test]
321 fn committee_basic() {
322 let epoch = 100;
324 let num_of_authorities = 10;
325 let authority_stakes = (1..=num_of_authorities).map(|s| s as Stake).collect();
326 let (committee, _) = local_committee_and_keys(epoch, authority_stakes);
327
328 assert_eq!(committee.size(), num_of_authorities);
330 for (i, authority) in committee.authorities() {
331 assert_eq!((i.value() + 1) as Stake, authority.stake);
332 }
333
334 assert_eq!(committee.total_stake(), 55);
336 assert_eq!(committee.quorum_threshold(), 37);
337 assert_eq!(committee.validity_threshold(), 19);
338 }
339
340 #[test]
341 fn committee_thresholds_across_sizes() {
342 struct Case {
343 n: usize,
344 stake: Stake,
345 total: Stake,
346 quorum: Stake,
347 validity: Stake,
348 }
349 let cases = [
350 Case {
351 n: 11,
352 stake: 1,
353 total: 11,
354 quorum: 8,
355 validity: 4,
356 },
357 Case {
358 n: 12,
359 stake: 10,
360 total: 120,
361 quorum: 81,
362 validity: 40,
363 },
364 ];
365
366 for case in cases {
367 let stakes = vec![case.stake; case.n];
368 let (committee, _) = local_committee_and_keys(100, stakes);
369 assert_eq!(committee.total_stake(), case.total);
370 assert_eq!(committee.quorum_threshold(), case.quorum);
371 assert_eq!(committee.validity_threshold(), case.validity);
372 }
373 }
374
375 fn create_committee_with_total_stake(num_authorities: usize, total_stake: Stake) -> Committee {
376 assert!(num_authorities > 0);
379 let per = total_stake / num_authorities as Stake;
380 let mut stakes = vec![per; num_authorities];
381 *stakes.last_mut().unwrap() = total_stake - per * (num_authorities as Stake - 1);
382 let (committee, _) = local_committee_and_keys_with_test_options(0, stakes, false);
383 assert_eq!(committee.total_stake(), total_stake);
384 committee
385 }
386
387 #[test]
388 fn committee_v3_thresholds_across_actual_stakes() {
389 struct Case {
400 name: &'static str,
401 num_authorities: usize,
402 actual: Stake,
403 malicious: Stake,
404 crash: Stake,
405 validity: Stake,
406 cert: Stake,
407 quorum: Stake,
408 }
409 let cases = [
410 Case {
412 name: "no scaling",
413 num_authorities: 4,
414 actual: 10_001,
415 malicious: 1_250,
416 crash: 1_250,
417 validity: 1_251,
418 cert: 3_751,
419 quorum: 7_501,
420 },
421 Case {
424 name: "tight boundary",
425 num_authorities: 7,
426 actual: 10_002,
427 malicious: 1_250,
428 crash: 1_250,
429 validity: 1_251,
430 cert: 3_751,
431 quorum: 7_502,
432 },
433 Case {
435 name: "scale with remainder",
436 num_authorities: 10,
437 actual: 15_000,
438 malicious: 1_250,
439 crash: 1_250,
440 validity: 1_875,
441 cert: 5_623,
442 quorum: 11_252,
443 },
444 Case {
447 name: "aggressive scaling",
448 num_authorities: 5,
449 actual: 20_002,
450 malicious: 1,
451 crash: 1,
452 validity: 2_501,
453 cert: 7_501,
454 quorum: 15_002,
455 },
456 Case {
458 name: "crash-only (f=0)",
459 num_authorities: 4,
460 actual: 10_000,
461 malicious: 0,
462 crash: 1_000,
463 validity: 1,
464 cert: 3_334,
465 quorum: 6_667,
466 },
467 Case {
470 name: "byzantine-only (c=0)",
471 num_authorities: 6,
472 actual: 10_000,
473 malicious: 1_000,
474 crash: 0,
475 validity: 2_000,
476 cert: 3_999,
477 quorum: 8_001,
478 },
479 ];
480
481 for case in cases {
482 let seed = create_committee_with_total_stake(case.num_authorities, case.actual);
483 let committee = Committee::new_v3(
484 seed.epoch(),
485 seed.authorities_slice().to_vec(),
486 case.malicious,
487 case.crash,
488 );
489 assert_eq!(committee.size(), case.num_authorities, "{}", case.name);
490 assert_eq!(committee.total_stake(), case.actual, "{}", case.name);
491 assert_eq!(
492 committee.validity_threshold(),
493 case.validity,
494 "{}",
495 case.name
496 );
497 assert_eq!(
498 committee.certification_threshold(),
499 case.cert,
500 "{}",
501 case.name
502 );
503 assert_eq!(committee.quorum_threshold(), case.quorum, "{}", case.name);
504 }
505 }
506
507 #[test]
508 fn committee_v3_no_fault_budget_single_authority() {
509 let (seed, _) = local_committee_and_keys_with_test_options(0, vec![100 as Stake], false);
511 let committee = Committee::new_v3(seed.epoch(), seed.authorities_slice().to_vec(), 0, 0);
512 assert_eq!(committee.validity_threshold(), 1);
513 assert_eq!(committee.certification_threshold(), 1);
514 assert_eq!(committee.quorum_threshold(), 100);
515 }
516
517 #[test]
518 #[should_panic(expected = "Total stake cannot be zero!")]
519 fn committee_v3_zero_actual_stake_panics() {
520 let zero_stake_authorities: Vec<Authority> = {
521 let (seed, _) =
522 local_committee_and_keys_with_test_options(0, vec![1 as Stake; 4], false);
523 seed.authorities_slice()
524 .iter()
525 .map(|a| Authority {
526 stake: 0,
527 ..a.clone()
528 })
529 .collect()
530 };
531 Committee::new_v3(0, zero_stake_authorities, 1_250, 1_250);
532 }
533}