1use 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) =
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 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 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) = 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) = 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: Vec::new(),
509 upgrade_data: None,
510 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 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}