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