consensus_config/
committee.rs

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