Skip to main content

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().into_iter().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(anchor) = transaction.orchard_anchor() {
92        tracing::debug!(
93            ?anchor,
94            ?tx_index_in_block,
95            ?height,
96            "observed orchard anchor",
97        );
98
99        if !parent_chain
100            .map(|chain| chain.orchard_anchors.contains(&anchor))
101            .unwrap_or(false)
102            && !finalized_state.contains_orchard_anchor(&anchor)
103        {
104            return Err(ValidateContextError::UnknownOrchardAnchor {
105                anchor,
106                height,
107                tx_index_in_block,
108                transaction_hash,
109            });
110        }
111
112        tracing::debug!(
113            ?anchor,
114            ?tx_index_in_block,
115            ?height,
116            "validated orchard anchor",
117        );
118    }
119
120    // Ironwood Actions
121    //
122    // MUST refer to some earlier block’s final Ironwood treestate.
123    //
124    // # Consensus
125    //
126    // > The anchorIronwood field of the transaction, whenever it exists
127    // > (i.e. when there are any Ironwood Action descriptions), MUST refer to some
128    // > earlier block’s final Ironwood treestate.
129    //
130    // <https://zips.z.cash/protocol/protocol.pdf#actions>
131    //
132    // Ironwood reuses the Orchard tree root type, in a separate anchor set.
133    if let Some(anchor) = transaction.ironwood_anchor() {
134        tracing::debug!(
135            ?anchor,
136            ?tx_index_in_block,
137            ?height,
138            "observed ironwood anchor",
139        );
140
141        if !parent_chain
142            .map(|chain| chain.ironwood_anchors.contains(&anchor))
143            .unwrap_or(false)
144            && !finalized_state.contains_ironwood_anchor(&anchor)
145        {
146            return Err(ValidateContextError::UnknownIronwoodAnchor {
147                anchor,
148                height,
149                tx_index_in_block,
150                transaction_hash,
151            });
152        }
153
154        tracing::debug!(
155            ?anchor,
156            ?tx_index_in_block,
157            ?height,
158            "validated ironwood anchor",
159        );
160    }
161
162    Ok(())
163}
164
165/// This function fetches and returns the Sprout final treestates from the state,
166/// so [`sprout_anchors_refer_to_treestates()`] can check Sprout final and interstitial treestates,
167/// without accessing the disk.
168///
169/// Sprout anchors may also refer to the interstitial output treestate of any prior
170/// `JoinSplit` _within the same transaction_; these are created on the fly
171/// in [`sprout_anchors_refer_to_treestates()`].
172#[tracing::instrument(skip(sprout_final_treestates, finalized_state, parent_chain, transaction))]
173fn fetch_sprout_final_treestates(
174    sprout_final_treestates: &mut HashMap<
175        sprout::tree::Root,
176        Arc<sprout::tree::NoteCommitmentTree>,
177    >,
178    finalized_state: &ZebraDb,
179    parent_chain: Option<&Arc<Chain>>,
180    transaction: &Arc<Transaction>,
181    tx_index_in_block: Option<usize>,
182    height: Option<Height>,
183) {
184    // Fetch and return Sprout JoinSplit final treestates
185    for (joinsplit_index_in_tx, js) in transaction.sprout_joinsplit_descriptions().enumerate() {
186        let anchor = sprout::tree::Root::from(js.anchor());
187
188        // Avoid duplicate fetches
189        if sprout_final_treestates.contains_key(&anchor) {
190            continue;
191        }
192
193        let input_tree = parent_chain
194            .and_then(|chain| chain.sprout_trees_by_anchor.get(&anchor).cloned())
195            .or_else(|| finalized_state.sprout_tree_by_anchor(&anchor));
196
197        if let Some(input_tree) = input_tree {
198            sprout_final_treestates.insert(anchor, input_tree);
199
200            tracing::debug!(
201                sprout_final_treestate_count = ?sprout_final_treestates.len(),
202                ?anchor,
203                ?joinsplit_index_in_tx,
204                ?tx_index_in_block,
205                ?height,
206                "observed sprout final treestate anchor",
207            );
208        }
209    }
210
211    tracing::trace!(
212        sprout_final_treestate_count = ?sprout_final_treestates.len(),
213        ?sprout_final_treestates,
214        ?height,
215        "returning sprout final treestate anchors",
216    );
217}
218
219/// Checks the Sprout anchors specified by `transactions`.
220///
221/// Sprout anchors may refer to some earlier block's final treestate (like
222/// Sapling and Orchard do exclusively) _or_ to the interstitial output
223/// treestate of any prior `JoinSplit` _within the same transaction_.
224///
225/// This method searches for anchors in the supplied `sprout_final_treestates`
226/// (which must be populated with all treestates pointed to in the `semantically_verified` block;
227/// see [`fetch_sprout_final_treestates()`]); or in the interstitial
228/// treestates which are computed on the fly in this function.
229#[tracing::instrument(skip(sprout_final_treestates, transaction))]
230fn sprout_anchors_refer_to_treestates(
231    sprout_final_treestates: &HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>>,
232    transaction: &Arc<Transaction>,
233    transaction_hash: TransactionHash,
234    tx_index_in_block: Option<usize>,
235    height: Option<Height>,
236) -> Result<(), ValidateContextError> {
237    // Sprout JoinSplits, with interstitial treestates to check as well.
238    let mut interstitial_trees: HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>> =
239        HashMap::new();
240
241    let js_descriptions: Vec<_> = transaction.sprout_joinsplit_descriptions().collect();
242    let joinsplit_count = js_descriptions.len();
243
244    for (joinsplit_index_in_tx, js) in js_descriptions.iter().enumerate() {
245        let anchor = sprout::tree::Root::from(js.anchor());
246        // Check all anchor sets, including the one for interstitial
247        // anchors.
248        //
249        // The anchor is checked and the matching tree is obtained,
250        // which is used to create the interstitial tree state for this
251        // JoinSplit:
252        //
253        // > For each JoinSplit description in a transaction, an
254        // > interstitial output treestate is constructed which adds the
255        // > note commitments and nullifiers specified in that JoinSplit
256        // > description to the input treestate referred to by its
257        // > anchor. This interstitial output treestate is available for
258        // > use as the anchor of subsequent JoinSplit descriptions in
259        // > the same transaction.
260        //
261        // <https://zips.z.cash/protocol/protocol.pdf#joinsplit>
262        //
263        // # Consensus
264        //
265        // > The anchor of each JoinSplit description in a transaction
266        // > MUST refer to either some earlier block’s final Sprout
267        // > treestate, or to the interstitial output treestate of any
268        // > prior JoinSplit description in the same transaction.
269        //
270        // > For the first JoinSplit description of a transaction, the
271        // > anchor MUST be the output Sprout treestate of a previous
272        // > block.
273        //
274        // <https://zips.z.cash/protocol/protocol.pdf#joinsplit>
275        //
276        // Note that in order to satisfy the latter consensus rule above,
277        // [`interstitial_trees`] is always empty in the first iteration
278        // of the loop.
279        let input_tree = interstitial_trees
280            .get(&anchor)
281            .cloned()
282            .or_else(|| sprout_final_treestates.get(&anchor).cloned());
283
284        tracing::trace!(
285            ?input_tree,
286            final_lookup = ?sprout_final_treestates.get(&anchor),
287            interstitial_lookup = ?interstitial_trees.get(&anchor),
288            interstitial_tree_count = ?interstitial_trees.len(),
289            ?interstitial_trees,
290            ?height,
291            "looked up sprout treestate anchor",
292        );
293
294        let mut input_tree = match input_tree {
295            Some(tree) => tree,
296            None => {
297                tracing::debug!(
298                    ?anchor,
299                    ?joinsplit_index_in_tx,
300                    ?tx_index_in_block,
301                    ?height,
302                    "failed to find sprout anchor",
303                );
304                return Err(ValidateContextError::UnknownSproutAnchor {
305                    anchor,
306                    height,
307                    tx_index_in_block,
308                    transaction_hash,
309                });
310            }
311        };
312
313        tracing::debug!(
314            ?anchor,
315            ?joinsplit_index_in_tx,
316            ?tx_index_in_block,
317            ?height,
318            "validated sprout anchor",
319        );
320
321        // The last interstitial treestate in a transaction can never be used,
322        // so we avoid generating it.
323        if joinsplit_index_in_tx == joinsplit_count - 1 {
324            continue;
325        }
326
327        let input_tree_inner = Arc::make_mut(&mut input_tree);
328
329        // Add new anchors to the interstitial note commitment tree.
330        for cm_bytes in js.commitments() {
331            let cm = sprout::commitment::NoteCommitment::from(*cm_bytes);
332            input_tree_inner.append(cm)?;
333        }
334
335        interstitial_trees.insert(input_tree.root(), input_tree);
336
337        tracing::debug!(
338            ?anchor,
339            ?joinsplit_index_in_tx,
340            ?tx_index_in_block,
341            ?height,
342            "observed sprout interstitial anchor",
343        );
344    }
345
346    Ok(())
347}
348
349/// Accepts a [`ZebraDb`], [`Chain`], and [`SemanticallyVerifiedBlock`].
350///
351/// Iterates over the transactions in the [`SemanticallyVerifiedBlock`] checking the final Sapling and Orchard anchors.
352///
353/// This method checks for anchors computed from the final treestate of each block in
354/// the `parent_chain` or `finalized_state`.
355#[tracing::instrument(skip_all)]
356pub(crate) fn block_sapling_orchard_anchors_refer_to_final_treestates(
357    finalized_state: &ZebraDb,
358    parent_chain: &Arc<Chain>,
359    semantically_verified: &SemanticallyVerifiedBlock,
360) -> Result<(), ValidateContextError> {
361    semantically_verified
362        .block
363        .transactions
364        .iter()
365        .enumerate()
366        .try_for_each(|(tx_index_in_block, transaction)| {
367            sapling_orchard_anchors_refer_to_final_treestates(
368                finalized_state,
369                Some(parent_chain),
370                transaction,
371                semantically_verified.transaction_hashes[tx_index_in_block],
372                Some(tx_index_in_block),
373                Some(semantically_verified.height),
374            )
375        })
376}
377
378/// Accepts a [`ZebraDb`], [`Arc<Chain>`](Chain), and [`SemanticallyVerifiedBlock`].
379///
380/// Iterates over the transactions in the [`SemanticallyVerifiedBlock`], and fetches the Sprout final treestates
381/// from the state.
382///
383/// Returns a `HashMap` of the Sprout final treestates from the state for [`sprout_anchors_refer_to_treestates()`]
384/// to check Sprout final and interstitial treestates without accessing the disk.
385///
386/// Sprout anchors may also refer to the interstitial output treestate of any prior
387/// `JoinSplit` _within the same transaction_; these are created on the fly
388/// in [`sprout_anchors_refer_to_treestates()`].
389#[tracing::instrument(skip_all)]
390pub(crate) fn block_fetch_sprout_final_treestates(
391    finalized_state: &ZebraDb,
392    parent_chain: &Arc<Chain>,
393    semantically_verified: &SemanticallyVerifiedBlock,
394) -> HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>> {
395    let mut sprout_final_treestates = HashMap::new();
396
397    for (tx_index_in_block, transaction) in
398        semantically_verified.block.transactions.iter().enumerate()
399    {
400        fetch_sprout_final_treestates(
401            &mut sprout_final_treestates,
402            finalized_state,
403            Some(parent_chain),
404            transaction,
405            Some(tx_index_in_block),
406            Some(semantically_verified.height),
407        );
408    }
409
410    sprout_final_treestates
411}
412
413/// Accepts a [`ZebraDb`], [`Arc<Chain>`](Chain), [`Arc<Block>`](Block), and an
414/// [`Arc<[transaction::Hash]>`](TransactionHash) of hashes corresponding to the transactions in [`Block`]
415///
416/// Iterates over the transactions in the [`Block`] checking the final Sprout anchors.
417///
418/// Sprout anchors may refer to some earlier block's final treestate (like
419/// Sapling and Orchard do exclusively) _or_ to the interstitial output
420/// treestate of any prior `JoinSplit` _within the same transaction_.
421///
422/// This method searches for anchors in the supplied `sprout_final_treestates`
423/// (which must be populated with all treestates pointed to in the `semantically_verified` block;
424/// see [`fetch_sprout_final_treestates()`]); or in the interstitial
425/// treestates which are computed on the fly in this function.
426#[tracing::instrument(skip(sprout_final_treestates, block, transaction_hashes))]
427pub(crate) fn block_sprout_anchors_refer_to_treestates(
428    sprout_final_treestates: HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>>,
429    block: Arc<Block>,
430    // Only used for debugging
431    transaction_hashes: Arc<[TransactionHash]>,
432    height: Height,
433) -> Result<(), ValidateContextError> {
434    tracing::trace!(
435        sprout_final_treestate_count = ?sprout_final_treestates.len(),
436        ?sprout_final_treestates,
437        ?height,
438        "received sprout final treestate anchors",
439    );
440
441    let check_tx_sprout_anchors = |(tx_index_in_block, transaction)| {
442        sprout_anchors_refer_to_treestates(
443            &sprout_final_treestates,
444            transaction,
445            transaction_hashes[tx_index_in_block],
446            Some(tx_index_in_block),
447            Some(height),
448        )?;
449
450        Ok(())
451    };
452
453    // The overhead for a parallel iterator is unwarranted if sprout_final_treestates is empty
454    // because it will either return an error for the first transaction or only check that `joinsplit_data`
455    // is `None` for each transaction.
456    if sprout_final_treestates.is_empty() {
457        // The block has no valid sprout anchors
458        block
459            .transactions
460            .iter()
461            .enumerate()
462            .try_for_each(check_tx_sprout_anchors)
463    } else {
464        block
465            .transactions
466            .par_iter()
467            .enumerate()
468            .try_for_each(check_tx_sprout_anchors)
469    }
470}
471
472/// Accepts a [`ZebraDb`], an optional [`Option<Arc<Chain>>`](Chain), and an [`UnminedTx`].
473///
474/// Checks the final Sprout, Sapling and Orchard anchors specified in the [`UnminedTx`].
475///
476/// This method checks for anchors computed from the final treestate of each block in
477/// the `parent_chain` or `finalized_state`.
478#[tracing::instrument(skip_all)]
479pub(crate) fn tx_anchors_refer_to_final_treestates(
480    finalized_state: &ZebraDb,
481    parent_chain: Option<&Arc<Chain>>,
482    unmined_tx: &UnminedTx,
483) -> Result<(), ValidateContextError> {
484    sapling_orchard_anchors_refer_to_final_treestates(
485        finalized_state,
486        parent_chain,
487        &unmined_tx.transaction,
488        unmined_tx.id.mined_id(),
489        None,
490        None,
491    )?;
492
493    // If there are no sprout transactions in the block, avoid running a rayon scope
494    if unmined_tx.transaction.has_sprout_joinsplit_data() {
495        let mut sprout_final_treestates = HashMap::new();
496
497        fetch_sprout_final_treestates(
498            &mut sprout_final_treestates,
499            finalized_state,
500            parent_chain,
501            &unmined_tx.transaction,
502            None,
503            None,
504        );
505
506        let mut sprout_anchors_result = None;
507        rayon::in_place_scope_fifo(|s| {
508            // This check is expensive, because it updates a note commitment tree for each sprout JoinSplit.
509            // Since we could be processing attacker-controlled mempool transactions, we need to run each one
510            // in its own thread, separately from tokio's blocking I/O threads. And if we are under heavy load,
511            // we want verification to finish in order, so that later transactions can't delay earlier ones.
512            s.spawn_fifo(|_s| {
513                tracing::trace!(
514                    sprout_final_treestate_count = ?sprout_final_treestates.len(),
515                    ?sprout_final_treestates,
516                    "received sprout final treestate anchors",
517                );
518
519                sprout_anchors_result = Some(sprout_anchors_refer_to_treestates(
520                    &sprout_final_treestates,
521                    &unmined_tx.transaction,
522                    unmined_tx.id.mined_id(),
523                    None,
524                    None,
525                ));
526            });
527        });
528
529        sprout_anchors_result.expect("scope has finished")?;
530    }
531
532    Ok(())
533}