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}