1use std::{
8 cmp::max,
9 collections::{BTreeMap, BTreeSet},
10};
11
12use alloy::primitives::U256;
13use anyhow::Context;
14use hotshot_types::{
15 drb::DrbResult,
16 stake_table::HSStakeTable,
17 traits::{
18 election::{Membership, NoStakeTableHash},
19 node_implementation::NodeType,
20 signature_key::{SignatureKey, StakeTableEntryType},
21 },
22 PeerConfig,
23};
24use hotshot_utils::anytrace::Result;
25
26use crate::{Arc, RwLock};
27
28type EligibleLeaders<T> = (Vec<PeerConfig<T>>, Vec<PeerConfig<T>>);
30
31type StakeTables<T> = (HSStakeTable<T>, HSStakeTable<T>);
33
34type IndexedStakeTables<T> = (
36 BTreeMap<<T as NodeType>::SignatureKey, PeerConfig<T>>,
37 BTreeMap<<T as NodeType>::SignatureKey, PeerConfig<T>>,
38);
39
40#[derive(Clone, Debug, Eq, PartialEq, Hash)]
41pub struct TwoStaticCommittees<T: NodeType> {
43 eligible_leaders: EligibleLeaders<T>,
47
48 stake_table: StakeTables<T>,
50
51 da_stake_table: StakeTables<T>,
53
54 indexed_stake_table: IndexedStakeTables<T>,
56
57 indexed_da_stake_table: IndexedStakeTables<T>,
59
60 drb_results: BTreeMap<T::Epoch, DrbResult>,
62
63 first_epoch: Option<T::Epoch>,
66}
67
68impl<TYPES: NodeType> Membership<TYPES> for TwoStaticCommittees<TYPES> {
69 type Error = hotshot_utils::anytrace::Error;
70 type StakeTableHash = NoStakeTableHash;
71 fn new(committee_members: Vec<PeerConfig<TYPES>>, da_members: Vec<PeerConfig<TYPES>>) -> Self {
73 let eligible_leaders: Vec<PeerConfig<TYPES>> = committee_members
75 .clone()
76 .into_iter()
77 .filter(|member| member.stake_table_entry.stake() > U256::ZERO)
78 .collect();
79
80 let eligible_leaders1 = eligible_leaders
81 .iter()
82 .enumerate()
83 .filter(|(idx, _)| idx % 2 == 0)
84 .map(|(_, leader)| leader.clone())
85 .collect();
86 let eligible_leaders2 = eligible_leaders
87 .iter()
88 .enumerate()
89 .filter(|(idx, _)| idx % 2 == 1)
90 .map(|(_, leader)| leader.clone())
91 .collect();
92
93 let members: Vec<PeerConfig<TYPES>> = committee_members
95 .clone()
96 .into_iter()
97 .filter(|member| member.stake_table_entry.stake() > U256::ZERO)
98 .collect();
99
100 let members1: Vec<PeerConfig<TYPES>> = members
101 .iter()
102 .enumerate()
103 .filter(|(idx, _)| idx % 2 == 0)
104 .map(|(_, leader)| leader.clone())
105 .collect();
106 let members2: Vec<PeerConfig<TYPES>> = members
107 .iter()
108 .enumerate()
109 .filter(|(idx, _)| idx % 2 == 1)
110 .map(|(_, leader)| leader.clone())
111 .collect();
112
113 let da_members: Vec<PeerConfig<TYPES>> = da_members
115 .clone()
116 .into_iter()
117 .filter(|member| member.stake_table_entry.stake() > U256::ZERO)
118 .collect();
119
120 let da_members1: Vec<PeerConfig<TYPES>> = da_members
121 .iter()
122 .enumerate()
123 .filter(|(idx, _)| idx % 2 == 0)
124 .map(|(_, leader)| leader.clone())
125 .collect();
126 let da_members2: Vec<PeerConfig<TYPES>> = da_members
127 .iter()
128 .enumerate()
129 .filter(|(idx, _)| idx % 2 == 1)
130 .map(|(_, leader)| leader.clone())
131 .collect();
132
133 let indexed_stake_table1: BTreeMap<TYPES::SignatureKey, _> = members1
135 .iter()
136 .map(|member| {
137 (
138 TYPES::SignatureKey::public_key(&member.stake_table_entry),
139 member.clone(),
140 )
141 })
142 .collect();
143
144 let indexed_stake_table2: BTreeMap<TYPES::SignatureKey, _> = members2
145 .iter()
146 .map(|member| {
147 (
148 TYPES::SignatureKey::public_key(&member.stake_table_entry),
149 member.clone(),
150 )
151 })
152 .collect();
153
154 let indexed_da_stake_table1: BTreeMap<TYPES::SignatureKey, _> = da_members1
156 .iter()
157 .map(|member| {
158 (
159 TYPES::SignatureKey::public_key(&member.stake_table_entry),
160 member.clone(),
161 )
162 })
163 .collect();
164
165 let indexed_da_stake_table2: BTreeMap<TYPES::SignatureKey, _> = da_members2
166 .iter()
167 .map(|member| {
168 (
169 TYPES::SignatureKey::public_key(&member.stake_table_entry),
170 member.clone(),
171 )
172 })
173 .collect();
174
175 Self {
176 eligible_leaders: (eligible_leaders1, eligible_leaders2),
177 stake_table: (members1.into(), members2.into()),
178 da_stake_table: (da_members1.into(), da_members2.into()),
179 indexed_stake_table: (indexed_stake_table1, indexed_stake_table2),
180 indexed_da_stake_table: (indexed_da_stake_table1, indexed_da_stake_table2),
181 first_epoch: None,
182 drb_results: BTreeMap::new(),
183 }
184 }
185
186 fn stake_table(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> HSStakeTable<TYPES> {
188 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
189 if *epoch != 0 && *epoch % 2 == 0 {
190 self.stake_table.0.clone()
191 } else {
192 self.stake_table.1.clone()
193 }
194 }
195
196 fn da_stake_table(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> HSStakeTable<TYPES> {
198 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
199 if *epoch != 0 && *epoch % 2 == 0 {
200 self.da_stake_table.0.clone()
201 } else {
202 self.da_stake_table.1.clone()
203 }
204 }
205
206 fn committee_members(
208 &self,
209 _view_number: <TYPES as NodeType>::View,
210 epoch: Option<<TYPES as NodeType>::Epoch>,
211 ) -> BTreeSet<<TYPES as NodeType>::SignatureKey> {
212 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
213 if *epoch != 0 && *epoch % 2 == 0 {
214 self.stake_table
215 .0
216 .iter()
217 .map(|sc| TYPES::SignatureKey::public_key(&sc.stake_table_entry))
218 .collect()
219 } else {
220 self.stake_table
221 .1
222 .iter()
223 .map(|sc| TYPES::SignatureKey::public_key(&sc.stake_table_entry))
224 .collect()
225 }
226 }
227
228 async fn get_epoch_drb(
229 membership: Arc<RwLock<Self>>,
230 epoch: TYPES::Epoch,
231 ) -> anyhow::Result<DrbResult> {
232 let membership_reader = membership.read().await;
233
234 membership_reader
235 .drb_results
236 .get(&epoch)
237 .context("DRB result missing")
238 .copied()
239 }
240
241 fn da_committee_members(
243 &self,
244 _view_number: <TYPES as NodeType>::View,
245 epoch: Option<<TYPES as NodeType>::Epoch>,
246 ) -> BTreeSet<<TYPES as NodeType>::SignatureKey> {
247 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
248 if *epoch != 0 && *epoch % 2 == 0 {
249 self.da_stake_table
250 .0
251 .iter()
252 .map(|da| TYPES::SignatureKey::public_key(&da.stake_table_entry))
253 .collect()
254 } else {
255 self.da_stake_table
256 .1
257 .iter()
258 .map(|da| TYPES::SignatureKey::public_key(&da.stake_table_entry))
259 .collect()
260 }
261 }
262
263 fn stake(
265 &self,
266 pub_key: &<TYPES as NodeType>::SignatureKey,
267 epoch: Option<<TYPES as NodeType>::Epoch>,
268 ) -> Option<PeerConfig<TYPES>> {
269 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
271 if *epoch != 0 && *epoch % 2 == 0 {
272 self.indexed_stake_table.0.get(pub_key).cloned()
273 } else {
274 self.indexed_stake_table.1.get(pub_key).cloned()
275 }
276 }
277
278 fn da_stake(
280 &self,
281 pub_key: &<TYPES as NodeType>::SignatureKey,
282 epoch: Option<<TYPES as NodeType>::Epoch>,
283 ) -> Option<PeerConfig<TYPES>> {
284 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
286 if *epoch != 0 && *epoch % 2 == 0 {
287 self.indexed_da_stake_table.0.get(pub_key).cloned()
288 } else {
289 self.indexed_da_stake_table.1.get(pub_key).cloned()
290 }
291 }
292
293 fn has_stake(
295 &self,
296 pub_key: &<TYPES as NodeType>::SignatureKey,
297 epoch: Option<<TYPES as NodeType>::Epoch>,
298 ) -> bool {
299 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
300 if *epoch != 0 && *epoch % 2 == 0 {
301 self.indexed_stake_table
302 .0
303 .get(pub_key)
304 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
305 } else {
306 self.indexed_stake_table
307 .1
308 .get(pub_key)
309 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
310 }
311 }
312 fn has_da_stake(
314 &self,
315 pub_key: &<TYPES as NodeType>::SignatureKey,
316 epoch: Option<<TYPES as NodeType>::Epoch>,
317 ) -> bool {
318 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
319 if *epoch != 0 && *epoch % 2 == 0 {
320 self.indexed_da_stake_table
321 .0
322 .get(pub_key)
323 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
324 } else {
325 self.indexed_da_stake_table
326 .1
327 .get(pub_key)
328 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
329 }
330 }
331
332 fn lookup_leader(
334 &self,
335 view_number: <TYPES as NodeType>::View,
336 epoch: Option<<TYPES as NodeType>::Epoch>,
337 ) -> Result<TYPES::SignatureKey> {
338 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
339 if *epoch != 0 && *epoch % 2 == 0 {
340 #[allow(clippy::cast_possible_truncation)]
341 let index = *view_number as usize % self.eligible_leaders.0.len();
342 let res = self.eligible_leaders.0[index].clone();
343 Ok(TYPES::SignatureKey::public_key(&res.stake_table_entry))
344 } else {
345 #[allow(clippy::cast_possible_truncation)]
346 let index = *view_number as usize % self.eligible_leaders.1.len();
347 let res = self.eligible_leaders.1[index].clone();
348 Ok(TYPES::SignatureKey::public_key(&res.stake_table_entry))
349 }
350 }
351
352 fn total_nodes(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> usize {
354 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
355 if *epoch != 0 && *epoch % 2 == 0 {
356 self.stake_table.0.len()
357 } else {
358 self.stake_table.1.len()
359 }
360 }
361
362 fn da_total_nodes(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> usize {
364 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
365 if *epoch != 0 && *epoch % 2 == 0 {
366 self.da_stake_table.0.len()
367 } else {
368 self.da_stake_table.1.len()
369 }
370 }
371
372 fn success_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
374 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
375 if *epoch != 0 && *epoch % 2 == 0 {
376 U256::from(((self.stake_table.0.len() as u64 * 2) / 3) + 1)
377 } else {
378 U256::from(((self.stake_table.1.len() as u64 * 2) / 3) + 1)
379 }
380 }
381
382 fn da_success_threshold(&self, epoch: Option<TYPES::Epoch>) -> U256 {
384 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
385 if *epoch != 0 && *epoch % 2 == 0 {
386 U256::from(((self.da_stake_table.0.len() as u64 * 2) / 3) + 1)
387 } else {
388 U256::from(((self.da_stake_table.1.len() as u64 * 2) / 3) + 1)
389 }
390 }
391
392 fn failure_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
394 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
395 if *epoch != 0 && *epoch % 2 == 0 {
396 U256::from(((self.stake_table.0.len() as u64) / 3) + 1)
397 } else {
398 U256::from(((self.stake_table.1.len() as u64) / 3) + 1)
399 }
400 }
401
402 fn upgrade_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
404 let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees");
405 if *epoch != 0 && *epoch % 2 == 0 {
406 U256::from(max(
407 (self.stake_table.0.len() as u64 * 9) / 10,
408 ((self.stake_table.0.len() as u64 * 2) / 3) + 1,
409 ))
410 } else {
411 U256::from(max(
412 (self.stake_table.1.len() as u64 * 9) / 10,
413 ((self.stake_table.1.len() as u64 * 2) / 3) + 1,
414 ))
415 }
416 }
417 fn has_stake_table(&self, _epoch: TYPES::Epoch) -> bool {
418 true
419 }
420 fn has_randomized_stake_table(&self, _epoch: TYPES::Epoch) -> anyhow::Result<bool> {
421 Ok(true)
422 }
423
424 fn add_drb_result(&mut self, epoch: <TYPES as NodeType>::Epoch, drb_result: DrbResult) {
425 self.drb_results.insert(epoch, drb_result);
426 }
427
428 fn set_first_epoch(&mut self, epoch: TYPES::Epoch, initial_drb_result: DrbResult) {
429 self.first_epoch = Some(epoch);
430
431 self.add_drb_result(epoch, initial_drb_result);
432 self.add_drb_result(epoch + 1, initial_drb_result);
433 }
434
435 fn first_epoch(&self) -> Option<TYPES::Epoch> {
436 self.first_epoch
437 }
438}