1use std::{
7 collections::{BTreeMap, BTreeSet},
8 marker::PhantomData,
9};
10
11use alloy::primitives::U256;
12use anyhow::Context;
13use hotshot_types::{
14 drb::DrbResult,
15 stake_table::HSStakeTable,
16 traits::{
17 election::{Membership, NoStakeTableHash},
18 node_implementation::{ConsensusTime, NodeType},
19 signature_key::{SignatureKey, StakeTableEntryType},
20 },
21 PeerConfig,
22};
23use hotshot_utils::anytrace::Result;
24use rand::{rngs::StdRng, Rng};
25use tracing::error;
26
27use crate::{traits::election::helpers::QuorumFilterConfig, Arc, RwLock};
28
29#[derive(Clone, Debug, Eq, PartialEq, Hash)]
30pub struct RandomizedCommitteeMembers<
32 T: NodeType,
33 CONFIG: QuorumFilterConfig,
34 DaConfig: QuorumFilterConfig,
35> {
36 eligible_leaders: Vec<PeerConfig<T>>,
40
41 stake_table: Vec<PeerConfig<T>>,
43
44 da_stake_table: Vec<PeerConfig<T>>,
46
47 indexed_stake_table: BTreeMap<T::SignatureKey, PeerConfig<T>>,
49
50 indexed_da_stake_table: BTreeMap<T::SignatureKey, PeerConfig<T>>,
52
53 first_epoch: Option<T::Epoch>,
56
57 drb_results: BTreeMap<T::Epoch, DrbResult>,
59
60 _pd: PhantomData<CONFIG>,
62
63 _da_pd: PhantomData<DaConfig>,
65}
66
67impl<TYPES: NodeType, CONFIG: QuorumFilterConfig, DaConfig: QuorumFilterConfig>
68 RandomizedCommitteeMembers<TYPES, CONFIG, DaConfig>
69{
70 fn make_quorum_filter(&self, epoch: <TYPES as NodeType>::Epoch) -> BTreeSet<usize> {
72 CONFIG::execute(epoch.u64(), self.stake_table.len())
73 }
74
75 fn make_da_quorum_filter(&self, epoch: <TYPES as NodeType>::Epoch) -> BTreeSet<usize> {
77 DaConfig::execute(epoch.u64(), self.da_stake_table.len())
78 }
79
80 fn debug_display_offsets(&self) {
82 static START: std::sync::Once = std::sync::Once::new();
84
85 START.call_once(|| {
86 error!(
87 "{} offsets for Quorum filter:",
88 std::any::type_name::<CONFIG>()
89 );
90 for epoch in 1..=10 {
91 error!(
92 " epoch {epoch}: {:?}",
93 self.make_quorum_filter(<TYPES as NodeType>::Epoch::new(epoch))
94 );
95 }
96
97 error!(
98 "{} offsets for DA Quorum filter:",
99 std::any::type_name::<DaConfig>()
100 );
101 for epoch in 1..=10 {
102 error!(
103 " epoch {epoch}: {:?}",
104 self.make_da_quorum_filter(<TYPES as NodeType>::Epoch::new(epoch))
105 );
106 }
107 });
108 }
109}
110
111impl<TYPES: NodeType, CONFIG: QuorumFilterConfig, DaConfig: QuorumFilterConfig> Membership<TYPES>
112 for RandomizedCommitteeMembers<TYPES, CONFIG, DaConfig>
113{
114 type Error = hotshot_utils::anytrace::Error;
115 type StakeTableHash = NoStakeTableHash;
116 fn new(committee_members: Vec<PeerConfig<TYPES>>, da_members: Vec<PeerConfig<TYPES>>) -> Self {
118 let eligible_leaders = committee_members
120 .iter()
121 .filter(|&member| member.stake_table_entry.stake() > U256::ZERO)
122 .cloned()
123 .collect();
124
125 let members: Vec<PeerConfig<TYPES>> = committee_members
127 .iter()
128 .filter(|&entry| entry.stake_table_entry.stake() > U256::ZERO)
129 .cloned()
130 .collect();
131
132 let da_members: Vec<PeerConfig<TYPES>> = da_members
134 .iter()
135 .filter(|&entry| entry.stake_table_entry.stake() > U256::ZERO)
136 .cloned()
137 .collect();
138
139 let indexed_stake_table = members
141 .iter()
142 .map(|entry| {
143 (
144 TYPES::SignatureKey::public_key(&entry.stake_table_entry),
145 entry.clone(),
146 )
147 })
148 .collect();
149
150 let indexed_da_stake_table = da_members
152 .iter()
153 .map(|entry| {
154 (
155 TYPES::SignatureKey::public_key(&entry.stake_table_entry),
156 entry.clone(),
157 )
158 })
159 .collect();
160
161 let s = Self {
162 eligible_leaders,
163 stake_table: members,
164 da_stake_table: da_members,
165 indexed_stake_table,
166 indexed_da_stake_table,
167 first_epoch: None,
168 drb_results: BTreeMap::new(),
169 _pd: PhantomData,
170 _da_pd: PhantomData,
171 };
172
173 s.debug_display_offsets();
174
175 s
176 }
177
178 fn stake_table(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> HSStakeTable<TYPES> {
180 if let Some(epoch) = epoch {
181 let filter = self.make_quorum_filter(epoch);
182 self.stake_table
184 .iter()
185 .enumerate()
186 .filter(|(idx, _)| filter.contains(idx))
187 .map(|(_, v)| v.clone())
188 .collect()
189 } else {
190 self.stake_table.clone()
191 }
192 .into()
193 }
194
195 fn da_stake_table(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> HSStakeTable<TYPES> {
197 if let Some(epoch) = epoch {
198 let filter = self.make_da_quorum_filter(epoch);
199 self.da_stake_table
201 .iter()
202 .enumerate()
203 .filter(|(idx, _)| filter.contains(idx))
204 .map(|(_, v)| v.clone())
205 .collect()
206 } else {
207 self.da_stake_table.clone()
208 }
209 .into()
210 }
211
212 fn committee_members(
214 &self,
215 _view_number: <TYPES as NodeType>::View,
216 epoch: Option<<TYPES as NodeType>::Epoch>,
217 ) -> BTreeSet<<TYPES as NodeType>::SignatureKey> {
218 if let Some(epoch) = epoch {
219 let filter = self.make_quorum_filter(epoch);
220 self.stake_table
221 .iter()
222 .enumerate()
223 .filter(|(idx, _)| filter.contains(idx))
224 .map(|(_, v)| TYPES::SignatureKey::public_key(&v.stake_table_entry))
225 .collect()
226 } else {
227 self.stake_table
228 .iter()
229 .map(|config| TYPES::SignatureKey::public_key(&config.stake_table_entry))
230 .collect()
231 }
232 }
233
234 fn da_committee_members(
236 &self,
237 _view_number: <TYPES as NodeType>::View,
238 epoch: Option<<TYPES as NodeType>::Epoch>,
239 ) -> BTreeSet<<TYPES as NodeType>::SignatureKey> {
240 if let Some(epoch) = epoch {
241 let filter = self.make_da_quorum_filter(epoch);
242 self.da_stake_table
243 .iter()
244 .enumerate()
245 .filter(|(idx, _)| filter.contains(idx))
246 .map(|(_, v)| TYPES::SignatureKey::public_key(&v.stake_table_entry))
247 .collect()
248 } else {
249 self.da_stake_table
250 .iter()
251 .map(|config| TYPES::SignatureKey::public_key(&config.stake_table_entry))
252 .collect()
253 }
254 }
255 fn stake(
257 &self,
258 pub_key: &<TYPES as NodeType>::SignatureKey,
259 epoch: Option<<TYPES as NodeType>::Epoch>,
260 ) -> Option<PeerConfig<TYPES>> {
261 if let Some(epoch) = epoch {
262 let filter = self.make_quorum_filter(epoch);
263 let actual_members: BTreeSet<_> = self
264 .stake_table
265 .iter()
266 .enumerate()
267 .filter(|(idx, _)| filter.contains(idx))
268 .map(|(_, v)| TYPES::SignatureKey::public_key(&v.stake_table_entry))
269 .collect();
270
271 if actual_members.contains(pub_key) {
272 self.indexed_stake_table.get(pub_key).cloned()
274 } else {
275 None
277 }
278 } else {
279 self.indexed_stake_table.get(pub_key).cloned()
280 }
281 }
282
283 fn da_stake(
285 &self,
286 pub_key: &<TYPES as NodeType>::SignatureKey,
287 epoch: Option<<TYPES as NodeType>::Epoch>,
288 ) -> Option<PeerConfig<TYPES>> {
289 if let Some(epoch) = epoch {
290 let filter = self.make_da_quorum_filter(epoch);
291 let actual_members: BTreeSet<_> = self
292 .da_stake_table
293 .iter()
294 .enumerate()
295 .filter(|(idx, _)| filter.contains(idx))
296 .map(|(_, v)| TYPES::SignatureKey::public_key(&v.stake_table_entry))
297 .collect();
298
299 if actual_members.contains(pub_key) {
300 self.indexed_da_stake_table.get(pub_key).cloned()
302 } else {
303 None
305 }
306 } else {
307 self.indexed_da_stake_table.get(pub_key).cloned()
308 }
309 }
310
311 fn has_stake(
313 &self,
314 pub_key: &<TYPES as NodeType>::SignatureKey,
315 epoch: Option<<TYPES as NodeType>::Epoch>,
316 ) -> bool {
317 if let Some(epoch) = epoch {
318 let filter = self.make_quorum_filter(epoch);
319 let actual_members: BTreeSet<_> = self
320 .stake_table
321 .iter()
322 .enumerate()
323 .filter(|(idx, _)| filter.contains(idx))
324 .map(|(_, v)| TYPES::SignatureKey::public_key(&v.stake_table_entry))
325 .collect();
326
327 if actual_members.contains(pub_key) {
328 self.indexed_stake_table
329 .get(pub_key)
330 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
331 } else {
332 false
334 }
335 } else {
336 self.indexed_stake_table
337 .get(pub_key)
338 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
339 }
340 }
341
342 fn has_da_stake(
344 &self,
345 pub_key: &<TYPES as NodeType>::SignatureKey,
346 epoch: Option<<TYPES as NodeType>::Epoch>,
347 ) -> bool {
348 if let Some(epoch) = epoch {
349 let filter = self.make_da_quorum_filter(epoch);
350 let actual_members: BTreeSet<_> = self
351 .da_stake_table
352 .iter()
353 .enumerate()
354 .filter(|(idx, _)| filter.contains(idx))
355 .map(|(_, v)| TYPES::SignatureKey::public_key(&v.stake_table_entry))
356 .collect();
357
358 if actual_members.contains(pub_key) {
359 self.indexed_da_stake_table
360 .get(pub_key)
361 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
362 } else {
363 false
365 }
366 } else {
367 self.indexed_da_stake_table
368 .get(pub_key)
369 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
370 }
371 }
372
373 fn lookup_leader(
375 &self,
376 view_number: <TYPES as NodeType>::View,
377 epoch: Option<<TYPES as NodeType>::Epoch>,
378 ) -> Result<TYPES::SignatureKey> {
379 if let Some(epoch) = epoch {
380 let filter = self.make_quorum_filter(epoch);
381 let leader_vec: Vec<_> = self
382 .stake_table
383 .iter()
384 .enumerate()
385 .filter(|(idx, _)| filter.contains(idx))
386 .map(|(idx, v)| (idx, v.clone()))
387 .collect();
388
389 let mut rng: StdRng = rand::SeedableRng::seed_from_u64(*view_number);
390
391 let randomized_view_number: u64 = rng.gen_range(0..=u64::MAX);
392 #[allow(clippy::cast_possible_truncation)]
393 let index = randomized_view_number as usize % leader_vec.len();
394
395 let res = leader_vec[index].clone();
396
397 tracing::debug!(
398 "RandomizedCommitteeMembers lookup_leader, view_number: {view_number}, epoch: \
399 {epoch}, leader: {}",
400 res.0
401 );
402
403 Ok(TYPES::SignatureKey::public_key(&res.1.stake_table_entry))
404 } else {
405 let mut rng: StdRng = rand::SeedableRng::seed_from_u64(*view_number);
406
407 let randomized_view_number: u64 = rng.gen_range(0..=u64::MAX);
408 #[allow(clippy::cast_possible_truncation)]
409 let index = randomized_view_number as usize % self.eligible_leaders.len();
410
411 let res = self.eligible_leaders[index].clone();
412
413 Ok(TYPES::SignatureKey::public_key(&res.stake_table_entry))
414 }
415 }
416
417 fn total_nodes(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> usize {
419 if let Some(epoch) = epoch {
420 self.make_quorum_filter(epoch).len()
421 } else {
422 self.stake_table.len()
423 }
424 }
425
426 fn da_total_nodes(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> usize {
428 if let Some(epoch) = epoch {
429 self.make_da_quorum_filter(epoch).len()
430 } else {
431 self.da_stake_table.len()
432 }
433 }
434
435 fn success_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
437 ((self.total_stake(epoch) * U256::from(2)) / U256::from(3)) + U256::from(1)
438 }
439
440 fn da_success_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
442 ((self.total_da_stake(epoch) * U256::from(2)) / U256::from(3)) + U256::from(1)
443 }
444
445 fn failure_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
447 (self.total_stake(epoch) / U256::from(3)) + U256::from(1)
448 }
449
450 fn upgrade_threshold(&self, epoch: Option<<TYPES as NodeType>::Epoch>) -> U256 {
452 let len = self.total_stake(epoch);
453
454 U256::max(
455 (len * U256::from(9)) / U256::from(10),
456 ((len * U256::from(2)) / U256::from(3)) + U256::from(1),
457 )
458 }
459 fn has_stake_table(&self, _epoch: TYPES::Epoch) -> bool {
460 true
461 }
462 fn has_randomized_stake_table(&self, _epoch: TYPES::Epoch) -> anyhow::Result<bool> {
463 Ok(true)
464 }
465
466 fn add_drb_result(&mut self, epoch: <TYPES as NodeType>::Epoch, drb_result: DrbResult) {
467 self.drb_results.insert(epoch, drb_result);
468 }
469
470 fn set_first_epoch(&mut self, epoch: TYPES::Epoch, initial_drb_result: DrbResult) {
471 self.first_epoch = Some(epoch);
472
473 self.add_drb_result(epoch, initial_drb_result);
474 self.add_drb_result(epoch + 1, initial_drb_result);
475 }
476
477 fn first_epoch(&self) -> Option<TYPES::Epoch> {
478 self.first_epoch
479 }
480
481 async fn get_epoch_drb(
482 membership: Arc<RwLock<Self>>,
483 epoch: TYPES::Epoch,
484 ) -> anyhow::Result<DrbResult> {
485 let membership_reader = membership.read().await;
486
487 membership_reader
488 .drb_results
489 .get(&epoch)
490 .context("DRB result missing")
491 .copied()
492 }
493}