1use std::{
2 collections::HashSet, marker::PhantomData, result::Result::Ok, sync::Arc, time::Duration,
3};
4
5use alloy::primitives::U256;
6use async_lock::RwLock;
7use hotshot_types::{
8 data::Leaf2,
9 drb::{compute_drb_result, DrbInput, DrbResult},
10 stake_table::HSStakeTable,
11 traits::{
12 election::{Membership, NoStakeTableHash},
13 node_implementation::{NodeType, Versions},
14 signature_key::SignatureKey,
15 storage::{null_load_drb_progress_fn, null_store_drb_progress_fn},
16 },
17 PeerConfig,
18};
19
20#[derive(Clone, Debug, Eq, PartialEq)]
21pub struct DummyCatchupCommittee<TYPES: NodeType, V: Versions, InnerTypes: NodeType> {
22 inner: InnerTypes::Membership,
23 epochs: HashSet<TYPES::Epoch>,
24 drbs: HashSet<TYPES::Epoch>,
25 _phantom: PhantomData<V>,
26}
27
28impl<TYPES: NodeType, V: Versions, InnerTypes: NodeType>
29 DummyCatchupCommittee<TYPES, V, InnerTypes>
30{
31 fn assert_has_stake_table(&self, epoch: Option<TYPES::Epoch>) {
32 let Some(epoch) = epoch else {
33 return;
34 };
35 assert!(
36 self.epochs.contains(&epoch),
37 "Failed stake table check for epoch {epoch}"
38 );
39 }
40 fn assert_has_randomized_stake_table(&self, epoch: Option<TYPES::Epoch>) {
41 let Some(epoch) = epoch else {
42 return;
43 };
44 assert!(
45 self.drbs.contains(&epoch),
46 "Failed drb check for epoch {epoch}"
47 );
48 }
49
50 fn convert_peer_config<FromTypes, IntoTypes>(
51 peer_config: PeerConfig<FromTypes>,
52 ) -> PeerConfig<IntoTypes>
53 where
54 FromTypes: NodeType,
55 IntoTypes: NodeType,
56 <IntoTypes::SignatureKey as SignatureKey>::StakeTableEntry:
57 From<<FromTypes::SignatureKey as SignatureKey>::StakeTableEntry>,
58 IntoTypes::StateSignatureKey: From<FromTypes::StateSignatureKey>,
59 {
60 PeerConfig {
61 stake_table_entry: peer_config.stake_table_entry.into(),
62 state_ver_key: Into::<IntoTypes::StateSignatureKey>::into(peer_config.state_ver_key),
63 }
64 }
65}
66
67impl<TYPES: NodeType, V: Versions, InnerTypes: NodeType> Membership<TYPES>
68 for DummyCatchupCommittee<TYPES, V, InnerTypes>
69where
70 TYPES::BlockHeader: Default,
71 TYPES::InstanceState: Default,
72 InnerTypes::Epoch: From<TYPES::Epoch>,
73 TYPES::Epoch: From<InnerTypes::Epoch>,
74 InnerTypes::View: From<TYPES::View>,
75 TYPES::SignatureKey: From<InnerTypes::SignatureKey>,
76 for<'a> &'a InnerTypes::SignatureKey: From<&'a TYPES::SignatureKey>,
77 <InnerTypes::SignatureKey as SignatureKey>::StakeTableEntry:
78 From<<TYPES::SignatureKey as SignatureKey>::StakeTableEntry>,
79 InnerTypes::StateSignatureKey: From<TYPES::StateSignatureKey>,
80 <TYPES::SignatureKey as SignatureKey>::StakeTableEntry:
81 From<<InnerTypes::SignatureKey as SignatureKey>::StakeTableEntry>,
82 TYPES::StateSignatureKey: From<InnerTypes::StateSignatureKey>,
83{
84 type Error = <InnerTypes::Membership as Membership<InnerTypes>>::Error;
85
86 type StakeTableHash = NoStakeTableHash;
87
88 fn new(
89 stake_committee_members: Vec<hotshot_types::PeerConfig<TYPES>>,
92 da_committee_members: Vec<hotshot_types::PeerConfig<TYPES>>,
93 ) -> Self {
94 Self {
95 inner: Membership::new(
96 stake_committee_members
97 .into_iter()
98 .map(Self::convert_peer_config)
99 .collect(),
100 da_committee_members
101 .into_iter()
102 .map(Self::convert_peer_config)
103 .collect(),
104 ),
105 epochs: HashSet::new(),
106 drbs: HashSet::new(),
107 _phantom: PhantomData,
108 }
109 }
110
111 fn stake_table(&self, epoch: Option<TYPES::Epoch>) -> HSStakeTable<TYPES> {
112 self.assert_has_stake_table(epoch);
113 let peer_configs = self.inner.stake_table(epoch.map(Into::into)).0;
114 HSStakeTable(
115 peer_configs
116 .into_iter()
117 .map(Self::convert_peer_config)
118 .collect(),
119 )
120 }
121
122 fn da_stake_table(&self, epoch: Option<TYPES::Epoch>) -> HSStakeTable<TYPES> {
123 self.assert_has_stake_table(epoch);
124 let peer_configs = self.inner.da_stake_table(epoch.map(Into::into)).0;
125 HSStakeTable(
126 peer_configs
127 .into_iter()
128 .map(Self::convert_peer_config)
129 .collect(),
130 )
131 }
132
133 fn committee_members(
134 &self,
135 view_number: TYPES::View,
136 epoch: Option<TYPES::Epoch>,
137 ) -> std::collections::BTreeSet<TYPES::SignatureKey> {
138 self.assert_has_stake_table(epoch);
139 self.inner
140 .committee_members(view_number.into(), epoch.map(Into::into))
141 .into_iter()
142 .map(Into::<TYPES::SignatureKey>::into)
143 .collect()
144 }
145
146 fn da_committee_members(
147 &self,
148 view_number: TYPES::View,
149 epoch: Option<TYPES::Epoch>,
150 ) -> std::collections::BTreeSet<TYPES::SignatureKey> {
151 self.assert_has_stake_table(epoch);
152 self.inner
153 .da_committee_members(view_number.into(), epoch.map(Into::into))
154 .into_iter()
155 .map(Into::<TYPES::SignatureKey>::into)
156 .collect()
157 }
158
159 fn stake(
160 &self,
161 pub_key: &TYPES::SignatureKey,
162 epoch: Option<TYPES::Epoch>,
163 ) -> Option<hotshot_types::PeerConfig<TYPES>> {
164 self.assert_has_stake_table(epoch);
165 self.inner
166 .stake(pub_key.into(), epoch.map(Into::into))
167 .map(Self::convert_peer_config)
168 }
169
170 fn da_stake(
171 &self,
172 pub_key: &TYPES::SignatureKey,
173 epoch: Option<TYPES::Epoch>,
174 ) -> Option<hotshot_types::PeerConfig<TYPES>> {
175 self.assert_has_stake_table(epoch);
176 self.inner
177 .da_stake(pub_key.into(), epoch.map(Into::into))
178 .map(Self::convert_peer_config)
179 }
180
181 fn has_stake(&self, pub_key: &TYPES::SignatureKey, epoch: Option<TYPES::Epoch>) -> bool {
182 self.assert_has_stake_table(epoch);
183 self.inner.has_stake(pub_key.into(), epoch.map(Into::into))
184 }
185
186 fn has_da_stake(&self, pub_key: &TYPES::SignatureKey, epoch: Option<TYPES::Epoch>) -> bool {
187 self.assert_has_stake_table(epoch);
188 self.inner
189 .has_da_stake(pub_key.into(), epoch.map(Into::into))
190 }
191
192 fn lookup_leader(
193 &self,
194 view: TYPES::View,
195 epoch: Option<TYPES::Epoch>,
196 ) -> std::result::Result<TYPES::SignatureKey, Self::Error> {
197 self.assert_has_randomized_stake_table(epoch);
198 self.inner
199 .lookup_leader(view.into(), epoch.map(Into::into))
200 .map(Into::<TYPES::SignatureKey>::into)
201 }
202
203 fn total_nodes(&self, epoch: Option<TYPES::Epoch>) -> usize {
204 self.assert_has_stake_table(epoch);
205 self.inner.total_nodes(epoch.map(Into::into))
206 }
207
208 fn da_total_nodes(&self, epoch: Option<TYPES::Epoch>) -> usize {
209 self.assert_has_stake_table(epoch);
210 self.inner.da_total_nodes(epoch.map(Into::into))
211 }
212
213 fn success_threshold(&self, epoch: Option<TYPES::Epoch>) -> U256 {
214 self.assert_has_stake_table(epoch);
215 self.inner.success_threshold(epoch.map(Into::into))
216 }
217
218 fn da_success_threshold(&self, epoch: Option<TYPES::Epoch>) -> U256 {
219 self.assert_has_stake_table(epoch);
220 self.inner.da_success_threshold(epoch.map(Into::into))
221 }
222
223 fn failure_threshold(&self, epoch: Option<TYPES::Epoch>) -> U256 {
224 self.assert_has_stake_table(epoch);
225 self.inner.failure_threshold(epoch.map(Into::into))
226 }
227
228 fn upgrade_threshold(&self, epoch: Option<TYPES::Epoch>) -> U256 {
229 self.assert_has_stake_table(epoch);
230 self.inner.upgrade_threshold(epoch.map(Into::into))
231 }
232
233 fn has_stake_table(&self, epoch: TYPES::Epoch) -> bool {
234 self.epochs.contains(&epoch)
235 }
236
237 fn has_randomized_stake_table(&self, epoch: TYPES::Epoch) -> anyhow::Result<bool> {
238 Ok(self.drbs.contains(&epoch))
239 }
240
241 async fn get_epoch_root(
242 _membership: Arc<RwLock<Self>>,
243 _block_height: u64,
244 _epoch: TYPES::Epoch,
245 ) -> anyhow::Result<Leaf2<TYPES>> {
246 tokio::time::sleep(Duration::from_millis(10)).await;
247 let leaf = Leaf2::genesis::<V>(
248 &TYPES::ValidatedState::default(),
249 &TYPES::InstanceState::default(),
250 )
251 .await;
252 Ok(leaf)
253 }
254
255 async fn get_epoch_drb(
256 membership: Arc<RwLock<Self>>,
257 epoch: TYPES::Epoch,
258 ) -> anyhow::Result<DrbResult> {
259 tokio::time::sleep(Duration::from_millis(10)).await;
260 if !membership.read().await.drbs.contains(&epoch) {
261 anyhow::bail!("Missing DRB for epoch {epoch}");
262 }
263
264 let default_drb = {
265 let root_leaf: Leaf2<TYPES> = Leaf2::genesis::<V>(
266 &TYPES::ValidatedState::default(),
267 &TYPES::InstanceState::default(),
268 )
269 .await;
270
271 let Ok(drb_seed_input_vec) = bincode::serialize(&root_leaf.justify_qc().signatures)
272 else {
273 panic!("Failed to serialize root leaf");
274 };
275 let drb_difficulty = 10;
276 let mut drb_seed_input = [0u8; 32];
277 let len = drb_seed_input_vec.len().min(32);
278 drb_seed_input[..len].copy_from_slice(&drb_seed_input_vec[..len]);
279 let drb_input = DrbInput {
280 epoch: 0,
281 iteration: 0,
282 value: drb_seed_input,
283 difficulty_level: drb_difficulty,
284 };
285
286 let store_drb_progress_fn = null_store_drb_progress_fn();
287 let load_drb_progress_fn = null_load_drb_progress_fn();
288
289 compute_drb_result(drb_input, store_drb_progress_fn, load_drb_progress_fn).await
290 };
291 Ok(default_drb)
292 }
293
294 fn add_drb_result(&mut self, epoch: TYPES::Epoch, drb_result: hotshot_types::drb::DrbResult) {
295 self.drbs.insert(epoch);
296 self.inner.add_drb_result(epoch.into(), drb_result);
297 }
298
299 fn set_first_epoch(
300 &mut self,
301 epoch: TYPES::Epoch,
302 initial_drb_result: hotshot_types::drb::DrbResult,
303 ) {
304 self.epochs.insert(epoch);
305 self.epochs.insert(epoch + 1);
306 self.drbs.insert(epoch);
307 self.drbs.insert(epoch + 1);
308 self.inner.set_first_epoch(epoch.into(), initial_drb_result);
309 }
310
311 async fn add_epoch_root(
312 membership: Arc<RwLock<Self>>,
313 epoch: TYPES::Epoch,
314 _block_header: TYPES::BlockHeader,
315 ) -> anyhow::Result<()> {
316 let mut membership_writer = membership.write().await;
317 tracing::error!("Adding epoch root for {epoch}");
318 membership_writer.epochs.insert(epoch);
319 Ok(())
320 }
321
322 fn first_epoch(&self) -> Option<TYPES::Epoch> {
323 self.inner.first_epoch().map(Into::into)
324 }
325}