zebra_state/service/non_finalized_state/chain.rs
1//! [`Chain`] implements a single non-finalized blockchain,
2//! starting at the finalized tip.
3
4use std::{
5 cmp::Ordering,
6 collections::{BTreeMap, BTreeSet, HashMap, HashSet},
7 ops::{Deref, DerefMut, RangeInclusive},
8 sync::Arc,
9};
10
11use chrono::{DateTime, Utc};
12use mset::MultiSet;
13use tracing::instrument;
14
15use zebra_chain::{
16 amount::{Amount, NegativeAllowed, NonNegative},
17 block::{self, Height},
18 block_info::BlockInfo,
19 history_tree::HistoryTree,
20 ironwood, orchard,
21 parallel::tree::NoteCommitmentTrees,
22 parameters::Network,
23 primitives::zcash_history::BlockCommitmentTreeRoots,
24 primitives::Groth16Proof,
25 sapling,
26 serialization::ZcashSerialize as _,
27 sprout,
28 subtree::{NoteCommitmentSubtree, NoteCommitmentSubtreeData, NoteCommitmentSubtreeIndex},
29 transaction::{
30 self,
31 Transaction::{self, *},
32 },
33 transparent,
34 value_balance::ValueBalance,
35 work::difficulty::PartialCumulativeWork,
36};
37
38use crate::{
39 request::Treestate, service::check, ContextuallyVerifiedBlock, HashOrHeight, OutputLocation,
40 TransactionLocation, ValidateContextError,
41};
42
43#[cfg(feature = "indexer")]
44use crate::request::Spend;
45
46use self::index::TransparentTransfers;
47
48pub mod index;
49
50/// A single non-finalized partial chain, from the child of the finalized tip,
51/// to a non-finalized chain tip.
52#[derive(Clone, Debug, Default)]
53pub struct Chain {
54 // Config
55 //
56 /// The configured network for this chain.
57 network: Network,
58
59 /// The internal state of this chain.
60 inner: ChainInner,
61
62 // Diagnostics
63 //
64 /// The last height this chain forked at. Diagnostics only.
65 ///
66 /// This field is only used for metrics. It is not consensus-critical, and it is not checked for
67 /// equality.
68 ///
69 /// We keep the same last fork height in both sides of a clone, because every new block clones a
70 /// chain, even if it's just growing that chain.
71 ///
72 /// # Note
73 ///
74 /// Most diagnostics are implemented on the `NonFinalizedState`, rather than each chain. Some
75 /// diagnostics only use the best chain, and others need to modify the Chain state, but that's
76 /// difficult with `Arc<Chain>`s.
77 pub(super) last_fork_height: Option<Height>,
78}
79
80/// Spending transaction id type when the `indexer` feature is selected.
81#[cfg(feature = "indexer")]
82pub(crate) type SpendingTransactionId = transaction::Hash;
83
84/// Spending transaction id type when the `indexer` feature is not selected.
85#[cfg(not(feature = "indexer"))]
86pub(crate) type SpendingTransactionId = ();
87
88/// The internal state of [`Chain`].
89#[derive(Clone, Debug, PartialEq, Eq, Default)]
90pub struct ChainInner {
91 // Blocks, heights, hashes, and transaction locations
92 //
93 /// The contextually valid blocks which form this non-finalized partial chain, in height order.
94 pub(crate) blocks: BTreeMap<block::Height, ContextuallyVerifiedBlock>,
95
96 /// An index of block heights for each block hash in `blocks`.
97 pub height_by_hash: HashMap<block::Hash, block::Height>,
98
99 /// An index of [`TransactionLocation`]s for each transaction hash in `blocks`.
100 pub tx_loc_by_hash: HashMap<transaction::Hash, TransactionLocation>,
101
102 // Transparent outputs and spends
103 //
104 /// The [`transparent::Utxo`]s created by `blocks`.
105 ///
106 /// Note that these UTXOs may not be unspent.
107 /// Outputs can be spent by later transactions or blocks in the chain.
108 //
109 // TODO: replace OutPoint with OutputLocation?
110 pub(crate) created_utxos: HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
111 /// The spending transaction ids by [`transparent::OutPoint`]s spent by `blocks`,
112 /// including spent outputs created by earlier transactions or blocks in the chain.
113 ///
114 /// Note: Spending transaction ids are only tracked when the `indexer` feature is selected.
115 pub(crate) spent_utxos: HashMap<transparent::OutPoint, SpendingTransactionId>,
116
117 // Note commitment trees
118 //
119 /// The Sprout note commitment tree for each anchor.
120 /// This is required for interstitial states.
121 ///
122 /// When a chain is forked from the finalized tip, also contains the finalized tip root.
123 /// This extra root is removed when the first non-finalized block is committed.
124 pub(crate) sprout_trees_by_anchor:
125 HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>>,
126 /// The Sprout note commitment tree for each height.
127 ///
128 /// When a chain is forked from the finalized tip, also contains the finalized tip tree.
129 /// This extra tree is removed when the first non-finalized block is committed.
130 pub(crate) sprout_trees_by_height:
131 BTreeMap<block::Height, Arc<sprout::tree::NoteCommitmentTree>>,
132
133 /// The Sapling note commitment tree for each height.
134 ///
135 /// When a chain is forked from the finalized tip, also contains the finalized tip tree.
136 /// This extra tree is removed when the first non-finalized block is committed.
137 pub(crate) sapling_trees_by_height:
138 BTreeMap<block::Height, Arc<sapling::tree::NoteCommitmentTree>>,
139
140 /// The Orchard note commitment tree for each height.
141 ///
142 /// When a chain is forked from the finalized tip, also contains the finalized tip tree.
143 /// This extra tree is removed when the first non-finalized block is committed.
144 pub(crate) orchard_trees_by_height:
145 BTreeMap<block::Height, Arc<orchard::tree::NoteCommitmentTree>>,
146
147 /// The Ironwood note commitment tree for each height (NU6.3).
148 ///
149 /// Ironwood reuses the Orchard tree type. When a chain is forked from the finalized tip, also
150 /// contains the finalized tip tree, which is removed when the first non-finalized block is
151 /// committed.
152 pub(crate) ironwood_trees_by_height:
153 BTreeMap<block::Height, Arc<orchard::tree::NoteCommitmentTree>>,
154
155 // History trees
156 //
157 /// The ZIP-221 history tree for each height, including all finalized blocks,
158 /// and the non-finalized `blocks` below that height in this chain.
159 ///
160 /// When a chain is forked from the finalized tip, also contains the finalized tip tree.
161 /// This extra tree is removed when the first non-finalized block is committed.
162 pub(crate) history_trees_by_height: BTreeMap<block::Height, Arc<HistoryTree>>,
163
164 // Anchors
165 //
166 /// The Sprout anchors created by `blocks`.
167 ///
168 /// When a chain is forked from the finalized tip, also contains the finalized tip root.
169 /// This extra root is removed when the first non-finalized block is committed.
170 pub(crate) sprout_anchors: MultiSet<sprout::tree::Root>,
171 /// The Sprout anchors created by each block in `blocks`.
172 ///
173 /// When a chain is forked from the finalized tip, also contains the finalized tip root.
174 /// This extra root is removed when the first non-finalized block is committed.
175 pub(crate) sprout_anchors_by_height: BTreeMap<block::Height, sprout::tree::Root>,
176
177 /// The Sapling anchors created by `blocks`.
178 ///
179 /// When a chain is forked from the finalized tip, also contains the finalized tip root.
180 /// This extra root is removed when the first non-finalized block is committed.
181 pub(crate) sapling_anchors: MultiSet<sapling::tree::Root>,
182 /// The Sapling anchors created by each block in `blocks`.
183 ///
184 /// When a chain is forked from the finalized tip, also contains the finalized tip root.
185 /// This extra root is removed when the first non-finalized block is committed.
186 pub(crate) sapling_anchors_by_height: BTreeMap<block::Height, sapling::tree::Root>,
187 /// A list of Sapling subtrees completed in the non-finalized state
188 pub(crate) sapling_subtrees:
189 BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<sapling_crypto::Node>>,
190
191 /// The Orchard anchors created by `blocks`.
192 ///
193 /// When a chain is forked from the finalized tip, also contains the finalized tip root.
194 /// This extra root is removed when the first non-finalized block is committed.
195 pub(crate) orchard_anchors: MultiSet<orchard::tree::Root>,
196 /// The Orchard anchors created by each block in `blocks`.
197 ///
198 /// When a chain is forked from the finalized tip, also contains the finalized tip root.
199 /// This extra root is removed when the first non-finalized block is committed.
200 pub(crate) orchard_anchors_by_height: BTreeMap<block::Height, orchard::tree::Root>,
201 /// A list of Orchard subtrees completed in the non-finalized state
202 pub(crate) orchard_subtrees:
203 BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<orchard::tree::Node>>,
204
205 /// The Ironwood anchors created by `blocks` (NU6.3). Reuses the Orchard tree root type.
206 ///
207 /// When a chain is forked from the finalized tip, also contains the finalized tip root, which
208 /// is removed when the first non-finalized block is committed.
209 pub(crate) ironwood_anchors: MultiSet<orchard::tree::Root>,
210 /// The Ironwood anchors created by each block in `blocks`.
211 pub(crate) ironwood_anchors_by_height: BTreeMap<block::Height, orchard::tree::Root>,
212 /// A list of Ironwood subtrees completed in the non-finalized state.
213 pub(crate) ironwood_subtrees:
214 BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<orchard::tree::Node>>,
215
216 // Nullifiers
217 //
218 /// The Sprout nullifiers revealed by `blocks` and, if the `indexer` feature is selected,
219 /// the id of the transaction that revealed them.
220 pub(crate) sprout_nullifiers: HashMap<sprout::Nullifier, SpendingTransactionId>,
221 /// The Sapling nullifiers revealed by `blocks` and, if the `indexer` feature is selected,
222 /// the id of the transaction that revealed them.
223 pub(crate) sapling_nullifiers: HashMap<sapling::Nullifier, SpendingTransactionId>,
224 /// The Orchard nullifiers revealed by `blocks` and, if the `indexer` feature is selected,
225 /// the id of the transaction that revealed them.
226 pub(crate) orchard_nullifiers: HashMap<orchard::Nullifier, SpendingTransactionId>,
227 /// The Ironwood nullifiers revealed by `blocks` and, if the `indexer` feature is selected,
228 /// the id of the transaction that revealed them.
229 pub(crate) ironwood_nullifiers: HashMap<ironwood::Nullifier, SpendingTransactionId>,
230
231 // Transparent Transfers
232 // TODO: move to the transparent section
233 //
234 /// Partial transparent address index data from `blocks`.
235 pub(super) partial_transparent_transfers: HashMap<transparent::Address, TransparentTransfers>,
236
237 // Chain Work
238 //
239 /// The cumulative work represented by `blocks`.
240 ///
241 /// Since the best chain is determined by the largest cumulative work,
242 /// the work represented by finalized blocks can be ignored,
243 /// because they are common to all non-finalized chains.
244 pub(super) partial_cumulative_work: PartialCumulativeWork,
245
246 // Chain Pools
247 //
248 /// The chain value pool balances of the tip of this [`Chain`], including the block value pool
249 /// changes from all finalized blocks, and the non-finalized blocks in this chain.
250 ///
251 /// When a new chain is created from the finalized tip, it is initialized with the finalized tip
252 /// chain value pool balances.
253 pub(crate) chain_value_pools: ValueBalance<NonNegative>,
254 /// The block info after the given block height.
255 pub(crate) block_info_by_height: BTreeMap<block::Height, BlockInfo>,
256}
257
258impl Chain {
259 /// Create a new Chain with the given finalized tip trees and network.
260 ///
261 /// The subtree fields of `note_commitment_trees` are unused: a forked chain starts tracking
262 /// subtrees from empty and fills them from its own block commits.
263 pub(crate) fn new(
264 network: &Network,
265 finalized_tip_height: Height,
266 note_commitment_trees: NoteCommitmentTrees,
267 history_tree: Arc<HistoryTree>,
268 finalized_tip_chain_value_pools: ValueBalance<NonNegative>,
269 ) -> Self {
270 // Passing the trees in a named struct (rather than four adjacent positional arguments, two
271 // of them the same `Arc<orchard::tree::NoteCommitmentTree>` type) makes an orchard/ironwood
272 // swap a compile error instead of silent tree corruption.
273 let NoteCommitmentTrees {
274 sprout: sprout_note_commitment_tree,
275 sapling: sapling_note_commitment_tree,
276 orchard: orchard_note_commitment_tree,
277 ironwood: ironwood_note_commitment_tree,
278 ..
279 } = note_commitment_trees;
280
281 let inner = ChainInner {
282 blocks: Default::default(),
283 height_by_hash: Default::default(),
284 tx_loc_by_hash: Default::default(),
285 created_utxos: Default::default(),
286 spent_utxos: Default::default(),
287 sprout_anchors: MultiSet::new(),
288 sprout_anchors_by_height: Default::default(),
289 sprout_trees_by_anchor: Default::default(),
290 sprout_trees_by_height: Default::default(),
291 sapling_anchors: MultiSet::new(),
292 sapling_anchors_by_height: Default::default(),
293 sapling_trees_by_height: Default::default(),
294 sapling_subtrees: Default::default(),
295 orchard_anchors: MultiSet::new(),
296 orchard_anchors_by_height: Default::default(),
297 orchard_trees_by_height: Default::default(),
298 orchard_subtrees: Default::default(),
299 ironwood_anchors: MultiSet::new(),
300 ironwood_anchors_by_height: Default::default(),
301 ironwood_trees_by_height: Default::default(),
302 ironwood_subtrees: Default::default(),
303 sprout_nullifiers: Default::default(),
304 sapling_nullifiers: Default::default(),
305 orchard_nullifiers: Default::default(),
306 ironwood_nullifiers: Default::default(),
307 partial_transparent_transfers: Default::default(),
308 partial_cumulative_work: Default::default(),
309 history_trees_by_height: Default::default(),
310 chain_value_pools: finalized_tip_chain_value_pools,
311 block_info_by_height: Default::default(),
312 };
313
314 let mut chain = Self {
315 network: network.clone(),
316 inner,
317 last_fork_height: None,
318 };
319
320 chain.add_sprout_tree_and_anchor(finalized_tip_height, sprout_note_commitment_tree);
321 chain.add_sapling_tree_and_anchor(finalized_tip_height, sapling_note_commitment_tree);
322 chain.add_orchard_tree_and_anchor(finalized_tip_height, orchard_note_commitment_tree);
323 chain.add_ironwood_tree_and_anchor(finalized_tip_height, ironwood_note_commitment_tree);
324 chain.add_history_tree(finalized_tip_height, history_tree);
325
326 chain
327 }
328
329 /// Is the internal state of `self` the same as `other`?
330 ///
331 /// [`Chain`] has custom [`Eq`] and [`Ord`] implementations based on proof of work,
332 /// which are used to select the best chain. So we can't derive [`Eq`] for [`Chain`].
333 ///
334 /// Unlike the custom trait impls, this method returns `true` if the entire internal state
335 /// of two chains is equal.
336 ///
337 /// If the internal states are different, it returns `false`,
338 /// even if the blocks in the two chains are equal.
339 #[cfg(any(test, feature = "proptest-impl"))]
340 pub fn eq_internal_state(&self, other: &Chain) -> bool {
341 self.inner == other.inner
342 }
343
344 /// Returns the last fork height if that height is still in the non-finalized state.
345 /// Otherwise, if that fork has been finalized, returns `None`.
346 #[allow(dead_code)]
347 pub fn recent_fork_height(&self) -> Option<Height> {
348 self.last_fork_height
349 .filter(|last| last >= &self.non_finalized_root_height())
350 }
351
352 /// Returns this chain fork's length, if its fork is still in the non-finalized state.
353 /// Otherwise, if the fork has been finalized, returns `None`.
354 #[allow(dead_code)]
355 pub fn recent_fork_length(&self) -> Option<u32> {
356 let fork_length = self.non_finalized_tip_height() - self.recent_fork_height()?;
357
358 // If the fork is above the tip, it is invalid, so just return `None`
359 // (Ignoring invalid data is ok because this is metrics-only code.)
360 fork_length.try_into().ok()
361 }
362
363 /// Push a contextually valid non-finalized block into this chain as the new tip.
364 ///
365 /// If the block is invalid, drops this chain, and returns an error.
366 ///
367 /// Note: a [`ContextuallyVerifiedBlock`] isn't actually contextually valid until
368 /// [`Self::update_chain_tip_with`] returns success.
369 #[instrument(level = "debug", skip(self, block), fields(block = %block.block))]
370 pub fn push(mut self, block: ContextuallyVerifiedBlock) -> Result<Chain, ValidateContextError> {
371 // update cumulative data members
372 self.update_chain_tip_with(&block)?;
373
374 tracing::debug!(block = %block.block, "adding block to chain");
375 self.blocks.insert(block.height, block);
376
377 Ok(self)
378 }
379
380 /// Pops the lowest height block of the non-finalized portion of a chain,
381 /// and returns it with its associated treestate.
382 #[instrument(level = "debug", skip(self))]
383 pub(crate) fn pop_root(&mut self) -> (ContextuallyVerifiedBlock, Treestate) {
384 // Obtain the lowest height.
385 let block_height = self.non_finalized_root_height();
386
387 // Obtain the treestate associated with the block being finalized.
388 let treestate = self
389 .treestate(block_height.into())
390 .expect("The treestate must be present for the root height.");
391
392 if treestate.note_commitment_trees.sapling_subtree.is_some() {
393 self.sapling_subtrees.pop_first();
394 }
395
396 if treestate.note_commitment_trees.orchard_subtree.is_some() {
397 self.orchard_subtrees.pop_first();
398 }
399
400 if treestate.note_commitment_trees.ironwood_subtree.is_some() {
401 self.ironwood_subtrees.pop_first();
402 }
403
404 // Remove the lowest height block from `self.blocks`.
405 let block = self
406 .blocks
407 .remove(&block_height)
408 .expect("only called while blocks is populated");
409
410 // Update cumulative data members.
411 self.revert_chain_with(&block, RevertPosition::Root);
412
413 (block, treestate)
414 }
415
416 /// Returns the block at the provided height and all of its descendant blocks.
417 pub fn child_blocks(&self, block_height: &block::Height) -> Vec<ContextuallyVerifiedBlock> {
418 self.blocks
419 .range(block_height..)
420 .map(|(_h, b)| b.clone())
421 .collect()
422 }
423
424 /// Returns a new chain without the invalidated block or its descendants.
425 pub fn invalidate_block(
426 &self,
427 block_hash: block::Hash,
428 ) -> Option<(Self, Vec<ContextuallyVerifiedBlock>)> {
429 let block_height = self.height_by_hash(block_hash)?;
430 let mut new_chain = self.fork(block_hash)?;
431 new_chain.pop_tip();
432 new_chain.last_fork_height = self.last_fork_height.min(Some(block_height));
433 Some((new_chain, self.child_blocks(&block_height)))
434 }
435
436 /// Returns the height of the chain root.
437 pub fn non_finalized_root_height(&self) -> block::Height {
438 self.blocks
439 .keys()
440 .next()
441 .cloned()
442 .expect("only called while blocks is populated")
443 }
444
445 /// Fork and return a chain at the block with the given `fork_tip`, if it is part of this
446 /// chain. Otherwise, if this chain does not contain `fork_tip`, returns `None`.
447 pub fn fork(&self, fork_tip: block::Hash) -> Option<Self> {
448 if !self.height_by_hash.contains_key(&fork_tip) {
449 return None;
450 }
451
452 let mut forked = self.clone();
453
454 // Revert blocks above the fork
455 while forked.non_finalized_tip_hash() != fork_tip {
456 forked.pop_tip();
457
458 forked.last_fork_height = Some(forked.non_finalized_tip_height());
459 }
460
461 Some(forked)
462 }
463
464 /// Returns the [`Network`] for this chain.
465 pub fn network(&self) -> Network {
466 self.network.clone()
467 }
468
469 /// Returns the [`ContextuallyVerifiedBlock`] with [`block::Hash`] or
470 /// [`Height`], if it exists in this chain.
471 pub fn block(&self, hash_or_height: HashOrHeight) -> Option<&ContextuallyVerifiedBlock> {
472 let height =
473 hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
474
475 self.blocks.get(&height)
476 }
477
478 /// Returns the [`Transaction`] with [`transaction::Hash`], if it exists in this chain.
479 pub fn transaction(
480 &self,
481 hash: transaction::Hash,
482 ) -> Option<(&Arc<Transaction>, block::Height, DateTime<Utc>)> {
483 self.tx_loc_by_hash.get(&hash).map(|tx_loc| {
484 (
485 &self.blocks[&tx_loc.height].block.transactions[tx_loc.index.as_usize()],
486 tx_loc.height,
487 self.blocks[&tx_loc.height].block.header.time,
488 )
489 })
490 }
491
492 /// Returns the [`Transaction`] at [`TransactionLocation`], if it exists in this chain.
493 #[allow(dead_code)]
494 pub fn transaction_by_loc(&self, tx_loc: TransactionLocation) -> Option<&Arc<Transaction>> {
495 self.blocks
496 .get(&tx_loc.height)?
497 .block
498 .transactions
499 .get(tx_loc.index.as_usize())
500 }
501
502 /// Returns the [`transaction::Hash`] for the transaction at [`TransactionLocation`],
503 /// if it exists in this chain.
504 #[allow(dead_code)]
505 pub fn transaction_hash_by_loc(
506 &self,
507 tx_loc: TransactionLocation,
508 ) -> Option<&transaction::Hash> {
509 self.blocks
510 .get(&tx_loc.height)?
511 .transaction_hashes
512 .get(tx_loc.index.as_usize())
513 }
514
515 /// Returns the [`transaction::Hash`]es in the block with `hash_or_height`,
516 /// if it exists in this chain.
517 ///
518 /// Hashes are returned in block order.
519 ///
520 /// Returns `None` if the block is not found.
521 pub fn transaction_hashes_for_block(
522 &self,
523 hash_or_height: HashOrHeight,
524 ) -> Option<Arc<[transaction::Hash]>> {
525 let transaction_hashes = self.block(hash_or_height)?.transaction_hashes.clone();
526
527 Some(transaction_hashes)
528 }
529
530 /// Returns the [`block::Hash`] for `height`, if it exists in this chain.
531 pub fn hash_by_height(&self, height: Height) -> Option<block::Hash> {
532 let hash = self.blocks.get(&height)?.hash;
533
534 Some(hash)
535 }
536
537 /// Returns the [`Height`] for `hash`, if it exists in this chain.
538 pub fn height_by_hash(&self, hash: block::Hash) -> Option<Height> {
539 self.height_by_hash.get(&hash).cloned()
540 }
541
542 /// Returns true is the chain contains the given block hash.
543 /// Returns false otherwise.
544 pub fn contains_block_hash(&self, hash: block::Hash) -> bool {
545 self.height_by_hash.contains_key(&hash)
546 }
547
548 /// Returns true is the chain contains the given block height.
549 /// Returns false otherwise.
550 pub fn contains_block_height(&self, height: Height) -> bool {
551 self.blocks.contains_key(&height)
552 }
553
554 /// Returns true is the chain contains the given block hash or height.
555 /// Returns false otherwise.
556 #[allow(dead_code)]
557 pub fn contains_hash_or_height(&self, hash_or_height: impl Into<HashOrHeight>) -> bool {
558 use HashOrHeight::*;
559
560 let hash_or_height = hash_or_height.into();
561
562 match hash_or_height {
563 Hash(hash) => self.contains_block_hash(hash),
564 Height(height) => self.contains_block_height(height),
565 }
566 }
567
568 /// Returns the non-finalized tip block height and hash.
569 pub fn non_finalized_tip(&self) -> (Height, block::Hash) {
570 (
571 self.non_finalized_tip_height(),
572 self.non_finalized_tip_hash(),
573 )
574 }
575
576 /// Returns the non-finalized tip block height, hash, and total pool value balances.
577 pub fn non_finalized_tip_with_value_balance(
578 &self,
579 ) -> (Height, block::Hash, ValueBalance<NonNegative>) {
580 (
581 self.non_finalized_tip_height(),
582 self.non_finalized_tip_hash(),
583 self.chain_value_pools,
584 )
585 }
586
587 /// Returns the total pool balance after the block specified by
588 /// [`HashOrHeight`], if it exists in the non-finalized [`Chain`].
589 pub fn block_info(&self, hash_or_height: HashOrHeight) -> Option<BlockInfo> {
590 let height =
591 hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
592
593 self.block_info_by_height.get(&height).cloned()
594 }
595
596 /// Returns the Sprout note commitment tree of the tip of this [`Chain`],
597 /// including all finalized notes, and the non-finalized notes in this chain.
598 ///
599 /// If the chain is empty, instead returns the tree of the finalized tip,
600 /// which was supplied in [`Chain::new()`]
601 ///
602 /// # Panics
603 ///
604 /// If this chain has no sprout trees. (This should be impossible.)
605 pub fn sprout_note_commitment_tree_for_tip(&self) -> Arc<sprout::tree::NoteCommitmentTree> {
606 self.sprout_trees_by_height
607 .last_key_value()
608 .expect("only called while sprout_trees_by_height is populated")
609 .1
610 .clone()
611 }
612
613 /// Returns the Sprout [`NoteCommitmentTree`](sprout::tree::NoteCommitmentTree) specified by
614 /// a [`HashOrHeight`], if it exists in the non-finalized [`Chain`].
615 pub fn sprout_tree(
616 &self,
617 hash_or_height: HashOrHeight,
618 ) -> Option<Arc<sprout::tree::NoteCommitmentTree>> {
619 let height =
620 hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
621
622 self.sprout_trees_by_height
623 .range(..=height)
624 .next_back()
625 .map(|(_height, tree)| tree.clone())
626 }
627
628 /// Adds the Sprout `tree` to the tree and anchor indexes at `height`.
629 ///
630 /// `height` can be either:
631 ///
632 /// - the height of a new block that has just been added to the chain tip, or
633 /// - the finalized tip height—the height of the parent of the first block of a new chain.
634 ///
635 /// Stores only the first tree in each series of identical trees.
636 ///
637 /// # Panics
638 ///
639 /// - If there's a tree already stored at `height`.
640 /// - If there's an anchor already stored at `height`.
641 fn add_sprout_tree_and_anchor(
642 &mut self,
643 height: Height,
644 tree: Arc<sprout::tree::NoteCommitmentTree>,
645 ) {
646 // Having updated all the note commitment trees and nullifier sets in
647 // this block, the roots of the note commitment trees as of the last
648 // transaction are the anchor treestates of this block.
649 //
650 // Use the previously cached root which was calculated in parallel.
651 let anchor = tree.root();
652 trace!(?height, ?anchor, "adding sprout tree");
653
654 // Add the new tree only if:
655 //
656 // - it differs from the previous one, or
657 // - there's no previous tree.
658 if height.is_min()
659 || self
660 .sprout_tree(height.previous().expect("prev height").into())
661 .is_none_or(|prev_tree| prev_tree != tree)
662 {
663 assert_eq!(
664 self.sprout_trees_by_height.insert(height, tree.clone()),
665 None,
666 "incorrect overwrite of sprout tree: trees must be reverted then inserted",
667 );
668 }
669
670 // Store the root.
671 assert_eq!(
672 self.sprout_anchors_by_height.insert(height, anchor),
673 None,
674 "incorrect overwrite of sprout anchor: anchors must be reverted then inserted",
675 );
676
677 // Multiple inserts are expected here,
678 // because the anchors only change if a block has shielded transactions.
679 self.sprout_anchors.insert(anchor);
680 self.sprout_trees_by_anchor.insert(anchor, tree);
681 }
682
683 /// Removes the Sprout tree and anchor indexes at `height`.
684 ///
685 /// `height` can be at two different [`RevertPosition`]s in the chain:
686 ///
687 /// - a tip block above a chain fork—only the tree and anchor at that height are removed, or
688 /// - a root block—all trees and anchors at and below that height are removed, including
689 /// temporary finalized tip trees.
690 ///
691 /// # Panics
692 ///
693 /// - If the anchor being removed is not present.
694 /// - If there is no tree at `height`.
695 fn remove_sprout_tree_and_anchor(&mut self, position: RevertPosition, height: Height) {
696 let (removed_heights, highest_removed_tree) = if position == RevertPosition::Root {
697 (
698 // Remove all trees and anchors at or below the removed block.
699 // This makes sure the temporary trees from finalized tip forks are removed.
700 self.sprout_anchors_by_height
701 .keys()
702 .cloned()
703 .filter(|index_height| *index_height <= height)
704 .collect(),
705 // Cache the highest (rightmost) tree before its removal.
706 self.sprout_tree(height.into()),
707 )
708 } else {
709 // Just remove the reverted tip trees and anchors.
710 // We don't need to cache the highest (rightmost) tree.
711 (vec![height], None)
712 };
713
714 for height in &removed_heights {
715 let anchor = self
716 .sprout_anchors_by_height
717 .remove(height)
718 .expect("Sprout anchor must be present if block was added to chain");
719
720 self.sprout_trees_by_height.remove(height);
721
722 trace!(?height, ?position, ?anchor, "removing sprout tree");
723
724 // Multiple removals are expected here,
725 // because the anchors only change if a block has shielded transactions.
726 assert!(
727 self.sprout_anchors.remove(&anchor),
728 "Sprout anchor must be present if block was added to chain"
729 );
730 if !self.sprout_anchors.contains(&anchor) {
731 self.sprout_trees_by_anchor.remove(&anchor);
732 }
733 }
734
735 // # Invariant
736 //
737 // The height following after the removed heights in a non-empty non-finalized state must
738 // always have its tree.
739 //
740 // The loop above can violate the invariant, and if `position` is [`RevertPosition::Root`],
741 // it will always violate the invariant. We restore the invariant by storing the highest
742 // (rightmost) removed tree just above `height` if there is no tree at that height.
743 if !self.is_empty() && height < self.non_finalized_tip_height() {
744 let next_height = height
745 .next()
746 .expect("Zebra should never reach the max height in normal operation.");
747
748 self.sprout_trees_by_height
749 .entry(next_height)
750 .or_insert_with(|| {
751 highest_removed_tree.expect("There should be a cached removed tree.")
752 });
753 }
754 }
755
756 /// Returns the Sapling note commitment tree of the tip of this [`Chain`],
757 /// including all finalized notes, and the non-finalized notes in this chain.
758 ///
759 /// If the chain is empty, instead returns the tree of the finalized tip,
760 /// which was supplied in [`Chain::new()`]
761 ///
762 /// # Panics
763 ///
764 /// If this chain has no sapling trees. (This should be impossible.)
765 pub fn sapling_note_commitment_tree_for_tip(&self) -> Arc<sapling::tree::NoteCommitmentTree> {
766 self.sapling_trees_by_height
767 .last_key_value()
768 .expect("only called while sapling_trees_by_height is populated")
769 .1
770 .clone()
771 }
772
773 /// Returns the Sapling [`NoteCommitmentTree`](sapling::tree::NoteCommitmentTree) specified
774 /// by a [`HashOrHeight`], if it exists in the non-finalized [`Chain`].
775 pub fn sapling_tree(
776 &self,
777 hash_or_height: HashOrHeight,
778 ) -> Option<Arc<sapling::tree::NoteCommitmentTree>> {
779 let height =
780 hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
781
782 self.sapling_trees_by_height
783 .range(..=height)
784 .next_back()
785 .map(|(_height, tree)| tree.clone())
786 }
787
788 /// Returns the Sapling [`NoteCommitmentSubtree`] that was completed at a block with
789 /// [`HashOrHeight`], if it exists in the non-finalized [`Chain`].
790 ///
791 /// # Concurrency
792 ///
793 /// This method should not be used to get subtrees in concurrent code by height,
794 /// because the same heights in different chain forks can have different subtrees.
795 pub fn sapling_subtree(
796 &self,
797 hash_or_height: HashOrHeight,
798 ) -> Option<NoteCommitmentSubtree<sapling_crypto::Node>> {
799 let height =
800 hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
801
802 self.sapling_subtrees
803 .iter()
804 .find(|(_index, subtree)| subtree.end_height == height)
805 .map(|(index, subtree)| subtree.with_index(*index))
806 }
807
808 /// Returns a list of Sapling [`NoteCommitmentSubtree`]s in the provided range.
809 ///
810 /// Unlike the finalized state and `ReadRequest::SaplingSubtrees`, the returned subtrees
811 /// can start after `start_index`. These subtrees are continuous up to the tip.
812 ///
813 /// There is no API for retrieving single subtrees by index, because it can accidentally be
814 /// used to create an inconsistent list of subtrees after concurrent non-finalized and
815 /// finalized updates.
816 pub fn sapling_subtrees_in_range(
817 &self,
818 range: impl std::ops::RangeBounds<NoteCommitmentSubtreeIndex>,
819 ) -> BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<sapling_crypto::Node>> {
820 self.sapling_subtrees
821 .range(range)
822 .map(|(index, subtree)| (*index, *subtree))
823 .collect()
824 }
825
826 /// Returns the Sapling [`NoteCommitmentSubtree`] if it was completed at the tip height.
827 pub fn sapling_subtree_for_tip(&self) -> Option<NoteCommitmentSubtree<sapling_crypto::Node>> {
828 if !self.is_empty() {
829 let tip = self.non_finalized_tip_height();
830 self.sapling_subtree(tip.into())
831 } else {
832 None
833 }
834 }
835
836 /// Adds the Sapling `tree` to the tree and anchor indexes at `height`.
837 ///
838 /// `height` can be either:
839 ///
840 /// - the height of a new block that has just been added to the chain tip, or
841 /// - the finalized tip height—the height of the parent of the first block of a new chain.
842 ///
843 /// Stores only the first tree in each series of identical trees.
844 ///
845 /// # Panics
846 ///
847 /// - If there's a tree already stored at `height`.
848 /// - If there's an anchor already stored at `height`.
849 fn add_sapling_tree_and_anchor(
850 &mut self,
851 height: Height,
852 tree: Arc<sapling::tree::NoteCommitmentTree>,
853 ) {
854 let anchor = tree.root();
855 trace!(?height, ?anchor, "adding sapling tree");
856
857 // Add the new tree only if:
858 //
859 // - it differs from the previous one, or
860 // - there's no previous tree.
861 if height.is_min()
862 || self
863 .sapling_tree(height.previous().expect("prev height").into())
864 .is_none_or(|prev_tree| prev_tree != tree)
865 {
866 assert_eq!(
867 self.sapling_trees_by_height.insert(height, tree),
868 None,
869 "incorrect overwrite of sapling tree: trees must be reverted then inserted",
870 );
871 }
872
873 // Store the root.
874 assert_eq!(
875 self.sapling_anchors_by_height.insert(height, anchor),
876 None,
877 "incorrect overwrite of sapling anchor: anchors must be reverted then inserted",
878 );
879
880 // Multiple inserts are expected here,
881 // because the anchors only change if a block has shielded transactions.
882 self.sapling_anchors.insert(anchor);
883 }
884
885 /// Removes the Sapling tree and anchor indexes at `height`.
886 ///
887 /// `height` can be at two different [`RevertPosition`]s in the chain:
888 ///
889 /// - a tip block above a chain fork—only the tree and anchor at that height are removed, or
890 /// - a root block—all trees and anchors at and below that height are removed, including
891 /// temporary finalized tip trees.
892 ///
893 /// # Panics
894 ///
895 /// - If the anchor being removed is not present.
896 /// - If there is no tree at `height`.
897 fn remove_sapling_tree_and_anchor(&mut self, position: RevertPosition, height: Height) {
898 let (removed_heights, highest_removed_tree) = if position == RevertPosition::Root {
899 (
900 // Remove all trees and anchors at or below the removed block.
901 // This makes sure the temporary trees from finalized tip forks are removed.
902 self.sapling_anchors_by_height
903 .keys()
904 .cloned()
905 .filter(|index_height| *index_height <= height)
906 .collect(),
907 // Cache the highest (rightmost) tree before its removal.
908 self.sapling_tree(height.into()),
909 )
910 } else {
911 // Just remove the reverted tip trees and anchors.
912 // We don't need to cache the highest (rightmost) tree.
913 (vec![height], None)
914 };
915
916 for height in &removed_heights {
917 let anchor = self
918 .sapling_anchors_by_height
919 .remove(height)
920 .expect("Sapling anchor must be present if block was added to chain");
921
922 self.sapling_trees_by_height.remove(height);
923
924 trace!(?height, ?position, ?anchor, "removing sapling tree");
925
926 // Multiple removals are expected here,
927 // because the anchors only change if a block has shielded transactions.
928 assert!(
929 self.sapling_anchors.remove(&anchor),
930 "Sapling anchor must be present if block was added to chain"
931 );
932 }
933
934 // # Invariant
935 //
936 // The height following after the removed heights in a non-empty non-finalized state must
937 // always have its tree.
938 //
939 // The loop above can violate the invariant, and if `position` is [`RevertPosition::Root`],
940 // it will always violate the invariant. We restore the invariant by storing the highest
941 // (rightmost) removed tree just above `height` if there is no tree at that height.
942 if !self.is_empty() && height < self.non_finalized_tip_height() {
943 let next_height = height
944 .next()
945 .expect("Zebra should never reach the max height in normal operation.");
946
947 self.sapling_trees_by_height
948 .entry(next_height)
949 .or_insert_with(|| {
950 highest_removed_tree.expect("There should be a cached removed tree.")
951 });
952 }
953 }
954
955 /// Returns the Orchard note commitment tree of the tip of this [`Chain`],
956 /// including all finalized notes, and the non-finalized notes in this chain.
957 ///
958 /// If the chain is empty, instead returns the tree of the finalized tip,
959 /// which was supplied in [`Chain::new()`]
960 ///
961 /// # Panics
962 ///
963 /// If this chain has no orchard trees. (This should be impossible.)
964 pub fn orchard_note_commitment_tree_for_tip(&self) -> Arc<orchard::tree::NoteCommitmentTree> {
965 self.orchard_trees_by_height
966 .last_key_value()
967 .expect("only called while orchard_trees_by_height is populated")
968 .1
969 .clone()
970 }
971
972 /// Returns the Orchard
973 /// [`NoteCommitmentTree`](orchard::tree::NoteCommitmentTree) specified by a
974 /// [`HashOrHeight`], if it exists in the non-finalized [`Chain`].
975 pub fn orchard_tree(
976 &self,
977 hash_or_height: HashOrHeight,
978 ) -> Option<Arc<orchard::tree::NoteCommitmentTree>> {
979 let height =
980 hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
981
982 self.orchard_trees_by_height
983 .range(..=height)
984 .next_back()
985 .map(|(_height, tree)| tree.clone())
986 }
987
988 /// Returns the Orchard [`NoteCommitmentSubtree`] that was completed at a block with
989 /// [`HashOrHeight`], if it exists in the non-finalized [`Chain`].
990 ///
991 /// # Concurrency
992 ///
993 /// This method should not be used to get subtrees in concurrent code by height,
994 /// because the same heights in different chain forks can have different subtrees.
995 pub fn orchard_subtree(
996 &self,
997 hash_or_height: HashOrHeight,
998 ) -> Option<NoteCommitmentSubtree<orchard::tree::Node>> {
999 let height =
1000 hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
1001
1002 self.orchard_subtrees
1003 .iter()
1004 .find(|(_index, subtree)| subtree.end_height == height)
1005 .map(|(index, subtree)| subtree.with_index(*index))
1006 }
1007
1008 /// Returns a list of Orchard [`NoteCommitmentSubtree`]s in the provided range.
1009 ///
1010 /// Unlike the finalized state and `ReadRequest::OrchardSubtrees`, the returned subtrees
1011 /// can start after `start_index`. These subtrees are continuous up to the tip.
1012 ///
1013 /// There is no API for retrieving single subtrees by index, because it can accidentally be
1014 /// used to create an inconsistent list of subtrees after concurrent non-finalized and
1015 /// finalized updates.
1016 pub fn orchard_subtrees_in_range(
1017 &self,
1018 range: impl std::ops::RangeBounds<NoteCommitmentSubtreeIndex>,
1019 ) -> BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<orchard::tree::Node>> {
1020 self.orchard_subtrees
1021 .range(range)
1022 .map(|(index, subtree)| (*index, *subtree))
1023 .collect()
1024 }
1025
1026 /// Returns the Orchard [`NoteCommitmentSubtree`] if it was completed at the tip height.
1027 pub fn orchard_subtree_for_tip(&self) -> Option<NoteCommitmentSubtree<orchard::tree::Node>> {
1028 if !self.is_empty() {
1029 let tip = self.non_finalized_tip_height();
1030 self.orchard_subtree(tip.into())
1031 } else {
1032 None
1033 }
1034 }
1035
1036 /// Adds the Orchard `tree` to the tree and anchor indexes at `height`.
1037 ///
1038 /// `height` can be either:
1039 ///
1040 /// - the height of a new block that has just been added to the chain tip, or
1041 /// - the finalized tip height—the height of the parent of the first block of a new chain.
1042 ///
1043 /// Stores only the first tree in each series of identical trees.
1044 ///
1045 /// # Panics
1046 ///
1047 /// - If there's a tree already stored at `height`.
1048 /// - If there's an anchor already stored at `height`.
1049 fn add_orchard_tree_and_anchor(
1050 &mut self,
1051 height: Height,
1052 tree: Arc<orchard::tree::NoteCommitmentTree>,
1053 ) {
1054 let prev_tree = (!height.is_min())
1055 .then(|| self.orchard_tree(height.previous().expect("prev height").into()))
1056 .flatten();
1057
1058 // Deref once to `ChainInner` so the disjoint field borrows below can be split.
1059 let inner: &mut ChainInner = self;
1060 Self::add_note_commitment_tree_and_anchor(
1061 "orchard",
1062 &mut inner.orchard_trees_by_height,
1063 &mut inner.orchard_anchors_by_height,
1064 &mut inner.orchard_anchors,
1065 height,
1066 tree,
1067 prev_tree,
1068 );
1069 }
1070
1071 /// Removes the Orchard tree and anchor indexes at `height`.
1072 ///
1073 /// `height` can be at two different [`RevertPosition`]s in the chain:
1074 ///
1075 /// - a tip block above a chain fork—only the tree and anchor at that height are removed, or
1076 /// - a root block—all trees and anchors at and below that height are removed, including
1077 /// temporary finalized tip trees.
1078 ///
1079 /// # Panics
1080 ///
1081 /// - If the anchor being removed is not present.
1082 /// - If there is no tree at `height`.
1083 fn remove_orchard_tree_and_anchor(&mut self, position: RevertPosition, height: Height) {
1084 // Cache the highest (rightmost) tree before its removal, to restore the invariant below.
1085 let highest_removed_tree = (position == RevertPosition::Root)
1086 .then(|| self.orchard_tree(height.into()))
1087 .flatten();
1088 let non_finalized_tip_height = (!self.is_empty()).then(|| self.non_finalized_tip_height());
1089
1090 let inner: &mut ChainInner = self;
1091 Self::remove_note_commitment_tree_and_anchor(
1092 "orchard",
1093 &mut inner.orchard_trees_by_height,
1094 &mut inner.orchard_anchors_by_height,
1095 &mut inner.orchard_anchors,
1096 position,
1097 height,
1098 highest_removed_tree,
1099 non_finalized_tip_height,
1100 );
1101 }
1102
1103 /// Shared implementation of `add_{orchard,ironwood}_tree_and_anchor`.
1104 ///
1105 /// Ironwood reuses the Orchard note commitment tree and root types, so the two pools index into
1106 /// identically-typed maps; only the target fields and the `pool` log/panic label differ.
1107 /// `prev_tree` is the tree at the previous height (`None` at the minimum height).
1108 fn add_note_commitment_tree_and_anchor(
1109 pool: &'static str,
1110 trees_by_height: &mut BTreeMap<Height, Arc<orchard::tree::NoteCommitmentTree>>,
1111 anchors_by_height: &mut BTreeMap<Height, orchard::tree::Root>,
1112 anchors: &mut MultiSet<orchard::tree::Root>,
1113 height: Height,
1114 tree: Arc<orchard::tree::NoteCommitmentTree>,
1115 prev_tree: Option<Arc<orchard::tree::NoteCommitmentTree>>,
1116 ) {
1117 // Having updated all the note commitment trees and nullifier sets in this block, the roots
1118 // of the note commitment trees as of the last transaction are the anchor treestates of this
1119 // block. Use the previously cached root which was calculated in parallel.
1120 let anchor = tree.root();
1121 trace!(?height, ?anchor, pool, "adding note commitment tree");
1122
1123 // Add the new tree only if it differs from the previous one, or there's no previous tree.
1124 if height.is_min() || prev_tree.is_none_or(|prev_tree| prev_tree != tree) {
1125 assert_eq!(
1126 trees_by_height.insert(height, tree),
1127 None,
1128 "incorrect overwrite of {pool} tree: trees must be reverted then inserted",
1129 );
1130 }
1131
1132 // Store the root.
1133 assert_eq!(
1134 anchors_by_height.insert(height, anchor),
1135 None,
1136 "incorrect overwrite of {pool} anchor: anchors must be reverted then inserted",
1137 );
1138
1139 // Multiple inserts are expected here,
1140 // because the anchors only change if a block has shielded transactions.
1141 anchors.insert(anchor);
1142 }
1143
1144 /// Shared implementation of `remove_{orchard,ironwood}_tree_and_anchor`.
1145 ///
1146 /// `highest_removed_tree` is the tree at `height` cached before removal (only needed for
1147 /// [`RevertPosition::Root`]); `non_finalized_tip_height` is `None` when the chain is empty.
1148 #[allow(clippy::too_many_arguments)]
1149 fn remove_note_commitment_tree_and_anchor(
1150 pool: &'static str,
1151 trees_by_height: &mut BTreeMap<Height, Arc<orchard::tree::NoteCommitmentTree>>,
1152 anchors_by_height: &mut BTreeMap<Height, orchard::tree::Root>,
1153 anchors: &mut MultiSet<orchard::tree::Root>,
1154 position: RevertPosition,
1155 height: Height,
1156 highest_removed_tree: Option<Arc<orchard::tree::NoteCommitmentTree>>,
1157 non_finalized_tip_height: Option<Height>,
1158 ) {
1159 let removed_heights: Vec<Height> = if position == RevertPosition::Root {
1160 // Remove all trees and anchors at or below the removed block.
1161 // This makes sure the temporary trees from finalized tip forks are removed.
1162 anchors_by_height
1163 .keys()
1164 .cloned()
1165 .filter(|index_height| *index_height <= height)
1166 .collect()
1167 } else {
1168 // Just remove the reverted tip trees and anchors.
1169 vec![height]
1170 };
1171
1172 for height in &removed_heights {
1173 let anchor = anchors_by_height.remove(height).unwrap_or_else(|| {
1174 panic!("{pool} anchor must be present if block was added to chain")
1175 });
1176
1177 trees_by_height.remove(height);
1178
1179 trace!(
1180 ?height,
1181 ?position,
1182 ?anchor,
1183 pool,
1184 "removing note commitment tree"
1185 );
1186
1187 // Multiple removals are expected here,
1188 // because the anchors only change if a block has shielded transactions.
1189 assert!(
1190 anchors.remove(&anchor),
1191 "{pool} anchor must be present if block was added to chain"
1192 );
1193 }
1194
1195 // # Invariant
1196 //
1197 // The height following after the removed heights in a non-empty non-finalized state must
1198 // always have its tree.
1199 //
1200 // The loop above can violate the invariant, and if `position` is [`RevertPosition::Root`],
1201 // it will always violate the invariant. We restore the invariant by storing the highest
1202 // (rightmost) removed tree just above `height` if there is no tree at that height.
1203 if let Some(non_finalized_tip_height) = non_finalized_tip_height {
1204 if height < non_finalized_tip_height {
1205 let next_height = height
1206 .next()
1207 .expect("Zebra should never reach the max height in normal operation.");
1208
1209 trees_by_height.entry(next_height).or_insert_with(|| {
1210 highest_removed_tree.expect("There should be a cached removed tree.")
1211 });
1212 }
1213 }
1214 }
1215
1216 // Ironwood note commitment tree methods (NU6.3).
1217 //
1218 // Ironwood reuses the Orchard note commitment tree type, but maintains its own tree/anchor/
1219 // subtree indexes. These mirror the Orchard methods above.
1220
1221 /// Returns the Ironwood note commitment tree of the tip of this [`Chain`].
1222 ///
1223 /// # Panics
1224 ///
1225 /// If this chain has no ironwood trees. (This should be impossible.)
1226 pub fn ironwood_note_commitment_tree_for_tip(&self) -> Arc<orchard::tree::NoteCommitmentTree> {
1227 self.ironwood_trees_by_height
1228 .last_key_value()
1229 .expect("only called while ironwood_trees_by_height is populated")
1230 .1
1231 .clone()
1232 }
1233
1234 /// Returns the Ironwood [`NoteCommitmentTree`](orchard::tree::NoteCommitmentTree) specified by
1235 /// a [`HashOrHeight`], if it exists in the non-finalized [`Chain`].
1236 pub fn ironwood_tree(
1237 &self,
1238 hash_or_height: HashOrHeight,
1239 ) -> Option<Arc<orchard::tree::NoteCommitmentTree>> {
1240 let height =
1241 hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
1242
1243 self.ironwood_trees_by_height
1244 .range(..=height)
1245 .next_back()
1246 .map(|(_height, tree)| tree.clone())
1247 }
1248
1249 /// Returns the Ironwood [`NoteCommitmentSubtree`] that was completed at a block with
1250 /// [`HashOrHeight`], if it exists in the non-finalized [`Chain`].
1251 pub fn ironwood_subtree(
1252 &self,
1253 hash_or_height: HashOrHeight,
1254 ) -> Option<NoteCommitmentSubtree<orchard::tree::Node>> {
1255 let height =
1256 hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
1257
1258 self.ironwood_subtrees
1259 .iter()
1260 .find(|(_index, subtree)| subtree.end_height == height)
1261 .map(|(index, subtree)| subtree.with_index(*index))
1262 }
1263
1264 /// Returns a list of Ironwood [`NoteCommitmentSubtree`]s in the provided range.
1265 pub fn ironwood_subtrees_in_range(
1266 &self,
1267 range: impl std::ops::RangeBounds<NoteCommitmentSubtreeIndex>,
1268 ) -> BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<orchard::tree::Node>> {
1269 self.ironwood_subtrees
1270 .range(range)
1271 .map(|(index, subtree)| (*index, *subtree))
1272 .collect()
1273 }
1274
1275 /// Returns the Ironwood [`NoteCommitmentSubtree`] if it was completed at the tip height.
1276 pub fn ironwood_subtree_for_tip(&self) -> Option<NoteCommitmentSubtree<orchard::tree::Node>> {
1277 if !self.is_empty() {
1278 let tip = self.non_finalized_tip_height();
1279 self.ironwood_subtree(tip.into())
1280 } else {
1281 None
1282 }
1283 }
1284
1285 /// Adds the Ironwood `tree` to the tree and anchor indexes at `height`.
1286 ///
1287 /// See [`Chain::add_orchard_tree_and_anchor`] for the height semantics and invariants.
1288 fn add_ironwood_tree_and_anchor(
1289 &mut self,
1290 height: Height,
1291 tree: Arc<orchard::tree::NoteCommitmentTree>,
1292 ) {
1293 let prev_tree = (!height.is_min())
1294 .then(|| self.ironwood_tree(height.previous().expect("prev height").into()))
1295 .flatten();
1296
1297 let inner: &mut ChainInner = self;
1298 Self::add_note_commitment_tree_and_anchor(
1299 "ironwood",
1300 &mut inner.ironwood_trees_by_height,
1301 &mut inner.ironwood_anchors_by_height,
1302 &mut inner.ironwood_anchors,
1303 height,
1304 tree,
1305 prev_tree,
1306 );
1307 }
1308
1309 /// Removes the Ironwood tree and anchor indexes at `height`.
1310 ///
1311 /// See [`Chain::remove_orchard_tree_and_anchor`] for the revert-position semantics and invariants.
1312 fn remove_ironwood_tree_and_anchor(&mut self, position: RevertPosition, height: Height) {
1313 let highest_removed_tree = (position == RevertPosition::Root)
1314 .then(|| self.ironwood_tree(height.into()))
1315 .flatten();
1316 let non_finalized_tip_height = (!self.is_empty()).then(|| self.non_finalized_tip_height());
1317
1318 let inner: &mut ChainInner = self;
1319 Self::remove_note_commitment_tree_and_anchor(
1320 "ironwood",
1321 &mut inner.ironwood_trees_by_height,
1322 &mut inner.ironwood_anchors_by_height,
1323 &mut inner.ironwood_anchors,
1324 position,
1325 height,
1326 highest_removed_tree,
1327 non_finalized_tip_height,
1328 );
1329 }
1330
1331 /// Returns the History tree of the tip of this [`Chain`],
1332 /// including all finalized blocks, and the non-finalized blocks below the chain tip.
1333 ///
1334 /// If the chain is empty, instead returns the tree of the finalized tip,
1335 /// which was supplied in [`Chain::new()`]
1336 ///
1337 /// # Panics
1338 ///
1339 /// If this chain has no history trees. (This should be impossible.)
1340 pub fn history_block_commitment_tree(&self) -> Arc<HistoryTree> {
1341 self.history_trees_by_height
1342 .last_key_value()
1343 .expect("only called while history_trees_by_height is populated")
1344 .1
1345 .clone()
1346 }
1347
1348 /// Returns the [`HistoryTree`] specified by a [`HashOrHeight`], if it
1349 /// exists in the non-finalized [`Chain`].
1350 pub fn history_tree(&self, hash_or_height: HashOrHeight) -> Option<Arc<HistoryTree>> {
1351 let height =
1352 hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
1353
1354 self.history_trees_by_height.get(&height).cloned()
1355 }
1356
1357 /// Add the History `tree` to the history tree index at `height`.
1358 ///
1359 /// `height` can be either:
1360 /// - the height of a new block that has just been added to the chain tip, or
1361 /// - the finalized tip height: the height of the parent of the first block of a new chain.
1362 fn add_history_tree(&mut self, height: Height, tree: Arc<HistoryTree>) {
1363 // The history tree commits to all the blocks before this block.
1364 //
1365 // Use the previously cached root which was calculated in parallel.
1366 trace!(?height, "adding history tree");
1367
1368 assert_eq!(
1369 self.history_trees_by_height.insert(height, tree),
1370 None,
1371 "incorrect overwrite of history tree: trees must be reverted then inserted",
1372 );
1373 }
1374
1375 /// Remove the History tree index at `height`.
1376 ///
1377 /// `height` can be at two different [`RevertPosition`]s in the chain:
1378 /// - a tip block above a chain fork: only that height is removed, or
1379 /// - a root block: all trees below that height are removed,
1380 /// including temporary finalized tip trees.
1381 fn remove_history_tree(&mut self, position: RevertPosition, height: Height) {
1382 trace!(?height, ?position, "removing history tree");
1383
1384 if position == RevertPosition::Root {
1385 // Remove all trees at or below the reverted root block.
1386 // This makes sure the temporary trees from finalized tip forks are removed.
1387 self.history_trees_by_height
1388 .retain(|index_height, _tree| *index_height > height);
1389 } else {
1390 // Just remove the reverted tip tree.
1391 self.history_trees_by_height
1392 .remove(&height)
1393 .expect("History tree must be present if block was added to chain");
1394 }
1395 }
1396
1397 fn treestate(&self, hash_or_height: HashOrHeight) -> Option<Treestate> {
1398 let sprout_tree = self.sprout_tree(hash_or_height)?;
1399 let sapling_tree = self.sapling_tree(hash_or_height)?;
1400 let orchard_tree = self.orchard_tree(hash_or_height)?;
1401 let ironwood_tree = self.ironwood_tree(hash_or_height)?;
1402 let history_tree = self.history_tree(hash_or_height)?;
1403 let sapling_subtree = self.sapling_subtree(hash_or_height);
1404 let orchard_subtree = self.orchard_subtree(hash_or_height);
1405 let ironwood_subtree = self.ironwood_subtree(hash_or_height);
1406
1407 Some(Treestate::new(
1408 NoteCommitmentTrees {
1409 sprout: sprout_tree,
1410 sapling: sapling_tree,
1411 sapling_subtree,
1412 orchard: orchard_tree,
1413 orchard_subtree,
1414 ironwood: ironwood_tree,
1415 ironwood_subtree,
1416 },
1417 history_tree,
1418 ))
1419 }
1420
1421 /// Returns the block hash of the tip block.
1422 pub fn non_finalized_tip_hash(&self) -> block::Hash {
1423 self.blocks
1424 .values()
1425 .next_back()
1426 .expect("only called while blocks is populated")
1427 .hash
1428 }
1429
1430 /// Returns the non-finalized root block hash and height.
1431 #[allow(dead_code)]
1432 pub fn non_finalized_root(&self) -> (block::Hash, block::Height) {
1433 (
1434 self.non_finalized_root_hash(),
1435 self.non_finalized_root_height(),
1436 )
1437 }
1438
1439 /// Returns the block hash of the non-finalized root block.
1440 pub fn non_finalized_root_hash(&self) -> block::Hash {
1441 self.blocks
1442 .values()
1443 .next()
1444 .expect("only called while blocks is populated")
1445 .hash
1446 }
1447
1448 /// Returns the block hash of the `n`th block from the non-finalized root.
1449 ///
1450 /// This is the block at `non_finalized_root_height() + n`.
1451 #[allow(dead_code)]
1452 pub fn non_finalized_nth_hash(&self, n: usize) -> Option<block::Hash> {
1453 self.blocks.values().nth(n).map(|block| block.hash)
1454 }
1455
1456 /// Remove the highest height block of the non-finalized portion of a chain.
1457 fn pop_tip(&mut self) {
1458 let block_height = self.non_finalized_tip_height();
1459
1460 let block = self
1461 .blocks
1462 .remove(&block_height)
1463 .expect("only called while blocks is populated");
1464
1465 // If the popped block completed a Sapling or Orchard subtree, remove the corresponding
1466 // subtree from this chain too. Subtrees are inserted by `push` keyed by the highest subtree
1467 // index, so the last entry's `end_height` matches the popped block iff a subtree was
1468 // completed at that height.
1469 if self
1470 .sapling_subtrees
1471 .last_key_value()
1472 .is_some_and(|(_, subtree)| subtree.end_height == block_height)
1473 {
1474 self.sapling_subtrees.pop_last();
1475 }
1476 if self
1477 .orchard_subtrees
1478 .last_key_value()
1479 .is_some_and(|(_, subtree)| subtree.end_height == block_height)
1480 {
1481 self.orchard_subtrees.pop_last();
1482 }
1483 if self
1484 .ironwood_subtrees
1485 .last_key_value()
1486 .is_some_and(|(_, subtree)| subtree.end_height == block_height)
1487 {
1488 self.ironwood_subtrees.pop_last();
1489 }
1490
1491 assert!(
1492 !self.blocks.is_empty(),
1493 "Non-finalized chains must have at least one block to be valid"
1494 );
1495
1496 self.revert_chain_with(&block, RevertPosition::Tip);
1497 }
1498
1499 /// Return the non-finalized tip height for this chain.
1500 ///
1501 /// # Panics
1502 ///
1503 /// Panics if called while the chain is empty,
1504 /// or while the chain is updating its internal state with the first block.
1505 pub fn non_finalized_tip_height(&self) -> block::Height {
1506 self.max_block_height()
1507 .expect("only called while blocks is populated")
1508 }
1509
1510 /// Return the non-finalized tip height for this chain,
1511 /// or `None` if `self.blocks` is empty.
1512 fn max_block_height(&self) -> Option<block::Height> {
1513 self.blocks.keys().next_back().cloned()
1514 }
1515
1516 /// Return the non-finalized tip block for this chain,
1517 /// or `None` if `self.blocks` is empty.
1518 pub fn tip_block(&self) -> Option<&ContextuallyVerifiedBlock> {
1519 self.blocks.values().next_back()
1520 }
1521
1522 /// Returns true if the non-finalized part of this chain is empty.
1523 pub fn is_empty(&self) -> bool {
1524 self.blocks.is_empty()
1525 }
1526
1527 /// Returns the non-finalized length of this chain.
1528 #[allow(dead_code)]
1529 pub fn len(&self) -> usize {
1530 self.blocks.len()
1531 }
1532
1533 /// Returns the unspent transaction outputs (UTXOs) in this non-finalized chain.
1534 ///
1535 /// Callers should also check the finalized state for available UTXOs.
1536 /// If UTXOs remain unspent when a block is finalized, they are stored in the finalized state,
1537 /// and removed from the relevant chain(s).
1538 pub fn unspent_utxos(&self) -> HashMap<transparent::OutPoint, transparent::OrderedUtxo> {
1539 let mut unspent_utxos = self.created_utxos.clone();
1540 unspent_utxos.retain(|outpoint, _utxo| !self.spent_utxos.contains_key(outpoint));
1541
1542 unspent_utxos
1543 }
1544
1545 /// Returns the [`transparent::Utxo`] pointed to by the given
1546 /// [`transparent::OutPoint`] if it was created by this chain.
1547 ///
1548 /// UTXOs are returned regardless of whether they have been spent.
1549 pub fn created_utxo(&self, outpoint: &transparent::OutPoint) -> Option<transparent::Utxo> {
1550 self.created_utxos
1551 .get(outpoint)
1552 .map(|utxo| utxo.utxo.clone())
1553 }
1554
1555 /// Returns the [`Hash`](transaction::Hash) of the transaction that spent an output at
1556 /// the provided [`transparent::OutPoint`] or revealed the provided nullifier, if it exists
1557 /// and is spent or revealed by this chain.
1558 #[cfg(feature = "indexer")]
1559 pub fn spending_transaction_hash(&self, spend: &Spend) -> Option<transaction::Hash> {
1560 match spend {
1561 Spend::OutPoint(outpoint) => self.spent_utxos.get(outpoint),
1562 Spend::Sprout(nullifier) => self.sprout_nullifiers.get(nullifier),
1563 Spend::Sapling(nullifier) => self.sapling_nullifiers.get(nullifier),
1564 Spend::Orchard(nullifier) => self.orchard_nullifiers.get(nullifier),
1565 Spend::Ironwood(nullifier) => self.ironwood_nullifiers.get(nullifier),
1566 }
1567 .cloned()
1568 }
1569
1570 // Address index queries
1571
1572 /// Returns the transparent transfers for `addresses` in this non-finalized chain.
1573 ///
1574 /// If none of the addresses have an address index, returns an empty iterator.
1575 ///
1576 /// # Correctness
1577 ///
1578 /// Callers should apply the returned indexes to the corresponding finalized state indexes.
1579 ///
1580 /// The combined result will only be correct if the chains match.
1581 /// The exact type of match varies by query.
1582 pub fn partial_transparent_indexes<'a>(
1583 &'a self,
1584 addresses: &'a HashSet<transparent::Address>,
1585 ) -> impl Iterator<Item = &'a TransparentTransfers> {
1586 addresses
1587 .iter()
1588 .flat_map(|address| self.partial_transparent_transfers.get(address))
1589 }
1590
1591 /// Returns a tuple of the transparent balance change and the total received funds for
1592 /// `addresses` in this non-finalized chain.
1593 ///
1594 /// If the balance doesn't change for any of the addresses, returns zero.
1595 ///
1596 /// # Correctness
1597 ///
1598 /// Callers should apply this balance change to the finalized state balance for `addresses`.
1599 ///
1600 /// The total balance will only be correct if this partial chain matches the finalized state.
1601 /// Specifically, the root of this partial chain must be a child block of the finalized tip.
1602 pub fn partial_transparent_balance_change(
1603 &self,
1604 addresses: &HashSet<transparent::Address>,
1605 ) -> (Amount<NegativeAllowed>, u64) {
1606 let (balance, received) = self.partial_transparent_indexes(addresses).fold(
1607 (Ok(Amount::zero()), 0),
1608 |(balance, received), transfers| {
1609 let balance = balance + transfers.balance();
1610 (balance, received + transfers.received())
1611 },
1612 );
1613
1614 (balance.expect("unexpected amount overflow"), received)
1615 }
1616
1617 /// Returns the transparent UTXO changes for `addresses` in this non-finalized chain.
1618 ///
1619 /// If the UTXOs don't change for any of the addresses, returns empty lists.
1620 ///
1621 /// # Correctness
1622 ///
1623 /// Callers should apply these non-finalized UTXO changes to the finalized state UTXOs.
1624 ///
1625 /// The UTXOs will only be correct if the non-finalized chain matches or overlaps with
1626 /// the finalized state.
1627 ///
1628 /// Specifically, a block in the partial chain must be a child block of the finalized tip.
1629 /// (But the child block does not have to be the partial chain root.)
1630 pub fn partial_transparent_utxo_changes(
1631 &self,
1632 addresses: &HashSet<transparent::Address>,
1633 ) -> (
1634 BTreeMap<OutputLocation, transparent::Output>,
1635 BTreeSet<OutputLocation>,
1636 ) {
1637 let created_utxos = self
1638 .partial_transparent_indexes(addresses)
1639 .flat_map(|transfers| transfers.created_utxos())
1640 .map(|(out_loc, output)| (*out_loc, output.clone()))
1641 .collect();
1642
1643 let spent_utxos = self
1644 .partial_transparent_indexes(addresses)
1645 .flat_map(|transfers| transfers.spent_utxos())
1646 .cloned()
1647 .collect();
1648
1649 (created_utxos, spent_utxos)
1650 }
1651
1652 /// Returns the [`transaction::Hash`]es used by `addresses` to receive or spend funds,
1653 /// in the non-finalized chain, filtered using the `query_height_range`.
1654 ///
1655 /// If none of the addresses receive or spend funds in this partial chain, returns an empty list.
1656 ///
1657 /// # Correctness
1658 ///
1659 /// Callers should combine these non-finalized transactions with the finalized state transactions.
1660 ///
1661 /// The transaction IDs will only be correct if the non-finalized chain matches or overlaps with
1662 /// the finalized state.
1663 ///
1664 /// Specifically, a block in the partial chain must be a child block of the finalized tip.
1665 /// (But the child block does not have to be the partial chain root.)
1666 ///
1667 /// This condition does not apply if there is only one address.
1668 /// Since address transactions are only appended by blocks,
1669 /// and the finalized state query reads them in order,
1670 /// it is impossible to get inconsistent transactions for a single address.
1671 pub fn partial_transparent_tx_ids(
1672 &self,
1673 addresses: &HashSet<transparent::Address>,
1674 query_height_range: RangeInclusive<Height>,
1675 ) -> BTreeMap<TransactionLocation, transaction::Hash> {
1676 self.partial_transparent_indexes(addresses)
1677 .flat_map(|transfers| {
1678 transfers.tx_ids(&self.tx_loc_by_hash, query_height_range.clone())
1679 })
1680 .collect()
1681 }
1682
1683 /// Update the chain tip with the `contextually_valid` block,
1684 /// running note commitment tree updates in parallel with other updates.
1685 ///
1686 /// Used to implement `update_chain_tip_with::<ContextuallyVerifiedBlock>`.
1687 #[instrument(skip(self, contextually_valid), fields(block = %contextually_valid.block))]
1688 #[allow(clippy::unwrap_in_result)]
1689 fn update_chain_tip_with_block_parallel(
1690 &mut self,
1691 contextually_valid: &ContextuallyVerifiedBlock,
1692 ) -> Result<(), ValidateContextError> {
1693 let height = contextually_valid.height;
1694
1695 // Prepare data for parallel execution
1696 let mut nct = NoteCommitmentTrees {
1697 sprout: self.sprout_note_commitment_tree_for_tip(),
1698 sapling: self.sapling_note_commitment_tree_for_tip(),
1699 sapling_subtree: self.sapling_subtree_for_tip(),
1700 orchard: self.orchard_note_commitment_tree_for_tip(),
1701 orchard_subtree: self.orchard_subtree_for_tip(),
1702 ironwood: self.ironwood_note_commitment_tree_for_tip(),
1703 ironwood_subtree: self.ironwood_subtree_for_tip(),
1704 };
1705
1706 let mut tree_result = None;
1707 let mut partial_result = None;
1708
1709 // Run 4 tasks in parallel:
1710 // - sprout, sapling, and orchard tree updates and root calculations
1711 // - the rest of the Chain updates
1712 rayon::in_place_scope_fifo(|scope| {
1713 // Spawns a separate rayon task for each note commitment tree
1714 tree_result = Some(nct.update_trees_parallel(&contextually_valid.block.clone()));
1715
1716 scope.spawn_fifo(|_scope| {
1717 partial_result =
1718 Some(self.update_chain_tip_with_block_except_trees(contextually_valid));
1719 });
1720 });
1721
1722 tree_result.expect("scope has already finished")?;
1723 partial_result.expect("scope has already finished")?;
1724
1725 // Update the note commitment trees in the chain.
1726 self.add_sprout_tree_and_anchor(height, nct.sprout);
1727 self.add_sapling_tree_and_anchor(height, nct.sapling);
1728 self.add_orchard_tree_and_anchor(height, nct.orchard);
1729 self.add_ironwood_tree_and_anchor(height, nct.ironwood);
1730
1731 if let Some(subtree) = nct.sapling_subtree {
1732 self.sapling_subtrees
1733 .insert(subtree.index, subtree.into_data());
1734 }
1735 if let Some(subtree) = nct.orchard_subtree {
1736 self.orchard_subtrees
1737 .insert(subtree.index, subtree.into_data());
1738 }
1739 if let Some(subtree) = nct.ironwood_subtree {
1740 self.ironwood_subtrees
1741 .insert(subtree.index, subtree.into_data());
1742 }
1743
1744 let sapling_root = self.sapling_note_commitment_tree_for_tip().root();
1745 let orchard_root = self.orchard_note_commitment_tree_for_tip().root();
1746 let ironwood_root = self.ironwood_note_commitment_tree_for_tip().root();
1747
1748 // TODO: update the history trees in a rayon thread, if they show up in CPU profiles
1749 let mut history_tree = self.history_block_commitment_tree();
1750 let history_tree_mut = Arc::make_mut(&mut history_tree);
1751 history_tree_mut
1752 .push(
1753 &self.network,
1754 contextually_valid.block.clone(),
1755 BlockCommitmentTreeRoots {
1756 sapling: &sapling_root,
1757 orchard: &orchard_root,
1758 ironwood: &ironwood_root,
1759 },
1760 )
1761 .map_err(Arc::new)?;
1762
1763 self.add_history_tree(height, history_tree);
1764
1765 Ok(())
1766 }
1767
1768 /// Update the chain tip with the `contextually_valid` block,
1769 /// except for the note commitment and history tree updates.
1770 ///
1771 /// Used to implement `update_chain_tip_with::<ContextuallyVerifiedBlock>`.
1772 #[instrument(skip(self, contextually_valid), fields(block = %contextually_valid.block))]
1773 #[allow(clippy::unwrap_in_result)]
1774 fn update_chain_tip_with_block_except_trees(
1775 &mut self,
1776 contextually_valid: &ContextuallyVerifiedBlock,
1777 ) -> Result<(), ValidateContextError> {
1778 let (
1779 block,
1780 hash,
1781 height,
1782 new_outputs,
1783 spent_outputs,
1784 transaction_hashes,
1785 chain_value_pool_change,
1786 ) = (
1787 contextually_valid.block.as_ref(),
1788 contextually_valid.hash,
1789 contextually_valid.height,
1790 &contextually_valid.new_outputs,
1791 &contextually_valid.spent_outputs,
1792 &contextually_valid.transaction_hashes,
1793 &contextually_valid.chain_value_pool_change,
1794 );
1795
1796 // add hash to height_by_hash
1797 let prior_height = self.height_by_hash.insert(hash, height);
1798 assert!(
1799 prior_height.is_none(),
1800 "block heights must be unique within a single chain"
1801 );
1802
1803 // add work to partial cumulative work
1804 let block_work = block
1805 .header
1806 .difficulty_threshold
1807 .to_work()
1808 .expect("work has already been validated");
1809 self.partial_cumulative_work += block_work;
1810
1811 // for each transaction in block
1812 for (transaction_index, (transaction, transaction_hash)) in block
1813 .transactions
1814 .iter()
1815 .zip(transaction_hashes.iter().cloned())
1816 .enumerate()
1817 {
1818 let (
1819 inputs,
1820 outputs,
1821 joinsplit_data,
1822 sapling_shielded_data_per_spend_anchor,
1823 sapling_shielded_data_shared_anchor,
1824 orchard_shielded_data,
1825 ironwood_shielded_data,
1826 ) = match transaction.deref() {
1827 V4 {
1828 inputs,
1829 outputs,
1830 joinsplit_data,
1831 sapling_shielded_data,
1832 ..
1833 } => (
1834 inputs,
1835 outputs,
1836 joinsplit_data,
1837 sapling_shielded_data,
1838 &None,
1839 None,
1840 None,
1841 ),
1842 V5 {
1843 inputs,
1844 outputs,
1845 sapling_shielded_data,
1846 orchard_shielded_data,
1847 ..
1848 } => (
1849 inputs,
1850 outputs,
1851 &None,
1852 &None,
1853 sapling_shielded_data,
1854 orchard_shielded_data.as_ref(),
1855 None,
1856 ),
1857 V6 {
1858 inputs,
1859 outputs,
1860 sapling_shielded_data,
1861 orchard_shielded_data,
1862 ironwood_shielded_data,
1863 ..
1864 } => (
1865 inputs,
1866 outputs,
1867 &None,
1868 &None,
1869 sapling_shielded_data,
1870 orchard_shielded_data.as_ref().map(|data| data.data()),
1871 ironwood_shielded_data.as_ref(),
1872 ),
1873
1874 V1 { .. } | V2 { .. } | V3 { .. } => unreachable!(
1875 "older transaction versions only exist in finalized blocks, because of the mandatory canopy checkpoint",
1876 ),
1877 };
1878
1879 // Shielded-data updates run before the transparent updates and
1880 // the `tx_loc_by_hash` insert so that a duplicate transaction
1881 // (same hash → same nullifiers) is rejected with a clean
1882 // `Duplicate{Sprout|Sapling|Orchard}Nullifier` error by
1883 // `add_to_non_finalized_chain_unique` before reaching the
1884 // defense-in-depth assertions on `tx_loc_by_hash`,
1885 // `created_utxos`, and `spent_utxos` below.
1886 {
1887 #[cfg(not(feature = "indexer"))]
1888 let transaction_hash = ();
1889
1890 self.update_chain_tip_with(&(joinsplit_data, &transaction_hash))?;
1891 self.update_chain_tip_with(&(
1892 sapling_shielded_data_per_spend_anchor,
1893 &transaction_hash,
1894 ))?;
1895 self.update_chain_tip_with(&(
1896 sapling_shielded_data_shared_anchor,
1897 &transaction_hash,
1898 ))?;
1899 self.update_chain_tip_with(&(orchard_shielded_data, &transaction_hash))?;
1900
1901 // The Ironwood pool reuses orchard::ShieldedData, so its nullifiers are applied
1902 // through a distinct UpdateWith impl keyed on the ironwood::ShieldedData newtype
1903 // (which doesn't collide with the Orchard impl). It flows through the version match
1904 // above so a new tx version cannot silently skip it.
1905 self.update_chain_tip_with(&(ironwood_shielded_data, &transaction_hash))?;
1906 }
1907
1908 // add key `transaction.hash` and value `(height, tx_index)` to `tx_loc_by_hash`
1909 let transaction_location = TransactionLocation::from_usize(height, transaction_index);
1910 let prior_pair = self
1911 .tx_loc_by_hash
1912 .insert(transaction_hash, transaction_location);
1913 assert_eq!(
1914 prior_pair, None,
1915 "transactions must be unique within a single chain"
1916 );
1917
1918 // add the utxos this produced
1919 self.update_chain_tip_with(&(outputs, &transaction_hash, new_outputs))?;
1920 // delete the utxos this consumed
1921 self.update_chain_tip_with(&(inputs, &transaction_hash, spent_outputs))?;
1922 }
1923
1924 // update the chain value pool balances
1925 let size = block.zcash_serialized_size();
1926 self.update_chain_tip_with(&(*chain_value_pool_change, height, size))?;
1927
1928 Ok(())
1929 }
1930}
1931
1932impl Deref for Chain {
1933 type Target = ChainInner;
1934
1935 fn deref(&self) -> &Self::Target {
1936 &self.inner
1937 }
1938}
1939
1940impl DerefMut for Chain {
1941 fn deref_mut(&mut self) -> &mut Self::Target {
1942 &mut self.inner
1943 }
1944}
1945
1946/// The revert position being performed on a chain.
1947#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1948pub(crate) enum RevertPosition {
1949 /// The chain root is being reverted via [`Chain::pop_root`], when a block
1950 /// is finalized.
1951 Root,
1952
1953 /// The chain tip is being reverted via [`Chain::pop_tip`],
1954 /// when a chain is forked.
1955 Tip,
1956}
1957
1958/// Helper trait to organize inverse operations done on the [`Chain`] type.
1959///
1960/// Used to overload update and revert methods, based on the type of the argument,
1961/// and the position of the removed block in the chain.
1962///
1963/// This trait was motivated by the length of the `push`, [`Chain::pop_root`],
1964/// and [`Chain::pop_tip`] functions, and fear that it would be easy to
1965/// introduce bugs when updating them, unless the code was reorganized to keep
1966/// related operations adjacent to each other.
1967pub(crate) trait UpdateWith<T> {
1968 /// When `T` is added to the chain tip,
1969 /// update [`Chain`] cumulative data members to add data that are derived from `T`.
1970 fn update_chain_tip_with(&mut self, _: &T) -> Result<(), ValidateContextError>;
1971
1972 /// When `T` is removed from `position` in the chain,
1973 /// revert [`Chain`] cumulative data members to remove data that are derived from `T`.
1974 fn revert_chain_with(&mut self, _: &T, position: RevertPosition);
1975}
1976
1977impl UpdateWith<ContextuallyVerifiedBlock> for Chain {
1978 #[instrument(skip(self, contextually_valid), fields(block = %contextually_valid.block))]
1979 #[allow(clippy::unwrap_in_result)]
1980 fn update_chain_tip_with(
1981 &mut self,
1982 contextually_valid: &ContextuallyVerifiedBlock,
1983 ) -> Result<(), ValidateContextError> {
1984 self.update_chain_tip_with_block_parallel(contextually_valid)
1985 }
1986
1987 #[instrument(skip(self, contextually_valid), fields(block = %contextually_valid.block))]
1988 fn revert_chain_with(
1989 &mut self,
1990 contextually_valid: &ContextuallyVerifiedBlock,
1991 position: RevertPosition,
1992 ) {
1993 let (
1994 block,
1995 hash,
1996 height,
1997 new_outputs,
1998 spent_outputs,
1999 transaction_hashes,
2000 chain_value_pool_change,
2001 ) = (
2002 contextually_valid.block.as_ref(),
2003 contextually_valid.hash,
2004 contextually_valid.height,
2005 &contextually_valid.new_outputs,
2006 &contextually_valid.spent_outputs,
2007 &contextually_valid.transaction_hashes,
2008 &contextually_valid.chain_value_pool_change,
2009 );
2010
2011 // remove the blocks hash from `height_by_hash`
2012 assert!(
2013 self.height_by_hash.remove(&hash).is_some(),
2014 "hash must be present if block was added to chain"
2015 );
2016
2017 // TODO: move this to a Work or block header UpdateWith.revert...()?
2018 // remove work from partial_cumulative_work
2019 let block_work = block
2020 .header
2021 .difficulty_threshold
2022 .to_work()
2023 .expect("work has already been validated");
2024 self.partial_cumulative_work -= block_work;
2025
2026 // for each transaction in block
2027 for (transaction, transaction_hash) in
2028 block.transactions.iter().zip(transaction_hashes.iter())
2029 {
2030 let (
2031 inputs,
2032 outputs,
2033 joinsplit_data,
2034 sapling_shielded_data_per_spend_anchor,
2035 sapling_shielded_data_shared_anchor,
2036 orchard_shielded_data,
2037 ironwood_shielded_data,
2038 ) = match transaction.deref() {
2039 V4 {
2040 inputs,
2041 outputs,
2042 joinsplit_data,
2043 sapling_shielded_data,
2044 ..
2045 } => (
2046 inputs,
2047 outputs,
2048 joinsplit_data,
2049 sapling_shielded_data,
2050 &None,
2051 None,
2052 None,
2053 ),
2054 V5 {
2055 inputs,
2056 outputs,
2057 sapling_shielded_data,
2058 orchard_shielded_data,
2059 ..
2060 } => (
2061 inputs,
2062 outputs,
2063 &None,
2064 &None,
2065 sapling_shielded_data,
2066 orchard_shielded_data.as_ref(),
2067 None,
2068 ),
2069 V6 {
2070 inputs,
2071 outputs,
2072 sapling_shielded_data,
2073 orchard_shielded_data,
2074 ironwood_shielded_data,
2075 ..
2076 } => (
2077 inputs,
2078 outputs,
2079 &None,
2080 &None,
2081 sapling_shielded_data,
2082 orchard_shielded_data.as_ref().map(|data| data.data()),
2083 ironwood_shielded_data.as_ref(),
2084 ),
2085
2086 V1 { .. } | V2 { .. } | V3 { .. } => unreachable!(
2087 "older transaction versions only exist in finalized blocks, because of the mandatory canopy checkpoint",
2088 ),
2089 };
2090
2091 // remove the utxos this produced
2092 self.revert_chain_with(&(outputs, transaction_hash, new_outputs), position);
2093 // reset the utxos this consumed
2094 self.revert_chain_with(&(inputs, transaction_hash, spent_outputs), position);
2095
2096 // TODO: move this to the history tree UpdateWith.revert...()?
2097 // remove `transaction.hash` from `tx_loc_by_hash`
2098 assert!(
2099 self.tx_loc_by_hash.remove(transaction_hash).is_some(),
2100 "transactions must be present if block was added to chain"
2101 );
2102
2103 // remove the shielded data
2104
2105 #[cfg(not(feature = "indexer"))]
2106 let transaction_hash = &();
2107
2108 self.revert_chain_with(&(joinsplit_data, transaction_hash), position);
2109 self.revert_chain_with(
2110 &(sapling_shielded_data_per_spend_anchor, transaction_hash),
2111 position,
2112 );
2113 self.revert_chain_with(
2114 &(sapling_shielded_data_shared_anchor, transaction_hash),
2115 position,
2116 );
2117 self.revert_chain_with(&(orchard_shielded_data, transaction_hash), position);
2118
2119 // Revert the Ironwood nullifiers through the matching UpdateWith impl (see
2120 // `update_chain_tip_with_block_except_trees`). It flows through the version match above
2121 // so the revert stays in lockstep with the update for every tx version.
2122 self.revert_chain_with(&(ironwood_shielded_data, transaction_hash), position);
2123 }
2124
2125 // TODO: move these to the shielded UpdateWith.revert...()?
2126 self.remove_sprout_tree_and_anchor(position, height);
2127 self.remove_sapling_tree_and_anchor(position, height);
2128 self.remove_orchard_tree_and_anchor(position, height);
2129 self.remove_ironwood_tree_and_anchor(position, height);
2130
2131 // TODO: move this to the history tree UpdateWith.revert...()?
2132 self.remove_history_tree(position, height);
2133
2134 // revert the chain value pool balances, if needed
2135 // note that size is 0 because it isn't need for reverting
2136 self.revert_chain_with(&(*chain_value_pool_change, height, 0), position);
2137 }
2138}
2139
2140// Created UTXOs
2141//
2142// TODO: replace arguments with a struct
2143impl
2144 UpdateWith<(
2145 // The outputs from a transaction in this block
2146 &Vec<transparent::Output>,
2147 // The hash of the transaction that the outputs are from
2148 &transaction::Hash,
2149 // The UTXOs for all outputs created by this transaction (or block)
2150 &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
2151 )> for Chain
2152{
2153 #[allow(clippy::unwrap_in_result)]
2154 fn update_chain_tip_with(
2155 &mut self,
2156 &(created_outputs, creating_tx_hash, block_created_outputs): &(
2157 &Vec<transparent::Output>,
2158 &transaction::Hash,
2159 &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
2160 ),
2161 ) -> Result<(), ValidateContextError> {
2162 for output_index in 0..created_outputs.len() {
2163 let outpoint = transparent::OutPoint {
2164 hash: *creating_tx_hash,
2165 index: output_index.try_into().expect("valid indexes fit in u32"),
2166 };
2167 let created_utxo = block_created_outputs
2168 .get(&outpoint)
2169 .expect("new_outputs contains all created UTXOs");
2170
2171 // Update the chain's created UTXOs
2172 let previous_entry = self.created_utxos.insert(outpoint, created_utxo.clone());
2173 assert_eq!(
2174 previous_entry, None,
2175 "unexpected created output: duplicate update or duplicate UTXO",
2176 );
2177
2178 // Update the address index with this UTXO
2179 if let Some(receiving_address) = created_utxo.utxo.output.address(&self.network) {
2180 let address_transfers = self
2181 .partial_transparent_transfers
2182 .entry(receiving_address)
2183 .or_default();
2184
2185 address_transfers.update_chain_tip_with(&(&outpoint, created_utxo))?;
2186 }
2187 }
2188
2189 Ok(())
2190 }
2191
2192 fn revert_chain_with(
2193 &mut self,
2194 &(created_outputs, creating_tx_hash, block_created_outputs): &(
2195 &Vec<transparent::Output>,
2196 &transaction::Hash,
2197 &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
2198 ),
2199 position: RevertPosition,
2200 ) {
2201 for output_index in 0..created_outputs.len() {
2202 let outpoint = transparent::OutPoint {
2203 hash: *creating_tx_hash,
2204 index: output_index.try_into().expect("valid indexes fit in u32"),
2205 };
2206 let created_utxo = block_created_outputs
2207 .get(&outpoint)
2208 .expect("new_outputs contains all created UTXOs");
2209
2210 // Revert the chain's created UTXOs
2211 let removed_entry = self.created_utxos.remove(&outpoint);
2212 assert!(
2213 removed_entry.is_some(),
2214 "unexpected revert of created output: duplicate revert or duplicate UTXO",
2215 );
2216
2217 // Revert the address index for this UTXO
2218 if let Some(receiving_address) = created_utxo.utxo.output.address(&self.network) {
2219 let address_transfers = self
2220 .partial_transparent_transfers
2221 .get_mut(&receiving_address)
2222 .expect("block has previously been applied to the chain");
2223
2224 address_transfers.revert_chain_with(&(&outpoint, created_utxo), position);
2225
2226 // Remove this transfer if it is now empty
2227 if address_transfers.is_empty() {
2228 self.partial_transparent_transfers
2229 .remove(&receiving_address);
2230 }
2231 }
2232 }
2233 }
2234}
2235
2236// Transparent inputs
2237//
2238// TODO: replace arguments with a struct
2239impl
2240 UpdateWith<(
2241 // The inputs from a transaction in this block
2242 &Vec<transparent::Input>,
2243 // The hash of the transaction that the inputs are from
2244 // (not the transaction the spent output was created by)
2245 &transaction::Hash,
2246 // The outputs for all inputs spent in this transaction (or block)
2247 &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
2248 )> for Chain
2249{
2250 fn update_chain_tip_with(
2251 &mut self,
2252 &(spending_inputs, spending_tx_hash, spent_outputs): &(
2253 &Vec<transparent::Input>,
2254 &transaction::Hash,
2255 &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
2256 ),
2257 ) -> Result<(), ValidateContextError> {
2258 for spending_input in spending_inputs.iter() {
2259 let spent_outpoint = if let Some(spent_outpoint) = spending_input.outpoint() {
2260 spent_outpoint
2261 } else {
2262 continue;
2263 };
2264
2265 #[cfg(feature = "indexer")]
2266 let insert_value = *spending_tx_hash;
2267 #[cfg(not(feature = "indexer"))]
2268 let insert_value = ();
2269
2270 // Index the spent outpoint in the chain
2271 let was_spend_newly_inserted = self
2272 .spent_utxos
2273 .insert(spent_outpoint, insert_value)
2274 .is_none();
2275 assert!(
2276 was_spend_newly_inserted,
2277 "unexpected duplicate spent output: should be checked earlier"
2278 );
2279
2280 // TODO: fix tests to supply correct spent outputs, then turn this into an expect()
2281 let spent_output = if let Some(spent_output) = spent_outputs.get(&spent_outpoint) {
2282 spent_output
2283 } else if !cfg!(test) {
2284 panic!("unexpected missing spent output: all spent outputs must be indexed");
2285 } else {
2286 continue;
2287 };
2288
2289 // Index the spent output for the address
2290 if let Some(spending_address) = spent_output.utxo.output.address(&self.network) {
2291 let address_transfers = self
2292 .partial_transparent_transfers
2293 .entry(spending_address)
2294 .or_default();
2295
2296 address_transfers.update_chain_tip_with(&(
2297 spending_input,
2298 spending_tx_hash,
2299 spent_output,
2300 ))?;
2301 }
2302 }
2303
2304 Ok(())
2305 }
2306
2307 fn revert_chain_with(
2308 &mut self,
2309 &(spending_inputs, spending_tx_hash, spent_outputs): &(
2310 &Vec<transparent::Input>,
2311 &transaction::Hash,
2312 &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
2313 ),
2314 position: RevertPosition,
2315 ) {
2316 for spending_input in spending_inputs.iter() {
2317 let spent_outpoint = if let Some(spent_outpoint) = spending_input.outpoint() {
2318 spent_outpoint
2319 } else {
2320 continue;
2321 };
2322
2323 // Revert the spent outpoint in the chain
2324 let was_spent_outpoint_removed = self.spent_utxos.remove(&spent_outpoint).is_some();
2325 assert!(
2326 was_spent_outpoint_removed,
2327 "spent_utxos must be present if block was added to chain"
2328 );
2329
2330 // TODO: fix tests to supply correct spent outputs, then turn this into an expect()
2331 let spent_output = if let Some(spent_output) = spent_outputs.get(&spent_outpoint) {
2332 spent_output
2333 } else if !cfg!(test) {
2334 panic!(
2335 "unexpected missing reverted spent output: all spent outputs must be indexed"
2336 );
2337 } else {
2338 continue;
2339 };
2340
2341 // Revert the spent output for the address
2342 if let Some(receiving_address) = spent_output.utxo.output.address(&self.network) {
2343 let address_transfers = self
2344 .partial_transparent_transfers
2345 .get_mut(&receiving_address)
2346 .expect("block has previously been applied to the chain");
2347
2348 address_transfers
2349 .revert_chain_with(&(spending_input, spending_tx_hash, spent_output), position);
2350
2351 // Remove this transfer if it is now empty
2352 if address_transfers.is_empty() {
2353 self.partial_transparent_transfers
2354 .remove(&receiving_address);
2355 }
2356 }
2357 }
2358 }
2359}
2360
2361impl
2362 UpdateWith<(
2363 &Option<transaction::JoinSplitData<Groth16Proof>>,
2364 &SpendingTransactionId,
2365 )> for Chain
2366{
2367 #[instrument(skip(self, joinsplit_data))]
2368 fn update_chain_tip_with(
2369 &mut self,
2370 &(joinsplit_data, revealing_tx_id): &(
2371 &Option<transaction::JoinSplitData<Groth16Proof>>,
2372 &SpendingTransactionId,
2373 ),
2374 ) -> Result<(), ValidateContextError> {
2375 if let Some(joinsplit_data) = joinsplit_data {
2376 // We do note commitment tree updates in parallel rayon threads.
2377
2378 check::nullifier::add_to_non_finalized_chain_unique(
2379 &mut self.sprout_nullifiers,
2380 joinsplit_data.nullifiers().copied(),
2381 *revealing_tx_id,
2382 )?;
2383 }
2384 Ok(())
2385 }
2386
2387 /// # Panics
2388 ///
2389 /// Panics if any nullifier is missing from the chain when we try to remove it.
2390 ///
2391 /// See [`check::nullifier::remove_from_non_finalized_chain`] for details.
2392 #[instrument(skip(self, joinsplit_data))]
2393 fn revert_chain_with(
2394 &mut self,
2395 &(joinsplit_data, _revealing_tx_id): &(
2396 &Option<transaction::JoinSplitData<Groth16Proof>>,
2397 &SpendingTransactionId,
2398 ),
2399 _position: RevertPosition,
2400 ) {
2401 if let Some(joinsplit_data) = joinsplit_data {
2402 // Note commitments are removed from the Chain during a fork,
2403 // by removing trees above the fork height from the note commitment index.
2404 // This happens when reverting the block itself.
2405
2406 check::nullifier::remove_from_non_finalized_chain(
2407 &mut self.sprout_nullifiers,
2408 joinsplit_data.nullifiers().copied(),
2409 );
2410 }
2411 }
2412}
2413
2414impl<AnchorV>
2415 UpdateWith<(
2416 &Option<sapling::ShieldedData<AnchorV>>,
2417 &SpendingTransactionId,
2418 )> for Chain
2419where
2420 AnchorV: sapling::AnchorVariant + Clone,
2421{
2422 #[instrument(skip(self, sapling_shielded_data))]
2423 fn update_chain_tip_with(
2424 &mut self,
2425 &(sapling_shielded_data, revealing_tx_id): &(
2426 &Option<sapling::ShieldedData<AnchorV>>,
2427 &SpendingTransactionId,
2428 ),
2429 ) -> Result<(), ValidateContextError> {
2430 if let Some(sapling_shielded_data) = sapling_shielded_data {
2431 // We do note commitment tree updates in parallel rayon threads.
2432
2433 check::nullifier::add_to_non_finalized_chain_unique(
2434 &mut self.sapling_nullifiers,
2435 sapling_shielded_data.nullifiers().copied(),
2436 *revealing_tx_id,
2437 )?;
2438 }
2439 Ok(())
2440 }
2441
2442 /// # Panics
2443 ///
2444 /// Panics if any nullifier is missing from the chain when we try to remove it.
2445 ///
2446 /// See [`check::nullifier::remove_from_non_finalized_chain`] for details.
2447 #[instrument(skip(self, sapling_shielded_data))]
2448 fn revert_chain_with(
2449 &mut self,
2450 &(sapling_shielded_data, _revealing_tx_id): &(
2451 &Option<sapling::ShieldedData<AnchorV>>,
2452 &SpendingTransactionId,
2453 ),
2454 _position: RevertPosition,
2455 ) {
2456 if let Some(sapling_shielded_data) = sapling_shielded_data {
2457 // Note commitments are removed from the Chain during a fork,
2458 // by removing trees above the fork height from the note commitment index.
2459 // This happens when reverting the block itself.
2460
2461 check::nullifier::remove_from_non_finalized_chain(
2462 &mut self.sapling_nullifiers,
2463 sapling_shielded_data.nullifiers().copied(),
2464 );
2465 }
2466 }
2467}
2468
2469impl UpdateWith<(Option<&orchard::ShieldedData>, &SpendingTransactionId)> for Chain {
2470 #[instrument(skip(self, orchard_shielded_data))]
2471 fn update_chain_tip_with(
2472 &mut self,
2473 &(orchard_shielded_data, revealing_tx_id): &(
2474 Option<&orchard::ShieldedData>,
2475 &SpendingTransactionId,
2476 ),
2477 ) -> Result<(), ValidateContextError> {
2478 if let Some(orchard_shielded_data) = orchard_shielded_data {
2479 // We do note commitment tree updates in parallel rayon threads.
2480
2481 check::nullifier::add_to_non_finalized_chain_unique(
2482 &mut self.orchard_nullifiers,
2483 orchard_shielded_data.nullifiers().copied(),
2484 *revealing_tx_id,
2485 )?;
2486 }
2487 Ok(())
2488 }
2489
2490 /// # Panics
2491 ///
2492 /// Panics if any nullifier is missing from the chain when we try to remove it.
2493 ///
2494 /// See [`check::nullifier::remove_from_non_finalized_chain`] for details.
2495 #[instrument(skip(self, orchard_shielded_data))]
2496 fn revert_chain_with(
2497 &mut self,
2498 (orchard_shielded_data, _revealing_tx_id): &(
2499 Option<&orchard::ShieldedData>,
2500 &SpendingTransactionId,
2501 ),
2502 _position: RevertPosition,
2503 ) {
2504 if let Some(orchard_shielded_data) = orchard_shielded_data {
2505 // Note commitments are removed from the Chain during a fork,
2506 // by removing trees above the fork height from the note commitment index.
2507 // This happens when reverting the block itself.
2508
2509 check::nullifier::remove_from_non_finalized_chain(
2510 &mut self.orchard_nullifiers,
2511 orchard_shielded_data.nullifiers().copied(),
2512 );
2513 }
2514 }
2515}
2516
2517impl UpdateWith<(Option<&ironwood::ShieldedData>, &SpendingTransactionId)> for Chain {
2518 #[instrument(skip(self, ironwood_shielded_data))]
2519 fn update_chain_tip_with(
2520 &mut self,
2521 &(ironwood_shielded_data, revealing_tx_id): &(
2522 Option<&ironwood::ShieldedData>,
2523 &SpendingTransactionId,
2524 ),
2525 ) -> Result<(), ValidateContextError> {
2526 if let Some(ironwood_shielded_data) = ironwood_shielded_data {
2527 // The Ironwood pool reuses orchard::ShieldedData but commits to a disjoint nullifier
2528 // set, so its nullifiers are wrapped in the ironwood::Nullifier newtype.
2529 check::nullifier::add_to_non_finalized_chain_unique(
2530 &mut self.ironwood_nullifiers,
2531 ironwood_shielded_data
2532 .data()
2533 .nullifiers()
2534 .map(|nullifier| ironwood::Nullifier::from(*nullifier)),
2535 *revealing_tx_id,
2536 )?;
2537 }
2538 Ok(())
2539 }
2540
2541 /// # Panics
2542 ///
2543 /// Panics if any nullifier is missing from the chain when we try to remove it.
2544 ///
2545 /// See [`check::nullifier::remove_from_non_finalized_chain`] for details.
2546 #[instrument(skip(self, ironwood_shielded_data))]
2547 fn revert_chain_with(
2548 &mut self,
2549 (ironwood_shielded_data, _revealing_tx_id): &(
2550 Option<&ironwood::ShieldedData>,
2551 &SpendingTransactionId,
2552 ),
2553 _position: RevertPosition,
2554 ) {
2555 if let Some(ironwood_shielded_data) = ironwood_shielded_data {
2556 check::nullifier::remove_from_non_finalized_chain(
2557 &mut self.ironwood_nullifiers,
2558 ironwood_shielded_data
2559 .data()
2560 .nullifiers()
2561 .map(|nullifier| ironwood::Nullifier::from(*nullifier)),
2562 );
2563 }
2564 }
2565}
2566
2567impl UpdateWith<(ValueBalance<NegativeAllowed>, Height, usize)> for Chain {
2568 #[allow(clippy::unwrap_in_result)]
2569 fn update_chain_tip_with(
2570 &mut self,
2571 (block_value_pool_change, height, size): &(ValueBalance<NegativeAllowed>, Height, usize),
2572 ) -> Result<(), ValidateContextError> {
2573 match self
2574 .chain_value_pools
2575 .add_chain_value_pool_change(*block_value_pool_change)
2576 {
2577 Ok(chain_value_pools) => {
2578 self.chain_value_pools = chain_value_pools;
2579 self.block_info_by_height
2580 .insert(*height, BlockInfo::new(chain_value_pools, *size as u32));
2581 }
2582 Err(value_balance_error) => Err(ValidateContextError::AddValuePool {
2583 value_balance_error,
2584 chain_value_pools: Box::new(self.chain_value_pools),
2585 block_value_pool_change: Box::new(*block_value_pool_change),
2586 height: Some(*height),
2587 })?,
2588 };
2589
2590 Ok(())
2591 }
2592
2593 /// Revert the chain state using a block chain value pool change.
2594 ///
2595 /// When forking from the tip, subtract the block's chain value pool change.
2596 ///
2597 /// When finalizing the root, leave the chain value pool balances unchanged.
2598 /// [`ChainInner::chain_value_pools`] tracks the chain value pools for all finalized blocks, and
2599 /// the non-finalized blocks in this chain. So finalizing the root doesn't change the set of
2600 /// blocks it tracks.
2601 ///
2602 /// # Panics
2603 ///
2604 /// Panics if the chain pool value balance is invalid after we subtract the block value pool
2605 /// change.
2606 fn revert_chain_with(
2607 &mut self,
2608 (block_value_pool_change, height, _size): &(ValueBalance<NegativeAllowed>, Height, usize),
2609 position: RevertPosition,
2610 ) {
2611 use std::ops::Neg;
2612
2613 if position == RevertPosition::Tip {
2614 self.chain_value_pools = self
2615 .chain_value_pools
2616 .add_chain_value_pool_change(block_value_pool_change.neg())
2617 .expect("reverting the tip will leave the pools in a previously valid state");
2618 }
2619 self.block_info_by_height.remove(height);
2620 }
2621}
2622
2623impl Ord for Chain {
2624 /// Chain order for the [`NonFinalizedState`][1]'s `chain_set`.
2625 ///
2626 /// Chains with higher cumulative Proof of Work are [`Ordering::Greater`],
2627 /// breaking ties using the tip block hash.
2628 ///
2629 /// Despite the consensus rules, Zebra uses the tip block hash as a
2630 /// tie-breaker. Zebra blocks are downloaded in parallel, so download
2631 /// timestamps may not be unique. (And Zebra currently doesn't track
2632 /// download times, because [`Block`](block::Block)s are immutable.)
2633 ///
2634 /// This departure from the consensus rules may delay network convergence,
2635 /// for as long as the greater hash belongs to the later mined block.
2636 /// But Zebra nodes should converge as soon as the tied work is broken.
2637 ///
2638 /// "At a given point in time, each full validator is aware of a set of candidate blocks.
2639 /// These form a tree rooted at the genesis block, where each node in the tree
2640 /// refers to its parent via the hashPrevBlock block header field.
2641 ///
2642 /// A path from the root toward the leaves of the tree consisting of a sequence
2643 /// of one or more valid blocks consistent with consensus rules,
2644 /// is called a valid block chain.
2645 ///
2646 /// In order to choose the best valid block chain in its view of the overall block tree,
2647 /// a node sums the work ... of all blocks in each valid block chain,
2648 /// and considers the valid block chain with greatest total work to be best.
2649 ///
2650 /// To break ties between leaf blocks, a node will prefer the block that it received first.
2651 ///
2652 /// The consensus protocol is designed to ensure that for any given block height,
2653 /// the vast majority of nodes should eventually agree on their best valid block chain
2654 /// up to that height."
2655 ///
2656 /// <https://zips.z.cash/protocol/protocol.pdf#blockchain>
2657 ///
2658 /// # Correctness
2659 ///
2660 /// `Chain::cmp` is used in a `BTreeSet`, so the fields accessed by `cmp` must not have
2661 /// interior mutability.
2662 ///
2663 /// `cmp` returns [`Ordering::Equal`] only when both the cumulative work and
2664 /// the tip hash match. The [`NonFinalizedState::chain_set`][2] is a
2665 /// `BTreeSet<Arc<Chain>>`, so an attempt to insert a chain that compares
2666 /// equal to an existing entry is a no-op rather than a process-fatal panic.
2667 /// Callers that need to replace such a chain must remove the existing entry
2668 /// first.
2669 ///
2670 /// [1]: super::NonFinalizedState
2671 /// [2]: super::NonFinalizedState::chain_set
2672 fn cmp(&self, other: &Self) -> Ordering {
2673 if self.partial_cumulative_work != other.partial_cumulative_work {
2674 self.partial_cumulative_work
2675 .cmp(&other.partial_cumulative_work)
2676 } else {
2677 let self_hash = self
2678 .blocks
2679 .values()
2680 .last()
2681 .expect("always at least 1 element")
2682 .hash;
2683
2684 let other_hash = other
2685 .blocks
2686 .values()
2687 .last()
2688 .expect("always at least 1 element")
2689 .hash;
2690
2691 // This comparison is a tie-breaker within the local node, so it does not need to
2692 // be consistent with the ordering on `ExpandedDifficulty` and `block::Hash`.
2693 self_hash.0.cmp(&other_hash.0)
2694 }
2695 }
2696}
2697
2698impl PartialOrd for Chain {
2699 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
2700 Some(self.cmp(other))
2701 }
2702}
2703
2704impl PartialEq for Chain {
2705 /// Chain equality for [`NonFinalizedState::chain_set`][1], using proof of
2706 /// work, then the tip block hash as a tie-breaker.
2707 ///
2708 /// Two chains with the same cumulative work and tip hash are equal; the
2709 /// `chain_set` uses this to keep tip hashes unique.
2710 ///
2711 /// [1]: super::NonFinalizedState::chain_set
2712 fn eq(&self, other: &Self) -> bool {
2713 self.partial_cmp(other) == Some(Ordering::Equal)
2714 }
2715}
2716
2717impl Eq for Chain {}
2718
2719#[cfg(test)]
2720impl Chain {
2721 /// Inserts the supplied Sapling note commitment subtree into the chain.
2722 pub(crate) fn insert_sapling_subtree(
2723 &mut self,
2724 subtree: NoteCommitmentSubtree<sapling_crypto::Node>,
2725 ) {
2726 self.inner
2727 .sapling_subtrees
2728 .insert(subtree.index, subtree.into_data());
2729 }
2730
2731 /// Inserts the supplied Orchard note commitment subtree into the chain.
2732 pub(crate) fn insert_orchard_subtree(
2733 &mut self,
2734 subtree: NoteCommitmentSubtree<orchard::tree::Node>,
2735 ) {
2736 self.inner
2737 .orchard_subtrees
2738 .insert(subtree.index, subtree.into_data());
2739 }
2740}