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}