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