hotshot_example_types/membership/
randomized_committee_members.rs1use std::{
7 collections::{BTreeMap, BTreeSet},
8 marker::PhantomData,
9};
10
11use anyhow::Context;
12use hotshot_types::{
13 drb::DrbResult,
14 traits::signature_key::{
15 LCV1StateSignatureKey, LCV2StateSignatureKey, LCV3StateSignatureKey, SignatureKey,
16 StateSignatureKey,
17 },
18};
19use rand::{rngs::StdRng, Rng};
20use tracing::error;
21
22use crate::membership::{
23 helpers::QuorumFilterConfig,
24 stake_table::{TestDaCommittees, TestStakeTable, TestStakeTableEntry},
25};
26
27#[derive(Clone, Debug, Eq, PartialEq, Hash)]
28pub struct RandomizedCommitteeMembers<
29 PubKey: SignatureKey,
30 StatePubKey: StateSignatureKey + LCV1StateSignatureKey + LCV2StateSignatureKey + LCV3StateSignatureKey,
31 QuorumConfig: QuorumFilterConfig,
32 DaConfig: QuorumFilterConfig,
33> {
34 quorum_members: Vec<TestStakeTableEntry<PubKey, StatePubKey>>,
35
36 da_members: Vec<TestStakeTableEntry<PubKey, StatePubKey>>,
37
38 first_epoch: Option<u64>,
39
40 epochs: BTreeSet<u64>,
41
42 drb_results: BTreeMap<u64, DrbResult>,
43
44 da_committees: TestDaCommittees<PubKey, StatePubKey>,
45
46 _quorum_pd: PhantomData<QuorumConfig>,
47
48 _da_pd: PhantomData<DaConfig>,
49}
50
51impl<
52 PubKey: SignatureKey,
53 StatePubKey: StateSignatureKey + LCV1StateSignatureKey + LCV2StateSignatureKey + LCV3StateSignatureKey,
54 QuorumConfig: QuorumFilterConfig,
55 DaConfig: QuorumFilterConfig,
56 > RandomizedCommitteeMembers<PubKey, StatePubKey, QuorumConfig, DaConfig>
57{
58 fn make_quorum_filter(&self, epoch: u64) -> BTreeSet<usize> {
60 QuorumConfig::execute(epoch, self.quorum_members.len())
61 }
62
63 fn make_da_quorum_filter(&self, epoch: u64) -> BTreeSet<usize> {
65 DaConfig::execute(epoch, self.da_members.len())
66 }
67
68 fn debug_display_offsets(&self) {
70 static START: std::sync::Once = std::sync::Once::new();
72
73 START.call_once(|| {
74 error!(
75 "{} offsets for Quorum filter:",
76 std::any::type_name::<QuorumConfig>()
77 );
78 for epoch in 1..=10 {
79 error!(" epoch {epoch}: {:?}", self.make_quorum_filter(epoch));
80 }
81
82 error!(
83 "{} offsets for DA Quorum filter:",
84 std::any::type_name::<DaConfig>()
85 );
86 for epoch in 1..=10 {
87 error!(" epoch {epoch}: {:?}", self.make_da_quorum_filter(epoch));
88 }
89 });
90 }
91}
92
93impl<
94 PubKey: SignatureKey,
95 StatePubKey: StateSignatureKey + LCV1StateSignatureKey + LCV2StateSignatureKey + LCV3StateSignatureKey,
96 QuorumConfig: QuorumFilterConfig,
97 DaConfig: QuorumFilterConfig,
98 > TestStakeTable<PubKey, StatePubKey>
99 for RandomizedCommitteeMembers<PubKey, StatePubKey, QuorumConfig, DaConfig>
100{
101 fn new(
102 quorum_members: Vec<TestStakeTableEntry<PubKey, StatePubKey>>,
103 da_members: Vec<TestStakeTableEntry<PubKey, StatePubKey>>,
104 ) -> Self {
105 let result = Self {
106 quorum_members,
107 da_members,
108 first_epoch: None,
109 epochs: BTreeSet::new(),
110 drb_results: BTreeMap::new(),
111 da_committees: TestDaCommittees::new(),
112 _quorum_pd: PhantomData,
113 _da_pd: PhantomData,
114 };
115
116 result.debug_display_offsets();
117
118 result
119 }
120
121 fn full_stake_table(&self) -> Vec<TestStakeTableEntry<PubKey, StatePubKey>> {
122 self.quorum_members.clone()
123 }
124
125 fn stake_table(&self, epoch: Option<u64>) -> Vec<TestStakeTableEntry<PubKey, StatePubKey>> {
126 if let Some(epoch) = epoch {
127 let filter = self.make_quorum_filter(epoch);
128 self.quorum_members
129 .iter()
130 .enumerate()
131 .filter(|(idx, _)| filter.contains(idx))
132 .map(|(_, v)| v.clone())
133 .collect()
134 } else {
135 self.quorum_members.clone()
136 }
137 }
138
139 fn da_stake_table(&self, epoch: Option<u64>) -> Vec<TestStakeTableEntry<PubKey, StatePubKey>> {
140 let da_members = self
141 .da_committees
142 .get(epoch)
143 .unwrap_or(self.da_members.clone());
144 if let Some(epoch) = epoch {
145 let filter = self.make_da_quorum_filter(epoch);
146 da_members
147 .into_iter()
148 .enumerate()
149 .filter(|(idx, _)| filter.contains(idx))
150 .map(|(_, v)| v)
151 .collect()
152 } else {
153 da_members.clone()
154 }
155 }
156
157 fn lookup_leader(&self, view_number: u64, epoch: Option<u64>) -> anyhow::Result<PubKey> {
158 let stake_table = self.stake_table(epoch);
159 let mut rng: StdRng = rand::SeedableRng::seed_from_u64(view_number);
160
161 let randomized_view_number: u64 = rng.gen_range(0..=u64::MAX);
162 let index = randomized_view_number as usize % stake_table.len();
163 let leader = stake_table[index].clone();
164
165 tracing::debug!(
166 "RandomizedCommitteeMembers lookup_leader, view_number: {view_number}, epoch: \
167 {epoch:?}, leader: {leader:?}",
168 );
169
170 Ok(leader.signature_key)
171 }
172
173 fn has_stake_table(&self, epoch: u64) -> bool {
174 self.epochs.contains(&epoch)
175 }
176
177 fn has_randomized_stake_table(&self, epoch: u64) -> anyhow::Result<bool> {
178 Ok(self.drb_results.contains_key(&epoch))
179 }
180
181 fn add_epoch_root(&mut self, epoch: u64) {
182 self.epochs.insert(epoch);
183 }
184
185 fn add_drb_result(&mut self, epoch: u64, drb_result: DrbResult) {
186 self.drb_results.insert(epoch, drb_result);
187 }
188
189 fn set_first_epoch(&mut self, epoch: u64, initial_drb_result: DrbResult) {
190 self.first_epoch = Some(epoch);
191
192 self.add_epoch_root(epoch);
193 self.add_epoch_root(epoch + 1);
194
195 self.add_drb_result(epoch, initial_drb_result);
196 self.add_drb_result(epoch + 1, initial_drb_result);
197 }
198
199 fn get_epoch_drb(&self, epoch: u64) -> anyhow::Result<DrbResult> {
200 self.drb_results
201 .get(&epoch)
202 .context("DRB result missing")
203 .copied()
204 }
205
206 fn first_epoch(&self) -> Option<u64> {
207 self.first_epoch
208 }
209
210 fn add_da_committee(
211 &mut self,
212 first_epoch: u64,
213 committee: Vec<TestStakeTableEntry<PubKey, StatePubKey>>,
214 ) {
215 self.da_committees.add(first_epoch, committee);
216 }
217}