zebra_state/service/check/
anchors.rs

1//! Checks for whether cited anchors are previously-computed note commitment
2//! tree roots.
3
4use std::{collections::HashMap, sync::Arc};
5
6use rayon::prelude::*;
7
8use zebra_chain::{
9    block::{Block, Height},
10    sprout,
11    transaction::{Hash as TransactionHash, Transaction, UnminedTx},
12};
13
14use crate::{
15    service::{finalized_state::ZebraDb, non_finalized_state::Chain},
16    SemanticallyVerifiedBlock, ValidateContextError,
17};
18
19/// Checks the final Sapling and Orchard anchors specified by `transaction`
20///
21/// This method checks for anchors computed from the final treestate of each block in
22/// the `parent_chain` or `finalized_state`.
23#[tracing::instrument(skip(finalized_state, parent_chain, transaction))]
24fn sapling_orchard_anchors_refer_to_final_treestates(
25    finalized_state: &ZebraDb,
26    parent_chain: Option<&Arc<Chain>>,
27    transaction: &Arc<Transaction>,
28    transaction_hash: TransactionHash,
29    tx_index_in_block: Option<usize>,
30    height: Option<Height>,
31) -> Result<(), ValidateContextError> {
32    // Sapling Spends
33    //
34    // MUST refer to some earlier block’s final Sapling treestate.
35    //
36    // # Consensus
37    //
38    // > The anchor of each Spend description MUST refer to some earlier
39    // > block’s final Sapling treestate. The anchor is encoded separately
40    // > in each Spend description for v4 transactions, or encoded once and
41    // > shared between all Spend descriptions in a v5 transaction.
42    //
43    // <https://zips.z.cash/protocol/protocol.pdf#spendsandoutputs>
44    //
45    // This rule is also implemented in
46    // [`zebra_chain::sapling::shielded_data`].
47    //
48    // The "earlier treestate" check is implemented here.
49    for (anchor_index_in_tx, anchor) in transaction.sapling_anchors().enumerate() {
50        tracing::debug!(
51            ?anchor,
52            ?anchor_index_in_tx,
53            ?tx_index_in_block,
54            ?height,
55            "observed sapling anchor",
56        );
57
58        if !parent_chain
59            .map(|chain| chain.sapling_anchors.contains(&anchor))
60            .unwrap_or(false)
61            && !finalized_state.contains_sapling_anchor(&anchor)
62        {
63            return Err(ValidateContextError::UnknownSaplingAnchor {
64                anchor,
65                height,
66                tx_index_in_block,
67                transaction_hash,
68            });
69        }
70
71        tracing::debug!(
72            ?anchor,
73            ?anchor_index_in_tx,
74            ?tx_index_in_block,
75            ?height,
76            "validated sapling anchor",
77        );
78    }
79
80    // Orchard Actions
81    //
82    // MUST refer to some earlier block’s final Orchard treestate.
83    //
84    // # Consensus
85    //
86    // > The anchorOrchard field of the transaction, whenever it exists
87    // > (i.e. when there are any Action descriptions), MUST refer to some
88    // > earlier block’s final Orchard treestate.
89    //
90    // <https://zips.z.cash/protocol/protocol.pdf#actions>
91    if let Some(orchard_shielded_data) = transaction.orchard_shielded_data() {
92        tracing::debug!(
93            ?orchard_shielded_data.shared_anchor,
94            ?tx_index_in_block,
95            ?height,
96            "observed orchard anchor",
97        );
98
99        if !parent_chain
100            .map(|chain| {
101                chain
102                    .orchard_anchors
103                    .contains(&orchard_shielded_data.shared_anchor)
104            })
105            .unwrap_or(false)
106            && !finalized_state.contains_orchard_anchor(&orchard_shielded_data.shared_anchor)
107        {
108            return Err(ValidateContextError::UnknownOrchardAnchor {
109                anchor: orchard_shielded_data.shared_anchor,
110                height,
111                tx_index_in_block,
112                transaction_hash,
113            });
114        }
115
116        tracing::debug!(
117            ?orchard_shielded_data.shared_anchor,
118            ?tx_index_in_block,
119            ?height,
120            "validated orchard anchor",
121        );
122    }
123
124    Ok(())
125}
126
127/// This function fetches and returns the Sprout final treestates from the state,
128/// so [`sprout_anchors_refer_to_treestates()`] can check Sprout final and interstitial treestates,
129/// without accessing the disk.
130///
131/// Sprout anchors may also refer to the interstitial output treestate of any prior
132/// `JoinSplit` _within the same transaction_; these are created on the fly
133/// in [`sprout_anchors_refer_to_treestates()`].
134#[tracing::instrument(skip(sprout_final_treestates, finalized_state, parent_chain, transaction))]
135fn fetch_sprout_final_treestates(
136    sprout_final_treestates: &mut HashMap<
137        sprout::tree::Root,
138        Arc<sprout::tree::NoteCommitmentTree>,
139    >,
140    finalized_state: &ZebraDb,
141    parent_chain: Option<&Arc<Chain>>,
142    transaction: &Arc<Transaction>,
143    tx_index_in_block: Option<usize>,
144    height: Option<Height>,
145) {
146    // Fetch and return Sprout JoinSplit final treestates
147    for (joinsplit_index_in_tx, joinsplit) in transaction.sprout_groth16_joinsplits().enumerate() {
148        // Avoid duplicate fetches
149        if sprout_final_treestates.contains_key(&joinsplit.anchor) {
150            continue;
151        }
152
153        let input_tree = parent_chain
154            .and_then(|chain| chain.sprout_trees_by_anchor.get(&joinsplit.anchor).cloned())
155            .or_else(|| finalized_state.sprout_tree_by_anchor(&joinsplit.anchor));
156
157        if let Some(input_tree) = input_tree {
158            sprout_final_treestates.insert(joinsplit.anchor, input_tree);
159
160            /* TODO:
161               - fix tests that generate incorrect root data
162               - assert that joinsplit.anchor matches input_tree.root() during tests,
163                 but don't assert in production, because the check is CPU-intensive,
164                 and sprout_anchors_refer_to_treestates() constructs the map correctly
165            */
166
167            tracing::debug!(
168                sprout_final_treestate_count = ?sprout_final_treestates.len(),
169                ?joinsplit.anchor,
170                ?joinsplit_index_in_tx,
171                ?tx_index_in_block,
172                ?height,
173                "observed sprout final treestate anchor",
174            );
175        }
176    }
177
178    tracing::trace!(
179        sprout_final_treestate_count = ?sprout_final_treestates.len(),
180        ?sprout_final_treestates,
181        ?height,
182        "returning sprout final treestate anchors",
183    );
184}
185
186/// Checks the Sprout anchors specified by `transactions`.
187///
188/// Sprout anchors may refer to some earlier block's final treestate (like
189/// Sapling and Orchard do exclusively) _or_ to the interstitial output
190/// treestate of any prior `JoinSplit` _within the same transaction_.
191///
192/// This method searches for anchors in the supplied `sprout_final_treestates`
193/// (which must be populated with all treestates pointed to in the `semantically_verified` block;
194/// see [`fetch_sprout_final_treestates()`]); or in the interstitial
195/// treestates which are computed on the fly in this function.
196#[tracing::instrument(skip(sprout_final_treestates, transaction))]
197fn sprout_anchors_refer_to_treestates(
198    sprout_final_treestates: &HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>>,
199    transaction: &Arc<Transaction>,
200    transaction_hash: TransactionHash,
201    tx_index_in_block: Option<usize>,
202    height: Option<Height>,
203) -> Result<(), ValidateContextError> {
204    // Sprout JoinSplits, with interstitial treestates to check as well.
205    let mut interstitial_trees: HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>> =
206        HashMap::new();
207
208    let joinsplit_count = transaction.sprout_groth16_joinsplits().count();
209
210    for (joinsplit_index_in_tx, joinsplit) in transaction.sprout_groth16_joinsplits().enumerate() {
211        // Check all anchor sets, including the one for interstitial
212        // anchors.
213        //
214        // The anchor is checked and the matching tree is obtained,
215        // which is used to create the interstitial tree state for this
216        // JoinSplit:
217        //
218        // > For each JoinSplit description in a transaction, an
219        // > interstitial output treestate is constructed which adds the
220        // > note commitments and nullifiers specified in that JoinSplit
221        // > description to the input treestate referred to by its
222        // > anchor. This interstitial output treestate is available for
223        // > use as the anchor of subsequent JoinSplit descriptions in
224        // > the same transaction.
225        //
226        // <https://zips.z.cash/protocol/protocol.pdf#joinsplit>
227        //
228        // # Consensus
229        //
230        // > The anchor of each JoinSplit description in a transaction
231        // > MUST refer to either some earlier block’s final Sprout
232        // > treestate, or to the interstitial output treestate of any
233        // > prior JoinSplit description in the same transaction.
234        //
235        // > For the first JoinSplit description of a transaction, the
236        // > anchor MUST be the output Sprout treestate of a previous
237        // > block.
238        //
239        // <https://zips.z.cash/protocol/protocol.pdf#joinsplit>
240        //
241        // Note that in order to satisfy the latter consensus rule above,
242        // [`interstitial_trees`] is always empty in the first iteration
243        // of the loop.
244        let input_tree = interstitial_trees
245            .get(&joinsplit.anchor)
246            .cloned()
247            .or_else(|| sprout_final_treestates.get(&joinsplit.anchor).cloned());
248
249        tracing::trace!(
250            ?input_tree,
251            final_lookup = ?sprout_final_treestates.get(&joinsplit.anchor),
252            interstitial_lookup = ?interstitial_trees.get(&joinsplit.anchor),
253            interstitial_tree_count = ?interstitial_trees.len(),
254            ?interstitial_trees,
255            ?height,
256            "looked up sprout treestate anchor",
257        );
258
259        let mut input_tree = match input_tree {
260            Some(tree) => tree,
261            None => {
262                tracing::debug!(
263                    ?joinsplit.anchor,
264                    ?joinsplit_index_in_tx,
265                    ?tx_index_in_block,
266                    ?height,
267                    "failed to find sprout anchor",
268                );
269                return Err(ValidateContextError::UnknownSproutAnchor {
270                    anchor: joinsplit.anchor,
271                    height,
272                    tx_index_in_block,
273                    transaction_hash,
274                });
275            }
276        };
277
278        tracing::debug!(
279            ?joinsplit.anchor,
280            ?joinsplit_index_in_tx,
281            ?tx_index_in_block,
282            ?height,
283            "validated sprout anchor",
284        );
285
286        // The last interstitial treestate in a transaction can never be used,
287        // so we avoid generating it.
288        if joinsplit_index_in_tx == joinsplit_count - 1 {
289            continue;
290        }
291
292        let input_tree_inner = Arc::make_mut(&mut input_tree);
293
294        // Add new anchors to the interstitial note commitment tree.
295        for cm in joinsplit.commitments {
296            input_tree_inner
297                .append(cm)
298                .expect("note commitment should be appendable to the tree");
299        }
300
301        interstitial_trees.insert(input_tree.root(), input_tree);
302
303        tracing::debug!(
304            ?joinsplit.anchor,
305            ?joinsplit_index_in_tx,
306            ?tx_index_in_block,
307            ?height,
308            "observed sprout interstitial anchor",
309        );
310    }
311
312    Ok(())
313}
314
315/// Accepts a [`ZebraDb`], [`Chain`], and [`SemanticallyVerifiedBlock`].
316///
317/// Iterates over the transactions in the [`SemanticallyVerifiedBlock`] checking the final Sapling and Orchard anchors.
318///
319/// This method checks for anchors computed from the final treestate of each block in
320/// the `parent_chain` or `finalized_state`.
321#[tracing::instrument(skip_all)]
322pub(crate) fn block_sapling_orchard_anchors_refer_to_final_treestates(
323    finalized_state: &ZebraDb,
324    parent_chain: &Arc<Chain>,
325    semantically_verified: &SemanticallyVerifiedBlock,
326) -> Result<(), ValidateContextError> {
327    semantically_verified
328        .block
329        .transactions
330        .iter()
331        .enumerate()
332        .try_for_each(|(tx_index_in_block, transaction)| {
333            sapling_orchard_anchors_refer_to_final_treestates(
334                finalized_state,
335                Some(parent_chain),
336                transaction,
337                semantically_verified.transaction_hashes[tx_index_in_block],
338                Some(tx_index_in_block),
339                Some(semantically_verified.height),
340            )
341        })
342}
343
344/// Accepts a [`ZebraDb`], [`Arc<Chain>`](Chain), and [`SemanticallyVerifiedBlock`].
345///
346/// Iterates over the transactions in the [`SemanticallyVerifiedBlock`], and fetches the Sprout final treestates
347/// from the state.
348///
349/// Returns a `HashMap` of the Sprout final treestates from the state for [`sprout_anchors_refer_to_treestates()`]
350/// to check Sprout final and interstitial treestates without accessing the disk.
351///
352/// Sprout anchors may also refer to the interstitial output treestate of any prior
353/// `JoinSplit` _within the same transaction_; these are created on the fly
354/// in [`sprout_anchors_refer_to_treestates()`].
355#[tracing::instrument(skip_all)]
356pub(crate) fn block_fetch_sprout_final_treestates(
357    finalized_state: &ZebraDb,
358    parent_chain: &Arc<Chain>,
359    semantically_verified: &SemanticallyVerifiedBlock,
360) -> HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>> {
361    let mut sprout_final_treestates = HashMap::new();
362
363    for (tx_index_in_block, transaction) in
364        semantically_verified.block.transactions.iter().enumerate()
365    {
366        fetch_sprout_final_treestates(
367            &mut sprout_final_treestates,
368            finalized_state,
369            Some(parent_chain),
370            transaction,
371            Some(tx_index_in_block),
372            Some(semantically_verified.height),
373        );
374    }
375
376    sprout_final_treestates
377}
378
379/// Accepts a [`ZebraDb`], [`Arc<Chain>`](Chain), [`Arc<Block>`](Block), and an
380/// [`Arc<[transaction::Hash]>`](TransactionHash) of hashes corresponding to the transactions in [`Block`]
381///
382/// Iterates over the transactions in the [`Block`] checking the final Sprout anchors.
383///
384/// Sprout anchors may refer to some earlier block's final treestate (like
385/// Sapling and Orchard do exclusively) _or_ to the interstitial output
386/// treestate of any prior `JoinSplit` _within the same transaction_.
387///
388/// This method searches for anchors in the supplied `sprout_final_treestates`
389/// (which must be populated with all treestates pointed to in the `semantically_verified` block;
390/// see [`fetch_sprout_final_treestates()`]); or in the interstitial
391/// treestates which are computed on the fly in this function.
392#[tracing::instrument(skip(sprout_final_treestates, block, transaction_hashes))]
393pub(crate) fn block_sprout_anchors_refer_to_treestates(
394    sprout_final_treestates: HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>>,
395    block: Arc<Block>,
396    // Only used for debugging
397    transaction_hashes: Arc<[TransactionHash]>,
398    height: Height,
399) -> Result<(), ValidateContextError> {
400    tracing::trace!(
401        sprout_final_treestate_count = ?sprout_final_treestates.len(),
402        ?sprout_final_treestates,
403        ?height,
404        "received sprout final treestate anchors",
405    );
406
407    let check_tx_sprout_anchors = |(tx_index_in_block, transaction)| {
408        sprout_anchors_refer_to_treestates(
409            &sprout_final_treestates,
410            transaction,
411            transaction_hashes[tx_index_in_block],
412            Some(tx_index_in_block),
413            Some(height),
414        )?;
415
416        Ok(())
417    };
418
419    // The overhead for a parallel iterator is unwarranted if sprout_final_treestates is empty
420    // because it will either return an error for the first transaction or only check that `joinsplit_data`
421    // is `None` for each transaction.
422    if sprout_final_treestates.is_empty() {
423        // The block has no valid sprout anchors
424        block
425            .transactions
426            .iter()
427            .enumerate()
428            .try_for_each(check_tx_sprout_anchors)
429    } else {
430        block
431            .transactions
432            .par_iter()
433            .enumerate()
434            .try_for_each(check_tx_sprout_anchors)
435    }
436}
437
438/// Accepts a [`ZebraDb`], an optional [`Option<Arc<Chain>>`](Chain), and an [`UnminedTx`].
439///
440/// Checks the final Sprout, Sapling and Orchard anchors specified in the [`UnminedTx`].
441///
442/// This method checks for anchors computed from the final treestate of each block in
443/// the `parent_chain` or `finalized_state`.
444#[tracing::instrument(skip_all)]
445pub(crate) fn tx_anchors_refer_to_final_treestates(
446    finalized_state: &ZebraDb,
447    parent_chain: Option<&Arc<Chain>>,
448    unmined_tx: &UnminedTx,
449) -> Result<(), ValidateContextError> {
450    sapling_orchard_anchors_refer_to_final_treestates(
451        finalized_state,
452        parent_chain,
453        &unmined_tx.transaction,
454        unmined_tx.id.mined_id(),
455        None,
456        None,
457    )?;
458
459    // If there are no sprout transactions in the block, avoid running a rayon scope
460    if unmined_tx.transaction.has_sprout_joinsplit_data() {
461        let mut sprout_final_treestates = HashMap::new();
462
463        fetch_sprout_final_treestates(
464            &mut sprout_final_treestates,
465            finalized_state,
466            parent_chain,
467            &unmined_tx.transaction,
468            None,
469            None,
470        );
471
472        let mut sprout_anchors_result = None;
473        rayon::in_place_scope_fifo(|s| {
474            // This check is expensive, because it updates a note commitment tree for each sprout JoinSplit.
475            // Since we could be processing attacker-controlled mempool transactions, we need to run each one
476            // in its own thread, separately from tokio's blocking I/O threads. And if we are under heavy load,
477            // we want verification to finish in order, so that later transactions can't delay earlier ones.
478            s.spawn_fifo(|_s| {
479                tracing::trace!(
480                    sprout_final_treestate_count = ?sprout_final_treestates.len(),
481                    ?sprout_final_treestates,
482                    "received sprout final treestate anchors",
483                );
484
485                sprout_anchors_result = Some(sprout_anchors_refer_to_treestates(
486                    &sprout_final_treestates,
487                    &unmined_tx.transaction,
488                    unmined_tx.id.mined_id(),
489                    None,
490                    None,
491                ));
492            });
493        });
494
495        sprout_anchors_result.expect("scope has finished")?;
496    }
497
498    Ok(())
499}