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 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) =
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 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 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) = 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 let membership = self
276 .membership
277 .membership_for_epoch(self.epoch_number)
278 .unwrap();
279
280 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: Vec::new(),
488 upgrade_data: None,
489 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 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}