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