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}