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 .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
224/// Returns `ValidateContextError::OrphanedBlock` if the height of the given
225/// block is less than or equal to the finalized tip height.
226fn 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
240/// Returns `ValidateContextError::NonSequentialBlock` if the block height isn't
241/// equal to the parent_height+1.
242fn 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
256/// Validate the time and `difficulty_threshold` from a candidate block's
257/// header.
258///
259/// Uses the `difficulty_adjustment` context for the block to:
260/// * check that the candidate block's time is within the valid range,
261/// based on the network and candidate height, and
262/// * check that the expected difficulty is equal to the block's
263/// `difficulty_threshold`.
264///
265/// These checks are performed together, because the time field is used to
266/// calculate the expected difficulty adjustment.
267fn difficulty_threshold_and_time_are_valid(
268 difficulty_threshold: CompactDifficulty,
269 difficulty_adjustment: AdjustedDifficulty,
270) -> Result<(), ValidateContextError> {
271 // Check the block header time consensus rules from the Zcash specification
272 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 // # Consensus
280 //
281 // > For each block other than the genesis block, `nTime` MUST be strictly greater
282 // than the median-time-past of that block.
283 //
284 // https://zips.z.cash/protocol/protocol.pdf#blockheader
285 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 // # Consensus
297 //
298 // > For each block at block height 2 or greater on Mainnet, or block height 653_606
299 // or greater on Testnet, `nTime` MUST be less than or equal to the median-time-past
300 // of that block plus 90*60 seconds.
301 //
302 // https://zips.z.cash/protocol/protocol.pdf#blockheader
303 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 // # Consensus
311 //
312 // > For a block at block height `Height`, `nBits` MUST be equal to `ThresholdBits(Height)`.
313 //
314 // https://zips.z.cash/protocol/protocol.pdf#blockheader
315 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
326/// Check if zebra is following a legacy chain and return an error if so.
327///
328/// `nu5_activation_height` should be `NetworkUpgrade::Nu5.activation_height(network)`, and
329/// `max_legacy_chain_blocks` should be [`MAX_LEGACY_CHAIN_BLOCKS`](crate::constants::MAX_LEGACY_CHAIN_BLOCKS).
330/// They are only changed from the defaults for testing.
331pub(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 // Stop checking if the chain reaches Canopy. We won't find any more V5 transactions,
345 // so the rest of our checks are useless.
346 //
347 // If the cached tip is close to NU5 activation, but there aren't any V5 transactions in the
348 // chain yet, we could reach MAX_BLOCKS_TO_CHECK in Canopy, and incorrectly return an error.
349 if block
350 .coinbase_height()
351 .expect("valid blocks have coinbase heights")
352 < nu5_activation_height
353 {
354 return Ok(());
355 }
356
357 // If we are past our NU5 activation height, but there are no V5 transactions in recent blocks,
358 // the last Zebra instance that updated this cached state had no NU5 activation height.
359 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 // If a transaction `network_upgrade` field is different from the network upgrade calculated
369 // using our activation heights, the Zebra instance that verified those blocks had different
370 // network upgrade heights.
371 block
372 .check_transaction_network_upgrade_consistency(network)
373 .map_err(|error| {
374 format!("inconsistent network upgrade found in transaction: {error:?}")
375 })?;
376
377 // If we find at least one transaction with a valid `network_upgrade` field, the Zebra instance that
378 // verified those blocks used the same network upgrade heights. (Up to this point in the chain.)
379 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
392/// Perform initial contextual validity checks for the configured network,
393/// based on the committed finalized and non-finalized state.
394///
395/// Additional contextual validity checks are performed by the non-finalized [`Chain`].
396pub(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_ancestor_blocks(
402 non_finalized_state,
403 finalized_state,
404 semantically_verified.block.header.previous_block_hash,
405 );
406
407 // Security: check proof of work before any other checks
408 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}