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}