1use std::{
7 collections::{BTreeMap, BTreeSet},
8 marker::PhantomData,
9};
10
11use alloy::primitives::U256;
12use hotshot_types::{
13 drb::DrbResult,
14 stake_table::HSStakeTable,
15 traits::{
16 election::Membership,
17 node_implementation::{ConsensusTime, NodeType},
18 signature_key::{SignatureKey, StakeTableEntryType},
19 },
20 PeerConfig,
21};
22use hotshot_utils::anytrace::Result;
23use rand::{rngs::StdRng, Rng};
24use tracing::error;
25
26use crate::traits::election::helpers::QuorumFilterConfig;
27
28#[derive(Clone, Debug, Eq, PartialEq, Hash)]
29pub struct RandomizedCommitteeMembers<
31 T: NodeType,
32 CONFIG: QuorumFilterConfig,
33 DaConfig: QuorumFilterConfig,
34> {
35 eligible_leaders: Vec<PeerConfig<T>>,
39
40 stake_table: Vec<PeerConfig<T>>,
42
43 da_stake_table: Vec<PeerConfig<T>>,
45
46 indexed_stake_table: BTreeMap<T::SignatureKey, PeerConfig<T>>,
48
49 indexed_da_stake_table: BTreeMap<T::SignatureKey, PeerConfig<T>>,
51
52 first_epoch: Option<T::Epoch>,
55
56 _pd: PhantomData<CONFIG>,
58
59 _da_pd: PhantomData<DaConfig>,
61}
62
63impl<TYPES: NodeType, CONFIG: QuorumFilterConfig, DaConfig: QuorumFilterConfig>
64 RandomizedCommitteeMembers<TYPES, CONFIG, DaConfig>
65{
66 fn make_quorum_filter(&self, epoch: <TYPES as NodeType>::Epoch) -> BTreeSet<usize> {
68 CONFIG::execute(epoch.u64(), self.stake_table.len())
69 }
70
71 fn make_da_quorum_filter(&self, epoch: <TYPES as NodeType>::Epoch) -> BTreeSet<usize> {
73 DaConfig::execute(epoch.u64(), self.da_stake_table.len())
74 }
75
76 fn debug_display_offsets(&self) {
78 static START: std::sync::Once = std::sync::Once::new();
80
81 START.call_once(|| {
82 error!(
83 "{} offsets for Quorum filter:",
84 std::any::type_name::<CONFIG>()
85 );
86 for epoch in 1..=10 {
87 error!(
88 " epoch {epoch}: {:?}",
89 self.make_quorum_filter(<TYPES as NodeType>::Epoch::new(epoch))
90 );
91 }
92
93 error!(
94 "{} offsets for DA Quorum filter:",
95 std::any::type_name::<DaConfig>()
96 );
97 for epoch in 1..=10 {
98 error!(
99 " epoch {epoch}: {:?}",
100 self.make_da_quorum_filter(<TYPES as NodeType>::Epoch::new(epoch))
101 );
102 }
103 });
104 }
105}
106
107impl<TYPES: NodeType, CONFIG: QuorumFilterConfig, DaConfig: QuorumFilterConfig> Membership<TYPES>
108 for RandomizedCommitteeMembers<TYPES, CONFIG, DaConfig>
109{
110 type Error = hotshot_utils::anytrace::Error;
111 fn new(committee_members: Vec<PeerConfig<TYPES>>, da_members: Vec<PeerConfig<TYPES>>) -> Self {
113 let eligible_leaders = committee_members
115 .iter()
116 .filter(|&member| member.stake_table_entry.stake() > U256::ZERO)
117 .cloned()
118 .collect();
119
120 let members: Vec<PeerConfig<TYPES>> = committee_members
122 .iter()
123 .filter(|&entry| entry.stake_table_entry.stake() > U256::ZERO)
124 .cloned()
125 .collect();
126
127 let da_members: Vec<PeerConfig<TYPES>> = da_members
129 .iter()
130 .filter(|&entry| entry.stake_table_entry.stake() > U256::ZERO)
131 .cloned()
132 .collect();
133
134 let indexed_stake_table = members
136 .iter()
137 .map(|entry| {
138 (
139 TYPES::SignatureKey::public_key(&entry.stake_table_entry),
140 entry.clone(),
141 )
142 })
143 .collect();
144
145 let indexed_da_stake_table = da_members
147 .iter()
148 .map(|entry| {
149 (
150 TYPES::SignatureKey::public_key(&entry.stake_table_entry),
151 entry.clone(),
152 )
153 })
154 .collect();
155
156 let s = Self {
157 eligible_leaders,
158 stake_table: members,
159 da_stake_table: da_members,
160 indexed_stake_table,
161 indexed_da_stake_table,
162 first_epoch: None,
163 _pd: PhantomData,
164 _da_pd: PhantomData,
165 };
166
167 s.debug_display_offsets();
168
169 s
170 }
171
172 fn stake_table(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> HSStakeTable<TYPES> {
174 if let Some(epoch) = epoch {
175 let filter = self.make_quorum_filter(epoch);
176 self.stake_table
178 .iter()
179 .enumerate()
180 .filter(|(idx, _)| filter.contains(idx))
181 .map(|(_, v)| v.clone())
182 .collect()
183 } else {
184 self.stake_table.clone()
185 }
186 .into()
187 }
188
189 fn da_stake_table(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> HSStakeTable<TYPES> {
191 if let Some(epoch) = epoch {
192 let filter = self.make_da_quorum_filter(epoch);
193 self.da_stake_table
195 .iter()
196 .enumerate()
197 .filter(|(idx, _)| filter.contains(idx))
198 .map(|(_, v)| v.clone())
199 .collect()
200 } else {
201 self.da_stake_table.clone()
202 }
203 .into()
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 if let Some(epoch) = epoch {
213 let filter = self.make_quorum_filter(epoch);
214 self.stake_table
215 .iter()
216 .enumerate()
217 .filter(|(idx, _)| filter.contains(idx))
218 .map(|(_, v)| TYPES::SignatureKey::public_key(&v.stake_table_entry))
219 .collect()
220 } else {
221 self.stake_table
222 .iter()
223 .map(|config| TYPES::SignatureKey::public_key(&config.stake_table_entry))
224 .collect()
225 }
226 }
227
228 fn da_committee_members(
230 &self,
231 _view_number: <TYPES as NodeType>::View,
232 epoch: Option<<TYPES as NodeType>::Epoch>,
233 ) -> BTreeSet<<TYPES as NodeType>::SignatureKey> {
234 if let Some(epoch) = epoch {
235 let filter = self.make_da_quorum_filter(epoch);
236 self.da_stake_table
237 .iter()
238 .enumerate()
239 .filter(|(idx, _)| filter.contains(idx))
240 .map(|(_, v)| TYPES::SignatureKey::public_key(&v.stake_table_entry))
241 .collect()
242 } else {
243 self.da_stake_table
244 .iter()
245 .map(|config| TYPES::SignatureKey::public_key(&config.stake_table_entry))
246 .collect()
247 }
248 }
249 fn stake(
251 &self,
252 pub_key: &<TYPES as NodeType>::SignatureKey,
253 epoch: Option<<TYPES as NodeType>::Epoch>,
254 ) -> Option<PeerConfig<TYPES>> {
255 if let Some(epoch) = epoch {
256 let filter = self.make_quorum_filter(epoch);
257 let actual_members: BTreeSet<_> = self
258 .stake_table
259 .iter()
260 .enumerate()
261 .filter(|(idx, _)| filter.contains(idx))
262 .map(|(_, v)| TYPES::SignatureKey::public_key(&v.stake_table_entry))
263 .collect();
264
265 if actual_members.contains(pub_key) {
266 self.indexed_stake_table.get(pub_key).cloned()
268 } else {
269 None
271 }
272 } else {
273 self.indexed_stake_table.get(pub_key).cloned()
274 }
275 }
276
277 fn da_stake(
279 &self,
280 pub_key: &<TYPES as NodeType>::SignatureKey,
281 epoch: Option<<TYPES as NodeType>::Epoch>,
282 ) -> Option<PeerConfig<TYPES>> {
283 if let Some(epoch) = epoch {
284 let filter = self.make_da_quorum_filter(epoch);
285 let actual_members: BTreeSet<_> = self
286 .da_stake_table
287 .iter()
288 .enumerate()
289 .filter(|(idx, _)| filter.contains(idx))
290 .map(|(_, v)| TYPES::SignatureKey::public_key(&v.stake_table_entry))
291 .collect();
292
293 if actual_members.contains(pub_key) {
294 self.indexed_da_stake_table.get(pub_key).cloned()
296 } else {
297 None
299 }
300 } else {
301 self.indexed_da_stake_table.get(pub_key).cloned()
302 }
303 }
304
305 fn has_stake(
307 &self,
308 pub_key: &<TYPES as NodeType>::SignatureKey,
309 epoch: Option<<TYPES as NodeType>::Epoch>,
310 ) -> bool {
311 if let Some(epoch) = epoch {
312 let filter = self.make_quorum_filter(epoch);
313 let actual_members: BTreeSet<_> = self
314 .stake_table
315 .iter()
316 .enumerate()
317 .filter(|(idx, _)| filter.contains(idx))
318 .map(|(_, v)| TYPES::SignatureKey::public_key(&v.stake_table_entry))
319 .collect();
320
321 if actual_members.contains(pub_key) {
322 self.indexed_stake_table
323 .get(pub_key)
324 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
325 } else {
326 false
328 }
329 } else {
330 self.indexed_stake_table
331 .get(pub_key)
332 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
333 }
334 }
335
336 fn has_da_stake(
338 &self,
339 pub_key: &<TYPES as NodeType>::SignatureKey,
340 epoch: Option<<TYPES as NodeType>::Epoch>,
341 ) -> bool {
342 if let Some(epoch) = epoch {
343 let filter = self.make_da_quorum_filter(epoch);
344 let actual_members: BTreeSet<_> = self
345 .da_stake_table
346 .iter()
347 .enumerate()
348 .filter(|(idx, _)| filter.contains(idx))
349 .map(|(_, v)| TYPES::SignatureKey::public_key(&v.stake_table_entry))
350 .collect();
351
352 if actual_members.contains(pub_key) {
353 self.indexed_da_stake_table
354 .get(pub_key)
355 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
356 } else {
357 false
359 }
360 } else {
361 self.indexed_da_stake_table
362 .get(pub_key)
363 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
364 }
365 }
366
367 fn lookup_leader(
369 &self,
370 view_number: <TYPES as NodeType>::View,
371 epoch: Option<<TYPES as NodeType>::Epoch>,
372 ) -> Result<TYPES::SignatureKey> {
373 if let Some(epoch) = epoch {
374 let filter = self.make_quorum_filter(epoch);
375 let leader_vec: Vec<_> = self
376 .stake_table
377 .iter()
378 .enumerate()
379 .filter(|(idx, _)| filter.contains(idx))
380 .map(|(idx, v)| (idx, v.clone()))
381 .collect();
382
383 let mut rng: StdRng = rand::SeedableRng::seed_from_u64(*view_number);
384
385 let randomized_view_number: u64 = rng.gen_range(0..=u64::MAX);
386 #[allow(clippy::cast_possible_truncation)]
387 let index = randomized_view_number as usize % leader_vec.len();
388
389 let res = leader_vec[index].clone();
390
391 tracing::debug!("RandomizedCommitteeMembers lookup_leader, view_number: {view_number}, epoch: {epoch}, leader: {}", res.0);
392
393 Ok(TYPES::SignatureKey::public_key(&res.1.stake_table_entry))
394 } else {
395 let mut rng: StdRng = rand::SeedableRng::seed_from_u64(*view_number);
396
397 let randomized_view_number: u64 = rng.gen_range(0..=u64::MAX);
398 #[allow(clippy::cast_possible_truncation)]
399 let index = randomized_view_number as usize % self.eligible_leaders.len();
400
401 let res = self.eligible_leaders[index].clone();
402
403 Ok(TYPES::SignatureKey::public_key(&res.stake_table_entry))
404 }
405 }
406
407 fn total_nodes(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> usize {
409 if let Some(epoch) = epoch {
410 self.make_quorum_filter(epoch).len()
411 } else {
412 self.stake_table.len()
413 }
414 }
415
416 fn da_total_nodes(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> usize {
418 if let Some(epoch) = epoch {
419 self.make_da_quorum_filter(epoch).len()
420 } else {
421 self.da_stake_table.len()
422 }
423 }
424
425 fn success_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
427 ((self.total_stake(epoch) * U256::from(2)) / U256::from(3)) + U256::from(1)
428 }
429
430 fn da_success_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
432 ((self.total_da_stake(epoch) * U256::from(2)) / U256::from(3)) + U256::from(1)
433 }
434
435 fn failure_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
437 (self.total_stake(epoch) / U256::from(3)) + U256::from(1)
438 }
439
440 fn upgrade_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
442 let len = self.total_stake(epoch);
443
444 U256::max(
445 (len * U256::from(9)) / U256::from(10),
446 ((len * U256::from(2)) / U256::from(3)) + U256::from(1),
447 )
448 }
449 fn has_stake_table(&self, _epoch: TYPES::Epoch) -> bool {
450 true
451 }
452 fn has_randomized_stake_table(&self, _epoch: TYPES::Epoch) -> anyhow::Result<bool> {
453 Ok(true)
454 }
455
456 fn add_drb_result(&mut self, _epoch: <TYPES as NodeType>::Epoch, _drb_result: DrbResult) {}
457
458 fn set_first_epoch(&mut self, epoch: TYPES::Epoch, _initial_drb_result: DrbResult) {
459 self.first_epoch = Some(epoch);
460 }
461
462 fn first_epoch(&self) -> Option<TYPES::Epoch> {
463 self.first_epoch
464 }
465}