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