hotshot/traits/election/
static_committee.rs

1// Copyright (c) 2021-2024 Espresso Systems (espressosys.com)
2// This file is part of the HotShot repository.
3
4// You should have received a copy of the MIT License
5// along with the HotShot repository. If not, see <https://mit-license.org/>.
6
7use std::collections::{BTreeMap, BTreeSet};
8
9use alloy::primitives::U256;
10use anyhow::Context;
11use hotshot_types::{
12    drb::DrbResult,
13    stake_table::HSStakeTable,
14    traits::{
15        election::{Membership, NoStakeTableHash},
16        node_implementation::NodeType,
17        signature_key::{SignatureKey, StakeTableEntryType},
18    },
19    PeerConfig,
20};
21use hotshot_utils::anytrace::*;
22
23use crate::{Arc, RwLock};
24
25#[derive(Clone, Debug, Eq, PartialEq, Hash)]
26/// The static committee election
27pub struct StaticCommittee<T: NodeType> {
28    /// The nodes eligible for leadership.
29    /// NOTE: This is currently a hack because the DA leader needs to be the quorum
30    /// leader but without voting rights.
31    eligible_leaders: Vec<PeerConfig<T>>,
32
33    /// The nodes on the committee and their stake
34    stake_table: HSStakeTable<T>,
35
36    /// The nodes on the committee and their stake
37    da_stake_table: HSStakeTable<T>,
38
39    /// The nodes on the committee and their stake, indexed by public key
40    indexed_stake_table: BTreeMap<T::SignatureKey, PeerConfig<T>>,
41
42    /// The nodes on the committee and their stake, indexed by public key
43    indexed_da_stake_table: BTreeMap<T::SignatureKey, PeerConfig<T>>,
44
45    /// The first epoch which will be encountered. For testing, will panic if an epoch-carrying function is called
46    /// when first_epoch is None or is Some greater than that epoch.
47    first_epoch: Option<T::Epoch>,
48
49    /// `DrbResult`s indexed by epoch
50    drb_results: BTreeMap<T::Epoch, DrbResult>,
51}
52
53impl<TYPES: NodeType> StaticCommittee<TYPES> {
54    fn check_first_epoch(&self, epoch: Option<<TYPES as NodeType>::Epoch>) {
55        if let Some(epoch) = epoch {
56            if let Some(first_epoch) = self.first_epoch {
57                assert!(
58                    first_epoch <= epoch,
59                    "Called a method in StaticCommittee where first_epoch={first_epoch:} but \
60                     epoch={epoch}"
61                );
62            } else {
63                panic!(
64                    "Called a method in StaticCommittee with non-None epoch={epoch}, but \
65                     set_first_epoch was not yet called"
66                );
67            }
68        }
69    }
70}
71
72impl<TYPES: NodeType> Membership<TYPES> for StaticCommittee<TYPES> {
73    type Error = hotshot_utils::anytrace::Error;
74    type StakeTableHash = NoStakeTableHash;
75    /// Create a new election
76    fn new(committee_members: Vec<PeerConfig<TYPES>>, da_members: Vec<PeerConfig<TYPES>>) -> Self {
77        // For each eligible leader, get the stake table entry
78        let eligible_leaders: Vec<PeerConfig<TYPES>> = committee_members
79            .clone()
80            .into_iter()
81            .filter(|member| member.stake_table_entry.stake() > U256::ZERO)
82            .collect();
83
84        // For each member, get the stake table entry
85        let members: Vec<PeerConfig<TYPES>> = committee_members
86            .into_iter()
87            .filter(|member| member.stake_table_entry.stake() > U256::ZERO)
88            .collect();
89
90        // For each member, get the stake table entry
91        let da_members: Vec<PeerConfig<TYPES>> = da_members
92            .into_iter()
93            .filter(|member| member.stake_table_entry.stake() > U256::ZERO)
94            .collect();
95
96        // Index the stake table by public key
97        let indexed_stake_table: BTreeMap<TYPES::SignatureKey, _> = members
98            .iter()
99            .map(|entry| {
100                (
101                    TYPES::SignatureKey::public_key(&entry.stake_table_entry),
102                    entry.clone(),
103                )
104            })
105            .collect();
106
107        // Index the stake table by public key
108        let indexed_da_stake_table: BTreeMap<TYPES::SignatureKey, _> = da_members
109            .iter()
110            .map(|entry| {
111                (
112                    TYPES::SignatureKey::public_key(&entry.stake_table_entry),
113                    entry.clone(),
114                )
115            })
116            .collect();
117
118        Self {
119            eligible_leaders,
120            stake_table: members.into(),
121            da_stake_table: da_members.into(),
122            indexed_stake_table,
123            indexed_da_stake_table,
124            first_epoch: None,
125            drb_results: BTreeMap::new(),
126        }
127    }
128
129    /// Get the stake table for the current view
130    fn stake_table(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> HSStakeTable<TYPES> {
131        self.check_first_epoch(epoch);
132        self.stake_table.clone()
133    }
134
135    /// Get the stake table for the current view
136    fn da_stake_table(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> HSStakeTable<TYPES> {
137        self.check_first_epoch(epoch);
138        self.da_stake_table.clone()
139    }
140
141    /// Get all members of the committee for the current view
142    fn committee_members(
143        &self,
144        _view_number: <TYPES as NodeType>::View,
145        epoch: Option<<TYPES as NodeType>::Epoch>,
146    ) -> BTreeSet<<TYPES as NodeType>::SignatureKey> {
147        self.check_first_epoch(epoch);
148        self.stake_table
149            .iter()
150            .map(|sc| TYPES::SignatureKey::public_key(&sc.stake_table_entry))
151            .collect()
152    }
153
154    /// Get all members of the committee for the current view
155    fn da_committee_members(
156        &self,
157        _view_number: <TYPES as NodeType>::View,
158        epoch: Option<<TYPES as NodeType>::Epoch>,
159    ) -> BTreeSet<<TYPES as NodeType>::SignatureKey> {
160        self.check_first_epoch(epoch);
161        self.da_stake_table
162            .iter()
163            .map(|da| TYPES::SignatureKey::public_key(&da.stake_table_entry))
164            .collect()
165    }
166
167    /// Get the stake table entry for a public key
168    fn stake(
169        &self,
170        pub_key: &<TYPES as NodeType>::SignatureKey,
171        epoch: Option<<TYPES as NodeType>::Epoch>,
172    ) -> Option<PeerConfig<TYPES>> {
173        self.check_first_epoch(epoch);
174        // Only return the stake if it is above zero
175        self.indexed_stake_table.get(pub_key).cloned()
176    }
177
178    /// Get the DA stake table entry for a public key
179    fn da_stake(
180        &self,
181        pub_key: &<TYPES as NodeType>::SignatureKey,
182        epoch: Option<<TYPES as NodeType>::Epoch>,
183    ) -> Option<PeerConfig<TYPES>> {
184        self.check_first_epoch(epoch);
185        // Only return the stake if it is above zero
186        self.indexed_da_stake_table.get(pub_key).cloned()
187    }
188
189    /// Check if a node has stake in the committee
190    fn has_stake(
191        &self,
192        pub_key: &<TYPES as NodeType>::SignatureKey,
193        epoch: Option<<TYPES as NodeType>::Epoch>,
194    ) -> bool {
195        self.check_first_epoch(epoch);
196        self.indexed_stake_table
197            .get(pub_key)
198            .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
199    }
200
201    /// Check if a node has stake in the committee
202    fn has_da_stake(
203        &self,
204        pub_key: &<TYPES as NodeType>::SignatureKey,
205        epoch: Option<<TYPES as NodeType>::Epoch>,
206    ) -> bool {
207        self.check_first_epoch(epoch);
208        self.indexed_da_stake_table
209            .get(pub_key)
210            .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
211    }
212
213    /// Index the vector of public keys with the current view number
214    fn lookup_leader(
215        &self,
216        view_number: <TYPES as NodeType>::View,
217        epoch: Option<<TYPES as NodeType>::Epoch>,
218    ) -> Result<TYPES::SignatureKey> {
219        self.check_first_epoch(epoch);
220        #[allow(clippy::cast_possible_truncation)]
221        let index = *view_number as usize % self.eligible_leaders.len();
222        let res = self.eligible_leaders[index].clone();
223        Ok(TYPES::SignatureKey::public_key(&res.stake_table_entry))
224    }
225
226    /// Get the total number of nodes in the committee
227    fn total_nodes(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> usize {
228        self.check_first_epoch(epoch);
229        self.stake_table.len()
230    }
231
232    /// Get the total number of DA nodes in the committee
233    fn da_total_nodes(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> usize {
234        self.check_first_epoch(epoch);
235        self.da_stake_table.len()
236    }
237
238    /// Get the voting success threshold for the committee
239    fn success_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
240        self.check_first_epoch(epoch);
241        ((self.total_stake(epoch) * U256::from(2)) / U256::from(3)) + U256::from(1)
242    }
243
244    /// Get the voting success threshold for the committee
245    fn da_success_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
246        self.check_first_epoch(epoch);
247        ((self.total_da_stake(epoch) * U256::from(2)) / U256::from(3)) + U256::from(1)
248    }
249
250    /// Get the voting failure threshold for the committee
251    fn failure_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
252        self.check_first_epoch(epoch);
253        (self.total_stake(epoch) / U256::from(3)) + U256::from(1)
254    }
255
256    /// Get the voting upgrade threshold for the committee
257    fn upgrade_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
258        self.check_first_epoch(epoch);
259        let len = self.total_stake(epoch);
260
261        U256::max(
262            (len * U256::from(9)) / U256::from(10),
263            ((len * U256::from(2)) / U256::from(3)) + U256::from(1),
264        )
265    }
266    fn has_stake_table(&self, _epoch: TYPES::Epoch) -> bool {
267        true
268    }
269    fn has_randomized_stake_table(&self, _epoch: TYPES::Epoch) -> anyhow::Result<bool> {
270        Ok(true)
271    }
272
273    fn add_drb_result(&mut self, epoch: <TYPES as NodeType>::Epoch, drb_result: DrbResult) {
274        self.drb_results.insert(epoch, drb_result);
275    }
276
277    fn set_first_epoch(&mut self, epoch: TYPES::Epoch, initial_drb_result: DrbResult) {
278        self.first_epoch = Some(epoch);
279
280        self.add_drb_result(epoch, initial_drb_result);
281        self.add_drb_result(epoch + 1, initial_drb_result);
282    }
283
284    fn first_epoch(&self) -> Option<TYPES::Epoch> {
285        self.first_epoch
286    }
287
288    async fn get_epoch_drb(
289        membership: Arc<RwLock<Self>>,
290        epoch: TYPES::Epoch,
291    ) -> anyhow::Result<DrbResult> {
292        let membership_reader = membership.read().await;
293
294        membership_reader
295            .drb_results
296            .get(&epoch)
297            .context("DRB result missing")
298            .copied()
299    }
300}