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