hotshot_testing/
view_generator.rs

1// Copyright (c) 2021-2024 Espresso Systems (espressosys.com)
2// This file is part of the HotShot repository.
3
4// You should have received a copy of the MIT License
5// along with the HotShot repository. If not, see <https://mit-license.org/>.
6
7use std::{
8    cmp::max,
9    marker::PhantomData,
10    pin::Pin,
11    sync::Arc,
12    task::{Context, Poll},
13};
14
15use committable::Committable;
16use futures::{FutureExt, Stream, future::BoxFuture};
17use hotshot::types::{BLSPubKey, SignatureKey, SystemContextHandle};
18use hotshot_example_types::{
19    block_types::{TestBlockHeader, TestBlockPayload, TestTransaction},
20    node_types::{MemoryImpl, TestTypes},
21    state_types::{TestInstanceState, TestValidatedState},
22};
23use hotshot_types::{
24    data::{
25        DaProposal2, EpochNumber, Leaf2, QuorumProposal2, QuorumProposalWrapper, VidDisperse,
26        VidDisperseShare, ViewChangeEvidence2, ViewNumber,
27    },
28    epoch_membership::{EpochMembership, EpochMembershipCoordinator},
29    message::{Proposal, UpgradeLock},
30    simple_certificate::{
31        DaCertificate2, QuorumCertificate2, TimeoutCertificate2, UpgradeCertificate,
32        ViewSyncFinalizeCertificate2,
33    },
34    simple_vote::{
35        DaData2, DaVote2, QuorumData2, QuorumVote2, TimeoutData2, TimeoutVote2,
36        UpgradeProposalData, UpgradeVote, ViewSyncFinalizeData2, ViewSyncFinalizeVote2,
37    },
38    traits::{BlockPayload, consensus_api::ConsensusApi, node_implementation::NodeType},
39    utils::{EpochTransitionIndicator, genesis_epoch_from_version},
40};
41use rand::{Rng, thread_rng};
42use sha2::{Digest, Sha256};
43use versions::Upgrade;
44
45use crate::helpers::{
46    TestNodeKeyMap, build_cert, build_da_certificate, build_vid_proposal, da_payload_commitment,
47};
48
49#[derive(Clone)]
50pub struct TestView {
51    pub da_proposal: Proposal<TestTypes, DaProposal2<TestTypes>>,
52    pub quorum_proposal: Proposal<TestTypes, QuorumProposalWrapper<TestTypes>>,
53    pub leaf: Leaf2<TestTypes>,
54    pub view_number: ViewNumber,
55    pub epoch_number: Option<EpochNumber>,
56    pub membership: EpochMembershipCoordinator<TestTypes>,
57    pub node_key_map: Arc<TestNodeKeyMap>,
58    pub vid_disperse: Proposal<TestTypes, VidDisperse<TestTypes>>,
59    pub vid_proposal: (
60        Vec<Proposal<TestTypes, VidDisperseShare<TestTypes>>>,
61        <TestTypes as NodeType>::SignatureKey,
62    ),
63    pub leader_public_key: <TestTypes as NodeType>::SignatureKey,
64    pub da_certificate: DaCertificate2<TestTypes>,
65    pub transactions: Vec<TestTransaction>,
66    upgrade_data: Option<UpgradeProposalData>,
67    formed_upgrade_certificate: Option<UpgradeCertificate<TestTypes>>,
68    view_sync_finalize_data: Option<ViewSyncFinalizeData2>,
69    timeout_cert_data: Option<TimeoutData2>,
70    upgrade_lock: UpgradeLock<TestTypes>,
71}
72
73impl TestView {
74    async fn find_leader_key_pair(
75        membership: &EpochMembership<TestTypes>,
76        node_key_map: &Arc<TestNodeKeyMap>,
77        view_number: ViewNumber,
78    ) -> (
79        <<TestTypes as NodeType>::SignatureKey as SignatureKey>::PrivateKey,
80        <TestTypes as NodeType>::SignatureKey,
81    ) {
82        let leader = membership
83            .leader(view_number)
84            .await
85            .expect("expected Membership::leader to succeed");
86
87        let sk = node_key_map
88            .get(&leader)
89            .expect("expected Membership::leader public key to be in node_key_map");
90
91        (sk.clone(), leader)
92    }
93
94    pub async fn genesis(
95        membership: &EpochMembershipCoordinator<TestTypes>,
96        node_key_map: Arc<TestNodeKeyMap>,
97        upgrade: Upgrade,
98    ) -> Self {
99        let genesis_view = ViewNumber::new(1);
100        let genesis_epoch = genesis_epoch_from_version(upgrade.base);
101        let upgrade_lock = UpgradeLock::new(upgrade);
102
103        let transactions = Vec::new();
104
105        let (block_payload, metadata) =
106            <TestBlockPayload as BlockPayload<TestTypes>>::from_transactions(
107                transactions.clone(),
108                &TestValidatedState::default(),
109                &TestInstanceState::default(),
110            )
111            .await
112            .unwrap();
113
114        let builder_commitment = <TestBlockPayload as BlockPayload<TestTypes>>::builder_commitment(
115            &block_payload,
116            &metadata,
117        );
118        let epoch_membership = membership
119            .membership_for_epoch(genesis_epoch)
120            .await
121            .unwrap();
122        //let (private_key, public_key) = key_pair_for_id::<TestTypes>(*genesis_view);
123        let (private_key, public_key) =
124            Self::find_leader_key_pair(&epoch_membership, &node_key_map, genesis_view).await;
125
126        let leader_public_key = public_key;
127
128        let genesis_version = upgrade_lock.version_infallible(genesis_view);
129
130        let payload_commitment = da_payload_commitment::<TestTypes>(
131            &epoch_membership,
132            transactions.clone(),
133            &metadata,
134            genesis_version,
135        )
136        .await;
137
138        let (vid_disperse, vid_proposal) = build_vid_proposal::<TestTypes>(
139            &epoch_membership,
140            genesis_view,
141            genesis_epoch,
142            &block_payload,
143            &metadata,
144            &private_key,
145            &upgrade_lock,
146        )
147        .await;
148
149        let da_certificate = build_da_certificate(
150            &epoch_membership,
151            genesis_view,
152            genesis_epoch,
153            transactions.clone(),
154            &metadata,
155            &public_key,
156            &private_key,
157            &upgrade_lock,
158        )
159        .await
160        .unwrap();
161
162        let block_header = TestBlockHeader::new(
163            &Leaf2::<TestTypes>::genesis(
164                &TestValidatedState::default(),
165                &TestInstanceState::default(),
166                upgrade.base,
167            )
168            .await,
169            payload_commitment,
170            builder_commitment,
171            metadata,
172            genesis_version,
173        );
174
175        let quorum_proposal_inner = QuorumProposalWrapper::<TestTypes> {
176            proposal: QuorumProposal2::<TestTypes> {
177                block_header: block_header.clone(),
178                view_number: genesis_view,
179                epoch: genesis_epoch,
180                justify_qc: QuorumCertificate2::genesis(
181                    &TestValidatedState::default(),
182                    &TestInstanceState::default(),
183                    upgrade,
184                )
185                .await,
186                next_epoch_justify_qc: None,
187                upgrade_certificate: None,
188                view_change_evidence: None,
189                next_drb_result: None,
190                state_cert: None,
191            },
192        };
193
194        let encoded_transactions = Arc::from(TestTransaction::encode(&transactions));
195        let encoded_transactions_hash = Sha256::digest(&encoded_transactions);
196        let block_payload_signature =
197            <TestTypes as NodeType>::SignatureKey::sign(&private_key, &encoded_transactions_hash)
198                .expect("Failed to sign block payload");
199
200        let da_proposal_inner = DaProposal2::<TestTypes> {
201            encoded_transactions: encoded_transactions.clone(),
202            metadata,
203            view_number: genesis_view,
204            epoch: genesis_epoch,
205            epoch_transition_indicator: EpochTransitionIndicator::NotInTransition,
206        };
207
208        let da_proposal = Proposal {
209            data: da_proposal_inner,
210            signature: block_payload_signature,
211            _pd: PhantomData,
212        };
213
214        let mut leaf = Leaf2::from_quorum_proposal(&quorum_proposal_inner);
215        leaf.fill_block_payload_unchecked(TestBlockPayload {
216            transactions: transactions.clone(),
217        });
218
219        let signature = <BLSPubKey as SignatureKey>::sign(&private_key, leaf.commit().as_ref())
220            .expect("Failed to sign leaf commitment!");
221
222        let quorum_proposal = Proposal {
223            data: quorum_proposal_inner,
224            signature,
225            _pd: PhantomData,
226        };
227
228        TestView {
229            quorum_proposal,
230            leaf,
231            view_number: genesis_view,
232            epoch_number: genesis_epoch,
233            membership: membership.clone(),
234            node_key_map,
235            vid_disperse,
236            vid_proposal: (vid_proposal, public_key),
237            da_certificate,
238            transactions,
239            leader_public_key,
240            upgrade_data: None,
241            formed_upgrade_certificate: None,
242            view_sync_finalize_data: None,
243            timeout_cert_data: None,
244            da_proposal,
245            upgrade_lock,
246        }
247    }
248
249    /// Moves the generator to the next view by referencing an ancestor. To have a standard,
250    /// sequentially ordered set of generated test views, use the `next_view` function. Otherwise,
251    /// this method can be used to start from an ancestor (whose view is at least one view older
252    /// than the current view) and construct valid views without the data structures in the task
253    /// failing by expecting views that they has never seen.
254    pub async fn next_view_from_ancestor(&self, ancestor: TestView) -> Self {
255        let old = ancestor;
256        let old_view = old.view_number;
257        let old_epoch = old.epoch_number;
258
259        // This ensures that we're always moving forward in time since someone could pass in any
260        // test view here.
261        let next_view = max(old_view, self.view_number) + 1;
262
263        let transactions = &self.transactions;
264
265        let quorum_data = QuorumData2 {
266            leaf_commit: old.leaf.commit(),
267            epoch: old_epoch,
268            block_number: old_epoch.is_some().then(|| old.leaf.height()),
269        };
270
271        //let (old_private_key, old_public_key) = key_pair_for_id::<TestTypes>(*old_view);
272        let (old_private_key, old_public_key) = Self::find_leader_key_pair(
273            &self
274                .membership
275                .membership_for_epoch(old_epoch)
276                .await
277                .unwrap(),
278            &self.node_key_map,
279            old_view,
280        )
281        .await;
282
283        //let (private_key, public_key) = key_pair_for_id::<TestTypes>(*next_view);
284        let (private_key, public_key) = Self::find_leader_key_pair(
285            &self
286                .membership
287                .membership_for_epoch(self.epoch_number)
288                .await
289                .unwrap(),
290            &self.node_key_map,
291            next_view,
292        )
293        .await;
294
295        let leader_public_key = public_key;
296
297        let (block_payload, metadata) =
298            <TestBlockPayload as BlockPayload<TestTypes>>::from_transactions(
299                transactions.clone(),
300                &TestValidatedState::default(),
301                &TestInstanceState::default(),
302            )
303            .await
304            .unwrap();
305        let builder_commitment = <TestBlockPayload as BlockPayload<TestTypes>>::builder_commitment(
306            &block_payload,
307            &metadata,
308        );
309
310        let version = self.upgrade_lock.version_infallible(next_view);
311        let membership = self
312            .membership
313            .membership_for_epoch(self.epoch_number)
314            .await
315            .unwrap();
316        let payload_commitment = da_payload_commitment::<TestTypes>(
317            &membership,
318            transactions.clone(),
319            &metadata,
320            version,
321        )
322        .await;
323
324        let (vid_disperse, vid_proposal) = build_vid_proposal::<TestTypes>(
325            &membership,
326            next_view,
327            self.epoch_number,
328            &block_payload,
329            &metadata,
330            &private_key,
331            &self.upgrade_lock,
332        )
333        .await;
334
335        let da_certificate = build_da_certificate::<TestTypes>(
336            &membership,
337            next_view,
338            self.epoch_number,
339            transactions.clone(),
340            &metadata,
341            &public_key,
342            &private_key,
343            &self.upgrade_lock,
344        )
345        .await
346        .unwrap();
347
348        let quorum_certificate = build_cert::<
349            TestTypes,
350            QuorumData2<TestTypes>,
351            QuorumVote2<TestTypes>,
352            QuorumCertificate2<TestTypes>,
353        >(
354            quorum_data,
355            &membership,
356            old_view,
357            &old_public_key,
358            &old_private_key,
359            &self.upgrade_lock,
360        )
361        .await;
362
363        let upgrade_certificate = if let Some(ref data) = self.upgrade_data {
364            let cert = build_cert::<
365                TestTypes,
366                UpgradeProposalData,
367                UpgradeVote<TestTypes>,
368                UpgradeCertificate<TestTypes>,
369            >(
370                data.clone(),
371                &membership,
372                next_view,
373                &public_key,
374                &private_key,
375                &self.upgrade_lock,
376            )
377            .await;
378
379            Some(cert)
380        } else {
381            self.formed_upgrade_certificate.clone()
382        };
383
384        let view_sync_certificate = if let Some(ref data) = self.view_sync_finalize_data {
385            let cert = build_cert::<
386                TestTypes,
387                ViewSyncFinalizeData2,
388                ViewSyncFinalizeVote2<TestTypes>,
389                ViewSyncFinalizeCertificate2<TestTypes>,
390            >(
391                data.clone(),
392                &membership,
393                next_view,
394                &public_key,
395                &private_key,
396                &self.upgrade_lock,
397            )
398            .await;
399
400            Some(cert)
401        } else {
402            None
403        };
404
405        let timeout_certificate = if let Some(ref data) = self.timeout_cert_data {
406            let cert = build_cert::<
407                TestTypes,
408                TimeoutData2,
409                TimeoutVote2<TestTypes>,
410                TimeoutCertificate2<TestTypes>,
411            >(
412                data.clone(),
413                &membership,
414                next_view,
415                &public_key,
416                &private_key,
417                &self.upgrade_lock,
418            )
419            .await;
420
421            Some(cert)
422        } else {
423            None
424        };
425
426        let view_change_evidence = if let Some(tc) = timeout_certificate {
427            Some(ViewChangeEvidence2::Timeout(tc))
428        } else {
429            view_sync_certificate.map(ViewChangeEvidence2::ViewSync)
430        };
431
432        let random = thread_rng().gen_range(0..=u64::MAX);
433
434        let block_header = TestBlockHeader {
435            block_number: *next_view,
436            timestamp: *next_view,
437            timestamp_millis: *next_view * 1_000,
438            payload_commitment,
439            builder_commitment,
440            metadata,
441            random,
442            version,
443        };
444
445        let proposal = QuorumProposalWrapper::<TestTypes> {
446            proposal: QuorumProposal2::<TestTypes> {
447                block_header: block_header.clone(),
448                view_number: next_view,
449                epoch: old_epoch,
450                justify_qc: quorum_certificate.clone(),
451                next_epoch_justify_qc: None,
452                upgrade_certificate: upgrade_certificate.clone(),
453                view_change_evidence,
454                next_drb_result: None,
455                state_cert: None,
456            },
457        };
458
459        let mut leaf = Leaf2::from_quorum_proposal(&proposal);
460        leaf.fill_block_payload_unchecked(TestBlockPayload {
461            transactions: transactions.clone(),
462        });
463
464        let signature = <BLSPubKey as SignatureKey>::sign(&private_key, leaf.commit().as_ref())
465            .expect("Failed to sign leaf commitment.");
466
467        let quorum_proposal = Proposal {
468            data: proposal,
469            signature,
470            _pd: PhantomData,
471        };
472
473        let encoded_transactions = Arc::from(TestTransaction::encode(transactions));
474        let encoded_transactions_hash = Sha256::digest(&encoded_transactions);
475        let block_payload_signature =
476            <TestTypes as NodeType>::SignatureKey::sign(&private_key, &encoded_transactions_hash)
477                .expect("Failed to sign block payload");
478
479        let da_proposal_inner = DaProposal2::<TestTypes> {
480            encoded_transactions: encoded_transactions.clone(),
481            metadata,
482            view_number: next_view,
483            epoch: old_epoch,
484            epoch_transition_indicator: EpochTransitionIndicator::NotInTransition,
485        };
486
487        let da_proposal = Proposal {
488            data: da_proposal_inner,
489            signature: block_payload_signature,
490            _pd: PhantomData,
491        };
492
493        let upgrade_lock = UpgradeLock::new(self.upgrade_lock.upgrade());
494
495        TestView {
496            quorum_proposal,
497            leaf,
498            view_number: next_view,
499            epoch_number: self.epoch_number,
500            membership: self.membership.clone(),
501            node_key_map: self.node_key_map.clone(),
502            vid_disperse,
503            vid_proposal: (vid_proposal, public_key),
504            da_certificate,
505            leader_public_key,
506            // Transactions and upgrade data need to be manually injected each view,
507            // so we reset for the next view.
508            transactions: Vec::new(),
509            upgrade_data: None,
510            // We preserve the upgrade_certificate once formed,
511            // and reattach it on every future view until cleared.
512            formed_upgrade_certificate: upgrade_certificate,
513            view_sync_finalize_data: None,
514            timeout_cert_data: None,
515            da_proposal,
516            upgrade_lock,
517        }
518    }
519
520    pub async fn next_view(&self) -> Self {
521        self.next_view_from_ancestor(self.clone()).await
522    }
523
524    pub async fn create_quorum_vote(
525        &self,
526        handle: &SystemContextHandle<TestTypes, MemoryImpl>,
527    ) -> QuorumVote2<TestTypes> {
528        QuorumVote2::<TestTypes>::create_signed_vote(
529            QuorumData2 {
530                leaf_commit: self.leaf.commit(),
531                epoch: self.epoch_number,
532                block_number: Some(self.leaf.height()),
533            },
534            self.view_number,
535            &handle.public_key(),
536            handle.private_key(),
537            &handle.hotshot.upgrade_lock,
538        )
539        .await
540        .expect("Failed to generate a signature on QuorumVote")
541    }
542
543    pub async fn create_upgrade_vote(
544        &self,
545        data: UpgradeProposalData,
546        handle: &SystemContextHandle<TestTypes, MemoryImpl>,
547    ) -> UpgradeVote<TestTypes> {
548        UpgradeVote::<TestTypes>::create_signed_vote(
549            data,
550            self.view_number,
551            &handle.public_key(),
552            handle.private_key(),
553            &handle.hotshot.upgrade_lock,
554        )
555        .await
556        .expect("Failed to generate a signature on UpgradVote")
557    }
558
559    pub async fn create_da_vote(
560        &self,
561        data: DaData2,
562        handle: &SystemContextHandle<TestTypes, MemoryImpl>,
563    ) -> DaVote2<TestTypes> {
564        DaVote2::create_signed_vote(
565            data,
566            self.view_number,
567            &handle.public_key(),
568            handle.private_key(),
569            &handle.hotshot.upgrade_lock,
570        )
571        .await
572        .expect("Failed to sign DaData")
573    }
574}
575
576pub struct TestViewGenerator {
577    pub current_view: Option<TestView>,
578    pub membership: EpochMembershipCoordinator<TestTypes>,
579    pub node_key_map: Arc<TestNodeKeyMap>,
580    pub task: Option<BoxFuture<'static, TestView>>,
581    pub upgrade: Upgrade,
582}
583
584impl TestViewGenerator {
585    pub fn generate(
586        membership: EpochMembershipCoordinator<TestTypes>,
587        node_key_map: Arc<TestNodeKeyMap>,
588        upgrade: Upgrade,
589    ) -> Self {
590        TestViewGenerator {
591            current_view: None,
592            membership,
593            node_key_map,
594            task: None,
595            upgrade,
596        }
597    }
598
599    pub fn add_upgrade(&mut self, upgrade_proposal_data: UpgradeProposalData) {
600        if let Some(ref view) = self.current_view {
601            self.current_view = Some(TestView {
602                upgrade_data: Some(upgrade_proposal_data),
603                ..view.clone()
604            });
605        } else {
606            tracing::error!("Cannot attach upgrade proposal to the genesis view.");
607        }
608    }
609
610    pub fn add_transactions(&mut self, transactions: Vec<TestTransaction>) {
611        if let Some(ref view) = self.current_view {
612            self.current_view = Some(TestView {
613                transactions,
614                ..view.clone()
615            });
616        } else {
617            tracing::error!("Cannot attach transactions to the genesis view.");
618        }
619    }
620
621    pub fn add_view_sync_finalize(&mut self, view_sync_finalize_data: ViewSyncFinalizeData2) {
622        if let Some(ref view) = self.current_view {
623            self.current_view = Some(TestView {
624                view_sync_finalize_data: Some(view_sync_finalize_data),
625                ..view.clone()
626            });
627        } else {
628            tracing::error!("Cannot attach view sync finalize to the genesis view.");
629        }
630    }
631
632    pub fn add_timeout(&mut self, timeout_data: TimeoutData2) {
633        if let Some(ref view) = self.current_view {
634            self.current_view = Some(TestView {
635                timeout_cert_data: Some(timeout_data),
636                ..view.clone()
637            });
638        } else {
639            tracing::error!("Cannot attach timeout cert to the genesis view.")
640        }
641    }
642
643    /// Advances to the next view by skipping the current view and not adding it to the state tree.
644    /// This is useful when simulating that a timeout has occurred.
645    pub fn advance_view_number_by(&mut self, n: u64) {
646        if let Some(ref view) = self.current_view {
647            self.current_view = Some(TestView {
648                view_number: view.view_number + n,
649                ..view.clone()
650            })
651        } else {
652            tracing::error!("Cannot attach view sync finalize to the genesis view.");
653        }
654    }
655
656    pub async fn next_from_ancestor_view(&mut self, ancestor: TestView) {
657        if let Some(ref view) = self.current_view {
658            self.current_view = Some(view.next_view_from_ancestor(ancestor).await)
659        } else {
660            tracing::error!("Cannot attach ancestor to genesis view.");
661        }
662    }
663}
664
665impl Stream for TestViewGenerator {
666    type Item = TestView;
667
668    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
669        if self.task.is_none() {
670            let cur_view = self.current_view.clone();
671            let upgrade = self.upgrade;
672            self.task = Some(if let Some(view) = cur_view {
673                async move { TestView::next_view(&view).await }.boxed()
674            } else {
675                let epoch_membership = self.membership.clone();
676                let nkm = Arc::clone(&self.node_key_map);
677                async move { TestView::genesis(&epoch_membership, nkm, upgrade).await }.boxed()
678            });
679        }
680
681        match self.task.as_mut().unwrap().as_mut().poll(cx) {
682            Poll::Ready(test_view) => {
683                self.current_view = Some(test_view.clone());
684                self.task = None;
685                Poll::Ready(Some(test_view))
686            },
687            Poll::Pending => Poll::Pending,
688        }
689    }
690}