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
276impl AuthorityIndex {
277 pub fn new_for_test(index: u32) -> Self {
278 Self(index)
279 }
280}
281
282impl Display for AuthorityIndex {
283 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
284 write!(f, "[{}]", self.value())
285 }
286}
287
288impl<T, const N: usize> Index<AuthorityIndex> for [T; N] {
289 type Output = T;
290
291 fn index(&self, index: AuthorityIndex) -> &Self::Output {
292 self.get(index.value()).unwrap()
293 }
294}
295
296impl<T> Index<AuthorityIndex> for Vec<T> {
297 type Output = T;
298
299 fn index(&self, index: AuthorityIndex) -> &Self::Output {
300 self.get(index.value()).unwrap()
301 }
302}
303
304impl<T, const N: usize> IndexMut<AuthorityIndex> for [T; N] {
305 fn index_mut(&mut self, index: AuthorityIndex) -> &mut Self::Output {
306 self.get_mut(index.value()).unwrap()
307 }
308}
309
310impl<T> IndexMut<AuthorityIndex> for Vec<T> {
311 fn index_mut(&mut self, index: AuthorityIndex) -> &mut Self::Output {
312 self.get_mut(index.value()).unwrap()
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319 use crate::{local_committee_and_keys, local_committee_and_keys_with_test_options};
320
321 #[test]
322 fn committee_basic() {
323 let epoch = 100;
325 let num_of_authorities = 10;
326 let authority_stakes = (1..=num_of_authorities).map(|s| s as Stake).collect();
327 let (committee, _) = local_committee_and_keys(epoch, authority_stakes);
328
329 assert_eq!(committee.size(), num_of_authorities);
331 for (i, authority) in committee.authorities() {
332 assert_eq!((i.value() + 1) as Stake, authority.stake);
333 }
334
335 assert_eq!(committee.total_stake(), 55);
337 assert_eq!(committee.quorum_threshold(), 37);
338 assert_eq!(committee.validity_threshold(), 19);
339 }
340
341 #[test]
342 fn committee_thresholds_across_sizes() {
343 struct Case {
344 n: usize,
345 stake: Stake,
346 total: Stake,
347 quorum: Stake,
348 validity: Stake,
349 }
350 let cases = [
351 Case {
352 n: 11,
353 stake: 1,
354 total: 11,
355 quorum: 8,
356 validity: 4,
357 },
358 Case {
359 n: 12,
360 stake: 10,
361 total: 120,
362 quorum: 81,
363 validity: 40,
364 },
365 ];
366
367 for case in cases {
368 let stakes = vec![case.stake; case.n];
369 let (committee, _) = local_committee_and_keys(100, stakes);
370 assert_eq!(committee.total_stake(), case.total);
371 assert_eq!(committee.quorum_threshold(), case.quorum);
372 assert_eq!(committee.validity_threshold(), case.validity);
373 }
374 }
375
376 fn create_committee_with_total_stake(num_authorities: usize, total_stake: Stake) -> Committee {
377 assert!(num_authorities > 0);
380 let per = total_stake / num_authorities as Stake;
381 let mut stakes = vec![per; num_authorities];
382 *stakes.last_mut().unwrap() = total_stake - per * (num_authorities as Stake - 1);
383 let (committee, _) = local_committee_and_keys_with_test_options(0, stakes, false);
384 assert_eq!(committee.total_stake(), total_stake);
385 committee
386 }
387
388 #[test]
389 fn committee_v3_thresholds_across_actual_stakes() {
390 struct Case {
401 name: &'static str,
402 num_authorities: usize,
403 actual: Stake,
404 malicious: Stake,
405 crash: Stake,
406 validity: Stake,
407 cert: Stake,
408 quorum: Stake,
409 }
410 let cases = [
411 Case {
413 name: "no scaling",
414 num_authorities: 4,
415 actual: 10_001,
416 malicious: 1_250,
417 crash: 1_250,
418 validity: 1_251,
419 cert: 3_751,
420 quorum: 7_501,
421 },
422 Case {
425 name: "tight boundary",
426 num_authorities: 7,
427 actual: 10_002,
428 malicious: 1_250,
429 crash: 1_250,
430 validity: 1_251,
431 cert: 3_751,
432 quorum: 7_502,
433 },
434 Case {
436 name: "scale with remainder",
437 num_authorities: 10,
438 actual: 15_000,
439 malicious: 1_250,
440 crash: 1_250,
441 validity: 1_875,
442 cert: 5_623,
443 quorum: 11_252,
444 },
445 Case {
448 name: "aggressive scaling",
449 num_authorities: 5,
450 actual: 20_002,
451 malicious: 1,
452 crash: 1,
453 validity: 2_501,
454 cert: 7_501,
455 quorum: 15_002,
456 },
457 Case {
459 name: "crash-only (f=0)",
460 num_authorities: 4,
461 actual: 10_000,
462 malicious: 0,
463 crash: 1_000,
464 validity: 1,
465 cert: 3_334,
466 quorum: 6_667,
467 },
468 Case {
471 name: "byzantine-only (c=0)",
472 num_authorities: 6,
473 actual: 10_000,
474 malicious: 1_000,
475 crash: 0,
476 validity: 2_000,
477 cert: 3_999,
478 quorum: 8_001,
479 },
480 ];
481
482 for case in cases {
483 let seed = create_committee_with_total_stake(case.num_authorities, case.actual);
484 let committee = Committee::new_v3(
485 seed.epoch(),
486 seed.authorities_slice().to_vec(),
487 case.malicious,
488 case.crash,
489 );
490 assert_eq!(committee.size(), case.num_authorities, "{}", case.name);
491 assert_eq!(committee.total_stake(), case.actual, "{}", case.name);
492 assert_eq!(
493 committee.validity_threshold(),
494 case.validity,
495 "{}",
496 case.name
497 );
498 assert_eq!(
499 committee.certification_threshold(),
500 case.cert,
501 "{}",
502 case.name
503 );
504 assert_eq!(committee.quorum_threshold(), case.quorum, "{}", case.name);
505 }
506 }
507
508 #[test]
509 fn committee_v3_no_fault_budget_single_authority() {
510 let (seed, _) = local_committee_and_keys_with_test_options(0, vec![100 as Stake], false);
512 let committee = Committee::new_v3(seed.epoch(), seed.authorities_slice().to_vec(), 0, 0);
513 assert_eq!(committee.validity_threshold(), 1);
514 assert_eq!(committee.certification_threshold(), 1);
515 assert_eq!(committee.quorum_threshold(), 100);
516 }
517
518 #[test]
519 #[should_panic(expected = "Total stake cannot be zero!")]
520 fn committee_v3_zero_actual_stake_panics() {
521 let zero_stake_authorities: Vec<Authority> = {
522 let (seed, _) =
523 local_committee_and_keys_with_test_options(0, vec![1 as Stake; 4], false);
524 seed.authorities_slice()
525 .iter()
526 .map(|a| Authority {
527 stake: 0,
528 ..a.clone()
529 })
530 .collect()
531 };
532 Committee::new_v3(0, zero_stake_authorities, 1_250, 1_250);
533 }
534}