1use std::{fmt, hash::Hash, ops::Deref, sync::Arc};
5
6use bytes::Bytes;
7use consensus_config::{
8 AuthorityIndex, DefaultHashFunction, Epoch, ProtocolKeyPair, ProtocolKeySignature,
9 ProtocolPublicKey,
10};
11use consensus_types::block::{BlockDigest, BlockRef, BlockTimestampMs, Round, TransactionIndex};
12use enum_dispatch::enum_dispatch;
13use fastcrypto::hash::HashFunction;
14use itertools::Itertools as _;
15use serde::{Deserialize, Serialize};
16use shared_crypto::intent::{Intent, IntentMessage, IntentScope};
17
18use crate::{
19 commit::CommitVote,
20 context::Context,
21 ensure,
22 error::{ConsensusError, ConsensusResult},
23};
24
25pub(crate) const GENESIS_ROUND: Round = 0;
26
27#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Default, Debug)]
29pub struct Transaction {
30 data: Bytes,
31}
32
33impl Transaction {
34 pub fn new(data: Vec<u8>) -> Self {
35 Self { data: data.into() }
36 }
37
38 pub fn data(&self) -> &[u8] {
39 &self.data
40 }
41
42 pub fn into_data(self) -> Bytes {
43 self.data
44 }
45}
46#[derive(Clone, Deserialize, Serialize)]
50pub(crate) struct BlockTransactionVotes {
51 pub(crate) block_ref: BlockRef,
52 pub(crate) rejects: Vec<TransactionIndex>,
53}
54
55pub(crate) fn max_transaction_vote_targets(context: &Context) -> usize {
58 context.protocol_config.gc_depth().max(1) as usize * context.committee.size()
59}
60
61#[allow(private_interfaces)]
66#[derive(Clone, Deserialize, Serialize)]
67#[enum_dispatch(BlockAPI)]
68pub enum Block {
69 V1(BlockV1),
70 V2(BlockV2),
71 V3(BlockV3),
72}
73
74#[allow(private_interfaces)]
75#[enum_dispatch]
76pub trait BlockAPI {
77 fn epoch(&self) -> Epoch;
78 fn round(&self) -> Round;
79 fn author(&self) -> AuthorityIndex;
80 fn slot(&self) -> Slot;
81 fn timestamp_ms(&self) -> BlockTimestampMs;
82 fn ancestors(&self) -> &[BlockRef];
83
84 fn transactions(&self) -> &[Transaction];
86 fn transactions_data(&self) -> Vec<&[u8]>;
87
88 fn transaction_votes(&self) -> &[BlockTransactionVotes];
90
91 fn transaction_votes_cutoff_round(&self) -> Round;
94
95 fn commit_votes(&self) -> &[CommitVote];
97
98 fn misbehavior_reports(&self) -> &[MisbehaviorReport];
99}
100
101#[derive(Clone, Default, Deserialize, Serialize)]
102pub(crate) struct BlockV1 {
103 epoch: Epoch,
104 round: Round,
105 author: AuthorityIndex,
106 timestamp_ms: BlockTimestampMs,
107 ancestors: Vec<BlockRef>,
108 transactions: Vec<Transaction>,
109 commit_votes: Vec<CommitVote>,
110 misbehavior_reports: Vec<MisbehaviorReport>,
111}
112
113impl BlockV1 {
114 pub(crate) fn new(
115 epoch: Epoch,
116 round: Round,
117 author: AuthorityIndex,
118 timestamp_ms: BlockTimestampMs,
119 ancestors: Vec<BlockRef>,
120 transactions: Vec<Transaction>,
121 commit_votes: Vec<CommitVote>,
122 misbehavior_reports: Vec<MisbehaviorReport>,
123 ) -> BlockV1 {
124 Self {
125 epoch,
126 round,
127 author,
128 timestamp_ms,
129 ancestors,
130 transactions,
131 commit_votes,
132 misbehavior_reports,
133 }
134 }
135
136 fn genesis_block(context: &Context, author: AuthorityIndex) -> Self {
137 Self {
138 epoch: context.committee.epoch(),
139 round: GENESIS_ROUND,
140 author,
141 timestamp_ms: context.epoch_start_timestamp_ms,
142 ancestors: vec![],
143 transactions: vec![],
144 commit_votes: vec![],
145 misbehavior_reports: vec![],
146 }
147 }
148}
149
150impl BlockAPI for BlockV1 {
151 fn epoch(&self) -> Epoch {
152 self.epoch
153 }
154
155 fn round(&self) -> Round {
156 self.round
157 }
158
159 fn author(&self) -> AuthorityIndex {
160 self.author
161 }
162
163 fn slot(&self) -> Slot {
164 Slot::new(self.round, self.author)
165 }
166
167 fn timestamp_ms(&self) -> BlockTimestampMs {
168 self.timestamp_ms
169 }
170
171 fn ancestors(&self) -> &[BlockRef] {
172 &self.ancestors
173 }
174
175 fn transactions(&self) -> &[Transaction] {
176 &self.transactions
177 }
178
179 fn transactions_data(&self) -> Vec<&[u8]> {
180 self.transactions.iter().map(|t| t.data()).collect()
181 }
182
183 fn transaction_votes(&self) -> &[BlockTransactionVotes] {
184 &[]
185 }
186
187 fn transaction_votes_cutoff_round(&self) -> Round {
188 GENESIS_ROUND
189 }
190
191 fn commit_votes(&self) -> &[CommitVote] {
192 &self.commit_votes
193 }
194
195 fn misbehavior_reports(&self) -> &[MisbehaviorReport] {
196 &self.misbehavior_reports
197 }
198}
199
200#[derive(Clone, Default, Deserialize, Serialize)]
201pub(crate) struct BlockV2 {
202 epoch: Epoch,
203 round: Round,
204 author: AuthorityIndex,
205 timestamp_ms: BlockTimestampMs,
206 ancestors: Vec<BlockRef>,
207 transactions: Vec<Transaction>,
208 transaction_votes: Vec<BlockTransactionVotes>,
209 commit_votes: Vec<CommitVote>,
210 misbehavior_reports: Vec<MisbehaviorReport>,
211}
212
213impl BlockV2 {
214 pub(crate) fn new(
215 epoch: Epoch,
216 round: Round,
217 author: AuthorityIndex,
218 timestamp_ms: BlockTimestampMs,
219 ancestors: Vec<BlockRef>,
220 transactions: Vec<Transaction>,
221 transaction_votes: Vec<BlockTransactionVotes>,
222 commit_votes: Vec<CommitVote>,
223 misbehavior_reports: Vec<MisbehaviorReport>,
224 ) -> BlockV2 {
225 Self {
226 epoch,
227 round,
228 author,
229 timestamp_ms,
230 ancestors,
231 transactions,
232 transaction_votes,
233 commit_votes,
234 misbehavior_reports,
235 }
236 }
237
238 fn genesis_block(context: &Context, author: AuthorityIndex) -> Self {
239 Self {
240 epoch: context.committee.epoch(),
241 round: GENESIS_ROUND,
242 author,
243 timestamp_ms: context.epoch_start_timestamp_ms,
244 ancestors: vec![],
245 transactions: vec![],
246 transaction_votes: vec![],
247 commit_votes: vec![],
248 misbehavior_reports: vec![],
249 }
250 }
251}
252
253impl BlockAPI for BlockV2 {
254 fn epoch(&self) -> Epoch {
255 self.epoch
256 }
257
258 fn round(&self) -> Round {
259 self.round
260 }
261
262 fn author(&self) -> AuthorityIndex {
263 self.author
264 }
265
266 fn slot(&self) -> Slot {
267 Slot::new(self.round, self.author)
268 }
269
270 fn timestamp_ms(&self) -> BlockTimestampMs {
271 self.timestamp_ms
272 }
273
274 fn ancestors(&self) -> &[BlockRef] {
275 &self.ancestors
276 }
277
278 fn transactions(&self) -> &[Transaction] {
279 &self.transactions
280 }
281
282 fn transactions_data(&self) -> Vec<&[u8]> {
283 self.transactions.iter().map(|t| t.data()).collect()
284 }
285
286 fn transaction_votes(&self) -> &[BlockTransactionVotes] {
287 &self.transaction_votes
288 }
289
290 fn transaction_votes_cutoff_round(&self) -> Round {
291 GENESIS_ROUND
292 }
293
294 fn commit_votes(&self) -> &[CommitVote] {
295 &self.commit_votes
296 }
297
298 fn misbehavior_reports(&self) -> &[MisbehaviorReport] {
299 &self.misbehavior_reports
300 }
301}
302
303#[derive(Clone, Default, Deserialize, Serialize)]
304pub(crate) struct BlockV3 {
305 epoch: Epoch,
306 round: Round,
307 author: AuthorityIndex,
308 timestamp_ms: BlockTimestampMs,
309 ancestors: Vec<BlockRef>,
310 transactions: Vec<Transaction>,
311 transaction_votes: Vec<BlockTransactionVotes>,
312 transaction_votes_cutoff_round: Round,
313 commit_votes: Vec<CommitVote>,
314 misbehavior_reports: Vec<MisbehaviorReport>,
315}
316
317#[allow(dead_code)]
318impl BlockV3 {
319 pub(crate) fn new(
320 epoch: Epoch,
321 round: Round,
322 author: AuthorityIndex,
323 timestamp_ms: BlockTimestampMs,
324 ancestors: Vec<BlockRef>,
325 transactions: Vec<Transaction>,
326 transaction_votes: Vec<BlockTransactionVotes>,
327 transaction_votes_cutoff_round: Round,
328 commit_votes: Vec<CommitVote>,
329 misbehavior_reports: Vec<MisbehaviorReport>,
330 ) -> BlockV3 {
331 Self {
332 epoch,
333 round,
334 author,
335 timestamp_ms,
336 ancestors,
337 transactions,
338 transaction_votes,
339 transaction_votes_cutoff_round,
340 commit_votes,
341 misbehavior_reports,
342 }
343 }
344
345 fn genesis_block(context: &Context, author: AuthorityIndex) -> Self {
346 Self {
347 epoch: context.committee.epoch(),
348 round: GENESIS_ROUND,
349 author,
350 timestamp_ms: context.epoch_start_timestamp_ms,
351 ancestors: vec![],
352 transactions: vec![],
353 transaction_votes: vec![],
354 transaction_votes_cutoff_round: GENESIS_ROUND,
355 commit_votes: vec![],
356 misbehavior_reports: vec![],
357 }
358 }
359}
360
361impl BlockAPI for BlockV3 {
362 fn epoch(&self) -> Epoch {
363 self.epoch
364 }
365
366 fn round(&self) -> Round {
367 self.round
368 }
369
370 fn author(&self) -> AuthorityIndex {
371 self.author
372 }
373
374 fn slot(&self) -> Slot {
375 Slot::new(self.round, self.author)
376 }
377
378 fn timestamp_ms(&self) -> BlockTimestampMs {
379 self.timestamp_ms
380 }
381
382 fn ancestors(&self) -> &[BlockRef] {
383 &self.ancestors
384 }
385
386 fn transactions(&self) -> &[Transaction] {
387 &self.transactions
388 }
389
390 fn transactions_data(&self) -> Vec<&[u8]> {
391 self.transactions.iter().map(|t| t.data()).collect()
392 }
393
394 fn transaction_votes(&self) -> &[BlockTransactionVotes] {
395 &self.transaction_votes
396 }
397
398 fn transaction_votes_cutoff_round(&self) -> Round {
399 self.transaction_votes_cutoff_round
400 }
401
402 fn commit_votes(&self) -> &[CommitVote] {
403 &self.commit_votes
404 }
405
406 fn misbehavior_reports(&self) -> &[MisbehaviorReport] {
407 &self.misbehavior_reports
408 }
409}
410
411#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
414pub struct Slot {
415 pub round: Round,
416 pub authority: AuthorityIndex,
417}
418
419impl Slot {
420 pub fn new(round: Round, authority: AuthorityIndex) -> Self {
421 Self { round, authority }
422 }
423
424 pub fn new_for_test(round: Round, authority: u32) -> Self {
425 Self {
426 round,
427 authority: AuthorityIndex::new_for_test(authority),
428 }
429 }
430}
431
432impl From<BlockRef> for Slot {
433 fn from(value: BlockRef) -> Self {
434 Slot::new(value.round, value.author)
435 }
436}
437
438impl fmt::Display for Slot {
440 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441 write!(f, "{}{}", self.authority, self.round)
442 }
443}
444
445impl fmt::Debug for Slot {
446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447 write!(f, "{}", self)
448 }
449}
450
451#[derive(Deserialize, Serialize)]
456pub(crate) struct SignedBlock {
457 inner: Block,
458 signature: Bytes,
459}
460
461impl SignedBlock {
462 pub(crate) fn new_genesis(block: Block) -> Self {
464 Self {
465 inner: block,
466 signature: Bytes::default(),
467 }
468 }
469
470 pub(crate) fn new(block: Block, protocol_keypair: &ProtocolKeyPair) -> ConsensusResult<Self> {
471 let signature = compute_block_signature(&block, protocol_keypair)?;
472 Ok(Self {
473 inner: block,
474 signature: Bytes::copy_from_slice(signature.to_bytes()),
475 })
476 }
477
478 pub(crate) fn signature(&self) -> &Bytes {
479 &self.signature
480 }
481
482 pub(crate) fn verify_signature(&self, context: &Context) -> ConsensusResult<()> {
485 let block = &self.inner;
486 let committee = &context.committee;
487 ensure!(
488 committee.is_valid_index(block.author()),
489 ConsensusError::InvalidAuthorityIndex {
490 loc: format!("verifying signature {}", block.slot()),
491 index: block.author(),
492 max: committee.size() - 1
493 }
494 );
495 let authority = committee.authority(block.author());
496 verify_block_signature(block, self.signature(), &authority.protocol_key)
497 }
498
499 pub(crate) fn serialize(&self) -> Result<Bytes, bcs::Error> {
501 let bytes = bcs::to_bytes(self)?;
502 Ok(bytes.into())
503 }
504
505 #[cfg(test)]
507 pub(crate) fn clear_signature(&mut self) {
508 self.signature = Bytes::default();
509 }
510}
511
512#[derive(Serialize, Deserialize)]
516struct InnerBlockDigest([u8; consensus_config::DIGEST_LENGTH]);
517
518fn compute_inner_block_digest(block: &Block) -> ConsensusResult<InnerBlockDigest> {
520 let mut hasher = DefaultHashFunction::new();
521 bcs::serialize_into(&mut hasher, block).map_err(ConsensusError::SerializationFailure)?;
522 Ok(InnerBlockDigest(hasher.finalize().into()))
523}
524
525fn to_consensus_block_intent(digest: InnerBlockDigest) -> IntentMessage<InnerBlockDigest> {
527 IntentMessage::new(Intent::consensus_app(IntentScope::ConsensusBlock), digest)
528}
529
530fn compute_block_signature(
535 block: &Block,
536 protocol_keypair: &ProtocolKeyPair,
537) -> ConsensusResult<ProtocolKeySignature> {
538 let digest = compute_inner_block_digest(block)?;
539 let message = bcs::to_bytes(&to_consensus_block_intent(digest))
540 .map_err(ConsensusError::SerializationFailure)?;
541 Ok(protocol_keypair.sign(&message))
542}
543
544fn verify_block_signature(
545 block: &Block,
546 signature: &[u8],
547 protocol_pubkey: &ProtocolPublicKey,
548) -> ConsensusResult<()> {
549 let digest = compute_inner_block_digest(block)?;
550 let message = bcs::to_bytes(&to_consensus_block_intent(digest))
551 .map_err(ConsensusError::SerializationFailure)?;
552 let sig =
553 ProtocolKeySignature::from_bytes(signature).map_err(ConsensusError::MalformedSignature)?;
554 protocol_pubkey
555 .verify(&message, &sig)
556 .map_err(ConsensusError::SignatureVerificationFailure)
557}
558
559impl Deref for SignedBlock {
561 type Target = Block;
562
563 fn deref(&self) -> &Self::Target {
564 &self.inner
565 }
566}
567
568#[derive(Clone)]
571pub struct VerifiedBlock {
572 block: Arc<SignedBlock>,
573
574 digest: BlockDigest,
576 serialized: Bytes,
577}
578
579impl VerifiedBlock {
580 pub(crate) fn new_verified(signed_block: SignedBlock, serialized: Bytes) -> Self {
582 let digest = Self::compute_digest(&serialized);
583 VerifiedBlock {
584 block: Arc::new(signed_block),
585 digest,
586 serialized,
587 }
588 }
589
590 pub fn new_for_test(block: Block) -> Self {
592 let signed_block = SignedBlock {
594 inner: block,
595 signature: Default::default(),
596 };
597 let serialized: Bytes = bcs::to_bytes(&signed_block)
598 .expect("Serialization should not fail")
599 .into();
600 let digest = Self::compute_digest(&serialized);
601 VerifiedBlock {
602 block: Arc::new(signed_block),
603 digest,
604 serialized,
605 }
606 }
607
608 pub fn reference(&self) -> BlockRef {
610 BlockRef {
611 round: self.round(),
612 author: self.author(),
613 digest: self.digest(),
614 }
615 }
616
617 pub(crate) fn digest(&self) -> BlockDigest {
618 self.digest
619 }
620
621 pub(crate) fn serialized(&self) -> &Bytes {
623 &self.serialized
624 }
625
626 pub(crate) fn compute_digest(serialized: &[u8]) -> BlockDigest {
628 let mut hasher = DefaultHashFunction::new();
629 hasher.update(serialized);
630 BlockDigest(hasher.finalize().into())
631 }
632}
633
634impl Deref for VerifiedBlock {
636 type Target = Block;
637
638 fn deref(&self) -> &Self::Target {
639 &self.block.inner
640 }
641}
642
643impl PartialEq for VerifiedBlock {
644 fn eq(&self, other: &Self) -> bool {
645 self.digest() == other.digest()
646 }
647}
648
649impl fmt::Display for VerifiedBlock {
650 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
651 write!(f, "{}", self.reference())
652 }
653}
654
655impl fmt::Debug for VerifiedBlock {
657 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
658 write!(
659 f,
660 "{:?}([{}];{}ms;{}t;{}c)",
661 self.reference(),
662 self.ancestors().iter().map(|a| a.to_string()).join(", "),
663 self.timestamp_ms(),
664 self.transactions().len(),
665 self.commit_votes().len(),
666 )
667 }
668}
669
670#[derive(Clone, Debug)]
674pub(crate) struct ExtendedBlock {
675 pub block: VerifiedBlock,
676 pub excluded_ancestors: Vec<BlockRef>,
677}
678
679pub(crate) fn genesis_blocks(context: &Context) -> Vec<VerifiedBlock> {
682 context
683 .committee
684 .authorities()
685 .map(|(authority_index, _)| {
686 let block = if context.protocol_config.enable_v3() {
687 Block::V3(BlockV3::genesis_block(context, authority_index))
688 } else if context.protocol_config.transaction_voting_enabled() {
689 Block::V2(BlockV2::genesis_block(context, authority_index))
690 } else {
691 Block::V1(BlockV1::genesis_block(context, authority_index))
692 };
693 let signed_block = SignedBlock::new_genesis(block);
694 let serialized = signed_block
695 .serialize()
696 .expect("Genesis block serialization failed.");
697 VerifiedBlock::new_verified(signed_block, serialized)
699 })
700 .collect::<Vec<VerifiedBlock>>()
701}
702
703#[derive(Clone)]
706pub struct TestBlock {
707 block: BlockV2,
708}
709
710impl TestBlock {
711 pub fn new(round: Round, author: u32) -> Self {
712 Self {
713 block: BlockV2 {
714 round,
715 author: AuthorityIndex::new_for_test(author),
716 ..Default::default()
717 },
718 }
719 }
720
721 pub fn set_epoch(mut self, epoch: Epoch) -> Self {
722 self.block.epoch = epoch;
723 self
724 }
725
726 pub fn set_round(mut self, round: Round) -> Self {
727 self.block.round = round;
728 self
729 }
730
731 pub fn set_author(mut self, author: AuthorityIndex) -> Self {
732 self.block.author = author;
733 self
734 }
735
736 pub fn set_timestamp_ms(mut self, timestamp_ms: BlockTimestampMs) -> Self {
737 self.block.timestamp_ms = timestamp_ms;
738 self
739 }
740
741 pub fn set_ancestors(mut self, mut ancestors: Vec<BlockRef>) -> Self {
745 ancestors.sort_by(|a, b| {
746 if a.author == self.block.author {
747 return std::cmp::Ordering::Less;
748 }
749 if b.author == self.block.author {
750 return std::cmp::Ordering::Greater;
751 }
752 a.author.cmp(&b.author)
753 });
754 self.block.ancestors = ancestors;
755 self
756 }
757
758 pub fn set_ancestors_raw(mut self, ancestors: Vec<BlockRef>) -> Self {
760 self.block.ancestors = ancestors;
761 self
762 }
763
764 pub fn set_transactions(mut self, transactions: Vec<Transaction>) -> Self {
765 self.block.transactions = transactions;
766 self
767 }
768
769 pub(crate) fn set_transaction_votes(mut self, votes: Vec<BlockTransactionVotes>) -> Self {
770 self.block.transaction_votes = votes;
771 self
772 }
773
774 #[cfg(test)]
775 pub(crate) fn set_commit_votes(mut self, commit_votes: Vec<CommitVote>) -> Self {
776 self.block.commit_votes = commit_votes;
777 self
778 }
779
780 pub fn build(self) -> Block {
781 Block::V2(self.block)
782 }
783
784 #[cfg(test)]
785 pub(crate) fn build_v3(self, transaction_votes_cutoff_round: Round) -> Block {
786 let block = self.block;
787 Block::V3(BlockV3::new(
788 block.epoch,
789 block.round,
790 block.author,
791 block.timestamp_ms,
792 block.ancestors,
793 block.transactions,
794 block.transaction_votes,
795 transaction_votes_cutoff_round,
796 block.commit_votes,
797 block.misbehavior_reports,
798 ))
799 }
800}
801
802#[derive(Clone, Serialize, Deserialize, Debug)]
804pub struct MisbehaviorReport {
805 pub target: AuthorityIndex,
806 pub proof: MisbehaviorProof,
807}
808
809#[derive(Clone, Serialize, Deserialize, Debug)]
811pub enum MisbehaviorProof {
812 InvalidBlock(BlockRef),
813}
814
815#[cfg(test)]
819mod tests {
820 use std::sync::Arc;
821
822 use fastcrypto::error::FastCryptoError;
823
824 use crate::{
825 block::{BlockAPI, SignedBlock, TestBlock, genesis_blocks, max_transaction_vote_targets},
826 context::Context,
827 error::ConsensusError,
828 };
829
830 #[tokio::test]
831 async fn test_max_transaction_vote_targets() {
832 let (mut context, _) = Context::new_for_test(4);
833 context.protocol_config.set_gc_depth_for_testing(5);
834 assert_eq!(max_transaction_vote_targets(&context), 20);
835
836 context.protocol_config.set_gc_depth_for_testing(0);
838 assert_eq!(max_transaction_vote_targets(&context), 4);
839 }
840
841 #[tokio::test]
842 async fn test_sign_and_verify() {
843 let (context, key_pairs) = Context::new_for_test(4);
844 let context = Arc::new(context);
845
846 let block = TestBlock::new(10, 2).build();
848
849 let author_two_key = &key_pairs[2].1;
851 let signed_block = SignedBlock::new(block, author_two_key).expect("Shouldn't fail signing");
852
853 let result = signed_block.verify_signature(&context);
855 assert!(result.is_ok());
856
857 let block = TestBlock::new(10, 2).build();
859 let author_one_key = &key_pairs[1].1;
860 let signed_block = SignedBlock::new(block, author_one_key).expect("Shouldn't fail signing");
861
862 let result = signed_block.verify_signature(&context);
864 match result.err().unwrap() {
865 ConsensusError::SignatureVerificationFailure(err) => {
866 assert_eq!(err, FastCryptoError::InvalidSignature);
867 }
868 err => panic!("Unexpected error: {err:?}"),
869 }
870 }
871
872 #[tokio::test]
873 async fn test_genesis_blocks() {
874 let (context, _) = Context::new_for_test(4);
875 const TIMESTAMP_MS: u64 = 1000;
876 let context = Arc::new(context.with_epoch_start_timestamp_ms(TIMESTAMP_MS));
877 let blocks = genesis_blocks(&context);
878 for (i, block) in blocks.into_iter().enumerate() {
879 assert_eq!(block.author().value(), i);
880 assert_eq!(block.round(), 0);
881 assert_eq!(block.timestamp_ms(), TIMESTAMP_MS);
882 }
883 }
884}