zebra_state/service/check.rs
1//! Consensus critical contextual checks
2
3use std::{borrow::Borrow, 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_ancestor_blocks, check::difficulty::POW_ADJUSTMENT_BLOCK_SPAN,
17 finalized_state::ZebraDb, non_finalized_state::NonFinalizedState,
18 },
19 BoxError, SemanticallyVerifiedBlock, ValidateContextError,
20};
21
22// use self as check
23use super::check;
24
25// These types are used in doc links
26#[allow(unused_imports)]
27use crate::service::non_finalized_state::Chain;
28
29pub(crate) mod anchors;
30pub(crate) mod difficulty;
31pub(crate) mod nullifier;
32pub(crate) mod utxo;
33
34pub use utxo::transparent_coinbase_spend;
35
36#[cfg(test)]
37mod tests;
38
39pub(crate) use difficulty::AdjustedDifficulty;
40
41/// Check that the semantically verified block is contextually valid for `network`,
42/// based on the `finalized_tip_height` and `relevant_chain`.
43///
44/// This function performs checks that require a small number of recent blocks,
45/// including previous hash, previous height, and block difficulty.
46///
47/// The relevant chain is an iterator over the ancestors of `block`, starting
48/// with its parent block.
49#[tracing::instrument(skip(semantically_verified, finalized_tip_height, relevant_chain))]
50pub(crate) fn block_is_valid_for_recent_chain<C>(
51 semantically_verified: &SemanticallyVerifiedBlock,
52 network: &Network,
53 finalized_tip_height: Option<block::Height>,
54 relevant_chain: C,
55) -> Result<(), ValidateContextError>
56where
57 C: IntoIterator,
58 C::Item: Borrow<Block>,
59 C::IntoIter: ExactSizeIterator,
60{
61 let finalized_tip_height = finalized_tip_height
62 .expect("finalized state must contain at least one block to do contextual validation");
63 check::block_is_not_orphaned(finalized_tip_height, semantically_verified.height)?;
64
65 let relevant_chain: Vec<_> = relevant_chain
66 .into_iter()
67 .take(POW_ADJUSTMENT_BLOCK_SPAN)
68 .collect();
69
70 let Some(parent_block) = relevant_chain.first() else {
71 warn!(
72 ?semantically_verified,
73 ?finalized_tip_height,
74 "state must contain parent block to do contextual validation"
75 );
76
77 return Err(ValidateContextError::NotReadyToBeCommitted);
78 };
79
80 let parent_block = parent_block.borrow();
81 let parent_height = parent_block
82 .coinbase_height()
83 .expect("valid blocks have a coinbase height");
84 check::height_one_more_than_parent_height(parent_height, semantically_verified.height)?;
85
86 // skip this check during tests if we don't have enough blocks in the chain
87 // process_queued also checks the chain length, so we can skip this assertion during testing
88 // (tests that want to check this code should use the correct number of blocks)
89 //
90 // TODO: accept a NotReadyToBeCommitted error in those tests instead
91 #[cfg(test)]
92 if relevant_chain.len() < POW_ADJUSTMENT_BLOCK_SPAN {
93 return Ok(());
94 }
95
96 // In production, blocks without enough context are invalid.
97 //
98 // The BlockVerifierRouter makes sure that the first 1 million blocks (or more) are
99 // checkpoint verified. The state queues and block write task make sure that blocks are
100 // committed in strict height order. But this function is only called on semantically
101 // verified blocks, so there will be at least 1 million blocks in the state when it is
102 // called. So this error should never happen on Mainnet or the default Testnet.
103 //
104 // It's okay to use a relevant chain of fewer than `POW_ADJUSTMENT_BLOCK_SPAN` blocks, because
105 // the MedianTime function uses height 0 if passed a negative height by the ActualTimespan function:
106 // > ActualTimespan(height : N) := MedianTime(height) − MedianTime(height − PoWAveragingWindow)
107 // > MedianTime(height : N) := median([[ nTime(𝑖) for 𝑖 from max(0, height − PoWMedianBlockSpan) up to height − 1 ]])
108 // and the MeanTarget function only requires the past `PoWAveragingWindow` (17) blocks for heights above 17,
109 // > PoWLimit, if height ≤ PoWAveragingWindow
110 // > ([ToTarget(nBits(𝑖)) for 𝑖 from height−PoWAveragingWindow up to height−1]) otherwise
111 //
112 // See the 'Difficulty Adjustment' section (page 132) in the Zcash specification.
113 #[cfg(not(test))]
114 if relevant_chain.is_empty() {
115 return Err(ValidateContextError::NotReadyToBeCommitted);
116 }
117
118 let relevant_data = relevant_chain.iter().map(|block| {
119 (
120 block.borrow().header.difficulty_threshold,
121 block.borrow().header.time,
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/// Check that `block` is contextually valid for `network`, using
135/// the `history_tree` up to and including the previous block.
136#[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 // # Consensus
147 //
148 // > [Sapling and Blossom only, pre-Heartwood] hashLightClientRoot MUST
149 // > be LEBS2OSP_{256}(rt^{Sapling}) where rt^{Sapling} is the root of
150 // > the Sapling note commitment tree for the final Sapling treestate of
151 // > this block .
152 //
153 // https://zips.z.cash/protocol/protocol.pdf#blockheader
154 //
155 // We don't need to validate this rule since we checkpoint on Canopy.
156 //
157 // We also don't need to do anything in the other cases.
158 Ok(())
159 }
160 block::Commitment::ChainHistoryRoot(actual_history_tree_root) => {
161 // # Consensus
162 //
163 // > [Heartwood and Canopy only, pre-NU5] hashLightClientRoot MUST be set to the
164 // > hashChainHistoryRoot for this block , as specified in [ZIP-221].
165 //
166 // https://zips.z.cash/protocol/protocol.pdf#blockheader
167 //
168 // The network is checked by [`Block::commitment`] above; it will only
169 // return the chain history root if it's Heartwood or Canopy.
170 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 // # Consensus
186 //
187 // > [NU5 onward] hashBlockCommitments MUST be set to the value of
188 // > hashBlockCommitments for this block, as specified in [ZIP-244].
189 //
190 // The network is checked by [`Block::commitment`] above; it will only
191 // return the block commitments if it's NU5 onward.
192 let history_tree_root = history_tree
193 .hash()
194 .expect("the history tree of the previous block must exist since the current block has a ChainHistoryBlockTxAuthCommitment");
195 let auth_data_root = block.auth_data_root();
196
197 let hash_block_commitments = ChainHistoryBlockTxAuthCommitmentHash::from_commitments(
198 &history_tree_root,
199 &auth_data_root,
200 );
201
202 if actual_hash_block_commitments == hash_block_commitments {
203 Ok(())
204 } else {
205 Err(ValidateContextError::InvalidBlockCommitment(
206 CommitmentError::InvalidChainHistoryBlockTxAuthCommitment {
207 actual: actual_hash_block_commitments.into(),
208 expected: hash_block_commitments.into(),
209 },
210 ))
211 }
212 }
213 }
214}
215
216/// Returns `ValidateContextError::OrphanedBlock` if the height of the given
217/// block is less than or equal to the finalized tip height.
218fn block_is_not_orphaned(
219 finalized_tip_height: block::Height,
220 candidate_height: block::Height,
221) -> Result<(), ValidateContextError> {
222 if candidate_height <= finalized_tip_height {
223 Err(ValidateContextError::OrphanedBlock {
224 candidate_height,
225 finalized_tip_height,
226 })
227 } else {
228 Ok(())
229 }
230}
231
232/// Returns `ValidateContextError::NonSequentialBlock` if the block height isn't
233/// equal to the parent_height+1.
234fn height_one_more_than_parent_height(
235 parent_height: block::Height,
236 candidate_height: block::Height,
237) -> Result<(), ValidateContextError> {
238 if parent_height + 1 != Some(candidate_height) {
239 Err(ValidateContextError::NonSequentialBlock {
240 candidate_height,
241 parent_height,
242 })
243 } else {
244 Ok(())
245 }
246}
247
248/// Validate the time and `difficulty_threshold` from a candidate block's
249/// header.
250///
251/// Uses the `difficulty_adjustment` context for the block to:
252/// * check that the candidate block's time is within the valid range,
253/// based on the network and candidate height, and
254/// * check that the expected difficulty is equal to the block's
255/// `difficulty_threshold`.
256///
257/// These checks are performed together, because the time field is used to
258/// calculate the expected difficulty adjustment.
259fn difficulty_threshold_and_time_are_valid(
260 difficulty_threshold: CompactDifficulty,
261 difficulty_adjustment: AdjustedDifficulty,
262) -> Result<(), ValidateContextError> {
263 // Check the block header time consensus rules from the Zcash specification
264 let candidate_height = difficulty_adjustment.candidate_height();
265 let candidate_time = difficulty_adjustment.candidate_time();
266 let network = difficulty_adjustment.network();
267 let median_time_past = difficulty_adjustment.median_time_past();
268 let block_time_max =
269 median_time_past + Duration::seconds(difficulty::BLOCK_MAX_TIME_SINCE_MEDIAN.into());
270
271 // # Consensus
272 //
273 // > For each block other than the genesis block, `nTime` MUST be strictly greater
274 // than the median-time-past of that block.
275 //
276 // https://zips.z.cash/protocol/protocol.pdf#blockheader
277 let genesis_height = NetworkUpgrade::Genesis
278 .activation_height(&network)
279 .expect("Zebra always has a genesis height available");
280
281 if candidate_time <= median_time_past && candidate_height != genesis_height {
282 Err(ValidateContextError::TimeTooEarly {
283 candidate_time,
284 median_time_past,
285 })?
286 }
287
288 // # Consensus
289 //
290 // > For each block at block height 2 or greater on Mainnet, or block height 653_606
291 // or greater on Testnet, `nTime` MUST be less than or equal to the median-time-past
292 // of that block plus 90*60 seconds.
293 //
294 // https://zips.z.cash/protocol/protocol.pdf#blockheader
295 if network.is_max_block_time_enforced(candidate_height) && candidate_time > block_time_max {
296 Err(ValidateContextError::TimeTooLate {
297 candidate_time,
298 block_time_max,
299 })?
300 }
301
302 // # Consensus
303 //
304 // > For a block at block height `Height`, `nBits` MUST be equal to `ThresholdBits(Height)`.
305 //
306 // https://zips.z.cash/protocol/protocol.pdf#blockheader
307 let expected_difficulty = difficulty_adjustment.expected_difficulty_threshold();
308 if difficulty_threshold != expected_difficulty {
309 Err(ValidateContextError::InvalidDifficultyThreshold {
310 difficulty_threshold,
311 expected_difficulty,
312 })?
313 }
314
315 Ok(())
316}
317
318/// Check if zebra is following a legacy chain and return an error if so.
319///
320/// `nu5_activation_height` should be `NetworkUpgrade::Nu5.activation_height(network)`, and
321/// `max_legacy_chain_blocks` should be [`MAX_LEGACY_CHAIN_BLOCKS`](crate::constants::MAX_LEGACY_CHAIN_BLOCKS).
322/// They are only changed from the defaults for testing.
323pub(crate) fn legacy_chain<I>(
324 nu5_activation_height: block::Height,
325 ancestors: I,
326 network: &Network,
327 max_legacy_chain_blocks: usize,
328) -> Result<(), BoxError>
329where
330 I: Iterator<Item = Arc<Block>>,
331{
332 let mut ancestors = ancestors.peekable();
333 let tip_height = ancestors.peek().and_then(|block| block.coinbase_height());
334
335 for (index, block) in ancestors.enumerate() {
336 // Stop checking if the chain reaches Canopy. We won't find any more V5 transactions,
337 // so the rest of our checks are useless.
338 //
339 // If the cached tip is close to NU5 activation, but there aren't any V5 transactions in the
340 // chain yet, we could reach MAX_BLOCKS_TO_CHECK in Canopy, and incorrectly return an error.
341 if block
342 .coinbase_height()
343 .expect("valid blocks have coinbase heights")
344 < nu5_activation_height
345 {
346 return Ok(());
347 }
348
349 // If we are past our NU5 activation height, but there are no V5 transactions in recent blocks,
350 // the last Zebra instance that updated this cached state had no NU5 activation height.
351 if index >= max_legacy_chain_blocks {
352 return Err(format!(
353 "could not find any transactions in recent blocks: \
354 checked {index} blocks back from {:?}",
355 tip_height.expect("database contains valid blocks"),
356 )
357 .into());
358 }
359
360 // If a transaction `network_upgrade` field is different from the network upgrade calculated
361 // using our activation heights, the Zebra instance that verified those blocks had different
362 // network upgrade heights.
363 block
364 .check_transaction_network_upgrade_consistency(network)
365 .map_err(|error| {
366 format!("inconsistent network upgrade found in transaction: {error:?}")
367 })?;
368
369 // If we find at least one transaction with a valid `network_upgrade` field, the Zebra instance that
370 // verified those blocks used the same network upgrade heights. (Up to this point in the chain.)
371 let has_network_upgrade = block
372 .transactions
373 .iter()
374 .find_map(|trans| trans.network_upgrade())
375 .is_some();
376 if has_network_upgrade {
377 return Ok(());
378 }
379 }
380
381 Ok(())
382}
383
384/// Perform initial contextual validity checks for the configured network,
385/// based on the committed finalized and non-finalized state.
386///
387/// Additional contextual validity checks are performed by the non-finalized [`Chain`].
388pub(crate) fn initial_contextual_validity(
389 finalized_state: &ZebraDb,
390 non_finalized_state: &NonFinalizedState,
391 semantically_verified: &SemanticallyVerifiedBlock,
392) -> Result<(), ValidateContextError> {
393 let relevant_chain = any_ancestor_blocks(
394 non_finalized_state,
395 finalized_state,
396 semantically_verified.block.header.previous_block_hash,
397 );
398
399 // Security: check proof of work before any other checks
400 check::block_is_valid_for_recent_chain(
401 semantically_verified,
402 &non_finalized_state.network,
403 finalized_state.finalized_tip_height(),
404 relevant_chain,
405 )?;
406
407 check::nullifier::no_duplicates_in_finalized_chain(semantically_verified, finalized_state)?;
408
409 Ok(())
410}