hotshot/traits/election/
static_committee.rs1use 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)]
23pub struct StaticCommittee<T: NodeType> {
25 eligible_leaders: Vec<PeerConfig<T>>,
29
30 stake_table: HSStakeTable<T>,
32
33 da_stake_table: HSStakeTable<T>,
35
36 indexed_stake_table: BTreeMap<T::SignatureKey, PeerConfig<T>>,
38
39 indexed_da_stake_table: BTreeMap<T::SignatureKey, PeerConfig<T>>,
41
42 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 fn new(committee_members: Vec<PeerConfig<TYPES>>, da_members: Vec<PeerConfig<TYPES>>) -> Self {
63 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 let members: Vec<PeerConfig<TYPES>> = committee_members
72 .into_iter()
73 .filter(|member| member.stake_table_entry.stake() > U256::ZERO)
74 .collect();
75
76 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 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 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 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 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 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 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 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 self.indexed_stake_table.get(pub_key).cloned()
161 }
162
163 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 self.indexed_da_stake_table.get(pub_key).cloned()
172 }
173
174 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 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 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 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 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 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 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 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 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}