1use std::{
2 collections::HashSet,
3 fmt::{self, Debug},
4 sync::Arc,
5};
6
7use alloy::primitives::U256;
8use anyhow::anyhow;
9use async_broadcast::Receiver;
10use async_lock::RwLock as AsyncRwLock;
11use hotshot_types::{
12 PeerConfig,
13 data::{BlockNumber, EpochNumber, Leaf2, ViewNumber},
14 drb::DrbResult,
15 epoch_membership::EpochMembershipCoordinator,
16 event::Event,
17 traits::{
18 block_contents::BlockHeader,
19 election::{Membership, MembershipSnapshot, NoStakeTableHash, NonEpochMembershipSnapshot},
20 leaf_fetcher_network::LeafFetcherNetwork,
21 node_implementation::NodeType,
22 signature_key::StakeTableEntryType,
23 },
24 utils::{epoch_from_block_number, root_block_in_epoch, transition_block_for_epoch},
25};
26use parking_lot::RwLock;
27
28use crate::{
29 membership::{TestableMembership, fetcher::Leaf2Fetcher, stake_table::TestStakeTable},
30 storage_types::TestStorage,
31};
32
33#[derive(Clone)]
34pub struct StrictMembership<T, S>
35where
36 T: NodeType,
37 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
38{
39 inner: Arc<RwLock<Inner<T, S>>>,
40 epoch_height: BlockNumber,
41}
42
43struct Inner<T: NodeType, S> {
44 table: S,
45 epochs: HashSet<EpochNumber>,
46 drbs: HashSet<EpochNumber>,
47 fetcher: Option<Arc<AsyncRwLock<Leaf2Fetcher<T>>>>,
48}
49
50impl<T, S> Debug for StrictMembership<T, S>
51where
52 T: NodeType,
53 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
54{
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
56 let inner = self.inner.read();
57 f.debug_struct("StrictMembership")
58 .field("table", &inner.table)
59 .field("epochs", &inner.epochs)
60 .field("drbs", &inner.drbs)
61 .finish()
62 }
63}
64
65impl<T, S> TestableMembership<T> for StrictMembership<T, S>
66where
67 T: NodeType,
68 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
69{
70 fn new(
71 quorum_members: Vec<PeerConfig<T>>,
72 da_members: Vec<PeerConfig<T>>,
73 _public_key: T::SignatureKey,
74 epoch_height: u64,
75 ) -> Self {
76 Self {
77 inner: Arc::new(RwLock::new(Inner {
78 table: TestStakeTable::new(
79 quorum_members.into_iter().map(Into::into).collect(),
80 da_members.into_iter().map(Into::into).collect(),
81 ),
82 epochs: HashSet::new(),
83 drbs: HashSet::new(),
84 fetcher: None,
85 })),
86 epoch_height: epoch_height.into(),
87 }
88 }
89
90 fn set_leaf_fetcher(
91 &self,
92 network: Arc<dyn LeafFetcherNetwork<T>>,
93 storage: TestStorage<T>,
94 public_key: T::SignatureKey,
95 channel: Receiver<Event<T>>,
96 ) {
97 let mut fetcher = Leaf2Fetcher::new(network, storage, public_key);
98 fetcher.set_external_channel(channel);
99 self.inner.write().fetcher = Some(Arc::new(AsyncRwLock::new(fetcher)));
100 }
101}
102
103impl<T, S> StrictMembership<T, S>
104where
105 T: NodeType,
106 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
107{
108 pub fn add_quorum_committee(&self, first_epoch: EpochNumber, committee: Vec<PeerConfig<T>>) {
112 self.inner.write().table.add_quorum_committee(
113 *first_epoch,
114 committee.into_iter().map(Into::into).collect(),
115 );
116 }
117
118 pub fn register_epoch(&self, epoch: EpochNumber, drb: DrbResult) {
122 let mut inner = self.inner.write();
123 inner.epochs.insert(epoch);
124 inner.drbs.insert(epoch);
125 inner.table.add_epoch_root(*epoch);
126 inner.table.add_drb_result(*epoch, drb);
127 }
128}
129
130impl<T: NodeType, S> Inner<T, S> {
131 fn assert_has_stake_table(&self, epoch: Option<EpochNumber>) {
132 let Some(epoch) = epoch else {
133 return;
134 };
135 assert!(
136 self.epochs.contains(&epoch),
137 "Failed stake table check for epoch {epoch}"
138 );
139 }
140}
141
142impl<T, S> Membership<T> for StrictMembership<T, S>
143where
144 T: NodeType,
145 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
146{
147 type Error = StrictMembershipError;
148 type Snapshot = StrictEpochSnapshot<T, S>;
149 type NonEpochSnapshot = StrictNonEpochSnapshot<T, S>;
150
151 fn snapshot(&self, epoch: EpochNumber) -> Option<Self::Snapshot> {
152 let inner = self.inner.read();
153 if !inner.epochs.contains(&epoch) {
154 return None;
155 }
156 let has_drb = inner.drbs.contains(&epoch);
157 let first_epoch = inner.table.first_epoch().map(EpochNumber::new);
158 Some(StrictEpochSnapshot::build(
159 epoch,
160 first_epoch,
161 has_drb,
162 inner.table.clone(),
163 ))
164 }
165
166 fn non_epoch_snapshot(&self) -> Self::NonEpochSnapshot {
167 StrictNonEpochSnapshot::build(self.inner.read().table.clone())
168 }
169
170 fn add_drb_result(&self, e: EpochNumber, drb: DrbResult) {
171 let mut inner = self.inner.write();
172 inner.assert_has_stake_table(Some(e));
173 inner.drbs.insert(e);
174 inner.table.add_drb_result(*e, drb);
175 }
176
177 fn first_epoch(&self) -> Option<EpochNumber> {
178 self.inner.read().table.first_epoch().map(EpochNumber::new)
179 }
180
181 fn set_first_epoch(&self, e: EpochNumber, initial_drb_result: DrbResult) {
182 let mut inner = self.inner.write();
183 inner.epochs.insert(e);
184 inner.epochs.insert(e + 1);
185
186 inner.drbs.insert(e);
187 inner.drbs.insert(e + 1);
188
189 inner.table.set_first_epoch(*e, initial_drb_result);
190 }
191
192 async fn add_epoch_root(
193 &self,
194 hdr: T::BlockHeader,
195 _coordinator: &EpochMembershipCoordinator<T>,
196 ) -> Result<(), Self::Error> {
197 let epoch = epoch_from_block_number(hdr.block_number(), *self.epoch_height) + 2;
198
199 let mut inner = self.inner.write();
200 inner.epochs.insert(EpochNumber::new(epoch));
201 inner.table.add_epoch_root(epoch);
202
203 Ok(())
204 }
205
206 async fn get_epoch_root(
207 &self,
208 e: EpochNumber,
209 _coordinator: &EpochMembershipCoordinator<T>,
210 ) -> Result<Leaf2<T>, Self::Error> {
211 let block_height = root_block_in_epoch(*e, *self.epoch_height);
212
213 let (stake_table, fetcher) = {
214 let inner = self.inner.read();
215 let table = inner.table.stake_table(Some(*e));
216 let fetcher = inner
217 .fetcher
218 .clone()
219 .expect("get_epoch_root called before set_leaf_fetcher_network");
220 (table, fetcher)
221 };
222
223 for node in stake_table {
224 if let Ok(leaf) = fetcher
225 .read()
226 .await
227 .fetch_leaf(block_height, node.signature_key)
228 .await
229 {
230 return Ok(leaf);
231 }
232 }
233
234 Err(anyhow!("Failed to fetch epoch root from any peer").into())
235 }
236
237 async fn get_epoch_drb(
238 &self,
239 e: EpochNumber,
240 _coordinator: &EpochMembershipCoordinator<T>,
241 ) -> Result<DrbResult, Self::Error> {
242 let epoch_height = self.epoch_height;
243
244 let (epoch_drb, fetcher) = {
245 let state = self.inner.read();
246 let drb = state.table.get_epoch_drb(*e);
247 let fetcher = state.fetcher.clone();
248 (drb, fetcher)
249 };
250
251 if let Ok(drb_result) = epoch_drb {
252 Ok(drb_result)
253 } else {
254 let previous_epoch = match e.checked_sub(1) {
255 Some(epoch) => epoch,
256 None => {
257 return Err(anyhow!("Missing initial DRB result for epoch {e:?}").into());
258 },
259 };
260
261 let drb_block_height = transition_block_for_epoch(previous_epoch, *epoch_height);
262 let stake_table = self.inner.read().table.stake_table(Some(previous_epoch));
263 let fetcher = fetcher.expect("get_epoch_drb called before set_leaf_fetcher_network");
264
265 let mut drb_leaf = None;
266
267 for node in stake_table {
268 if let Ok(leaf) = fetcher
269 .read()
270 .await
271 .fetch_leaf(drb_block_height, node.signature_key)
272 .await
273 {
274 drb_leaf = Some(leaf);
275 break;
276 }
277 }
278
279 match drb_leaf {
280 Some(leaf) => Ok(leaf.next_drb_result.expect(
281 "We fetched a leaf that is missing a DRB result. This should be impossible.",
282 )),
283 None => Err(anyhow!(
284 "Failed to fetch leaf from all nodes. Height: {drb_block_height}"
285 )
286 .into()),
287 }
288 }
289 }
290
291 fn add_da_committee(&self, first_epoch: EpochNumber, committee: Vec<PeerConfig<T>>) {
292 self.inner.write().table.add_da_committee(
293 *first_epoch,
294 committee.into_iter().map(Into::into).collect(),
295 );
296 }
297}
298
299#[derive(Debug, thiserror::Error)]
300#[error("strict membership error: {0}")]
301pub struct StrictMembershipError(#[from] anyhow::Error);
302
303pub struct StrictEpochSnapshot<T, S>
308where
309 T: NodeType,
310 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
311{
312 epoch: EpochNumber,
313 first_epoch: Option<EpochNumber>,
314 has_drb: bool,
315 stake_table: Vec<PeerConfig<T>>,
316 da_stake_table: Vec<PeerConfig<T>>,
317 committee_keys: Vec<T::SignatureKey>,
318 da_committee_keys: Vec<T::SignatureKey>,
319 table: S,
320 _phantom: std::marker::PhantomData<T>,
321}
322
323impl<T, S> StrictEpochSnapshot<T, S>
324where
325 T: NodeType,
326 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
327{
328 fn build(
329 epoch: EpochNumber,
330 first_epoch: Option<EpochNumber>,
331 has_drb: bool,
332 table: S,
333 ) -> Self {
334 let stake_entries = table.stake_table(Some(*epoch));
335 let da_entries = table.da_stake_table(Some(*epoch));
336 let committee_keys = stake_entries
337 .iter()
338 .map(|e| e.signature_key.clone())
339 .collect();
340 let da_committee_keys = da_entries.iter().map(|e| e.signature_key.clone()).collect();
341 let stake_table = stake_entries.into_iter().map(Into::into).collect();
342 let da_stake_table = da_entries.into_iter().map(Into::into).collect();
343 Self {
344 epoch,
345 first_epoch,
346 has_drb,
347 stake_table,
348 da_stake_table,
349 committee_keys,
350 da_committee_keys,
351 table,
352 _phantom: std::marker::PhantomData,
353 }
354 }
355}
356
357impl<T, S> Clone for StrictEpochSnapshot<T, S>
358where
359 T: NodeType,
360 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
361{
362 fn clone(&self) -> Self {
363 Self {
364 epoch: self.epoch,
365 first_epoch: self.first_epoch,
366 has_drb: self.has_drb,
367 stake_table: self.stake_table.clone(),
368 da_stake_table: self.da_stake_table.clone(),
369 committee_keys: self.committee_keys.clone(),
370 da_committee_keys: self.da_committee_keys.clone(),
371 table: self.table.clone(),
372 _phantom: std::marker::PhantomData,
373 }
374 }
375}
376
377impl<T, S> Debug for StrictEpochSnapshot<T, S>
378where
379 T: NodeType,
380 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
381{
382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
383 f.debug_struct("StrictEpochSnapshot")
384 .field("epoch", &self.epoch)
385 .field("first_epoch", &self.first_epoch)
386 .field("has_drb", &self.has_drb)
387 .field("table", &self.table)
388 .finish()
389 }
390}
391
392impl<T, S> MembershipSnapshot<T> for StrictEpochSnapshot<T, S>
393where
394 T: NodeType,
395 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
396{
397 type Error = StrictMembershipError;
398 type StakeTableHash = NoStakeTableHash;
399
400 fn epoch(&self) -> EpochNumber {
401 self.epoch
402 }
403
404 fn first_epoch(&self) -> Option<EpochNumber> {
405 self.first_epoch
406 }
407
408 fn has_drb(&self) -> bool {
409 self.has_drb
410 }
411
412 fn stake_table(&self) -> impl ExactSizeIterator<Item = &PeerConfig<T>> + Send {
413 self.stake_table.iter()
414 }
415
416 fn da_stake_table(&self) -> impl ExactSizeIterator<Item = &PeerConfig<T>> + Send {
417 self.da_stake_table.iter()
418 }
419
420 fn committee_members(
421 &self,
422 _: ViewNumber,
423 ) -> impl ExactSizeIterator<Item = &T::SignatureKey> + Send {
424 self.committee_keys.iter()
425 }
426
427 fn da_committee_members(
428 &self,
429 _: ViewNumber,
430 ) -> impl ExactSizeIterator<Item = &T::SignatureKey> + Send {
431 self.da_committee_keys.iter()
432 }
433
434 fn stake(&self, key: &T::SignatureKey) -> Option<PeerConfig<T>> {
435 self.table
436 .stake(key.clone(), Some(*self.epoch))
437 .map(Into::into)
438 }
439
440 fn da_stake(&self, key: &T::SignatureKey) -> Option<PeerConfig<T>> {
441 self.table
442 .da_stake(key.clone(), Some(*self.epoch))
443 .map(Into::into)
444 }
445
446 fn has_stake(&self, key: &T::SignatureKey) -> bool {
447 self.stake(key)
448 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
449 }
450
451 fn has_da_stake(&self, key: &T::SignatureKey) -> bool {
452 self.da_stake(key)
453 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
454 }
455
456 fn lookup_leader(&self, view: ViewNumber) -> Result<T::SignatureKey, Self::Error> {
457 Ok(self.table.lookup_leader(*view, Some(*self.epoch))?)
458 }
459}
460
461pub struct StrictNonEpochSnapshot<T, S>
464where
465 T: NodeType,
466 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
467{
468 stake_table: Vec<PeerConfig<T>>,
469 da_stake_table: Vec<PeerConfig<T>>,
470 committee_keys: Vec<T::SignatureKey>,
471 da_committee_keys: Vec<T::SignatureKey>,
472 table: S,
473 _phantom: std::marker::PhantomData<T>,
474}
475
476impl<T, S> StrictNonEpochSnapshot<T, S>
477where
478 T: NodeType,
479 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
480{
481 fn build(table: S) -> Self {
482 let stake_entries = table.stake_table(None);
483 let da_entries = table.da_stake_table(None);
484 let committee_keys = stake_entries
485 .iter()
486 .map(|e| e.signature_key.clone())
487 .collect();
488 let da_committee_keys = da_entries.iter().map(|e| e.signature_key.clone()).collect();
489 let stake_table = stake_entries.into_iter().map(Into::into).collect();
490 let da_stake_table = da_entries.into_iter().map(Into::into).collect();
491 Self {
492 stake_table,
493 da_stake_table,
494 committee_keys,
495 da_committee_keys,
496 table,
497 _phantom: std::marker::PhantomData,
498 }
499 }
500}
501
502impl<T, S> Clone for StrictNonEpochSnapshot<T, S>
503where
504 T: NodeType,
505 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
506{
507 fn clone(&self) -> Self {
508 Self {
509 stake_table: self.stake_table.clone(),
510 da_stake_table: self.da_stake_table.clone(),
511 committee_keys: self.committee_keys.clone(),
512 da_committee_keys: self.da_committee_keys.clone(),
513 table: self.table.clone(),
514 _phantom: std::marker::PhantomData,
515 }
516 }
517}
518
519impl<T, S> Debug for StrictNonEpochSnapshot<T, S>
520where
521 T: NodeType,
522 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
523{
524 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
525 f.debug_struct("StrictNonEpochSnapshot")
526 .field("table", &self.table)
527 .finish()
528 }
529}
530
531impl<T, S> NonEpochMembershipSnapshot<T> for StrictNonEpochSnapshot<T, S>
532where
533 T: NodeType,
534 S: TestStakeTable<T::SignatureKey, T::StateSignatureKey>,
535{
536 type Error = StrictMembershipError;
537
538 fn stake_table(&self) -> impl ExactSizeIterator<Item = &PeerConfig<T>> + Send + '_ {
539 self.stake_table.iter()
540 }
541
542 fn da_stake_table(&self) -> impl ExactSizeIterator<Item = &PeerConfig<T>> + Send + '_ {
543 self.da_stake_table.iter()
544 }
545
546 fn committee_members(
547 &self,
548 _: ViewNumber,
549 ) -> impl ExactSizeIterator<Item = &T::SignatureKey> + Send + '_ {
550 self.committee_keys.iter()
551 }
552
553 fn da_committee_members(
554 &self,
555 _: ViewNumber,
556 ) -> impl ExactSizeIterator<Item = &T::SignatureKey> + Send + '_ {
557 self.da_committee_keys.iter()
558 }
559
560 fn stake(&self, key: &T::SignatureKey) -> Option<PeerConfig<T>> {
561 self.table.stake(key.clone(), None).map(Into::into)
562 }
563
564 fn da_stake(&self, key: &T::SignatureKey) -> Option<PeerConfig<T>> {
565 self.table.da_stake(key.clone(), None).map(Into::into)
566 }
567
568 fn has_stake(&self, key: &T::SignatureKey) -> bool {
569 self.stake(key)
570 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
571 }
572
573 fn has_da_stake(&self, key: &T::SignatureKey) -> bool {
574 self.da_stake(key)
575 .is_some_and(|x| x.stake_table_entry.stake() > U256::ZERO)
576 }
577
578 fn lookup_leader(&self, view: ViewNumber) -> Result<T::SignatureKey, Self::Error> {
579 Ok(self.table.lookup_leader(*view, None)?)
580 }
581}