hotshot/traits/election/
static_committee.rs1use 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)]
26pub struct StaticCommittee<T: NodeType> {
28 eligible_leaders: Vec<PeerConfig<T>>,
32
33 stake_table: HSStakeTable<T>,
35
36 da_stake_table: HSStakeTable<T>,
38
39 indexed_stake_table: BTreeMap<T::SignatureKey, PeerConfig<T>>,
41
42 indexed_da_stake_table: BTreeMap<T::SignatureKey, PeerConfig<T>>,
44
45 first_epoch: Option<T::Epoch>,
48
49 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 fn new(committee_members: Vec<PeerConfig<TYPES>>, da_members: Vec<PeerConfig<TYPES>>) -> Self {
77 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 let members: Vec<PeerConfig<TYPES>> = committee_members
86 .into_iter()
87 .filter(|member| member.stake_table_entry.stake() > U256::ZERO)
88 .collect();
89
90 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 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 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 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 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 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 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 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 self.indexed_stake_table.get(pub_key).cloned()
176 }
177
178 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 self.indexed_da_stake_table.get(pub_key).cloned()
187 }
188
189 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 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 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 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 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 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 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 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 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}