consensus_config/
committee.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{
5    fmt::{Display, Formatter},
6    ops::{Index, IndexMut},
7};
8
9use mysten_network::Multiaddr;
10use serde::{Deserialize, Serialize};
11
12use crate::{AuthorityPublicKey, NetworkPublicKey, ProtocolPublicKey};
13
14/// Committee of the consensus protocol is updated each epoch.
15pub type Epoch = u64;
16
17/// Voting power of an authority, roughly proportional to the actual amount of Sui staked
18/// by the authority.
19/// Total stake / voting power of all authorities should sum to 10,000.
20pub type Stake = u64;
21
22/// Committee is the set of authorities that participate in the consensus protocol for this epoch.
23/// Its configuration is stored and computed on chain.
24#[derive(Clone, Debug, Serialize, Deserialize)]
25pub struct Committee {
26    /// The epoch number of this committee
27    epoch: Epoch,
28    /// Total stake in the committee.
29    total_stake: Stake,
30    /// The quorum threshold (2f+1).
31    quorum_threshold: Stake,
32    /// The validity threshold (f+1).
33    validity_threshold: Stake,
34    /// Protocol and network info of each authority.
35    authorities: Vec<Authority>,
36}
37
38impl Committee {
39    pub fn new(epoch: Epoch, authorities: Vec<Authority>) -> Self {
40        assert!(!authorities.is_empty(), "Committee cannot be empty!");
41        assert!(
42            authorities.len() < u32::MAX as usize,
43            "Too many authorities ({})!",
44            authorities.len()
45        );
46
47        let total_stake: Stake = authorities.iter().map(|a| a.stake).sum();
48        assert_ne!(total_stake, 0, "Total stake cannot be zero!");
49
50        // Tolerate integer f faults when total stake is 3f+1.
51        let fault_tolerance = (total_stake - 1) / 3;
52        let quorum_threshold = total_stake - fault_tolerance;
53        let validity_threshold = fault_tolerance + 1;
54        assert!(
55            2 * quorum_threshold - fault_tolerance > total_stake,
56            "Quorum must intersect under maxim equivocations! Quorum: {quorum_threshold}, Fault tolerance: {fault_tolerance}, Total: {total_stake}"
57        );
58
59        Self {
60            epoch,
61            total_stake,
62            quorum_threshold,
63            validity_threshold,
64            authorities,
65        }
66    }
67
68    // -----------------------------------------------------------------------
69    // Accessors to Committee fields.
70
71    pub fn epoch(&self) -> Epoch {
72        self.epoch
73    }
74
75    pub fn total_stake(&self) -> Stake {
76        self.total_stake
77    }
78
79    pub fn quorum_threshold(&self) -> Stake {
80        self.quorum_threshold
81    }
82
83    pub fn validity_threshold(&self) -> Stake {
84        self.validity_threshold
85    }
86
87    pub fn stake(&self, authority_index: AuthorityIndex) -> Stake {
88        self.authorities[authority_index].stake
89    }
90
91    pub fn authority(&self, authority_index: AuthorityIndex) -> &Authority {
92        &self.authorities[authority_index]
93    }
94
95    pub fn authorities(&self) -> impl Iterator<Item = (AuthorityIndex, &Authority)> {
96        self.authorities
97            .iter()
98            .enumerate()
99            .map(|(i, a)| (AuthorityIndex(i as u32), a))
100    }
101
102    // -----------------------------------------------------------------------
103    // Helpers for Committee properties.
104
105    /// Returns true if the provided stake has reached quorum (2f+1).
106    pub fn reached_quorum(&self, stake: Stake) -> bool {
107        stake >= self.quorum_threshold()
108    }
109
110    /// Returns true if the provided stake has reached validity (f+1).
111    pub fn reached_validity(&self, stake: Stake) -> bool {
112        stake >= self.validity_threshold()
113    }
114
115    /// Converts an index to an AuthorityIndex, if valid.
116    /// Returns None if index is out of bound.
117    pub fn to_authority_index(&self, index: usize) -> Option<AuthorityIndex> {
118        if index < self.authorities.len() {
119            Some(AuthorityIndex(index as u32))
120        } else {
121            None
122        }
123    }
124
125    /// Returns true if the provided index is valid.
126    pub fn is_valid_index(&self, index: AuthorityIndex) -> bool {
127        index.value() < self.size()
128    }
129
130    /// Returns number of authorities in the committee.
131    pub fn size(&self) -> usize {
132        self.authorities.len()
133    }
134}
135
136/// Represents one authority in the committee.
137///
138/// NOTE: this is intentionally un-cloneable, to encourage only copying relevant fields.
139/// AuthorityIndex should be used to reference an authority instead.
140#[derive(Clone, Debug, Serialize, Deserialize)]
141pub struct Authority {
142    /// Voting power of the authority in the committee.
143    pub stake: Stake,
144    /// Network address for communicating with the authority.
145    pub address: Multiaddr,
146    /// The authority's hostname, for metrics and logging.
147    pub hostname: String,
148    /// The authority's public key as Sui identity.
149    pub authority_key: AuthorityPublicKey,
150    /// The authority's public key for verifying blocks.
151    pub protocol_key: ProtocolPublicKey,
152    /// The authority's public key for TLS and as network identity.
153    pub network_key: NetworkPublicKey,
154}
155
156/// Each authority is uniquely identified by its AuthorityIndex in the Committee.
157/// AuthorityIndex is between 0 (inclusive) and the total number of authorities (exclusive).
158///
159/// NOTE: for safety, invalid AuthorityIndex should be impossible to create. So AuthorityIndex
160/// should not be created or incremented outside of this file. AuthorityIndex received from peers
161/// should be validated before use.
162#[derive(
163    Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug, Default, Hash, Serialize, Deserialize,
164)]
165pub struct AuthorityIndex(u32);
166
167impl AuthorityIndex {
168    // Minimum committee size is 1, so 0 index is always valid.
169    pub const ZERO: Self = Self(0);
170
171    // Only for scanning rows in the database. Invalid elsewhere.
172    pub const MIN: Self = Self::ZERO;
173    pub const MAX: Self = Self(u32::MAX);
174
175    pub fn value(&self) -> usize {
176        self.0 as usize
177    }
178}
179
180impl AuthorityIndex {
181    pub fn new_for_test(index: u32) -> Self {
182        Self(index)
183    }
184}
185
186impl Display for AuthorityIndex {
187    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
188        write!(f, "[{}]", self.value())
189    }
190}
191
192impl<T, const N: usize> Index<AuthorityIndex> for [T; N] {
193    type Output = T;
194
195    fn index(&self, index: AuthorityIndex) -> &Self::Output {
196        self.get(index.value()).unwrap()
197    }
198}
199
200impl<T> Index<AuthorityIndex> for Vec<T> {
201    type Output = T;
202
203    fn index(&self, index: AuthorityIndex) -> &Self::Output {
204        self.get(index.value()).unwrap()
205    }
206}
207
208impl<T, const N: usize> IndexMut<AuthorityIndex> for [T; N] {
209    fn index_mut(&mut self, index: AuthorityIndex) -> &mut Self::Output {
210        self.get_mut(index.value()).unwrap()
211    }
212}
213
214impl<T> IndexMut<AuthorityIndex> for Vec<T> {
215    fn index_mut(&mut self, index: AuthorityIndex) -> &mut Self::Output {
216        self.get_mut(index.value()).unwrap()
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223    use crate::local_committee_and_keys;
224
225    #[test]
226    fn committee_basic() {
227        // GIVEN
228        let epoch = 100;
229        let num_of_authorities = 10;
230        let authority_stakes = (1..=num_of_authorities).map(|s| s as Stake).collect();
231        let (committee, _) = local_committee_and_keys(epoch, authority_stakes);
232
233        // THEN make sure the output Committee fields are populated correctly.
234        assert_eq!(committee.size(), num_of_authorities);
235        for (i, authority) in committee.authorities() {
236            assert_eq!((i.value() + 1) as Stake, authority.stake);
237        }
238
239        // AND ensure thresholds are calculated correctly.
240        assert_eq!(committee.total_stake(), 55);
241        assert_eq!(committee.quorum_threshold(), 37);
242        assert_eq!(committee.validity_threshold(), 19);
243    }
244
245    #[test]
246    fn committee_different_sizes() {
247        let epoch = 100;
248
249        {
250            let num_of_authorities = 11;
251            let authority_stakes = (1..=num_of_authorities).map(|_| 1 as Stake).collect();
252            let (committee, _) = local_committee_and_keys(epoch, authority_stakes);
253
254            // AND ensure thresholds are calculated correctly.
255            assert_eq!(committee.total_stake(), 11);
256            assert_eq!(committee.quorum_threshold(), 8);
257            assert_eq!(committee.validity_threshold(), 4);
258        }
259
260        {
261            let num_of_authorities = 12;
262            let authority_stakes = (1..=num_of_authorities).map(|_| 10 as Stake).collect();
263            let (committee, _) = local_committee_and_keys(epoch, authority_stakes);
264
265            // AND ensure thresholds are calculated correctly.
266            assert_eq!(committee.total_stake(), 120);
267            assert_eq!(committee.quorum_threshold(), 81);
268            assert_eq!(committee.validity_threshold(), 40);
269        }
270    }
271}