hotshot/traits/election/
dummy_catchup_membership.rs

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        // Note: eligible_leaders is currently a haMemck because the DA leader == the quorum leader
90        // but they should not have voting power.
91        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}