1use std::sync::Arc;
4
5use chrono::Duration;
6
7use zebra_chain::{
8 block::{self, Block, ChainHistoryBlockTxAuthCommitmentHash, CommitmentError},
9 history_tree::HistoryTree,
10 parameters::{Network, NetworkUpgrade},
11 work::difficulty::CompactDifficulty,
12};
13
14use crate::{
15 service::{
16 block_iter::{any_chain_ancestor_iter, Iter},
17 check::difficulty::pow_adjustment_block_span,
18 finalized_state::ZebraDb,
19 non_finalized_state::NonFinalizedState,
20 },
21 BoxError, SemanticallyVerifiedBlock, ValidateContextError,
22};
23
24use zebra_chain::{
25 amount::{Amount, NonNegative},
26 parameters::subsidy::{self, CoinbaseTransactionError, SubsidyError},
27 value_balance::ValueBalance,
28};
29
30use crate::ContextuallyVerifiedBlock;
31
32use super::check;
34
35#[allow(unused_imports)]
37use crate::service::non_finalized_state::Chain;
38
39pub(crate) mod anchors;
40pub(crate) mod difficulty;
41pub(crate) mod nullifier;
42pub(crate) mod utxo;
43
44pub use utxo::transparent_coinbase_spend;
45
46#[cfg(test)]
47mod tests;
48
49pub(crate) use difficulty::AdjustedDifficulty;
50
51#[tracing::instrument(skip(semantically_verified, finalized_tip_height, relevant_chain))]
60pub(crate) fn block_is_valid_for_recent_chain(
61 semantically_verified: &SemanticallyVerifiedBlock,
62 network: &Network,
63 finalized_tip_height: Option<block::Height>,
64 relevant_chain: Iter<block::Header>,
65) -> Result<(), ValidateContextError> {
66 let finalized_tip_height = finalized_tip_height
67 .expect("finalized state must contain at least one block to do contextual validation");
68 check::block_is_not_orphaned(finalized_tip_height, semantically_verified.height)?;
69
70 let block_span = pow_adjustment_block_span(network, semantically_verified.height);
72
73 let parent_height = relevant_chain.height;
74 let relevant_data: Vec<_> = relevant_chain
75 .take(block_span)
76 .map(|header| (header.difficulty_threshold, header.time))
77 .collect();
78
79 if relevant_data.is_empty() {
80 warn!(
81 ?semantically_verified,
82 ?finalized_tip_height,
83 "state must contain parent block to do contextual validation"
84 );
85
86 return Err(ValidateContextError::NotReadyToBeCommitted);
87 }
88
89 let parent_height = parent_height.expect("a parent header has an indexed height");
90 check::height_one_more_than_parent_height(parent_height, semantically_verified.height)?;
91
92 #[cfg(test)]
98 if relevant_data.len() < block_span {
99 return Ok(());
100 }
101
102 #[cfg(not(test))]
120 if relevant_data.is_empty() {
121 return Err(ValidateContextError::NotReadyToBeCommitted);
122 }
123
124 let difficulty_adjustment =
125 AdjustedDifficulty::new_from_block(&semantically_verified.block, network, relevant_data);
126 check::difficulty_threshold_and_time_are_valid(
127 semantically_verified.block.header.difficulty_threshold,
128 difficulty_adjustment,
129 )?;
130
131 Ok(())
132}
133
134#[tracing::instrument(skip(block, history_tree))]
137pub(crate) fn block_commitment_is_valid_for_chain_history(
138 block: Arc<Block>,
139 network: &Network,
140 history_tree: &HistoryTree,
141) -> Result<(), ValidateContextError> {
142 match block.commitment(network)? {
143 block::Commitment::PreSaplingReserved(_)
144 | block::Commitment::FinalSaplingRoot(_)
145 | block::Commitment::ChainHistoryActivationReserved => {
146 Ok(())
159 }
160 block::Commitment::ChainHistoryRoot(actual_history_tree_root) => {
161 let history_tree_root = history_tree
171 .hash()
172 .expect("the history tree of the previous block must exist since the current block has a ChainHistoryRoot");
173 if actual_history_tree_root == history_tree_root {
174 Ok(())
175 } else {
176 Err(ValidateContextError::InvalidBlockCommitment(
177 CommitmentError::InvalidChainHistoryRoot {
178 actual: actual_history_tree_root.into(),
179 expected: history_tree_root.into(),
180 },
181 ))
182 }
183 }
184 block::Commitment::ChainHistoryBlockTxAuthCommitment(actual_hash_block_commitments) => {
185 let history_tree_root = history_tree
193 .hash()
194 .or_else(|| {
195 (NetworkUpgrade::Heartwood.activation_height(network)
196 == block.coinbase_height())
197 .then_some(block::CHAIN_HISTORY_ACTIVATION_RESERVED.into())
198 })
199 .expect(
200 "the history tree of the previous block must exist \
201 since the current block has a ChainHistoryBlockTxAuthCommitment",
202 );
203 let auth_data_root = block.auth_data_root();
204
205 let hash_block_commitments = ChainHistoryBlockTxAuthCommitmentHash::from_commitments(
206 &history_tree_root,
207 &auth_data_root,
208 );
209
210 if actual_hash_block_commitments == hash_block_commitments {
211 Ok(())
212 } else {
213 Err(ValidateContextError::InvalidBlockCommitment(
214 CommitmentError::InvalidChainHistoryBlockTxAuthCommitment {
215 actual: actual_hash_block_commitments.into(),
216 expected: hash_block_commitments.into(),
217 },
218 ))
219 }
220 }
221 }
222}
223
224fn block_is_not_orphaned(
227 finalized_tip_height: block::Height,
228 candidate_height: block::Height,
229) -> Result<(), ValidateContextError> {
230 if candidate_height <= finalized_tip_height {
231 Err(ValidateContextError::OrphanedBlock {
232 candidate_height,
233 finalized_tip_height,
234 })
235 } else {
236 Ok(())
237 }
238}
239
240fn height_one_more_than_parent_height(
243 parent_height: block::Height,
244 candidate_height: block::Height,
245) -> Result<(), ValidateContextError> {
246 if parent_height + 1 != Some(candidate_height) {
247 Err(ValidateContextError::NonSequentialBlock {
248 candidate_height,
249 parent_height,
250 })
251 } else {
252 Ok(())
253 }
254}
255
256fn difficulty_threshold_and_time_are_valid(
268 difficulty_threshold: CompactDifficulty,
269 difficulty_adjustment: AdjustedDifficulty,
270) -> Result<(), ValidateContextError> {
271 let candidate_height = difficulty_adjustment.candidate_height();
273 let candidate_time = difficulty_adjustment.candidate_time();
274 let network = difficulty_adjustment.network();
275 let median_time_past = difficulty_adjustment.median_time_past();
276 let block_time_max =
277 median_time_past + Duration::seconds(difficulty::BLOCK_MAX_TIME_SINCE_MEDIAN.into());
278
279 let genesis_height = NetworkUpgrade::Genesis
286 .activation_height(&network)
287 .expect("Zebra always has a genesis height available");
288
289 if candidate_time <= median_time_past && candidate_height != genesis_height {
290 Err(ValidateContextError::TimeTooEarly {
291 candidate_time,
292 median_time_past,
293 })?
294 }
295
296 if network.is_max_block_time_enforced(candidate_height) && candidate_time > block_time_max {
304 Err(ValidateContextError::TimeTooLate {
305 candidate_time,
306 block_time_max,
307 })?
308 }
309
310 let expected_difficulty = difficulty_adjustment.expected_difficulty_threshold();
316 if difficulty_threshold != expected_difficulty {
317 Err(ValidateContextError::InvalidDifficultyThreshold {
318 difficulty_threshold,
319 expected_difficulty,
320 })?
321 }
322
323 Ok(())
324}
325
326pub(crate) fn legacy_chain<I>(
332 nu5_activation_height: block::Height,
333 ancestors: I,
334 network: &Network,
335 max_legacy_chain_blocks: usize,
336) -> Result<(), BoxError>
337where
338 I: Iterator<Item = Arc<Block>>,
339{
340 let mut ancestors = ancestors.peekable();
341 let tip_height = ancestors.peek().and_then(|block| block.coinbase_height());
342
343 for (index, block) in ancestors.enumerate() {
344 if block
350 .coinbase_height()
351 .expect("valid blocks have coinbase heights")
352 < nu5_activation_height
353 {
354 return Ok(());
355 }
356
357 if index >= max_legacy_chain_blocks {
360 return Err(format!(
361 "could not find any transactions in recent blocks: \
362 checked {index} blocks back from {:?}",
363 tip_height.expect("database contains valid blocks"),
364 )
365 .into());
366 }
367
368 block
372 .check_transaction_network_upgrade_consistency(network)
373 .map_err(|error| {
374 format!("inconsistent network upgrade found in transaction: {error:?}")
375 })?;
376
377 let has_network_upgrade = block
380 .transactions
381 .iter()
382 .find_map(|trans| trans.network_upgrade())
383 .is_some();
384 if has_network_upgrade {
385 return Ok(());
386 }
387 }
388
389 Ok(())
390}
391
392pub(crate) fn initial_contextual_validity(
397 finalized_state: &ZebraDb,
398 non_finalized_state: &NonFinalizedState,
399 semantically_verified: &SemanticallyVerifiedBlock,
400) -> Result<(), ValidateContextError> {
401 let relevant_chain = any_chain_ancestor_iter::<block::Header>(
402 non_finalized_state,
403 finalized_state,
404 semantically_verified.block.header.previous_block_hash,
405 );
406
407 check::block_is_valid_for_recent_chain(
409 semantically_verified,
410 &non_finalized_state.network,
411 finalized_state.finalized_tip_height(),
412 relevant_chain,
413 )?;
414
415 check::nullifier::no_duplicates_in_finalized_chain(semantically_verified, finalized_state)?;
416
417 Ok(())
418}
419
420pub(crate) fn nsm_subsidy_is_valid(
429 contextual: &ContextuallyVerifiedBlock,
430 network: &Network,
431 parent_chain_value_pools: ValueBalance<NonNegative>,
432 block_miner_fees: Amount<NonNegative>,
433) -> Result<(), ValidateContextError> {
434 let invalid_subsidy =
435 |subsidy_error: CoinbaseTransactionError| ValidateContextError::InvalidSubsidy {
436 subsidy_error,
437 height: contextual.height,
438 block_hash: contextual.hash,
439 };
440
441 let expected_block_subsidy = subsidy::block_subsidy_with_parent_pools(
442 contextual.height,
443 network,
444 parent_chain_value_pools,
445 )
446 .map_err(|error| invalid_subsidy(error.into()))?;
447
448 let deferred_pool_balance_change =
449 subsidy::subsidy_is_valid(&contextual.block, network, expected_block_subsidy)
450 .map_err(invalid_subsidy)?;
451
452 let coinbase_tx = contextual
453 .block
454 .transactions
455 .first()
456 .ok_or_else(|| invalid_subsidy(SubsidyError::NoCoinbase.into()))?;
457
458 subsidy::miner_fees_are_valid(
459 coinbase_tx,
460 contextual.height,
461 block_miner_fees,
462 expected_block_subsidy,
463 deferred_pool_balance_change,
464 network,
465 )
466 .map_err(invalid_subsidy)
467}