Skip to main content

zebra_state/service/non_finalized_state/
chain.rs

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