Skip to main content

starfish_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/// Committee size is currently limited to 256 as AuthorityIndex is u8.
26#[derive(Clone, Debug, Serialize, Deserialize)]
27pub struct Committee {
28    /// The epoch number of this committee
29    epoch: Epoch,
30    /// Total stake in the committee.
31    total_stake: Stake,
32    /// The quorum threshold (2f+1).
33    quorum_threshold: Stake,
34    /// The validity threshold (f+1).
35    validity_threshold: Stake,
36    /// Protocol and network info of each authority. Max number limited to
37    /// u8::MAX (256)
38    authorities: Vec<Authority>,
39    /// transaction data in a block is divided into info_length equal
40    /// parts(shards) that are encoded into n shards with erasure correcting
41    /// code info_length equals n-2f for the case with uniform stakes
42    info_length: usize,
43}
44
45impl Committee {
46    /// Panics on invalid input: empty or oversized committee, or zero or
47    /// overflowing stake. The committee is computed on chain, so violations
48    /// indicate a corrupted or misconstructed configuration rather than a
49    /// recoverable condition.
50    pub fn new(epoch: Epoch, authorities: Vec<Authority>) -> Self {
51        assert!(!authorities.is_empty(), "Committee cannot be empty!");
52        assert!(
53            authorities.len() < u8::MAX as usize,
54            "Too many authorities ({})!",
55            authorities.len()
56        );
57
58        for authority in &authorities {
59            assert!(
60                authority.stake > 0,
61                "Authority {} cannot have zero stake!",
62                authority.hostname
63            );
64        }
65
66        let total_stake = authorities
67            .iter()
68            .map(|a| a.stake)
69            .try_fold(0u64, u64::checked_add)
70            .expect("Total stake must not overflow u64!");
71        // Widen to u128 for the doubling; the result is at most total_stake.
72        let quorum_threshold = (2 * total_stake as u128 / 3 + 1) as u64;
73        let validity_threshold = total_stake.div_ceil(3);
74        let committee_size = authorities.len();
75        // f and info_length are computed for uniform stakes
76        // TODO: change when we implement encoding/decoding for non-uniform stakes
77        let f = (committee_size - 1) / 3;
78        let info_length = committee_size - 2 * f;
79        Self {
80            epoch,
81            total_stake,
82            quorum_threshold,
83            validity_threshold,
84            authorities,
85            info_length,
86        }
87    }
88
89    // -----------------------------------------------------------------------
90    // Accessors to Committee fields.
91
92    pub fn epoch(&self) -> Epoch {
93        self.epoch
94    }
95
96    pub fn total_stake(&self) -> Stake {
97        self.total_stake
98    }
99
100    pub fn quorum_threshold(&self) -> Stake {
101        self.quorum_threshold
102    }
103
104    pub fn validity_threshold(&self) -> Stake {
105        self.validity_threshold
106    }
107
108    pub fn info_length(&self) -> usize {
109        self.info_length
110    }
111
112    pub fn parity_length(&self) -> usize {
113        self.size() - self.info_length()
114    }
115
116    pub fn stake(&self, authority_index: AuthorityIndex) -> Stake {
117        self.authorities[authority_index].stake
118    }
119
120    pub fn authority(&self, authority_index: AuthorityIndex) -> &Authority {
121        &self.authorities[authority_index]
122    }
123
124    pub fn authorities(&self) -> impl Iterator<Item = (AuthorityIndex, &Authority)> {
125        self.authorities
126            .iter()
127            .enumerate()
128            .map(|(i, a)| (AuthorityIndex(i as u8), a))
129    }
130
131    // -----------------------------------------------------------------------
132    // Helpers for Committee properties.
133
134    /// Returns true if the provided stake has reached quorum (2f+1).
135    pub fn reached_quorum(&self, stake: Stake) -> bool {
136        stake >= self.quorum_threshold()
137    }
138
139    /// Returns true if the provided stake has reached validity (f+1).
140    pub fn reached_validity(&self, stake: Stake) -> bool {
141        stake >= self.validity_threshold()
142    }
143
144    /// Coverts an index to an AuthorityIndex, if valid.
145    /// Returns None if index is out of bound.
146    pub fn to_authority_index(&self, index: usize) -> Option<AuthorityIndex> {
147        if index < self.authorities.len() {
148            Some(AuthorityIndex(index as u8))
149        } else {
150            None
151        }
152    }
153
154    /// Returns true if the provided index is valid.
155    pub fn is_valid_index(&self, index: AuthorityIndex) -> bool {
156        index.value() < self.size()
157    }
158
159    /// Returns number of authorities in the committee.
160    pub fn size(&self) -> usize {
161        self.authorities.len()
162    }
163}
164
165/// Represents one authority in the committee.
166///
167/// NOTE: this is intentionally un-cloneable, to encourage only copying relevant
168/// fields. AuthorityIndex should be used to reference an authority instead.
169#[derive(Clone, Debug, Serialize, Deserialize)]
170pub struct Authority {
171    /// Voting power of the authority in the committee.
172    pub stake: Stake,
173    /// Network address for communicating with the authority.
174    pub address: Multiaddr,
175    /// The authority's hostname, for metrics and logging.
176    pub hostname: String,
177    /// The public key bytes corresponding to the private key that the validator
178    /// holds to sign transactions.
179    pub authority_key: AuthorityPublicKey,
180    /// The public key bytes corresponding to the private key that the validator
181    /// holds to sign consensus blocks.
182    pub protocol_key: ProtocolPublicKey,
183    /// The public key bytes corresponding to the private key that the validator
184    /// uses to establish TLS connections.
185    pub network_key: NetworkPublicKey,
186}
187
188/// Each authority is uniquely identified by its AuthorityIndex in the
189/// Committee. AuthorityIndex is between 0 (inclusive) and the total number of
190/// authorities (exclusive) limited by `u8` to 255.
191///
192/// NOTE: for safety, invalid AuthorityIndex should be impossible to create. So
193/// AuthorityIndex should not be created or incremented outside of this file.
194/// AuthorityIndex received from peers should be validated before use.
195#[derive(
196    Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug, Default, Hash, Serialize, Deserialize,
197)]
198pub struct AuthorityIndex(u8);
199
200impl AuthorityIndex {
201    // Minimum committee size is 1, so 0 index is always valid.
202    pub const ZERO: Self = Self(0);
203
204    // Only for scanning rows in the database. Invalid elsewhere.
205    pub const MIN: Self = Self::ZERO;
206    pub const MAX: Self = Self(u8::MAX);
207
208    pub fn value(&self) -> usize {
209        self.0 as usize
210    }
211}
212
213impl From<u8> for AuthorityIndex {
214    fn from(value: u8) -> Self {
215        Self(value)
216    }
217}
218
219impl AuthorityIndex {
220    pub fn new_for_test(index: u8) -> Self {
221        Self(index)
222    }
223}
224
225impl Display for AuthorityIndex {
226    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
227        write!(f, "[{}]", self.value())
228    }
229}
230
231impl<T, const N: usize> Index<AuthorityIndex> for [T; N] {
232    type Output = T;
233
234    fn index(&self, index: AuthorityIndex) -> &Self::Output {
235        self.get(index.value()).unwrap()
236    }
237}
238
239impl<T> Index<AuthorityIndex> for Vec<T> {
240    type Output = T;
241
242    fn index(&self, index: AuthorityIndex) -> &Self::Output {
243        self.get(index.value()).unwrap()
244    }
245}
246
247impl<T, const N: usize> IndexMut<AuthorityIndex> for [T; N] {
248    fn index_mut(&mut self, index: AuthorityIndex) -> &mut Self::Output {
249        self.get_mut(index.value()).unwrap()
250    }
251}
252
253impl<T> IndexMut<AuthorityIndex> for Vec<T> {
254    fn index_mut(&mut self, index: AuthorityIndex) -> &mut Self::Output {
255        self.get_mut(index.value()).unwrap()
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262    use crate::local_committee_and_keys;
263
264    #[test]
265    fn committee_basic() {
266        // GIVEN
267        let epoch = 100;
268        let num_of_authorities = 9;
269        let authority_stakes = (1..=9).map(|s| s as Stake).collect();
270        let (committee, _) = local_committee_and_keys(epoch, authority_stakes);
271
272        // THEN make sure the output Committee fields are populated correctly.
273        assert_eq!(committee.size(), num_of_authorities);
274        for (i, authority) in committee.authorities() {
275            assert_eq!((i.value() + 1) as Stake, authority.stake);
276        }
277
278        // AND ensure thresholds are calculated correctly.
279        assert_eq!(committee.total_stake(), 45);
280        assert_eq!(committee.quorum_threshold(), 31);
281        assert_eq!(committee.validity_threshold(), 15);
282    }
283}