1use bytes::Bytes;
5use consensus_types::block::{BlockRef, NUM_RESERVED_TRANSACTION_INDICES, TransactionIndex};
6use std::{collections::BTreeSet, sync::Arc};
7
8use crate::{
9 VerifiedBlock,
10 block::{
11 Block, BlockAPI, GENESIS_ROUND, SignedBlock, genesis_blocks, max_transaction_vote_targets,
12 },
13 context::Context,
14 error::{ConsensusError, ConsensusResult},
15 transaction::TransactionVerifier,
16};
17
18pub trait BlockVerifier: Send + Sync + 'static {
19 #[allow(private_interfaces)]
30 fn verify_and_vote(
31 &self,
32 block: SignedBlock,
33 serialized_block: Bytes,
34 ) -> ConsensusResult<(VerifiedBlock, Vec<TransactionIndex>)>;
35
36 fn vote(&self, block: &VerifiedBlock) -> ConsensusResult<Vec<TransactionIndex>>;
40}
41
42pub(crate) struct SignedBlockVerifier {
48 context: Arc<Context>,
49 genesis: BTreeSet<BlockRef>,
50 transaction_verifier: Arc<dyn TransactionVerifier>,
51}
52
53impl SignedBlockVerifier {
54 pub(crate) fn new(
55 context: Arc<Context>,
56 transaction_verifier: Arc<dyn TransactionVerifier>,
57 ) -> Self {
58 let genesis = genesis_blocks(&context)
59 .into_iter()
60 .map(|b| b.reference())
61 .collect();
62 Self {
63 context,
64 genesis,
65 transaction_verifier,
66 }
67 }
68
69 fn verify_block(&self, block: &SignedBlock) -> ConsensusResult<()> {
70 let committee = &self.context.committee;
71 if block.epoch() != committee.epoch() {
74 return Err(ConsensusError::WrongEpoch {
75 expected: committee.epoch(),
76 actual: block.epoch(),
77 });
78 }
79 if block.round() == 0 {
80 return Err(ConsensusError::UnexpectedGenesisBlock);
81 }
82 let enable_v3 = self.context.protocol_config.enable_v3();
86 let version_matches = match **block {
87 Block::V1(_) | Block::V2(_) => !enable_v3,
88 Block::V3(_) => enable_v3,
89 };
90 if !version_matches {
91 return Err(ConsensusError::UnexpectedBlockVersion {
92 version: if enable_v3 {
93 format!("block {} is not V3 but v3 is enabled", block.slot())
94 } else {
95 format!("block {} is V3 but v3 is disabled", block.slot())
96 },
97 });
98 }
99 if !committee.is_valid_index(block.author()) {
100 return Err(ConsensusError::InvalidAuthorityIndex {
101 loc: format!("verifying block {}", block.slot()),
102 index: block.author(),
103 max: committee.size() - 1,
104 });
105 }
106
107 block.verify_signature(&self.context)?;
109
110 if block.ancestors().len() > committee.size() {
113 return Err(ConsensusError::TooManyAncestors(
114 block.ancestors().len(),
115 committee.size(),
116 ));
117 }
118 if block.ancestors().is_empty() {
119 return Err(ConsensusError::InsufficientParentStakes {
120 parent_stakes: 0,
121 quorum: committee.quorum_threshold(),
122 });
123 }
124 let mut seen_ancestors = vec![false; committee.size()];
125 let mut parent_stakes = 0;
126 for (i, ancestor) in block.ancestors().iter().enumerate() {
127 if !committee.is_valid_index(ancestor.author) {
128 return Err(ConsensusError::InvalidAuthorityIndex {
129 loc: format!("ancestor {}", ancestor),
130 index: ancestor.author,
131 max: committee.size() - 1,
132 });
133 }
134 if (i == 0 && ancestor.author != block.author())
135 || (i > 0 && ancestor.author == block.author())
136 {
137 return Err(ConsensusError::InvalidAncestorPosition {
138 block_authority: block.author(),
139 ancestor_authority: ancestor.author,
140 position: i,
141 });
142 }
143 if ancestor.round >= block.round() {
144 return Err(ConsensusError::InvalidAncestorRound {
145 ancestor: ancestor.round,
146 block: block.round(),
147 });
148 }
149 if ancestor.round == GENESIS_ROUND && !self.genesis.contains(ancestor) {
150 return Err(ConsensusError::InvalidGenesisAncestor(*ancestor));
151 }
152 if seen_ancestors[ancestor.author] {
153 return Err(ConsensusError::DuplicatedAncestorsAuthority(
154 ancestor.author,
155 ));
156 }
157 seen_ancestors[ancestor.author] = true;
158 if ancestor.round == block.round().checked_sub(1).unwrap() {
160 parent_stakes += committee.stake(ancestor.author);
161 }
162 }
163 if !committee.reached_quorum(parent_stakes) {
164 return Err(ConsensusError::InsufficientParentStakes {
165 parent_stakes,
166 quorum: committee.quorum_threshold(),
167 });
168 }
169
170 if enable_v3 {
171 let cutoff = block.transaction_votes_cutoff_round();
172 if cutoff >= block.round() {
173 return Err(ConsensusError::InvalidTransactionVotesCutoff {
174 cutoff,
175 block: block.round(),
176 });
177 }
178 self.check_transaction_votes(block)?;
179 }
180
181 let batch: Vec<_> = block.transactions().iter().map(|t| t.data()).collect();
182 self.check_transactions(&batch)
183 }
184
185 fn check_transaction_votes(&self, block: &SignedBlock) -> ConsensusResult<()> {
186 let transaction_votes = block.transaction_votes();
187 if transaction_votes.is_empty() {
188 return Ok(());
189 }
190 let vote_target_limit = max_transaction_vote_targets(&self.context);
192 if transaction_votes.len() > vote_target_limit {
193 return Err(ConsensusError::InvalidTransactionVotes(format!(
194 "block at round {} from {} has {} vote targets but the limit is {}",
195 block.round(),
196 block.author(),
197 transaction_votes.len(),
198 vote_target_limit,
199 )));
200 }
201 let cutoff_round = block.transaction_votes_cutoff_round();
202 let transaction_limit = self
203 .context
204 .protocol_config
205 .max_num_transactions_in_block()
206 .min(
207 TransactionIndex::MAX
208 .saturating_sub(NUM_RESERVED_TRANSACTION_INDICES)
209 .into(),
210 ) as u16;
211
212 let mut vote_targets = BTreeSet::new();
213 for votes in transaction_votes {
214 if !vote_targets.insert(votes.block_ref) {
215 return Err(ConsensusError::InvalidTransactionVotes(format!(
216 "vote target {} appears more than once",
217 votes.block_ref,
218 )));
219 }
220 if votes.block_ref.round >= block.round() {
221 return Err(ConsensusError::InvalidTransactionVotes(format!(
222 "vote target {} must have a round less than block round {} from {}",
223 votes.block_ref,
224 block.round(),
225 block.author(),
226 )));
227 }
228 if votes.block_ref.round <= cutoff_round {
229 return Err(ConsensusError::InvalidTransactionVotes(format!(
230 "vote target {} is at or below cutoff round {}",
231 votes.block_ref, cutoff_round,
232 )));
233 }
234 if !self
235 .context
236 .committee
237 .is_valid_index(votes.block_ref.author)
238 {
239 return Err(ConsensusError::InvalidAuthorityIndex {
240 loc: format!("transaction vote block {}", votes.block_ref),
241 index: votes.block_ref.author,
242 max: self.context.committee.size() - 1,
243 });
244 }
245 if votes.rejects.is_empty() {
246 return Err(ConsensusError::InvalidTransactionVotes(format!(
247 "vote target {} has no reject indices",
248 votes.block_ref,
249 )));
250 }
251 if votes.rejects.len() > transaction_limit as usize {
252 return Err(ConsensusError::InvalidTransactionVotes(format!(
253 "vote target {} has {} reject indices but the limit is {}",
254 votes.block_ref,
255 votes.rejects.len(),
256 transaction_limit,
257 )));
258 }
259 if votes.rejects.windows(2).any(|pair| pair[0] >= pair[1]) {
260 return Err(ConsensusError::InvalidTransactionVotes(format!(
261 "reject indices for vote target {} are not strictly increasing: {:?}",
262 votes.block_ref, votes.rejects,
263 )));
264 }
265 if let Some(reject) = votes.rejects.last()
266 && *reject >= transaction_limit
267 {
268 return Err(ConsensusError::InvalidTransactionVotes(format!(
269 "reject index {} for vote target {} is not below limit {}",
270 reject, votes.block_ref, transaction_limit,
271 )));
272 }
273 }
274
275 Ok(())
276 }
277
278 pub(crate) fn check_transactions(&self, batch: &[&[u8]]) -> ConsensusResult<()> {
279 let max_transaction_size_limit =
280 self.context.protocol_config.max_transaction_size_bytes() as usize;
281 for t in batch {
282 if t.len() > max_transaction_size_limit && max_transaction_size_limit > 0 {
283 return Err(ConsensusError::TransactionTooLarge {
284 size: t.len(),
285 limit: max_transaction_size_limit,
286 });
287 }
288 }
289
290 let max_num_transactions_limit =
291 self.context.protocol_config.max_num_transactions_in_block() as usize;
292 if batch.len() > max_num_transactions_limit && max_num_transactions_limit > 0 {
293 return Err(ConsensusError::TooManyTransactions {
294 count: batch.len(),
295 limit: max_num_transactions_limit,
296 });
297 }
298
299 let total_transactions_size_limit = self
300 .context
301 .protocol_config
302 .max_transactions_in_block_bytes() as usize;
303 if batch.iter().map(|t| t.len()).sum::<usize>() > total_transactions_size_limit
304 && total_transactions_size_limit > 0
305 {
306 return Err(ConsensusError::TooManyTransactionBytes {
307 size: batch.len(),
308 limit: total_transactions_size_limit,
309 });
310 }
311 Ok(())
312 }
313}
314
315impl BlockVerifier for SignedBlockVerifier {
317 fn verify_and_vote(
318 &self,
319 block: SignedBlock,
320 serialized_block: Bytes,
321 ) -> ConsensusResult<(VerifiedBlock, Vec<TransactionIndex>)> {
322 self.verify_block(&block)?;
323
324 let verified_block = VerifiedBlock::new_verified(block, serialized_block);
326
327 let rejected_transactions = if self.context.protocol_config.transaction_voting_enabled() {
328 self.vote(&verified_block)?
329 } else {
330 self.transaction_verifier
331 .verify_batch(
332 &verified_block.reference(),
333 &verified_block.transactions_data(),
334 )
335 .map_err(|e| ConsensusError::InvalidTransaction(e.to_string()))?;
336 vec![]
337 };
338 Ok((verified_block, rejected_transactions))
339 }
340
341 fn vote(&self, block: &VerifiedBlock) -> ConsensusResult<Vec<TransactionIndex>> {
342 self.transaction_verifier
343 .verify_and_vote_batch(&block.reference(), &block.transactions_data())
344 .map_err(|e| ConsensusError::InvalidTransaction(e.to_string()))
345 }
346}
347
348pub struct NoopBlockVerifier;
350
351impl BlockVerifier for NoopBlockVerifier {
352 #[allow(private_interfaces)]
353 fn verify_and_vote(
354 &self,
355 _block: SignedBlock,
356 _serialized_block: Bytes,
357 ) -> ConsensusResult<(VerifiedBlock, Vec<TransactionIndex>)> {
358 Ok((
359 VerifiedBlock::new_verified(_block, _serialized_block),
360 vec![],
361 ))
362 }
363
364 fn vote(&self, _block: &VerifiedBlock) -> ConsensusResult<Vec<TransactionIndex>> {
365 Ok(vec![])
366 }
367}
368
369#[cfg(test)]
370mod test {
371 use consensus_config::{AuthorityIndex, ConsensusProtocolConfig};
372 use consensus_types::block::{BlockDigest, BlockRef, TransactionIndex};
373
374 use super::*;
375 use crate::{
376 block::{Block, BlockTransactionVotes, BlockV1, GENESIS_ROUND, TestBlock, Transaction},
377 context::Context,
378 transaction::{TransactionVerifier, ValidationError},
379 };
380
381 struct TxnSizeVerifier {}
382
383 impl TransactionVerifier for TxnSizeVerifier {
384 fn verify_batch(
386 &self,
387 _block_ref: &BlockRef,
388 transactions: &[&[u8]],
389 ) -> Result<(), ValidationError> {
390 for txn in transactions {
391 if txn.len() < 4 {
392 return Err(ValidationError::InvalidTransaction(format!(
393 "Length {} is too short!",
394 txn.len()
395 )));
396 }
397 }
398 Ok(())
399 }
400
401 fn verify_and_vote_batch(
404 &self,
405 _block_ref: &BlockRef,
406 batch: &[&[u8]],
407 ) -> Result<Vec<TransactionIndex>, ValidationError> {
408 let mut rejected_indices = vec![];
409 for (i, txn) in batch.iter().enumerate() {
410 if txn.len() < 4 {
411 return Err(ValidationError::InvalidTransaction(format!(
412 "Length {} is too short!",
413 txn.len()
414 )));
415 }
416 if txn.len() < 16 {
417 rejected_indices.push(i as TransactionIndex);
418 }
419 }
420 Ok(rejected_indices)
421 }
422 }
423
424 #[tokio::test]
425 async fn test_verify_block() {
426 let (context, keypairs) = Context::new_for_test(4);
427 let context = Arc::new(context);
428 const AUTHOR: u32 = 2;
429 let author_protocol_keypair = &keypairs[AUTHOR as usize].1;
430 let verifier = SignedBlockVerifier::new(context.clone(), Arc::new(TxnSizeVerifier {}));
431
432 let test_block = TestBlock::new(10, AUTHOR)
433 .set_ancestors_raw(vec![
434 BlockRef::new(9, AuthorityIndex::new_for_test(2), BlockDigest::MIN),
435 BlockRef::new(9, AuthorityIndex::new_for_test(0), BlockDigest::MIN),
436 BlockRef::new(9, AuthorityIndex::new_for_test(1), BlockDigest::MIN),
437 BlockRef::new(7, AuthorityIndex::new_for_test(3), BlockDigest::MIN),
438 ])
439 .set_transactions(vec![Transaction::new(vec![4; 8])]);
440
441 {
443 let block = test_block.clone().build();
444 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
445 verifier.verify_block(&signed_block).unwrap();
446 }
447
448 {
450 let block = test_block.clone().set_epoch(1).build();
451 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
452 assert!(matches!(
453 verifier.verify_block(&signed_block),
454 Err(ConsensusError::WrongEpoch {
455 expected: _,
456 actual: _
457 })
458 ));
459 }
460
461 {
463 let block = test_block.clone().set_round(0).build();
464 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
465 assert!(matches!(
466 verifier.verify_block(&signed_block),
467 Err(ConsensusError::UnexpectedGenesisBlock)
468 ));
469 }
470
471 {
473 let block = test_block
474 .clone()
475 .set_author(AuthorityIndex::new_for_test(4))
476 .build();
477 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
478 assert!(matches!(
479 verifier.verify_block(&signed_block),
480 Err(ConsensusError::InvalidAuthorityIndex {
481 loc: _,
482 index: _,
483 max: _
484 })
485 ));
486 }
487
488 {
490 let block = test_block
491 .clone()
492 .set_author(AuthorityIndex::new_for_test(1))
493 .build();
494 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
495 assert!(matches!(
496 verifier.verify_block(&signed_block),
497 Err(ConsensusError::SignatureVerificationFailure(_))
498 ));
499 }
500
501 {
503 let block = test_block.clone().build();
504 let signed_block = SignedBlock::new(block, &keypairs[3].1).unwrap();
505 assert!(matches!(
506 verifier.verify_block(&signed_block),
507 Err(ConsensusError::SignatureVerificationFailure(_))
508 ));
509 }
510
511 {
513 let block = test_block.clone().build();
514 let mut signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
515 signed_block.clear_signature();
516 assert!(matches!(
517 verifier.verify_block(&signed_block),
518 Err(ConsensusError::MalformedSignature(_))
519 ));
520 }
521
522 {
524 let block = test_block
525 .clone()
526 .set_ancestors_raw(vec![
527 BlockRef::new(9, AuthorityIndex::new_for_test(2), BlockDigest::MIN),
528 BlockRef::new(9, AuthorityIndex::new_for_test(0), BlockDigest::MIN),
529 BlockRef::new(9, AuthorityIndex::new_for_test(1), BlockDigest::MIN),
530 BlockRef::new(10, AuthorityIndex::new_for_test(3), BlockDigest::MIN),
531 ])
532 .build();
533 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
534 assert!(matches!(
535 verifier.verify_block(&signed_block),
536 Err(ConsensusError::InvalidAncestorRound {
537 ancestor: _,
538 block: _
539 })
540 ));
541 }
542
543 {
545 let block = test_block
546 .clone()
547 .set_ancestors_raw(vec![
548 BlockRef::new(9, AuthorityIndex::new_for_test(2), BlockDigest::MIN),
549 BlockRef::new(9, AuthorityIndex::new_for_test(0), BlockDigest::MIN),
550 BlockRef::new(8, AuthorityIndex::new_for_test(1), BlockDigest::MIN),
551 BlockRef::new(8, AuthorityIndex::new_for_test(3), BlockDigest::MIN),
552 ])
553 .build();
554 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
555 assert!(matches!(
556 verifier.verify_block(&signed_block),
557 Err(ConsensusError::InsufficientParentStakes {
558 parent_stakes: _,
559 quorum: _
560 })
561 ));
562 }
563
564 {
566 let block = test_block
567 .clone()
568 .set_ancestors_raw(vec![
569 BlockRef::new(9, AuthorityIndex::new_for_test(2), BlockDigest::MIN),
570 BlockRef::new(9, AuthorityIndex::new_for_test(0), BlockDigest::MIN),
571 BlockRef::new(8, AuthorityIndex::new_for_test(1), BlockDigest::MIN),
572 BlockRef::new(8, AuthorityIndex::new_for_test(3), BlockDigest::MIN),
573 BlockRef::new(9, AuthorityIndex::new_for_test(3), BlockDigest::MIN),
574 ])
575 .build();
576 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
577 assert!(matches!(
578 verifier.verify_block(&signed_block),
579 Err(ConsensusError::TooManyAncestors(_, _))
580 ));
581 }
582
583 {
585 let block = test_block
586 .clone()
587 .set_ancestors_raw(vec![
588 BlockRef::new(9, AuthorityIndex::new_for_test(0), BlockDigest::MIN),
589 BlockRef::new(8, AuthorityIndex::new_for_test(1), BlockDigest::MIN),
590 BlockRef::new(8, AuthorityIndex::new_for_test(3), BlockDigest::MIN),
591 ])
592 .build();
593 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
594 assert!(matches!(
595 verifier.verify_block(&signed_block),
596 Err(ConsensusError::InvalidAncestorPosition {
597 block_authority: _,
598 ancestor_authority: _,
599 position: _
600 })
601 ));
602 }
603
604 {
606 let block = test_block
607 .clone()
608 .set_ancestors_raw(vec![
609 BlockRef::new(9, AuthorityIndex::new_for_test(0), BlockDigest::MIN),
610 BlockRef::new(8, AuthorityIndex::new_for_test(1), BlockDigest::MIN),
611 BlockRef::new(8, AuthorityIndex::new_for_test(2), BlockDigest::MIN),
612 BlockRef::new(8, AuthorityIndex::new_for_test(3), BlockDigest::MIN),
613 ])
614 .build();
615 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
616 assert!(matches!(
617 verifier.verify_block(&signed_block),
618 Err(ConsensusError::InvalidAncestorPosition {
619 block_authority: _,
620 ancestor_authority: _,
621 position: _
622 })
623 ));
624 }
625
626 {
628 let block = test_block
629 .clone()
630 .set_ancestors_raw(vec![
631 BlockRef::new(8, AuthorityIndex::new_for_test(2), BlockDigest::MIN),
632 BlockRef::new(8, AuthorityIndex::new_for_test(1), BlockDigest::MIN),
633 BlockRef::new(8, AuthorityIndex::new_for_test(1), BlockDigest::MIN),
634 ])
635 .build();
636 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
637 assert!(matches!(
638 verifier.verify_block(&signed_block),
639 Err(ConsensusError::DuplicatedAncestorsAuthority(_))
640 ));
641 }
642
643 {
645 let block = test_block
646 .clone()
647 .set_transactions(vec![Transaction::new(vec![4; 257 * 1024])])
648 .build();
649 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
650 assert!(matches!(
651 verifier.verify_block(&signed_block),
652 Err(ConsensusError::TransactionTooLarge { size: _, limit: _ })
653 ));
654 }
655
656 {
658 let block = test_block
659 .clone()
660 .set_transactions((0..1000).map(|_| Transaction::new(vec![4; 8])).collect())
661 .build();
662 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
663 assert!(matches!(
664 verifier.verify_block(&signed_block),
665 Err(ConsensusError::TooManyTransactions { count: _, limit: _ })
666 ));
667 }
668
669 {
671 let block = test_block
672 .clone()
673 .set_transactions(
674 (0..100)
675 .map(|_| Transaction::new(vec![4; 8 * 1024]))
676 .collect(),
677 )
678 .build();
679 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
680 assert!(matches!(
681 verifier.verify_block(&signed_block),
682 Err(ConsensusError::TooManyTransactionBytes { size: _, limit: _ })
683 ));
684 }
685
686 {
688 let block = test_block
689 .clone()
690 .set_transactions(vec![
691 Transaction::new(vec![1; 4]),
692 Transaction::new(vec![1; 2]),
693 ])
694 .build();
695 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
696 let serialized_block = signed_block
697 .serialize()
698 .expect("Block serialization failed.");
699 assert!(matches!(
700 verifier.verify_and_vote(signed_block, serialized_block),
701 Err(ConsensusError::InvalidTransaction(_))
702 ));
703 }
704 }
705
706 #[tokio::test]
707 async fn test_block_version_matches_protocol_config() {
708 let (context, keypairs) = Context::new_for_test(4);
709 let epoch = context.committee.epoch();
710 const AUTHOR: u32 = 2;
711 let author_key = &keypairs[AUTHOR as usize].1;
712 let mut v3_context = context.clone();
713 v3_context.protocol_config.set_enable_v3_for_testing(true);
714 let genesis_ancestors = |context: &Context| {
717 let mut refs = genesis_blocks(context)
718 .iter()
719 .map(|block| block.reference())
720 .collect::<Vec<_>>();
721 refs.swap(0, AUTHOR as usize);
722 refs
723 };
724 let v2_genesis = genesis_ancestors(&context);
725 let v3_genesis = genesis_ancestors(&v3_context);
726 let verifier = SignedBlockVerifier::new(Arc::new(context), Arc::new(TxnSizeVerifier {}));
727 let v3_verifier =
728 SignedBlockVerifier::new(Arc::new(v3_context), Arc::new(TxnSizeVerifier {}));
729 let ancestors = vec![
730 BlockRef::new(9, AuthorityIndex::new_for_test(2), BlockDigest::MIN),
731 BlockRef::new(9, AuthorityIndex::new_for_test(0), BlockDigest::MIN),
732 BlockRef::new(9, AuthorityIndex::new_for_test(1), BlockDigest::MIN),
733 ];
734 let test_block = TestBlock::new(10, AUTHOR).set_ancestors_raw(ancestors.clone());
735 let v1 = SignedBlock::new(
736 Block::V1(BlockV1::new(
737 epoch,
738 10,
739 AuthorityIndex::new_for_test(AUTHOR),
740 0,
741 ancestors,
742 vec![],
743 vec![],
744 vec![],
745 )),
746 author_key,
747 )
748 .unwrap();
749 let v2 = SignedBlock::new(test_block.clone().build(), author_key).unwrap();
750 let v3 = SignedBlock::new(test_block.clone().build_v3(0), author_key).unwrap();
751 assert_eq!(v1.transaction_votes_cutoff_round(), GENESIS_ROUND);
752 assert_eq!(v2.transaction_votes_cutoff_round(), GENESIS_ROUND);
753
754 verifier.verify_block(&v1).unwrap();
756 verifier.verify_block(&v2).unwrap();
757 assert!(matches!(
758 verifier.verify_block(&v3),
759 Err(ConsensusError::UnexpectedBlockVersion { version })
760 if version.contains("is V3 but v3 is disabled")
761 ));
762
763 v3_verifier.verify_block(&v3).unwrap();
765 for block in [&v1, &v2] {
766 assert!(matches!(
767 v3_verifier.verify_block(block),
768 Err(ConsensusError::UnexpectedBlockVersion { version })
769 if version.contains("is not V3 but v3 is enabled")
770 ));
771 }
772
773 let round_1_v3 = SignedBlock::new(
776 TestBlock::new(1, AUTHOR)
777 .set_ancestors_raw(v3_genesis)
778 .build_v3(0),
779 author_key,
780 )
781 .unwrap();
782 v3_verifier.verify_block(&round_1_v3).unwrap();
783 assert!(matches!(
784 verifier.verify_block(&round_1_v3),
785 Err(ConsensusError::UnexpectedBlockVersion { version })
786 if version.contains("is V3 but v3 is disabled")
787 ));
788 let round_1_v2 = SignedBlock::new(
789 TestBlock::new(1, AUTHOR)
790 .set_ancestors_raw(v2_genesis)
791 .build(),
792 author_key,
793 )
794 .unwrap();
795 verifier.verify_block(&round_1_v2).unwrap();
796 assert!(matches!(
797 v3_verifier.verify_block(&round_1_v2),
798 Err(ConsensusError::UnexpectedBlockVersion { version })
799 if version.contains("is not V3 but v3 is enabled")
800 ));
801
802 let invalid_cutoff = SignedBlock::new(test_block.build_v3(10), author_key).unwrap();
803 assert!(matches!(
804 v3_verifier.verify_block(&invalid_cutoff),
805 Err(ConsensusError::InvalidTransactionVotesCutoff {
806 cutoff: 10,
807 block: 10,
808 })
809 ));
810 }
811
812 #[tokio::test]
813 async fn test_v3_transaction_votes_are_bounded_and_valid() {
814 let (mut context, keypairs) = Context::new_for_test(4);
815 context.protocol_config.set_enable_v3_for_testing(true);
816 context
817 .protocol_config
818 .set_max_num_transactions_in_block_for_testing(2);
819 let context = Arc::new(context);
820 const AUTHOR: u32 = 2;
821 let verifier = SignedBlockVerifier::new(context, Arc::new(TxnSizeVerifier {}));
822 let direct_target = BlockRef::new(9, AuthorityIndex::new_for_test(0), BlockDigest::MIN);
823 let old_ancestor = BlockRef::new(7, AuthorityIndex::new_for_test(3), BlockDigest::MIN);
824 let test_block = TestBlock::new(10, AUTHOR).set_ancestors_raw(vec![
825 BlockRef::new(9, AuthorityIndex::new_for_test(2), BlockDigest::MIN),
826 direct_target,
827 BlockRef::new(9, AuthorityIndex::new_for_test(1), BlockDigest::MIN),
828 ]);
829 let verify = |votes, cutoff_round| {
830 let block = test_block
831 .clone()
832 .set_transaction_votes(votes)
833 .build_v3(cutoff_round);
834 let signed_block = SignedBlock::new(block, &keypairs[AUTHOR as usize].1).unwrap();
835 verifier.verify_block(&signed_block)
836 };
837 let assert_invalid = |result: ConsensusResult<()>, reason: &str| match result {
838 Err(ConsensusError::InvalidTransactionVotes(message)) => {
839 assert!(message.contains(reason), "unexpected error: {message}");
840 }
841 other => panic!("expected invalid transaction votes, got {other:?}"),
842 };
843
844 verify(
845 vec![BlockTransactionVotes {
846 block_ref: direct_target,
847 rejects: vec![0, 1],
848 }],
849 8,
850 )
851 .unwrap();
852
853 assert_invalid(
854 verify(
855 vec![
856 BlockTransactionVotes {
857 block_ref: direct_target,
858 rejects: vec![0],
859 },
860 BlockTransactionVotes {
861 block_ref: direct_target,
862 rejects: vec![1],
863 },
864 ],
865 8,
866 ),
867 "appears more than once",
868 );
869 verify(
870 vec![BlockTransactionVotes {
871 block_ref: old_ancestor,
872 rejects: vec![0],
873 }],
874 6,
875 )
876 .unwrap();
877 assert_invalid(
878 verify(
879 vec![BlockTransactionVotes {
880 block_ref: BlockRef::new(10, AuthorityIndex::new_for_test(3), BlockDigest::MIN),
881 rejects: vec![0],
882 }],
883 8,
884 ),
885 "must have a round less than block round",
886 );
887 assert_invalid(
888 verify(
889 vec![BlockTransactionVotes {
890 block_ref: direct_target,
891 rejects: vec![0],
892 }],
893 9,
894 ),
895 "at or below cutoff",
896 );
897 assert_invalid(
898 verify(
899 vec![BlockTransactionVotes {
900 block_ref: direct_target,
901 rejects: vec![],
902 }],
903 8,
904 ),
905 "has no reject indices",
906 );
907 assert_invalid(
908 verify(
909 vec![BlockTransactionVotes {
910 block_ref: direct_target,
911 rejects: vec![0, 0],
912 }],
913 8,
914 ),
915 "not strictly increasing",
916 );
917 assert_invalid(
918 verify(
919 vec![BlockTransactionVotes {
920 block_ref: direct_target,
921 rejects: vec![0, 1, 2],
922 }],
923 8,
924 ),
925 "3 reject indices but the limit is 2",
926 );
927 assert_invalid(
928 verify(
929 vec![BlockTransactionVotes {
930 block_ref: direct_target,
931 rejects: vec![1, 0],
932 }],
933 8,
934 ),
935 "not strictly increasing",
936 );
937 assert_invalid(
938 verify(
939 vec![BlockTransactionVotes {
940 block_ref: direct_target,
941 rejects: vec![2],
942 }],
943 8,
944 ),
945 "is not below limit 2",
946 );
947 }
948
949 #[tokio::test]
950 async fn test_v3_transaction_count_limit() {
951 let (mut context, keypairs) = Context::new_for_test(4);
952 context.protocol_config.set_enable_v3_for_testing(true);
953 context
954 .protocol_config
955 .set_max_num_transactions_in_block_for_testing(2);
956 let context = Arc::new(context);
957 const AUTHOR: u32 = 2;
958 let verifier = SignedBlockVerifier::new(context, Arc::new(TxnSizeVerifier {}));
959 let test_block = TestBlock::new(10, AUTHOR).set_ancestors_raw(vec![
960 BlockRef::new(9, AuthorityIndex::new_for_test(2), BlockDigest::MIN),
961 BlockRef::new(9, AuthorityIndex::new_for_test(0), BlockDigest::MIN),
962 BlockRef::new(9, AuthorityIndex::new_for_test(1), BlockDigest::MIN),
963 ]);
964 let verify = |num_transactions: usize| {
965 let block = test_block
966 .clone()
967 .set_transactions(vec![Transaction::new(vec![1]); num_transactions])
968 .build_v3(8);
969 let signed_block = SignedBlock::new(block, &keypairs[AUTHOR as usize].1).unwrap();
970 verifier.verify_block(&signed_block)
971 };
972
973 verify(2).unwrap();
974 assert!(matches!(
975 verify(3),
976 Err(ConsensusError::TooManyTransactions { count: 3, limit: 2 })
977 ));
978 }
979
980 #[tokio::test]
981 async fn test_v3_transaction_vote_target_limit() {
982 let (mut context, keypairs) = Context::new_for_test(4);
983 context.protocol_config.set_enable_v3_for_testing(true);
984 context.protocol_config.set_gc_depth_for_testing(2);
985 let vote_target_limit = max_transaction_vote_targets(&context);
986 assert_eq!(vote_target_limit, 8);
987 let context = Arc::new(context);
988 const AUTHOR: u32 = 2;
989 let verifier = SignedBlockVerifier::new(context, Arc::new(TxnSizeVerifier {}));
990 let test_block = TestBlock::new(10, AUTHOR).set_ancestors_raw(vec![
991 BlockRef::new(9, AuthorityIndex::new_for_test(2), BlockDigest::MIN),
992 BlockRef::new(9, AuthorityIndex::new_for_test(0), BlockDigest::MIN),
993 BlockRef::new(9, AuthorityIndex::new_for_test(1), BlockDigest::MIN),
994 ]);
995 let mut transaction_votes = (8..=9)
997 .flat_map(|round| {
998 (0..4).map(move |author| BlockTransactionVotes {
999 block_ref: BlockRef::new(
1000 round,
1001 AuthorityIndex::new_for_test(author),
1002 BlockDigest::MIN,
1003 ),
1004 rejects: vec![0],
1005 })
1006 })
1007 .collect::<Vec<_>>();
1008 let verify = |transaction_votes: Vec<BlockTransactionVotes>| {
1009 let block = test_block
1010 .clone()
1011 .set_transaction_votes(transaction_votes)
1012 .build_v3(7);
1013 let signed_block = SignedBlock::new(block, &keypairs[AUTHOR as usize].1).unwrap();
1014 verifier.verify_block(&signed_block)
1015 };
1016
1017 assert_eq!(transaction_votes.len(), vote_target_limit);
1018 verify(transaction_votes.clone()).unwrap();
1019
1020 transaction_votes.push(BlockTransactionVotes {
1022 block_ref: BlockRef::new(9, AuthorityIndex::new_for_test(0), BlockDigest::MAX),
1023 rejects: vec![0],
1024 });
1025 assert!(matches!(
1026 verify(transaction_votes),
1027 Err(ConsensusError::InvalidTransactionVotes(message))
1028 if message.contains("has 9 vote targets but the limit is 8")
1029 ));
1030 }
1031
1032 #[tokio::test]
1035 async fn test_v3_transaction_votes_exclude_reserved_indices() {
1036 let first_reserved_index =
1037 TransactionIndex::MAX.saturating_sub(NUM_RESERVED_TRANSACTION_INDICES);
1038 let (mut context, keypairs) = Context::new_for_test(4);
1039 context.protocol_config.set_enable_v3_for_testing(true);
1040 context
1041 .protocol_config
1042 .set_max_num_transactions_in_block_for_testing(first_reserved_index.into());
1043 let context = Arc::new(context);
1044 const AUTHOR: u32 = 2;
1045 let verifier = SignedBlockVerifier::new(context, Arc::new(TxnSizeVerifier {}));
1046 let direct_target = BlockRef::new(9, AuthorityIndex::new_for_test(0), BlockDigest::MIN);
1047 let test_block = TestBlock::new(10, AUTHOR).set_ancestors_raw(vec![
1048 BlockRef::new(9, AuthorityIndex::new_for_test(2), BlockDigest::MIN),
1049 direct_target,
1050 BlockRef::new(9, AuthorityIndex::new_for_test(1), BlockDigest::MIN),
1051 ]);
1052 let verify = |rejects: Vec<TransactionIndex>| {
1053 let block = test_block
1054 .clone()
1055 .set_transaction_votes(vec![BlockTransactionVotes {
1056 block_ref: direct_target,
1057 rejects,
1058 }])
1059 .build_v3(8);
1060 let signed_block = SignedBlock::new(block, &keypairs[AUTHOR as usize].1).unwrap();
1061 verifier.verify_block(&signed_block)
1062 };
1063
1064 verify(vec![first_reserved_index - 1]).unwrap();
1065 assert!(matches!(
1066 verify(vec![first_reserved_index]),
1067 Err(ConsensusError::InvalidTransactionVotes(_))
1068 ));
1069 }
1070
1071 #[tokio::test]
1072 async fn test_verify_and_vote_transactions() {
1073 let mut protocol_config = ConsensusProtocolConfig::for_testing();
1074 protocol_config.set_transaction_voting_enabled_for_testing(true);
1075
1076 let (context, keypairs) = Context::new_for_test(4);
1077 let context = Arc::new(context.with_protocol_config(protocol_config));
1078
1079 const AUTHOR: u32 = 2;
1080 let author_protocol_keypair = &keypairs[AUTHOR as usize].1;
1081 let verifier = SignedBlockVerifier::new(context.clone(), Arc::new(TxnSizeVerifier {}));
1082
1083 let base_block = TestBlock::new(10, AUTHOR).set_ancestors_raw(vec![
1084 BlockRef::new(9, AuthorityIndex::new_for_test(2), BlockDigest::MIN),
1085 BlockRef::new(9, AuthorityIndex::new_for_test(0), BlockDigest::MIN),
1086 BlockRef::new(9, AuthorityIndex::new_for_test(1), BlockDigest::MIN),
1087 BlockRef::new(7, AuthorityIndex::new_for_test(3), BlockDigest::MIN),
1088 ]);
1089
1090 {
1092 let block = base_block
1093 .clone()
1094 .set_transactions(vec![
1095 Transaction::new(vec![1; 16]),
1096 Transaction::new(vec![2; 16]),
1097 Transaction::new(vec![3; 16]),
1098 Transaction::new(vec![4; 16]),
1099 ])
1100 .build();
1101 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
1102 let serialized_block = signed_block
1103 .serialize()
1104 .expect("Block serialization failed.");
1105 let (verified_block, rejected_transactions) = verifier
1106 .verify_and_vote(signed_block, serialized_block.clone())
1107 .unwrap();
1108 assert_eq!(rejected_transactions, Vec::<TransactionIndex>::new());
1109 assert_eq!(verified_block.serialized().clone(), serialized_block);
1110 }
1111
1112 {
1114 let block = base_block
1115 .clone()
1116 .set_transactions(vec![
1117 Transaction::new(vec![1; 16]),
1118 Transaction::new(vec![2; 8]),
1119 Transaction::new(vec![3; 16]),
1120 Transaction::new(vec![4; 9]),
1121 ])
1122 .build();
1123 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
1124 let serialized_block = signed_block
1125 .serialize()
1126 .expect("Block serialization failed.");
1127 let (_verified_block, rejected_transactions) = verifier
1128 .verify_and_vote(signed_block, serialized_block)
1129 .unwrap();
1130 assert_eq!(
1131 rejected_transactions,
1132 vec![1 as TransactionIndex, 3 as TransactionIndex],
1133 );
1134 }
1135
1136 {
1138 let block = base_block
1139 .clone()
1140 .set_transactions(vec![
1141 Transaction::new(vec![1; 16]),
1142 Transaction::new(vec![2; 8]),
1143 Transaction::new(vec![3; 1]), Transaction::new(vec![4; 9]),
1145 ])
1146 .build();
1147 let signed_block = SignedBlock::new(block, author_protocol_keypair).unwrap();
1148 let serialized_block = signed_block
1149 .serialize()
1150 .expect("Block serialization failed.");
1151 assert!(matches!(
1152 verifier.verify_and_vote(signed_block, serialized_block),
1153 Err(ConsensusError::InvalidTransaction(_))
1154 ));
1155 }
1156 }
1157}