1#![allow(clippy::too_many_arguments)]
17#![allow(clippy::type_complexity)]
18
19use super::*;
20use snarkvm_ledger_puzzle::Puzzle;
21use snarkvm_synthesizer_program::FinalizeOperation;
22
23use std::collections::HashSet;
24
25#[cfg(not(feature = "serial"))]
26use rayon::prelude::*;
27
28impl<N: Network> Block<N> {
29 pub fn verify(
35 &self,
36 previous_block: &Block<N>,
37 current_state_root: N::StateRoot,
38 previous_committee_lookback: &Committee<N>,
39 current_committee_lookback: &Committee<N>,
40 current_puzzle: &Puzzle<N>,
41 current_epoch_hash: N::BlockHash,
42 current_timestamp: i64,
43 ratified_finalize_operations: Vec<FinalizeOperation<N>>,
44 ) -> Result<(Vec<SolutionID<N>>, Vec<N::TransactionID>)> {
45 self.verify_hash(previous_block.height(), previous_block.hash())?;
47
48 let (
50 expected_round,
51 expected_height,
52 expected_timestamp,
53 expected_existing_solution_ids,
54 expected_existing_transaction_ids,
55 ) = self.verify_authority(
56 previous_block.round(),
57 previous_block.height(),
58 previous_committee_lookback,
59 current_committee_lookback,
60 )?;
61
62 let (
64 expected_cumulative_weight,
65 expected_cumulative_proof_target,
66 expected_coinbase_target,
67 expected_proof_target,
68 expected_last_coinbase_target,
69 expected_last_coinbase_timestamp,
70 expected_block_reward,
71 expected_puzzle_reward,
72 ) = self.verify_solutions(previous_block, current_puzzle, current_epoch_hash)?;
73
74 self.verify_ratifications(expected_block_reward, expected_puzzle_reward)?;
76
77 self.verify_transactions()?;
79
80 let expected_previous_state_root = current_state_root;
82 let expected_transactions_root = self.compute_transactions_root()?;
84 let expected_finalize_root = self.compute_finalize_root(ratified_finalize_operations)?;
86 let expected_ratifications_root = self.compute_ratifications_root()?;
88 let expected_solutions_root = self.compute_solutions_root()?;
90 let expected_subdag_root = self.compute_subdag_root()?;
92
93 self.header.verify(
95 expected_previous_state_root,
96 expected_transactions_root,
97 expected_finalize_root,
98 expected_ratifications_root,
99 expected_solutions_root,
100 expected_subdag_root,
101 expected_round,
102 expected_height,
103 expected_cumulative_weight,
104 expected_cumulative_proof_target,
105 expected_coinbase_target,
106 expected_proof_target,
107 expected_last_coinbase_target,
108 expected_last_coinbase_timestamp,
109 expected_timestamp,
110 current_timestamp,
111 )?;
112
113 Ok((expected_existing_solution_ids, expected_existing_transaction_ids))
115 }
116}
117
118impl<N: Network> Block<N> {
119 fn verify_hash(&self, previous_height: u32, previous_hash: N::BlockHash) -> Result<(), Error> {
121 let expected_height = previous_height.saturating_add(1);
123
124 ensure!(
126 self.previous_hash == previous_hash,
127 "Previous block hash is incorrect in block {expected_height} (found '{}', expected '{}')",
128 self.previous_hash,
129 previous_hash
130 );
131
132 let Ok(header_root) = self.header.to_root() else {
134 bail!("Failed to compute the Merkle root of the block header");
135 };
136 let candidate_hash = match N::hash_bhp1024(&to_bits_le![previous_hash, header_root]) {
138 Ok(candidate_hash) => candidate_hash,
139 Err(error) => bail!("Failed to compute the block hash for block {expected_height} - {error}"),
140 };
141 ensure!(
143 *self.block_hash == candidate_hash,
144 "Block hash is incorrect in block {expected_height} (found '{}', expected '{}')",
145 self.block_hash,
146 Into::<N::BlockHash>::into(candidate_hash)
147 );
148 Ok(())
150 }
151
152 fn verify_authority(
154 &self,
155 previous_round: u64,
156 previous_height: u32,
157 previous_committee_lookback: &Committee<N>,
158 current_committee_lookback: &Committee<N>,
159 ) -> Result<(u64, u32, i64, Vec<SolutionID<N>>, Vec<N::TransactionID>)> {
160 #[cfg(not(any(test, feature = "test")))]
162 ensure!(self.authority.is_quorum(), "The next block must be a quorum block");
163
164 let expected_height = previous_height.saturating_add(1);
166
167 let expected_round = match &self.authority {
169 Authority::Beacon(..) => previous_round.saturating_add(1),
171 Authority::Quorum(subdag) => {
173 subdag.check_certificate_order(expected_height)?;
175 ensure!(
177 subdag.anchor_round() > previous_round,
178 "Subdag anchor round is not after previous block round in block {} (found '{}', expected after '{}')",
179 expected_height,
180 subdag.anchor_round(),
181 previous_round
182 );
183 if previous_round != 0 {
185 for round in previous_round..=subdag.anchor_round() {
186 ensure!(
187 subdag.contains_key(&round),
188 "Subdag is missing round {round} in block {expected_height}",
189 );
190 }
191 }
192 subdag.anchor_round()
194 }
195 };
196 ensure!(
198 expected_round.saturating_sub(Committee::<N>::COMMITTEE_LOOKBACK_RANGE)
199 >= current_committee_lookback.starting_round(),
200 "Block {expected_height} has an invalid round (found '{}', expected at least '{}')",
201 expected_round.saturating_sub(Committee::<N>::COMMITTEE_LOOKBACK_RANGE),
202 current_committee_lookback.starting_round()
203 );
204
205 let (expected_existing_solution_ids, expected_existing_transaction_ids) = match &self.authority {
208 Authority::Beacon(signature) => {
209 let signer = signature.to_address();
211 ensure!(
213 current_committee_lookback.members().contains_key(&signer),
214 "Beacon block {expected_height} has a signer not in the committee (found '{signer}')",
215 );
216 ensure!(
218 signature.verify(&signer, &[*self.block_hash]),
219 "Signature is invalid in block {expected_height}"
220 );
221
222 (vec![], vec![])
223 }
224 Authority::Quorum(subdag) => {
225 let expected_leader = current_committee_lookback.get_leader(expected_round)?;
227 ensure!(
229 subdag.leader_address() == expected_leader,
230 "Quorum block {expected_height} is authored by an unexpected leader (found: {}, expected: {expected_leader})",
231 subdag.leader_address()
232 );
233 Self::check_subdag_transmissions(
238 subdag,
239 &self.solutions,
240 &self.aborted_solution_ids,
241 &self.transactions,
242 &self.aborted_transaction_ids,
243 )?
244 }
245 };
246
247 let expected_timestamp = match &self.authority {
249 Authority::Beacon(..) => self.timestamp(),
251 Authority::Quorum(subdag) => subdag.timestamp(previous_committee_lookback),
253 };
254
255 if let Authority::Quorum(subdag) = &self.authority {
257 ensure!(
259 subdag.leader_certificate().committee_id() == current_committee_lookback.id(),
260 "Leader certificate has an incorrect committee ID"
261 );
262
263 cfg_iter!(subdag).try_for_each(|(round, certificates)| {
265 let expected_committee_id = certificates
267 .first()
268 .map(|certificate| certificate.committee_id())
269 .ok_or(anyhow!("No certificates found for subdag round {round}"))?;
270 ensure!(
271 certificates.iter().skip(1).all(|certificate| certificate.committee_id() == expected_committee_id),
272 "Certificates on round {round} do not all have the same committee ID",
273 );
274 Ok(())
275 })?;
276 }
277
278 Ok((
280 expected_round,
281 expected_height,
282 expected_timestamp,
283 expected_existing_solution_ids,
284 expected_existing_transaction_ids,
285 ))
286 }
287
288 fn verify_ratifications(&self, expected_block_reward: u64, expected_puzzle_reward: u64) -> Result<()> {
290 let height = self.height();
291
292 ensure!(self.ratifications.len() >= 2, "Block {height} must contain at least 2 ratifications");
294
295 let mut ratifications_iter = self.ratifications.iter();
297
298 let block_reward = match ratifications_iter.next() {
300 Some(Ratify::BlockReward(block_reward)) => *block_reward,
301 _ => bail!("Block {height} is invalid - the first ratification must be a block reward"),
302 };
303 let puzzle_reward = match ratifications_iter.next() {
305 Some(Ratify::PuzzleReward(puzzle_reward)) => *puzzle_reward,
306 _ => bail!("Block {height} is invalid - the second ratification must be a puzzle reward"),
307 };
308
309 ensure!(
311 block_reward == expected_block_reward,
312 "Block {height} has an invalid block reward (found '{block_reward}', expected '{expected_block_reward}')",
313 );
314 ensure!(
316 puzzle_reward == expected_puzzle_reward,
317 "Block {height} has an invalid puzzle reward (found '{puzzle_reward}', expected '{expected_puzzle_reward}')",
318 );
319 Ok(())
320 }
321
322 fn verify_solutions(
324 &self,
325 previous_block: &Block<N>,
326 current_puzzle: &Puzzle<N>,
327 current_epoch_hash: N::BlockHash,
328 ) -> Result<(u128, u128, u64, u64, u64, i64, u64, u64)> {
329 let height = self.height();
330 let timestamp = self.timestamp();
331
332 ensure!(
335 self.solutions.len() <= N::MAX_SOLUTIONS,
336 "Block {height} contains too many prover solutions (found '{}', expected '{}')",
337 self.solutions.len(),
338 N::MAX_SOLUTIONS
339 );
340
341 ensure!(
344 self.aborted_solution_ids.len() <= Solutions::<N>::max_aborted_solutions(),
345 "Block {height} contains too many aborted solution IDs (found '{}')",
346 self.aborted_solution_ids.len(),
347 );
348
349 if has_duplicates(
351 self.solutions
352 .as_ref()
353 .map(PuzzleSolutions::solution_ids)
354 .into_iter()
355 .flatten()
356 .chain(self.aborted_solution_ids()),
357 ) {
358 bail!("Found a duplicate solution in block {height}");
359 }
360
361 let combined_proof_target = match self.solutions.deref() {
363 Some(solutions) => current_puzzle.get_combined_proof_target(solutions)?,
364 None => 0u128,
365 };
366
367 if let Some(coinbase) = self.solutions.deref() {
369 if let Err(e) = current_puzzle.check_solutions(coinbase, current_epoch_hash, previous_block.proof_target())
371 {
372 bail!("Block {height} contains an invalid puzzle proof - {e}");
373 }
374
375 if self.cumulative_proof_target() >= previous_block.coinbase_target() as u128 {
379 bail!("The cumulative proof target in block {height} must be less than the previous coinbase target")
380 }
381 };
382
383 let (
385 expected_coinbase_target,
386 expected_proof_target,
387 expected_cumulative_proof_target,
388 expected_cumulative_weight,
389 expected_last_coinbase_target,
390 expected_last_coinbase_timestamp,
391 ) = to_next_targets::<N>(
392 N::CONSENSUS_VERSION(height)?,
393 previous_block.cumulative_proof_target(),
394 combined_proof_target,
395 previous_block.coinbase_target(),
396 previous_block.cumulative_weight(),
397 previous_block.last_coinbase_target(),
398 previous_block.last_coinbase_timestamp(),
399 timestamp,
400 )?;
401
402 let expected_coinbase_reward = coinbase_reward::<N>(
404 height,
405 timestamp,
406 N::GENESIS_TIMESTAMP,
407 N::STARTING_SUPPLY,
408 N::REWARD_ANCHOR_TIME,
409 N::ANCHOR_HEIGHT,
410 N::BLOCK_TIME,
411 combined_proof_target,
412 u64::try_from(previous_block.cumulative_proof_target())?,
413 previous_block.coinbase_target(),
414 )?;
415
416 let expected_transaction_fees =
418 self.transactions.iter().map(|tx| Ok(*tx.priority_fee_amount()?)).sum::<Result<u64>>()?;
419
420 let time_since_last_block = timestamp.saturating_sub(previous_block.timestamp());
422 let expected_block_reward = block_reward::<N>(
424 height,
425 N::STARTING_SUPPLY,
426 N::BLOCK_TIME,
427 time_since_last_block,
428 expected_coinbase_reward,
429 expected_transaction_fees,
430 )?;
431 let expected_puzzle_reward = puzzle_reward(expected_coinbase_reward);
433
434 Ok((
435 expected_cumulative_weight,
436 expected_cumulative_proof_target,
437 expected_coinbase_target,
438 expected_proof_target,
439 expected_last_coinbase_target,
440 expected_last_coinbase_timestamp,
441 expected_block_reward,
442 expected_puzzle_reward,
443 ))
444 }
445
446 fn verify_transactions(&self) -> Result<()> {
448 let height = self.height();
449
450 if self.transactions.len() > Transactions::<N>::MAX_TRANSACTIONS {
453 bail!(
454 "Cannot validate a block with more than {} confirmed transactions",
455 Transactions::<N>::MAX_TRANSACTIONS
456 );
457 }
458
459 if self.aborted_transaction_ids.len() > Transactions::<N>::max_aborted_transactions() {
462 bail!(
463 "Cannot validate a block with more than {} aborted transaction IDs",
464 Transactions::<N>::max_aborted_transactions()
465 );
466 }
467
468 if has_duplicates(self.transaction_ids().chain(self.aborted_transaction_ids.iter())) {
470 bail!("Found a duplicate transaction in block {height}");
471 }
472
473 if has_duplicates(self.transition_ids()) {
475 bail!("Found a duplicate transition in block {height}");
476 }
477
478 if has_duplicates(
480 self.transactions().iter().filter_map(|tx| tx.transaction().deployment().map(|d| d.program_id())),
481 ) {
482 bail!("Found a duplicate program ID in block {height}");
483 }
484
485 if has_duplicates(self.input_ids()) {
489 bail!("Found a duplicate input ID in block {height}");
490 }
491 if has_duplicates(self.serial_numbers()) {
493 bail!("Found a duplicate serial number in block {height}");
494 }
495 if has_duplicates(self.tags()) {
497 bail!("Found a duplicate tag in block {height}");
498 }
499
500 if has_duplicates(self.output_ids()) {
504 bail!("Found a duplicate output ID in block {height}");
505 }
506 if has_duplicates(self.commitments()) {
508 bail!("Found a duplicate commitment in block {height}");
509 }
510 if has_duplicates(self.nonces()) {
512 bail!("Found a duplicate nonce in block {height}");
513 }
514
515 if has_duplicates(self.transition_public_keys()) {
519 bail!("Found a duplicate transition public key in block {height}");
520 }
521 if has_duplicates(self.transition_commitments()) {
523 bail!("Found a duplicate transition commitment in block {height}");
524 }
525 Ok(())
526 }
527}
528impl<N: Network> Block<N> {
529 fn compute_transactions_root(&self) -> Result<Field<N>> {
531 match self.transactions.to_transactions_root() {
532 Ok(transactions_root) => Ok(transactions_root),
533 Err(error) => bail!("Failed to compute the transactions root for block {} - {error}", self.height()),
534 }
535 }
536
537 fn compute_finalize_root(&self, ratified_finalize_operations: Vec<FinalizeOperation<N>>) -> Result<Field<N>> {
539 match self.transactions.to_finalize_root(ratified_finalize_operations) {
540 Ok(finalize_root) => Ok(finalize_root),
541 Err(error) => bail!("Failed to compute the finalize root for block {} - {error}", self.height()),
542 }
543 }
544
545 fn compute_ratifications_root(&self) -> Result<Field<N>> {
547 match self.ratifications.to_ratifications_root() {
548 Ok(ratifications_root) => Ok(ratifications_root),
549 Err(error) => bail!("Failed to compute the ratifications root for block {} - {error}", self.height()),
550 }
551 }
552
553 fn compute_solutions_root(&self) -> Result<Field<N>> {
555 self.solutions.to_solutions_root()
556 }
557
558 fn compute_subdag_root(&self) -> Result<Field<N>> {
560 match self.authority {
561 Authority::Quorum(ref subdag) => subdag.to_subdag_root(),
562 Authority::Beacon(_) => Ok(Field::zero()),
563 }
564 }
565
566 pub(super) fn check_subdag_transmissions(
569 subdag: &Subdag<N>,
570 solutions: &Option<PuzzleSolutions<N>>,
571 aborted_solution_ids: &[SolutionID<N>],
572 transactions: &Transactions<N>,
573 aborted_transaction_ids: &[N::TransactionID],
574 ) -> Result<(Vec<SolutionID<N>>, Vec<N::TransactionID>)> {
575 let mut solutions = solutions.as_ref().map(|s| s.deref()).into_iter().flatten().peekable();
577 let unconfirmed_transactions = cfg_iter!(transactions)
579 .map(|confirmed| confirmed.to_unconfirmed_transaction())
580 .collect::<Result<Vec<_>>>()?;
581 let mut unconfirmed_transactions = unconfirmed_transactions.iter().peekable();
582
583 let mut seen_transaction_ids = HashSet::new();
585 let mut seen_solution_ids = HashSet::new();
586
587 let mut aborted_or_existing_solution_ids = HashSet::new();
589 let mut aborted_or_existing_transaction_ids = HashSet::new();
591
592 for transmission_id in subdag.transmission_ids() {
594 match transmission_id {
599 TransmissionID::Ratification => {}
600 TransmissionID::Solution(solution_id, _) => {
601 if !seen_solution_ids.insert(solution_id) {
602 continue;
603 }
604 }
605 TransmissionID::Transaction(transaction_id, _) => {
606 if !seen_transaction_ids.insert(transaction_id) {
607 continue;
608 }
609 }
610 }
611
612 match transmission_id {
614 TransmissionID::Ratification => {}
615 TransmissionID::Solution(solution_id, _checksum) => {
616 match solutions.peek() {
617 Some((_, solution)) if solution.id() == *solution_id => {
620 solutions.next();
622 }
623 _ => {
625 if !aborted_or_existing_solution_ids.insert(*solution_id) {
626 bail!("Block contains a duplicate aborted solution ID (found '{solution_id}')");
627 }
628 }
629 }
630 }
631 TransmissionID::Transaction(transaction_id, checksum) => {
632 match unconfirmed_transactions.peek() {
633 Some(transaction)
635 if transaction.id() == *transaction_id
636 && Data::<Transaction<N>>::Buffer(transaction.to_bytes_le()?.into())
637 .to_checksum::<N>()?
638 == *checksum =>
639 {
640 unconfirmed_transactions.next();
642 }
643 _ => {
645 if !aborted_or_existing_transaction_ids.insert(*transaction_id) {
646 bail!("Block contains a duplicate aborted transaction ID (found '{transaction_id}')");
647 }
648 }
649 }
650 }
651 }
652 }
653
654 ensure!(solutions.next().is_none(), "There exist more solutions than expected.");
656 ensure!(unconfirmed_transactions.next().is_none(), "There exist more transactions than expected.");
658
659 for aborted_solution_id in aborted_solution_ids {
661 if !aborted_or_existing_solution_ids.contains(aborted_solution_id) {
663 bail!(
664 "Block contains an aborted solution ID that is not found in the subdag (found '{aborted_solution_id}')"
665 );
666 }
667 }
668 for aborted_transaction_id in aborted_transaction_ids {
670 if !aborted_or_existing_transaction_ids.contains(aborted_transaction_id) {
672 bail!(
673 "Block contains an aborted transaction ID that is not found in the subdag (found '{aborted_transaction_id}')"
674 );
675 }
676 }
677
678 let existing_solution_ids: Vec<_> = aborted_or_existing_solution_ids
680 .difference(&aborted_solution_ids.iter().copied().collect())
681 .copied()
682 .collect();
683 let existing_transaction_ids: Vec<_> = aborted_or_existing_transaction_ids
685 .difference(&aborted_transaction_ids.iter().copied().collect())
686 .copied()
687 .collect();
688
689 Ok((existing_solution_ids, existing_transaction_ids))
690 }
691}