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::{future::BoxFuture, FutureExt, Stream};
17use hotshot::types::{BLSPubKey, SignatureKey, SystemContextHandle};
18use hotshot_example_types::{
19    block_types::{TestBlockHeader, TestBlockPayload, TestTransaction},
20    node_types::{MemoryImpl, TestTypes, TestVersions},
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::{
39        consensus_api::ConsensusApi,
40        node_implementation::{ConsensusTime, NodeType, Versions},
41        BlockPayload,
42    },
43    utils::{genesis_epoch_from_version, EpochTransitionIndicator},
44};
45use rand::{thread_rng, Rng};
46use sha2::{Digest, Sha256};
47
48use crate::helpers::{
49    build_cert, build_da_certificate, build_vid_proposal, da_payload_commitment, TestNodeKeyMap,
50};
51
52#[derive(Clone)]
53pub struct TestView {
54    pub da_proposal: Proposal<TestTypes, DaProposal2<TestTypes>>,
55    pub quorum_proposal: Proposal<TestTypes, QuorumProposalWrapper<TestTypes>>,
56    pub leaf: Leaf2<TestTypes>,
57    pub view_number: ViewNumber,
58    pub epoch_number: Option<EpochNumber>,
59    pub membership: EpochMembershipCoordinator<TestTypes>,
60    pub node_key_map: Arc<TestNodeKeyMap>,
61    pub vid_disperse: Proposal<TestTypes, VidDisperse<TestTypes>>,
62    pub vid_proposal: (
63        Vec<Proposal<TestTypes, VidDisperseShare<TestTypes>>>,
64        <TestTypes as NodeType>::SignatureKey,
65    ),
66    pub leader_public_key: <TestTypes as NodeType>::SignatureKey,
67    pub da_certificate: DaCertificate2<TestTypes>,
68    pub transactions: Vec<TestTransaction>,
69    upgrade_data: Option<UpgradeProposalData<TestTypes>>,
70    formed_upgrade_certificate: Option<UpgradeCertificate<TestTypes>>,
71    view_sync_finalize_data: Option<ViewSyncFinalizeData2<TestTypes>>,
72    timeout_cert_data: Option<TimeoutData2<TestTypes>>,
73    upgrade_lock: UpgradeLock<TestTypes, TestVersions>,
74}
75
76impl TestView {
77    async fn find_leader_key_pair(
78        membership: &EpochMembership<TestTypes>,
79        node_key_map: &Arc<TestNodeKeyMap>,
80        view_number: <TestTypes as NodeType>::View,
81    ) -> (
82        <<TestTypes as NodeType>::SignatureKey as SignatureKey>::PrivateKey,
83        <TestTypes as NodeType>::SignatureKey,
84    ) {
85        let leader = membership
86            .leader(view_number)
87            .await
88            .expect("expected Membership::leader to succeed");
89
90        let sk = node_key_map
91            .get(&leader)
92            .expect("expected Membership::leader public key to be in node_key_map");
93
94        (sk.clone(), leader)
95    }
96
97    pub async fn genesis<V: Versions>(
98        membership: &EpochMembershipCoordinator<TestTypes>,
99        node_key_map: Arc<TestNodeKeyMap>,
100    ) -> Self {
101        let genesis_view = ViewNumber::new(1);
102        let genesis_epoch = genesis_epoch_from_version::<V, TestTypes>();
103        let upgrade_lock = UpgradeLock::new();
104
105        let transactions = Vec::new();
106
107        let (block_payload, metadata) =
108            <TestBlockPayload as BlockPayload<TestTypes>>::from_transactions(
109                transactions.clone(),
110                &TestValidatedState::default(),
111                &TestInstanceState::default(),
112            )
113            .await
114            .unwrap();
115
116        let builder_commitment = <TestBlockPayload as BlockPayload<TestTypes>>::builder_commitment(
117            &block_payload,
118            &metadata,
119        );
120        let epoch_membership = membership
121            .membership_for_epoch(genesis_epoch)
122            .await
123            .unwrap();
124        //let (private_key, public_key) = key_pair_for_id::<TestTypes>(*genesis_view);
125        let (private_key, public_key) =
126            Self::find_leader_key_pair(&epoch_membership, &node_key_map, genesis_view).await;
127
128        let leader_public_key = public_key;
129
130        let genesis_version = upgrade_lock.version_infallible(genesis_view).await;
131
132        let payload_commitment = da_payload_commitment::<TestTypes, TestVersions>(
133            &epoch_membership,
134            transactions.clone(),
135            &metadata,
136            genesis_version,
137        )
138        .await;
139
140        let (vid_disperse, vid_proposal) = build_vid_proposal::<TestTypes, TestVersions>(
141            &epoch_membership,
142            genesis_view,
143            genesis_epoch,
144            &block_payload,
145            &metadata,
146            &private_key,
147            &upgrade_lock,
148        )
149        .await;
150
151        let da_certificate = build_da_certificate(
152            &epoch_membership,
153            genesis_view,
154            genesis_epoch,
155            transactions.clone(),
156            &metadata,
157            &public_key,
158            &private_key,
159            &upgrade_lock,
160        )
161        .await
162        .unwrap();
163
164        let block_header = TestBlockHeader::new(
165            &Leaf2::<TestTypes>::genesis::<V>(
166                &TestValidatedState::default(),
167                &TestInstanceState::default(),
168            )
169            .await,
170            payload_commitment,
171            builder_commitment,
172            metadata,
173            genesis_version,
174        );
175
176        let quorum_proposal_inner = QuorumProposalWrapper::<TestTypes> {
177            proposal: QuorumProposal2::<TestTypes> {
178                block_header: block_header.clone(),
179                view_number: genesis_view,
180                epoch: genesis_epoch,
181                justify_qc: QuorumCertificate2::genesis::<TestVersions>(
182                    &TestValidatedState::default(),
183                    &TestInstanceState::default(),
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).await;
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, TestVersions>(
317            &membership,
318            transactions.clone(),
319            &metadata,
320            version,
321        )
322        .await;
323
324        let (vid_disperse, vid_proposal) = build_vid_proposal::<TestTypes, TestVersions>(
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, TestVersions>(
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            TestVersions,
351            QuorumData2<TestTypes>,
352            QuorumVote2<TestTypes>,
353            QuorumCertificate2<TestTypes>,
354        >(
355            quorum_data,
356            &membership,
357            old_view,
358            &old_public_key,
359            &old_private_key,
360            &self.upgrade_lock,
361        )
362        .await;
363
364        let upgrade_certificate = if let Some(ref data) = self.upgrade_data {
365            let cert = build_cert::<
366                TestTypes,
367                TestVersions,
368                UpgradeProposalData<TestTypes>,
369                UpgradeVote<TestTypes>,
370                UpgradeCertificate<TestTypes>,
371            >(
372                data.clone(),
373                &membership,
374                next_view,
375                &public_key,
376                &private_key,
377                &self.upgrade_lock,
378            )
379            .await;
380
381            Some(cert)
382        } else {
383            self.formed_upgrade_certificate.clone()
384        };
385
386        let view_sync_certificate = if let Some(ref data) = self.view_sync_finalize_data {
387            let cert = build_cert::<
388                TestTypes,
389                TestVersions,
390                ViewSyncFinalizeData2<TestTypes>,
391                ViewSyncFinalizeVote2<TestTypes>,
392                ViewSyncFinalizeCertificate2<TestTypes>,
393            >(
394                data.clone(),
395                &membership,
396                next_view,
397                &public_key,
398                &private_key,
399                &self.upgrade_lock,
400            )
401            .await;
402
403            Some(cert)
404        } else {
405            None
406        };
407
408        let timeout_certificate = if let Some(ref data) = self.timeout_cert_data {
409            let cert = build_cert::<
410                TestTypes,
411                TestVersions,
412                TimeoutData2<TestTypes>,
413                TimeoutVote2<TestTypes>,
414                TimeoutCertificate2<TestTypes>,
415            >(
416                data.clone(),
417                &membership,
418                next_view,
419                &public_key,
420                &private_key,
421                &self.upgrade_lock,
422            )
423            .await;
424
425            Some(cert)
426        } else {
427            None
428        };
429
430        let view_change_evidence = if let Some(tc) = timeout_certificate {
431            Some(ViewChangeEvidence2::Timeout(tc))
432        } else {
433            view_sync_certificate.map(ViewChangeEvidence2::ViewSync)
434        };
435
436        let random = thread_rng().gen_range(0..=u64::MAX);
437
438        let block_header = TestBlockHeader {
439            block_number: *next_view,
440            timestamp: *next_view,
441            timestamp_millis: *next_view * 1_000,
442            payload_commitment,
443            builder_commitment,
444            metadata,
445            random,
446            version,
447        };
448
449        let proposal = QuorumProposalWrapper::<TestTypes> {
450            proposal: QuorumProposal2::<TestTypes> {
451                block_header: block_header.clone(),
452                view_number: next_view,
453                epoch: old_epoch,
454                justify_qc: quorum_certificate.clone(),
455                next_epoch_justify_qc: None,
456                upgrade_certificate: upgrade_certificate.clone(),
457                view_change_evidence,
458                next_drb_result: None,
459                state_cert: None,
460            },
461        };
462
463        let mut leaf = Leaf2::from_quorum_proposal(&proposal);
464        leaf.fill_block_payload_unchecked(TestBlockPayload {
465            transactions: transactions.clone(),
466        });
467
468        let signature = <BLSPubKey as SignatureKey>::sign(&private_key, leaf.commit().as_ref())
469            .expect("Failed to sign leaf commitment.");
470
471        let quorum_proposal = Proposal {
472            data: proposal,
473            signature,
474            _pd: PhantomData,
475        };
476
477        let encoded_transactions = Arc::from(TestTransaction::encode(transactions));
478        let encoded_transactions_hash = Sha256::digest(&encoded_transactions);
479        let block_payload_signature =
480            <TestTypes as NodeType>::SignatureKey::sign(&private_key, &encoded_transactions_hash)
481                .expect("Failed to sign block payload");
482
483        let da_proposal_inner = DaProposal2::<TestTypes> {
484            encoded_transactions: encoded_transactions.clone(),
485            metadata,
486            view_number: next_view,
487            epoch: old_epoch,
488            epoch_transition_indicator: EpochTransitionIndicator::NotInTransition,
489        };
490
491        let da_proposal = Proposal {
492            data: da_proposal_inner,
493            signature: block_payload_signature,
494            _pd: PhantomData,
495        };
496
497        let upgrade_lock = UpgradeLock::new();
498
499        TestView {
500            quorum_proposal,
501            leaf,
502            view_number: next_view,
503            epoch_number: self.epoch_number,
504            membership: self.membership.clone(),
505            node_key_map: self.node_key_map.clone(),
506            vid_disperse,
507            vid_proposal: (vid_proposal, public_key),
508            da_certificate,
509            leader_public_key,
510            // Transactions and upgrade data need to be manually injected each view,
511            // so we reset for the next view.
512            transactions: Vec::new(),
513            upgrade_data: None,
514            // We preserve the upgrade_certificate once formed,
515            // and reattach it on every future view until cleared.
516            formed_upgrade_certificate: upgrade_certificate,
517            view_sync_finalize_data: None,
518            timeout_cert_data: None,
519            da_proposal,
520            upgrade_lock,
521        }
522    }
523
524    pub async fn next_view(&self) -> Self {
525        self.next_view_from_ancestor(self.clone()).await
526    }
527
528    pub async fn create_quorum_vote(
529        &self,
530        handle: &SystemContextHandle<TestTypes, MemoryImpl, TestVersions>,
531    ) -> QuorumVote2<TestTypes> {
532        QuorumVote2::<TestTypes>::create_signed_vote(
533            QuorumData2 {
534                leaf_commit: self.leaf.commit(),
535                epoch: self.epoch_number,
536                block_number: Some(self.leaf.height()),
537            },
538            self.view_number,
539            &handle.public_key(),
540            handle.private_key(),
541            &handle.hotshot.upgrade_lock,
542        )
543        .await
544        .expect("Failed to generate a signature on QuorumVote")
545    }
546
547    pub async fn create_upgrade_vote(
548        &self,
549        data: UpgradeProposalData<TestTypes>,
550        handle: &SystemContextHandle<TestTypes, MemoryImpl, TestVersions>,
551    ) -> UpgradeVote<TestTypes> {
552        UpgradeVote::<TestTypes>::create_signed_vote(
553            data,
554            self.view_number,
555            &handle.public_key(),
556            handle.private_key(),
557            &handle.hotshot.upgrade_lock,
558        )
559        .await
560        .expect("Failed to generate a signature on UpgradVote")
561    }
562
563    pub async fn create_da_vote(
564        &self,
565        data: DaData2<TestTypes>,
566        handle: &SystemContextHandle<TestTypes, MemoryImpl, TestVersions>,
567    ) -> DaVote2<TestTypes> {
568        DaVote2::create_signed_vote(
569            data,
570            self.view_number,
571            &handle.public_key(),
572            handle.private_key(),
573            &handle.hotshot.upgrade_lock,
574        )
575        .await
576        .expect("Failed to sign DaData")
577    }
578}
579
580pub struct TestViewGenerator<V: Versions> {
581    pub current_view: Option<TestView>,
582    pub membership: EpochMembershipCoordinator<TestTypes>,
583    pub node_key_map: Arc<TestNodeKeyMap>,
584    pub _pd: PhantomData<fn(V)>,
585    pub task: Option<BoxFuture<'static, TestView>>,
586}
587
588impl<V: Versions> TestViewGenerator<V> {
589    pub fn generate(
590        membership: EpochMembershipCoordinator<TestTypes>,
591        node_key_map: Arc<TestNodeKeyMap>,
592    ) -> Self {
593        TestViewGenerator {
594            current_view: None,
595            membership,
596            node_key_map,
597            _pd: PhantomData,
598            task: None,
599        }
600    }
601
602    pub fn add_upgrade(&mut self, upgrade_proposal_data: UpgradeProposalData<TestTypes>) {
603        if let Some(ref view) = self.current_view {
604            self.current_view = Some(TestView {
605                upgrade_data: Some(upgrade_proposal_data),
606                ..view.clone()
607            });
608        } else {
609            tracing::error!("Cannot attach upgrade proposal to the genesis view.");
610        }
611    }
612
613    pub fn add_transactions(&mut self, transactions: Vec<TestTransaction>) {
614        if let Some(ref view) = self.current_view {
615            self.current_view = Some(TestView {
616                transactions,
617                ..view.clone()
618            });
619        } else {
620            tracing::error!("Cannot attach transactions to the genesis view.");
621        }
622    }
623
624    pub fn add_view_sync_finalize(
625        &mut self,
626        view_sync_finalize_data: ViewSyncFinalizeData2<TestTypes>,
627    ) {
628        if let Some(ref view) = self.current_view {
629            self.current_view = Some(TestView {
630                view_sync_finalize_data: Some(view_sync_finalize_data),
631                ..view.clone()
632            });
633        } else {
634            tracing::error!("Cannot attach view sync finalize to the genesis view.");
635        }
636    }
637
638    pub fn add_timeout(&mut self, timeout_data: TimeoutData2<TestTypes>) {
639        if let Some(ref view) = self.current_view {
640            self.current_view = Some(TestView {
641                timeout_cert_data: Some(timeout_data),
642                ..view.clone()
643            });
644        } else {
645            tracing::error!("Cannot attach timeout cert to the genesis view.")
646        }
647    }
648
649    /// Advances to the next view by skipping the current view and not adding it to the state tree.
650    /// This is useful when simulating that a timeout has occurred.
651    pub fn advance_view_number_by(&mut self, n: u64) {
652        if let Some(ref view) = self.current_view {
653            self.current_view = Some(TestView {
654                view_number: view.view_number + n,
655                ..view.clone()
656            })
657        } else {
658            tracing::error!("Cannot attach view sync finalize to the genesis view.");
659        }
660    }
661
662    pub async fn next_from_ancestor_view(&mut self, ancestor: TestView) {
663        if let Some(ref view) = self.current_view {
664            self.current_view = Some(view.next_view_from_ancestor(ancestor).await)
665        } else {
666            tracing::error!("Cannot attach ancestor to genesis view.");
667        }
668    }
669}
670
671impl<V: Versions> Stream for TestViewGenerator<V> {
672    type Item = TestView;
673
674    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
675        if self.task.is_none() {
676            let cur_view = self.current_view.clone();
677            self.task = Some(if let Some(view) = cur_view {
678                async move { TestView::next_view(&view).await }.boxed()
679            } else {
680                let epoch_membership = self.membership.clone();
681                let nkm = Arc::clone(&self.node_key_map);
682                async move { TestView::genesis::<V>(&epoch_membership, nkm).await }.boxed()
683            });
684        }
685
686        match self.task.as_mut().unwrap().as_mut().poll(cx) {
687            Poll::Ready(test_view) => {
688                self.current_view = Some(test_view.clone());
689                self.task = None;
690                Poll::Ready(Some(test_view))
691            },
692            Poll::Pending => Poll::Pending,
693        }
694    }
695}