Skip to main content

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}