hotshot_example_types/membership/
strict_membership.rs1use std::{collections::HashSet, fmt, fmt::Debug, sync::Arc};
2
3use alloy::primitives::U256;
4use async_broadcast::Receiver;
5use async_lock::RwLock;
6use hotshot_types::{
7 data::Leaf2,
8 drb::DrbResult,
9 event::Event,
10 stake_table::HSStakeTable,
11 traits::{
12 block_contents::BlockHeader,
13 election::{Membership, NoStakeTableHash},
14 node_implementation::{ConsensusTime, NodeImplementation, NodeType},
15 signature_key::StakeTableEntryType,
16 },
17 utils::{epoch_from_block_number, root_block_in_epoch, transition_block_for_epoch},
18 PeerConfig,
19};
20
21use crate::{
22 membership::{fetcher::Leaf2Fetcher, stake_table::TestStakeTable},
23 storage_types::TestStorage,
24};
25
26#[derive(Clone)]
27pub struct StrictMembership<
28 TYPES: NodeType,
29 StakeTable: TestStakeTable<TYPES::SignatureKey, TYPES::StateSignatureKey>,
30> {
31 inner: StakeTable,
32 epochs: HashSet<TYPES::Epoch>,
33 drbs: HashSet<TYPES::Epoch>,
34 fetcher: Arc<RwLock<Leaf2Fetcher<TYPES>>>,
35 epoch_height: u64,
36}
37
38impl<TYPES, StakeTable> Debug for StrictMembership<TYPES, StakeTable>
39where
40 TYPES: NodeType,
41 StakeTable: TestStakeTable<TYPES::SignatureKey, TYPES::StateSignatureKey>,
42{
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
44 f.debug_struct("StrictMembership")
45 .field("inner", &self.inner)
46 .field("epochs", &self.epochs)
47 .field("drbs", &self.drbs)
48 .finish()
49 }
50}
51
52impl<
53 TYPES: NodeType,
54 StakeTable: TestStakeTable<TYPES::SignatureKey, TYPES::StateSignatureKey>,
55 > StrictMembership<TYPES, StakeTable>
56{
57 fn assert_has_stake_table(&self, epoch: Option<TYPES::Epoch>) {
58 let Some(epoch) = epoch else {
59 return;
60 };
61 assert!(
62 self.epochs.contains(&epoch),
63 "Failed stake table check for epoch {epoch}"
64 );
65 }
66 fn assert_has_randomized_stake_table(&self, epoch: Option<TYPES::Epoch>) {
67 let Some(epoch) = epoch else {
68 return;
69 };
70 assert!(
71 self.drbs.contains(&epoch),
72 "Failed drb check for epoch {epoch}"
73 );
74 }
75}
76
77impl<
78 TYPES: NodeType,
79 StakeTable: TestStakeTable<TYPES::SignatureKey, TYPES::StateSignatureKey>,
80 > Membership<TYPES> for StrictMembership<TYPES, StakeTable>
81{
82 type Error = anyhow::Error;
83 type StakeTableHash = NoStakeTableHash;
84 type Storage = TestStorage<TYPES>;
85
86 fn new<I: NodeImplementation<TYPES>>(
87 quorum_members: Vec<hotshot_types::PeerConfig<TYPES>>,
88 da_members: Vec<hotshot_types::PeerConfig<TYPES>>,
89 storage: Self::Storage,
90 network: Arc<<I as NodeImplementation<TYPES>>::Network>,
91 public_key: TYPES::SignatureKey,
92 epoch_height: u64,
93 ) -> Self {
94 let fetcher = Leaf2Fetcher::new::<I>(network, storage, public_key);
95
96 Self {
97 inner: TestStakeTable::new(
98 quorum_members.into_iter().map(Into::into).collect(),
99 da_members.into_iter().map(Into::into).collect(),
100 ),
101 epochs: HashSet::new(),
102 drbs: HashSet::new(),
103 fetcher: RwLock::new(fetcher).into(),
104 epoch_height,
105 }
106 }
107
108 async fn set_external_channel(&mut self, external_channel: Receiver<Event<TYPES>>) {
109 self.fetcher
110 .write()
111 .await
112 .set_external_channel(external_channel)
113 }
114
115 fn stake_table(&self, epoch: Option<TYPES::Epoch>) -> HSStakeTable<TYPES> {
116 self.assert_has_stake_table(epoch);
117 let peer_configs = self
118 .inner
119 .stake_table(epoch.map(|e| *e))
120 .into_iter()
121 .map(Into::into)
122 .collect();
123 HSStakeTable(peer_configs)
124 }
125
126 fn da_stake_table(&self, epoch: Option<TYPES::Epoch>) -> HSStakeTable<TYPES> {
127 self.assert_has_stake_table(epoch);
128 let peer_configs = self
129 .inner
130 .da_stake_table(epoch.map(|e| *e))
131 .into_iter()
132 .map(Into::into)
133 .collect();
134 HSStakeTable(peer_configs)
135 }
136
137 fn committee_members(
138 &self,
139 _view_number: TYPES::View,
140 epoch: Option<TYPES::Epoch>,
141 ) -> std::collections::BTreeSet<TYPES::SignatureKey> {
142 self.assert_has_stake_table(epoch);
143 self.inner
144 .stake_table(epoch.map(|e| *e))
145 .into_iter()
146 .map(|entry| entry.signature_key)
147 .collect()
148 }
149
150 fn da_committee_members(
151 &self,
152 _view_number: TYPES::View,
153 epoch: Option<TYPES::Epoch>,
154 ) -> std::collections::BTreeSet<TYPES::SignatureKey> {
155 self.assert_has_stake_table(epoch);
156 self.inner
157 .da_stake_table(epoch.map(|e| *e))
158 .into_iter()
159 .map(|entry| entry.signature_key)
160 .collect()
161 }
162
163 fn stake(
164 &self,
165 pub_key: &TYPES::SignatureKey,
166 epoch: Option<TYPES::Epoch>,
167 ) -> Option<hotshot_types::PeerConfig<TYPES>> {
168 self.assert_has_stake_table(epoch);
169 self.inner
170 .stake(pub_key.clone(), epoch.map(|e| *e))
171 .map(Into::into)
172 }
173
174 fn da_stake(
175 &self,
176 pub_key: &TYPES::SignatureKey,
177 epoch: Option<TYPES::Epoch>,
178 ) -> Option<hotshot_types::PeerConfig<TYPES>> {
179 self.assert_has_stake_table(epoch);
180 self.inner
181 .da_stake(pub_key.clone(), epoch.map(|e| *e))
182 .map(Into::into)
183 }
184
185 fn has_stake(
187 &self,
188 pub_key: &<TYPES as NodeType>::SignatureKey,
189 epoch: Option<<TYPES as NodeType>::Epoch>,
190 ) -> bool {
191 self.assert_has_stake_table(epoch);
192
193 self.stake(pub_key, epoch)
194 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
195 }
196
197 fn has_da_stake(
199 &self,
200 pub_key: &<TYPES as NodeType>::SignatureKey,
201 epoch: Option<<TYPES as NodeType>::Epoch>,
202 ) -> bool {
203 self.assert_has_stake_table(epoch);
204
205 self.da_stake(pub_key, epoch)
206 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
207 }
208
209 fn lookup_leader(
210 &self,
211 view: TYPES::View,
212 epoch: Option<TYPES::Epoch>,
213 ) -> anyhow::Result<TYPES::SignatureKey> {
214 self.assert_has_randomized_stake_table(epoch);
215 self.inner.lookup_leader(*view, epoch.map(|e| *e))
216 }
217
218 fn total_nodes(&self, epoch: Option<TYPES::Epoch>) -> usize {
219 self.assert_has_stake_table(epoch);
220 self.inner.stake_table(epoch.map(|e| *e)).len()
221 }
222
223 fn da_total_nodes(&self, epoch: Option<TYPES::Epoch>) -> usize {
224 self.assert_has_stake_table(epoch);
225 self.inner.da_stake_table(epoch.map(|e| *e)).len()
226 }
227
228 fn has_stake_table(&self, epoch: TYPES::Epoch) -> bool {
229 let has_stake_table = self.inner.has_stake_table(*epoch);
230
231 assert_eq!(has_stake_table, self.epochs.contains(&epoch));
232
233 has_stake_table
234 }
235
236 fn has_randomized_stake_table(&self, epoch: TYPES::Epoch) -> anyhow::Result<bool> {
237 if !self.has_stake_table(epoch) {
238 return Ok(false);
239 }
240 let has_randomized_stake_table = self.inner.has_randomized_stake_table(*epoch);
241
242 if let Ok(result) = has_randomized_stake_table {
243 assert_eq!(result, self.drbs.contains(&epoch));
244 } else {
245 assert!(!self.drbs.contains(&epoch));
246 }
247
248 has_randomized_stake_table
249 }
250
251 fn add_drb_result(&mut self, epoch: TYPES::Epoch, drb_result: hotshot_types::drb::DrbResult) {
252 self.assert_has_stake_table(Some(epoch));
253
254 self.drbs.insert(epoch);
255 self.inner.add_drb_result(*epoch, drb_result);
256 }
257
258 fn first_epoch(&self) -> Option<TYPES::Epoch> {
259 self.inner.first_epoch().map(TYPES::Epoch::new)
260 }
261
262 fn set_first_epoch(&mut self, epoch: TYPES::Epoch, initial_drb_result: DrbResult) {
263 self.epochs.insert(epoch);
264 self.epochs.insert(epoch + 1);
265
266 self.drbs.insert(epoch);
267 self.drbs.insert(epoch + 1);
268
269 self.inner.set_first_epoch(*epoch, initial_drb_result);
270 }
271
272 async fn add_epoch_root(
273 membership: Arc<RwLock<Self>>,
274 block_header: TYPES::BlockHeader,
275 ) -> anyhow::Result<()> {
276 let mut membership_writer = membership.write().await;
277
278 let epoch =
279 epoch_from_block_number(block_header.block_number(), membership_writer.epoch_height)
280 + 2;
281
282 membership_writer.epochs.insert(TYPES::Epoch::new(epoch));
283
284 membership_writer.inner.add_epoch_root(epoch);
285
286 Ok(())
287 }
288
289 async fn get_epoch_root(
290 membership: Arc<RwLock<Self>>,
291 epoch: TYPES::Epoch,
292 ) -> anyhow::Result<Leaf2<TYPES>> {
293 let membership_reader = membership.read().await;
294
295 let block_height = root_block_in_epoch(*epoch, membership_reader.epoch_height);
296
297 let stake_table = membership_reader.inner.stake_table(Some(*epoch));
298 let fetcher = membership_reader.fetcher.clone();
299
300 drop(membership_reader);
301
302 for node in stake_table {
303 if let Ok(leaf) = fetcher
304 .read()
305 .await
306 .fetch_leaf(block_height, node.signature_key)
307 .await
308 {
309 return Ok(leaf);
310 }
311 }
312
313 anyhow::bail!("Failed to fetch epoch root from any peer");
314 }
315
316 async fn get_epoch_drb(
317 membership: Arc<RwLock<Self>>,
318 epoch: TYPES::Epoch,
319 ) -> anyhow::Result<DrbResult> {
320 let membership_reader = membership.read().await;
321
322 let epoch_height = membership_reader.epoch_height;
323 let epoch_drb = membership_reader.inner.get_epoch_drb(*epoch);
324 let fetcher = membership_reader.fetcher.clone();
325
326 drop(membership_reader);
327
328 if let Ok(drb_result) = epoch_drb {
329 Ok(drb_result)
330 } else {
331 let previous_epoch = match epoch.checked_sub(1) {
332 Some(epoch) => epoch,
333 None => {
334 anyhow::bail!("Missing initial DRB result for epoch {epoch:?}");
335 },
336 };
337
338 let drb_block_height = transition_block_for_epoch(previous_epoch, epoch_height);
339
340 let membership_reader = membership.read().await;
341 let stake_table = membership_reader.inner.stake_table(Some(previous_epoch));
342 drop(membership_reader);
343
344 let mut drb_leaf = None;
345
346 for node in stake_table {
347 if let Ok(leaf) = fetcher
348 .read()
349 .await
350 .fetch_leaf(drb_block_height, node.signature_key)
351 .await
352 {
353 drb_leaf = Some(leaf);
354 break;
355 }
356 }
357
358 match drb_leaf {
359 Some(leaf) => Ok(leaf.next_drb_result.expect(
360 "We fetched a leaf that is missing a DRB result. This should be impossible.",
361 )),
362 None => {
363 anyhow::bail!(
364 "Failed to fetch leaf from all nodes. Height: {drb_block_height}"
365 );
366 },
367 }
368 }
369 }
370
371 fn add_da_committee(&mut self, first_epoch: u64, committee: Vec<PeerConfig<TYPES>>) {
372 self.inner
373 .add_da_committee(first_epoch, committee.into_iter().map(Into::into).collect());
374 }
375}