Skip to main content

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