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::tree::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 = None;
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::tree::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::tree::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::tree::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 assert!(
1244 !self.blocks.is_empty(),
1245 "Non-finalized chains must have at least one block to be valid"
1246 );
1247
1248 self.revert_chain_with(&block, RevertPosition::Tip);
1249 }
1250
1251 /// Return the non-finalized tip height for this chain.
1252 ///
1253 /// # Panics
1254 ///
1255 /// Panics if called while the chain is empty,
1256 /// or while the chain is updating its internal state with the first block.
1257 pub fn non_finalized_tip_height(&self) -> block::Height {
1258 self.max_block_height()
1259 .expect("only called while blocks is populated")
1260 }
1261
1262 /// Return the non-finalized tip height for this chain,
1263 /// or `None` if `self.blocks` is empty.
1264 fn max_block_height(&self) -> Option<block::Height> {
1265 self.blocks.keys().next_back().cloned()
1266 }
1267
1268 /// Return the non-finalized tip block for this chain,
1269 /// or `None` if `self.blocks` is empty.
1270 pub fn tip_block(&self) -> Option<&ContextuallyVerifiedBlock> {
1271 self.blocks.values().next_back()
1272 }
1273
1274 /// Returns true if the non-finalized part of this chain is empty.
1275 pub fn is_empty(&self) -> bool {
1276 self.blocks.is_empty()
1277 }
1278
1279 /// Returns the non-finalized length of this chain.
1280 #[allow(dead_code)]
1281 pub fn len(&self) -> usize {
1282 self.blocks.len()
1283 }
1284
1285 /// Returns the unspent transaction outputs (UTXOs) in this non-finalized chain.
1286 ///
1287 /// Callers should also check the finalized state for available UTXOs.
1288 /// If UTXOs remain unspent when a block is finalized, they are stored in the finalized state,
1289 /// and removed from the relevant chain(s).
1290 pub fn unspent_utxos(&self) -> HashMap<transparent::OutPoint, transparent::OrderedUtxo> {
1291 let mut unspent_utxos = self.created_utxos.clone();
1292 unspent_utxos.retain(|outpoint, _utxo| !self.spent_utxos.contains_key(outpoint));
1293
1294 unspent_utxos
1295 }
1296
1297 /// Returns the [`transparent::Utxo`] pointed to by the given
1298 /// [`transparent::OutPoint`] if it was created by this chain.
1299 ///
1300 /// UTXOs are returned regardless of whether they have been spent.
1301 pub fn created_utxo(&self, outpoint: &transparent::OutPoint) -> Option<transparent::Utxo> {
1302 self.created_utxos
1303 .get(outpoint)
1304 .map(|utxo| utxo.utxo.clone())
1305 }
1306
1307 /// Returns the [`Hash`](transaction::Hash) of the transaction that spent an output at
1308 /// the provided [`transparent::OutPoint`] or revealed the provided nullifier, if it exists
1309 /// and is spent or revealed by this chain.
1310 #[cfg(feature = "indexer")]
1311 pub fn spending_transaction_hash(&self, spend: &Spend) -> Option<transaction::Hash> {
1312 match spend {
1313 Spend::OutPoint(outpoint) => self.spent_utxos.get(outpoint),
1314 Spend::Sprout(nullifier) => self.sprout_nullifiers.get(nullifier),
1315 Spend::Sapling(nullifier) => self.sapling_nullifiers.get(nullifier),
1316 Spend::Orchard(nullifier) => self.orchard_nullifiers.get(nullifier),
1317 }
1318 .cloned()
1319 }
1320
1321 // Address index queries
1322
1323 /// Returns the transparent transfers for `addresses` in this non-finalized chain.
1324 ///
1325 /// If none of the addresses have an address index, returns an empty iterator.
1326 ///
1327 /// # Correctness
1328 ///
1329 /// Callers should apply the returned indexes to the corresponding finalized state indexes.
1330 ///
1331 /// The combined result will only be correct if the chains match.
1332 /// The exact type of match varies by query.
1333 pub fn partial_transparent_indexes<'a>(
1334 &'a self,
1335 addresses: &'a HashSet<transparent::Address>,
1336 ) -> impl Iterator<Item = &'a TransparentTransfers> {
1337 addresses
1338 .iter()
1339 .flat_map(|address| self.partial_transparent_transfers.get(address))
1340 }
1341
1342 /// Returns a tuple of the transparent balance change and the total received funds for
1343 /// `addresses` in this non-finalized chain.
1344 ///
1345 /// If the balance doesn't change for any of the addresses, returns zero.
1346 ///
1347 /// # Correctness
1348 ///
1349 /// Callers should apply this balance change to the finalized state balance for `addresses`.
1350 ///
1351 /// The total balance will only be correct if this partial chain matches the finalized state.
1352 /// Specifically, the root of this partial chain must be a child block of the finalized tip.
1353 pub fn partial_transparent_balance_change(
1354 &self,
1355 addresses: &HashSet<transparent::Address>,
1356 ) -> (Amount<NegativeAllowed>, u64) {
1357 let (balance, received) = self.partial_transparent_indexes(addresses).fold(
1358 (Ok(Amount::zero()), 0),
1359 |(balance, received), transfers| {
1360 let balance = balance + transfers.balance();
1361 (balance, received + transfers.received())
1362 },
1363 );
1364
1365 (balance.expect("unexpected amount overflow"), received)
1366 }
1367
1368 /// Returns the transparent UTXO changes for `addresses` in this non-finalized chain.
1369 ///
1370 /// If the UTXOs don't change for any of the addresses, returns empty lists.
1371 ///
1372 /// # Correctness
1373 ///
1374 /// Callers should apply these non-finalized UTXO changes to the finalized state UTXOs.
1375 ///
1376 /// The UTXOs will only be correct if the non-finalized chain matches or overlaps with
1377 /// the finalized state.
1378 ///
1379 /// Specifically, a block in the partial chain must be a child block of the finalized tip.
1380 /// (But the child block does not have to be the partial chain root.)
1381 pub fn partial_transparent_utxo_changes(
1382 &self,
1383 addresses: &HashSet<transparent::Address>,
1384 ) -> (
1385 BTreeMap<OutputLocation, transparent::Output>,
1386 BTreeSet<OutputLocation>,
1387 ) {
1388 let created_utxos = self
1389 .partial_transparent_indexes(addresses)
1390 .flat_map(|transfers| transfers.created_utxos())
1391 .map(|(out_loc, output)| (*out_loc, output.clone()))
1392 .collect();
1393
1394 let spent_utxos = self
1395 .partial_transparent_indexes(addresses)
1396 .flat_map(|transfers| transfers.spent_utxos())
1397 .cloned()
1398 .collect();
1399
1400 (created_utxos, spent_utxos)
1401 }
1402
1403 /// Returns the [`transaction::Hash`]es used by `addresses` to receive or spend funds,
1404 /// in the non-finalized chain, filtered using the `query_height_range`.
1405 ///
1406 /// If none of the addresses receive or spend funds in this partial chain, returns an empty list.
1407 ///
1408 /// # Correctness
1409 ///
1410 /// Callers should combine these non-finalized transactions with the finalized state transactions.
1411 ///
1412 /// The transaction IDs will only be correct if the non-finalized chain matches or overlaps with
1413 /// the finalized state.
1414 ///
1415 /// Specifically, a block in the partial chain must be a child block of the finalized tip.
1416 /// (But the child block does not have to be the partial chain root.)
1417 ///
1418 /// This condition does not apply if there is only one address.
1419 /// Since address transactions are only appended by blocks,
1420 /// and the finalized state query reads them in order,
1421 /// it is impossible to get inconsistent transactions for a single address.
1422 pub fn partial_transparent_tx_ids(
1423 &self,
1424 addresses: &HashSet<transparent::Address>,
1425 query_height_range: RangeInclusive<Height>,
1426 ) -> BTreeMap<TransactionLocation, transaction::Hash> {
1427 self.partial_transparent_indexes(addresses)
1428 .flat_map(|transfers| {
1429 transfers.tx_ids(&self.tx_loc_by_hash, query_height_range.clone())
1430 })
1431 .collect()
1432 }
1433
1434 /// Update the chain tip with the `contextually_valid` block,
1435 /// running note commitment tree updates in parallel with other updates.
1436 ///
1437 /// Used to implement `update_chain_tip_with::<ContextuallyVerifiedBlock>`.
1438 #[instrument(skip(self, contextually_valid), fields(block = %contextually_valid.block))]
1439 #[allow(clippy::unwrap_in_result)]
1440 fn update_chain_tip_with_block_parallel(
1441 &mut self,
1442 contextually_valid: &ContextuallyVerifiedBlock,
1443 ) -> Result<(), ValidateContextError> {
1444 let height = contextually_valid.height;
1445
1446 // Prepare data for parallel execution
1447 let mut nct = NoteCommitmentTrees {
1448 sprout: self.sprout_note_commitment_tree_for_tip(),
1449 sapling: self.sapling_note_commitment_tree_for_tip(),
1450 sapling_subtree: self.sapling_subtree_for_tip(),
1451 orchard: self.orchard_note_commitment_tree_for_tip(),
1452 orchard_subtree: self.orchard_subtree_for_tip(),
1453 };
1454
1455 let mut tree_result = None;
1456 let mut partial_result = None;
1457
1458 // Run 4 tasks in parallel:
1459 // - sprout, sapling, and orchard tree updates and root calculations
1460 // - the rest of the Chain updates
1461 rayon::in_place_scope_fifo(|scope| {
1462 // Spawns a separate rayon task for each note commitment tree
1463 tree_result = Some(nct.update_trees_parallel(&contextually_valid.block.clone()));
1464
1465 scope.spawn_fifo(|_scope| {
1466 partial_result =
1467 Some(self.update_chain_tip_with_block_except_trees(contextually_valid));
1468 });
1469 });
1470
1471 tree_result.expect("scope has already finished")?;
1472 partial_result.expect("scope has already finished")?;
1473
1474 // Update the note commitment trees in the chain.
1475 self.add_sprout_tree_and_anchor(height, nct.sprout);
1476 self.add_sapling_tree_and_anchor(height, nct.sapling);
1477 self.add_orchard_tree_and_anchor(height, nct.orchard);
1478
1479 if let Some(subtree) = nct.sapling_subtree {
1480 self.sapling_subtrees
1481 .insert(subtree.index, subtree.into_data());
1482 }
1483 if let Some(subtree) = nct.orchard_subtree {
1484 self.orchard_subtrees
1485 .insert(subtree.index, subtree.into_data());
1486 }
1487
1488 let sapling_root = self.sapling_note_commitment_tree_for_tip().root();
1489 let orchard_root = self.orchard_note_commitment_tree_for_tip().root();
1490
1491 // TODO: update the history trees in a rayon thread, if they show up in CPU profiles
1492 let mut history_tree = self.history_block_commitment_tree();
1493 let history_tree_mut = Arc::make_mut(&mut history_tree);
1494 history_tree_mut
1495 .push(
1496 &self.network,
1497 contextually_valid.block.clone(),
1498 &sapling_root,
1499 &orchard_root,
1500 )
1501 .map_err(Arc::new)?;
1502
1503 self.add_history_tree(height, history_tree);
1504
1505 Ok(())
1506 }
1507
1508 /// Update the chain tip with the `contextually_valid` block,
1509 /// except for the note commitment and history tree updates.
1510 ///
1511 /// Used to implement `update_chain_tip_with::<ContextuallyVerifiedBlock>`.
1512 #[instrument(skip(self, contextually_valid), fields(block = %contextually_valid.block))]
1513 #[allow(clippy::unwrap_in_result)]
1514 fn update_chain_tip_with_block_except_trees(
1515 &mut self,
1516 contextually_valid: &ContextuallyVerifiedBlock,
1517 ) -> Result<(), ValidateContextError> {
1518 let (
1519 block,
1520 hash,
1521 height,
1522 new_outputs,
1523 spent_outputs,
1524 transaction_hashes,
1525 chain_value_pool_change,
1526 ) = (
1527 contextually_valid.block.as_ref(),
1528 contextually_valid.hash,
1529 contextually_valid.height,
1530 &contextually_valid.new_outputs,
1531 &contextually_valid.spent_outputs,
1532 &contextually_valid.transaction_hashes,
1533 &contextually_valid.chain_value_pool_change,
1534 );
1535
1536 // add hash to height_by_hash
1537 let prior_height = self.height_by_hash.insert(hash, height);
1538 assert!(
1539 prior_height.is_none(),
1540 "block heights must be unique within a single chain"
1541 );
1542
1543 // add work to partial cumulative work
1544 let block_work = block
1545 .header
1546 .difficulty_threshold
1547 .to_work()
1548 .expect("work has already been validated");
1549 self.partial_cumulative_work += block_work;
1550
1551 // for each transaction in block
1552 for (transaction_index, (transaction, transaction_hash)) in block
1553 .transactions
1554 .iter()
1555 .zip(transaction_hashes.iter().cloned())
1556 .enumerate()
1557 {
1558 let (
1559 inputs,
1560 outputs,
1561 joinsplit_data,
1562 sapling_shielded_data_per_spend_anchor,
1563 sapling_shielded_data_shared_anchor,
1564 orchard_shielded_data,
1565 ) = match transaction.deref() {
1566 V4 {
1567 inputs,
1568 outputs,
1569 joinsplit_data,
1570 sapling_shielded_data,
1571 ..
1572 } => (inputs, outputs, joinsplit_data, sapling_shielded_data, &None, &None),
1573 V5 {
1574 inputs,
1575 outputs,
1576 sapling_shielded_data,
1577 orchard_shielded_data,
1578 ..
1579 } => (
1580 inputs,
1581 outputs,
1582 &None,
1583 &None,
1584 sapling_shielded_data,
1585 orchard_shielded_data,
1586 ),
1587 #[cfg(feature="tx_v6")]
1588 V6 {
1589 inputs,
1590 outputs,
1591 sapling_shielded_data,
1592 orchard_shielded_data,
1593 ..
1594 } => (
1595 inputs,
1596 outputs,
1597 &None,
1598 &None,
1599 sapling_shielded_data,
1600 orchard_shielded_data,
1601 ),
1602
1603 V1 { .. } | V2 { .. } | V3 { .. } => unreachable!(
1604 "older transaction versions only exist in finalized blocks, because of the mandatory canopy checkpoint",
1605 ),
1606 };
1607
1608 // add key `transaction.hash` and value `(height, tx_index)` to `tx_loc_by_hash`
1609 let transaction_location = TransactionLocation::from_usize(height, transaction_index);
1610 let prior_pair = self
1611 .tx_loc_by_hash
1612 .insert(transaction_hash, transaction_location);
1613 assert_eq!(
1614 prior_pair, None,
1615 "transactions must be unique within a single chain"
1616 );
1617
1618 // add the utxos this produced
1619 self.update_chain_tip_with(&(outputs, &transaction_hash, new_outputs))?;
1620 // delete the utxos this consumed
1621 self.update_chain_tip_with(&(inputs, &transaction_hash, spent_outputs))?;
1622
1623 // add the shielded data
1624
1625 #[cfg(not(feature = "indexer"))]
1626 let transaction_hash = ();
1627
1628 self.update_chain_tip_with(&(joinsplit_data, &transaction_hash))?;
1629 self.update_chain_tip_with(&(
1630 sapling_shielded_data_per_spend_anchor,
1631 &transaction_hash,
1632 ))?;
1633 self.update_chain_tip_with(&(sapling_shielded_data_shared_anchor, &transaction_hash))?;
1634 self.update_chain_tip_with(&(orchard_shielded_data, &transaction_hash))?;
1635 }
1636
1637 // update the chain value pool balances
1638 let size = block.zcash_serialized_size();
1639 self.update_chain_tip_with(&(*chain_value_pool_change, height, size))?;
1640
1641 Ok(())
1642 }
1643}
1644
1645impl Deref for Chain {
1646 type Target = ChainInner;
1647
1648 fn deref(&self) -> &Self::Target {
1649 &self.inner
1650 }
1651}
1652
1653impl DerefMut for Chain {
1654 fn deref_mut(&mut self) -> &mut Self::Target {
1655 &mut self.inner
1656 }
1657}
1658
1659/// The revert position being performed on a chain.
1660#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1661pub(crate) enum RevertPosition {
1662 /// The chain root is being reverted via [`Chain::pop_root`], when a block
1663 /// is finalized.
1664 Root,
1665
1666 /// The chain tip is being reverted via [`Chain::pop_tip`],
1667 /// when a chain is forked.
1668 Tip,
1669}
1670
1671/// Helper trait to organize inverse operations done on the [`Chain`] type.
1672///
1673/// Used to overload update and revert methods, based on the type of the argument,
1674/// and the position of the removed block in the chain.
1675///
1676/// This trait was motivated by the length of the `push`, [`Chain::pop_root`],
1677/// and [`Chain::pop_tip`] functions, and fear that it would be easy to
1678/// introduce bugs when updating them, unless the code was reorganized to keep
1679/// related operations adjacent to each other.
1680pub(crate) trait UpdateWith<T> {
1681 /// When `T` is added to the chain tip,
1682 /// update [`Chain`] cumulative data members to add data that are derived from `T`.
1683 fn update_chain_tip_with(&mut self, _: &T) -> Result<(), ValidateContextError>;
1684
1685 /// When `T` is removed from `position` in the chain,
1686 /// revert [`Chain`] cumulative data members to remove data that are derived from `T`.
1687 fn revert_chain_with(&mut self, _: &T, position: RevertPosition);
1688}
1689
1690impl UpdateWith<ContextuallyVerifiedBlock> for Chain {
1691 #[instrument(skip(self, contextually_valid), fields(block = %contextually_valid.block))]
1692 #[allow(clippy::unwrap_in_result)]
1693 fn update_chain_tip_with(
1694 &mut self,
1695 contextually_valid: &ContextuallyVerifiedBlock,
1696 ) -> Result<(), ValidateContextError> {
1697 self.update_chain_tip_with_block_parallel(contextually_valid)
1698 }
1699
1700 #[instrument(skip(self, contextually_valid), fields(block = %contextually_valid.block))]
1701 fn revert_chain_with(
1702 &mut self,
1703 contextually_valid: &ContextuallyVerifiedBlock,
1704 position: RevertPosition,
1705 ) {
1706 let (
1707 block,
1708 hash,
1709 height,
1710 new_outputs,
1711 spent_outputs,
1712 transaction_hashes,
1713 chain_value_pool_change,
1714 ) = (
1715 contextually_valid.block.as_ref(),
1716 contextually_valid.hash,
1717 contextually_valid.height,
1718 &contextually_valid.new_outputs,
1719 &contextually_valid.spent_outputs,
1720 &contextually_valid.transaction_hashes,
1721 &contextually_valid.chain_value_pool_change,
1722 );
1723
1724 // remove the blocks hash from `height_by_hash`
1725 assert!(
1726 self.height_by_hash.remove(&hash).is_some(),
1727 "hash must be present if block was added to chain"
1728 );
1729
1730 // TODO: move this to a Work or block header UpdateWith.revert...()?
1731 // remove work from partial_cumulative_work
1732 let block_work = block
1733 .header
1734 .difficulty_threshold
1735 .to_work()
1736 .expect("work has already been validated");
1737 self.partial_cumulative_work -= block_work;
1738
1739 // for each transaction in block
1740 for (transaction, transaction_hash) in
1741 block.transactions.iter().zip(transaction_hashes.iter())
1742 {
1743 let (
1744 inputs,
1745 outputs,
1746 joinsplit_data,
1747 sapling_shielded_data_per_spend_anchor,
1748 sapling_shielded_data_shared_anchor,
1749 orchard_shielded_data,
1750 ) = match transaction.deref() {
1751 V4 {
1752 inputs,
1753 outputs,
1754 joinsplit_data,
1755 sapling_shielded_data,
1756 ..
1757 } => (inputs, outputs, joinsplit_data, sapling_shielded_data, &None, &None),
1758 V5 {
1759 inputs,
1760 outputs,
1761 sapling_shielded_data,
1762 orchard_shielded_data,
1763 ..
1764 } => (
1765 inputs,
1766 outputs,
1767 &None,
1768 &None,
1769 sapling_shielded_data,
1770 orchard_shielded_data,
1771 ),
1772 #[cfg(feature="tx_v6")]
1773 V6 {
1774 inputs,
1775 outputs,
1776 sapling_shielded_data,
1777 orchard_shielded_data,
1778 ..
1779 } => (
1780 inputs,
1781 outputs,
1782 &None,
1783 &None,
1784 sapling_shielded_data,
1785 orchard_shielded_data,
1786 ),
1787
1788 V1 { .. } | V2 { .. } | V3 { .. } => unreachable!(
1789 "older transaction versions only exist in finalized blocks, because of the mandatory canopy checkpoint",
1790 ),
1791 };
1792
1793 // remove the utxos this produced
1794 self.revert_chain_with(&(outputs, transaction_hash, new_outputs), position);
1795 // reset the utxos this consumed
1796 self.revert_chain_with(&(inputs, transaction_hash, spent_outputs), position);
1797
1798 // TODO: move this to the history tree UpdateWith.revert...()?
1799 // remove `transaction.hash` from `tx_loc_by_hash`
1800 assert!(
1801 self.tx_loc_by_hash.remove(transaction_hash).is_some(),
1802 "transactions must be present if block was added to chain"
1803 );
1804
1805 // remove the shielded data
1806
1807 #[cfg(not(feature = "indexer"))]
1808 let transaction_hash = &();
1809
1810 self.revert_chain_with(&(joinsplit_data, transaction_hash), position);
1811 self.revert_chain_with(
1812 &(sapling_shielded_data_per_spend_anchor, transaction_hash),
1813 position,
1814 );
1815 self.revert_chain_with(
1816 &(sapling_shielded_data_shared_anchor, transaction_hash),
1817 position,
1818 );
1819 self.revert_chain_with(&(orchard_shielded_data, transaction_hash), position);
1820 }
1821
1822 // TODO: move these to the shielded UpdateWith.revert...()?
1823 self.remove_sprout_tree_and_anchor(position, height);
1824 self.remove_sapling_tree_and_anchor(position, height);
1825 self.remove_orchard_tree_and_anchor(position, height);
1826
1827 // TODO: move this to the history tree UpdateWith.revert...()?
1828 self.remove_history_tree(position, height);
1829
1830 // revert the chain value pool balances, if needed
1831 // note that size is 0 because it isn't need for reverting
1832 self.revert_chain_with(&(*chain_value_pool_change, height, 0), position);
1833 }
1834}
1835
1836// Created UTXOs
1837//
1838// TODO: replace arguments with a struct
1839impl
1840 UpdateWith<(
1841 // The outputs from a transaction in this block
1842 &Vec<transparent::Output>,
1843 // The hash of the transaction that the outputs are from
1844 &transaction::Hash,
1845 // The UTXOs for all outputs created by this transaction (or block)
1846 &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
1847 )> for Chain
1848{
1849 #[allow(clippy::unwrap_in_result)]
1850 fn update_chain_tip_with(
1851 &mut self,
1852 &(created_outputs, creating_tx_hash, block_created_outputs): &(
1853 &Vec<transparent::Output>,
1854 &transaction::Hash,
1855 &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
1856 ),
1857 ) -> Result<(), ValidateContextError> {
1858 for output_index in 0..created_outputs.len() {
1859 let outpoint = transparent::OutPoint {
1860 hash: *creating_tx_hash,
1861 index: output_index.try_into().expect("valid indexes fit in u32"),
1862 };
1863 let created_utxo = block_created_outputs
1864 .get(&outpoint)
1865 .expect("new_outputs contains all created UTXOs");
1866
1867 // Update the chain's created UTXOs
1868 let previous_entry = self.created_utxos.insert(outpoint, created_utxo.clone());
1869 assert_eq!(
1870 previous_entry, None,
1871 "unexpected created output: duplicate update or duplicate UTXO",
1872 );
1873
1874 // Update the address index with this UTXO
1875 if let Some(receiving_address) = created_utxo.utxo.output.address(&self.network) {
1876 let address_transfers = self
1877 .partial_transparent_transfers
1878 .entry(receiving_address)
1879 .or_default();
1880
1881 address_transfers.update_chain_tip_with(&(&outpoint, created_utxo))?;
1882 }
1883 }
1884
1885 Ok(())
1886 }
1887
1888 fn revert_chain_with(
1889 &mut self,
1890 &(created_outputs, creating_tx_hash, block_created_outputs): &(
1891 &Vec<transparent::Output>,
1892 &transaction::Hash,
1893 &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
1894 ),
1895 position: RevertPosition,
1896 ) {
1897 for output_index in 0..created_outputs.len() {
1898 let outpoint = transparent::OutPoint {
1899 hash: *creating_tx_hash,
1900 index: output_index.try_into().expect("valid indexes fit in u32"),
1901 };
1902 let created_utxo = block_created_outputs
1903 .get(&outpoint)
1904 .expect("new_outputs contains all created UTXOs");
1905
1906 // Revert the chain's created UTXOs
1907 let removed_entry = self.created_utxos.remove(&outpoint);
1908 assert!(
1909 removed_entry.is_some(),
1910 "unexpected revert of created output: duplicate revert or duplicate UTXO",
1911 );
1912
1913 // Revert the address index for this UTXO
1914 if let Some(receiving_address) = created_utxo.utxo.output.address(&self.network) {
1915 let address_transfers = self
1916 .partial_transparent_transfers
1917 .get_mut(&receiving_address)
1918 .expect("block has previously been applied to the chain");
1919
1920 address_transfers.revert_chain_with(&(&outpoint, created_utxo), position);
1921
1922 // Remove this transfer if it is now empty
1923 if address_transfers.is_empty() {
1924 self.partial_transparent_transfers
1925 .remove(&receiving_address);
1926 }
1927 }
1928 }
1929 }
1930}
1931
1932// Transparent inputs
1933//
1934// TODO: replace arguments with a struct
1935impl
1936 UpdateWith<(
1937 // The inputs from a transaction in this block
1938 &Vec<transparent::Input>,
1939 // The hash of the transaction that the inputs are from
1940 // (not the transaction the spent output was created by)
1941 &transaction::Hash,
1942 // The outputs for all inputs spent in this transaction (or block)
1943 &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
1944 )> for Chain
1945{
1946 fn update_chain_tip_with(
1947 &mut self,
1948 &(spending_inputs, spending_tx_hash, spent_outputs): &(
1949 &Vec<transparent::Input>,
1950 &transaction::Hash,
1951 &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
1952 ),
1953 ) -> Result<(), ValidateContextError> {
1954 for spending_input in spending_inputs.iter() {
1955 let spent_outpoint = if let Some(spent_outpoint) = spending_input.outpoint() {
1956 spent_outpoint
1957 } else {
1958 continue;
1959 };
1960
1961 #[cfg(feature = "indexer")]
1962 let insert_value = *spending_tx_hash;
1963 #[cfg(not(feature = "indexer"))]
1964 let insert_value = ();
1965
1966 // Index the spent outpoint in the chain
1967 let was_spend_newly_inserted = self
1968 .spent_utxos
1969 .insert(spent_outpoint, insert_value)
1970 .is_none();
1971 assert!(
1972 was_spend_newly_inserted,
1973 "unexpected duplicate spent output: should be checked earlier"
1974 );
1975
1976 // TODO: fix tests to supply correct spent outputs, then turn this into an expect()
1977 let spent_output = if let Some(spent_output) = spent_outputs.get(&spent_outpoint) {
1978 spent_output
1979 } else if !cfg!(test) {
1980 panic!("unexpected missing spent output: all spent outputs must be indexed");
1981 } else {
1982 continue;
1983 };
1984
1985 // Index the spent output for the address
1986 if let Some(spending_address) = spent_output.utxo.output.address(&self.network) {
1987 let address_transfers = self
1988 .partial_transparent_transfers
1989 .entry(spending_address)
1990 .or_default();
1991
1992 address_transfers.update_chain_tip_with(&(
1993 spending_input,
1994 spending_tx_hash,
1995 spent_output,
1996 ))?;
1997 }
1998 }
1999
2000 Ok(())
2001 }
2002
2003 fn revert_chain_with(
2004 &mut self,
2005 &(spending_inputs, spending_tx_hash, spent_outputs): &(
2006 &Vec<transparent::Input>,
2007 &transaction::Hash,
2008 &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
2009 ),
2010 position: RevertPosition,
2011 ) {
2012 for spending_input in spending_inputs.iter() {
2013 let spent_outpoint = if let Some(spent_outpoint) = spending_input.outpoint() {
2014 spent_outpoint
2015 } else {
2016 continue;
2017 };
2018
2019 // Revert the spent outpoint in the chain
2020 let was_spent_outpoint_removed = self.spent_utxos.remove(&spent_outpoint).is_some();
2021 assert!(
2022 was_spent_outpoint_removed,
2023 "spent_utxos must be present if block was added to chain"
2024 );
2025
2026 // TODO: fix tests to supply correct spent outputs, then turn this into an expect()
2027 let spent_output = if let Some(spent_output) = spent_outputs.get(&spent_outpoint) {
2028 spent_output
2029 } else if !cfg!(test) {
2030 panic!(
2031 "unexpected missing reverted spent output: all spent outputs must be indexed"
2032 );
2033 } else {
2034 continue;
2035 };
2036
2037 // Revert the spent output for the address
2038 if let Some(receiving_address) = spent_output.utxo.output.address(&self.network) {
2039 let address_transfers = self
2040 .partial_transparent_transfers
2041 .get_mut(&receiving_address)
2042 .expect("block has previously been applied to the chain");
2043
2044 address_transfers
2045 .revert_chain_with(&(spending_input, spending_tx_hash, spent_output), position);
2046
2047 // Remove this transfer if it is now empty
2048 if address_transfers.is_empty() {
2049 self.partial_transparent_transfers
2050 .remove(&receiving_address);
2051 }
2052 }
2053 }
2054 }
2055}
2056
2057impl
2058 UpdateWith<(
2059 &Option<transaction::JoinSplitData<Groth16Proof>>,
2060 &SpendingTransactionId,
2061 )> for Chain
2062{
2063 #[instrument(skip(self, joinsplit_data))]
2064 fn update_chain_tip_with(
2065 &mut self,
2066 &(joinsplit_data, revealing_tx_id): &(
2067 &Option<transaction::JoinSplitData<Groth16Proof>>,
2068 &SpendingTransactionId,
2069 ),
2070 ) -> Result<(), ValidateContextError> {
2071 if let Some(joinsplit_data) = joinsplit_data {
2072 // We do note commitment tree updates in parallel rayon threads.
2073
2074 check::nullifier::add_to_non_finalized_chain_unique(
2075 &mut self.sprout_nullifiers,
2076 joinsplit_data.nullifiers(),
2077 *revealing_tx_id,
2078 )?;
2079 }
2080 Ok(())
2081 }
2082
2083 /// # Panics
2084 ///
2085 /// Panics if any nullifier is missing from the chain when we try to remove it.
2086 ///
2087 /// See [`check::nullifier::remove_from_non_finalized_chain`] for details.
2088 #[instrument(skip(self, joinsplit_data))]
2089 fn revert_chain_with(
2090 &mut self,
2091 &(joinsplit_data, _revealing_tx_id): &(
2092 &Option<transaction::JoinSplitData<Groth16Proof>>,
2093 &SpendingTransactionId,
2094 ),
2095 _position: RevertPosition,
2096 ) {
2097 if let Some(joinsplit_data) = joinsplit_data {
2098 // Note commitments are removed from the Chain during a fork,
2099 // by removing trees above the fork height from the note commitment index.
2100 // This happens when reverting the block itself.
2101
2102 check::nullifier::remove_from_non_finalized_chain(
2103 &mut self.sprout_nullifiers,
2104 joinsplit_data.nullifiers(),
2105 );
2106 }
2107 }
2108}
2109
2110impl<AnchorV>
2111 UpdateWith<(
2112 &Option<sapling::ShieldedData<AnchorV>>,
2113 &SpendingTransactionId,
2114 )> for Chain
2115where
2116 AnchorV: sapling::AnchorVariant + Clone,
2117{
2118 #[instrument(skip(self, sapling_shielded_data))]
2119 fn update_chain_tip_with(
2120 &mut self,
2121 &(sapling_shielded_data, revealing_tx_id): &(
2122 &Option<sapling::ShieldedData<AnchorV>>,
2123 &SpendingTransactionId,
2124 ),
2125 ) -> Result<(), ValidateContextError> {
2126 if let Some(sapling_shielded_data) = sapling_shielded_data {
2127 // We do note commitment tree updates in parallel rayon threads.
2128
2129 check::nullifier::add_to_non_finalized_chain_unique(
2130 &mut self.sapling_nullifiers,
2131 sapling_shielded_data.nullifiers(),
2132 *revealing_tx_id,
2133 )?;
2134 }
2135 Ok(())
2136 }
2137
2138 /// # Panics
2139 ///
2140 /// Panics if any nullifier is missing from the chain when we try to remove it.
2141 ///
2142 /// See [`check::nullifier::remove_from_non_finalized_chain`] for details.
2143 #[instrument(skip(self, sapling_shielded_data))]
2144 fn revert_chain_with(
2145 &mut self,
2146 &(sapling_shielded_data, _revealing_tx_id): &(
2147 &Option<sapling::ShieldedData<AnchorV>>,
2148 &SpendingTransactionId,
2149 ),
2150 _position: RevertPosition,
2151 ) {
2152 if let Some(sapling_shielded_data) = sapling_shielded_data {
2153 // Note commitments are removed from the Chain during a fork,
2154 // by removing trees above the fork height from the note commitment index.
2155 // This happens when reverting the block itself.
2156
2157 check::nullifier::remove_from_non_finalized_chain(
2158 &mut self.sapling_nullifiers,
2159 sapling_shielded_data.nullifiers(),
2160 );
2161 }
2162 }
2163}
2164
2165impl UpdateWith<(&Option<orchard::ShieldedData>, &SpendingTransactionId)> for Chain {
2166 #[instrument(skip(self, orchard_shielded_data))]
2167 fn update_chain_tip_with(
2168 &mut self,
2169 &(orchard_shielded_data, revealing_tx_id): &(
2170 &Option<orchard::ShieldedData>,
2171 &SpendingTransactionId,
2172 ),
2173 ) -> Result<(), ValidateContextError> {
2174 if let Some(orchard_shielded_data) = orchard_shielded_data {
2175 // We do note commitment tree updates in parallel rayon threads.
2176
2177 check::nullifier::add_to_non_finalized_chain_unique(
2178 &mut self.orchard_nullifiers,
2179 orchard_shielded_data.nullifiers(),
2180 *revealing_tx_id,
2181 )?;
2182 }
2183 Ok(())
2184 }
2185
2186 /// # Panics
2187 ///
2188 /// Panics if any nullifier is missing from the chain when we try to remove it.
2189 ///
2190 /// See [`check::nullifier::remove_from_non_finalized_chain`] for details.
2191 #[instrument(skip(self, orchard_shielded_data))]
2192 fn revert_chain_with(
2193 &mut self,
2194 (orchard_shielded_data, _revealing_tx_id): &(
2195 &Option<orchard::ShieldedData>,
2196 &SpendingTransactionId,
2197 ),
2198 _position: RevertPosition,
2199 ) {
2200 if let Some(orchard_shielded_data) = orchard_shielded_data {
2201 // Note commitments are removed from the Chain during a fork,
2202 // by removing trees above the fork height from the note commitment index.
2203 // This happens when reverting the block itself.
2204
2205 check::nullifier::remove_from_non_finalized_chain(
2206 &mut self.orchard_nullifiers,
2207 orchard_shielded_data.nullifiers(),
2208 );
2209 }
2210 }
2211}
2212
2213impl UpdateWith<(ValueBalance<NegativeAllowed>, Height, usize)> for Chain {
2214 #[allow(clippy::unwrap_in_result)]
2215 fn update_chain_tip_with(
2216 &mut self,
2217 (block_value_pool_change, height, size): &(ValueBalance<NegativeAllowed>, Height, usize),
2218 ) -> Result<(), ValidateContextError> {
2219 match self
2220 .chain_value_pools
2221 .add_chain_value_pool_change(*block_value_pool_change)
2222 {
2223 Ok(chain_value_pools) => {
2224 self.chain_value_pools = chain_value_pools;
2225 self.block_info_by_height
2226 .insert(*height, BlockInfo::new(chain_value_pools, *size as u32));
2227 }
2228 Err(value_balance_error) => Err(ValidateContextError::AddValuePool {
2229 value_balance_error,
2230 chain_value_pools: self.chain_value_pools,
2231 block_value_pool_change: *block_value_pool_change,
2232 height: Some(*height),
2233 })?,
2234 };
2235
2236 Ok(())
2237 }
2238
2239 /// Revert the chain state using a block chain value pool change.
2240 ///
2241 /// When forking from the tip, subtract the block's chain value pool change.
2242 ///
2243 /// When finalizing the root, leave the chain value pool balances unchanged.
2244 /// [`ChainInner::chain_value_pools`] tracks the chain value pools for all finalized blocks, and
2245 /// the non-finalized blocks in this chain. So finalizing the root doesn't change the set of
2246 /// blocks it tracks.
2247 ///
2248 /// # Panics
2249 ///
2250 /// Panics if the chain pool value balance is invalid after we subtract the block value pool
2251 /// change.
2252 fn revert_chain_with(
2253 &mut self,
2254 (block_value_pool_change, height, _size): &(ValueBalance<NegativeAllowed>, Height, usize),
2255 position: RevertPosition,
2256 ) {
2257 use std::ops::Neg;
2258
2259 if position == RevertPosition::Tip {
2260 self.chain_value_pools = self
2261 .chain_value_pools
2262 .add_chain_value_pool_change(block_value_pool_change.neg())
2263 .expect("reverting the tip will leave the pools in a previously valid state");
2264 }
2265 self.block_info_by_height.remove(height);
2266 }
2267}
2268
2269impl Ord for Chain {
2270 /// Chain order for the [`NonFinalizedState`][1]'s `chain_set`.
2271 ///
2272 /// Chains with higher cumulative Proof of Work are [`Ordering::Greater`],
2273 /// breaking ties using the tip block hash.
2274 ///
2275 /// Despite the consensus rules, Zebra uses the tip block hash as a
2276 /// tie-breaker. Zebra blocks are downloaded in parallel, so download
2277 /// timestamps may not be unique. (And Zebra currently doesn't track
2278 /// download times, because [`Block`](block::Block)s are immutable.)
2279 ///
2280 /// This departure from the consensus rules may delay network convergence,
2281 /// for as long as the greater hash belongs to the later mined block.
2282 /// But Zebra nodes should converge as soon as the tied work is broken.
2283 ///
2284 /// "At a given point in time, each full validator is aware of a set of candidate blocks.
2285 /// These form a tree rooted at the genesis block, where each node in the tree
2286 /// refers to its parent via the hashPrevBlock block header field.
2287 ///
2288 /// A path from the root toward the leaves of the tree consisting of a sequence
2289 /// of one or more valid blocks consistent with consensus rules,
2290 /// is called a valid block chain.
2291 ///
2292 /// In order to choose the best valid block chain in its view of the overall block tree,
2293 /// a node sums the work ... of all blocks in each valid block chain,
2294 /// and considers the valid block chain with greatest total work to be best.
2295 ///
2296 /// To break ties between leaf blocks, a node will prefer the block that it received first.
2297 ///
2298 /// The consensus protocol is designed to ensure that for any given block height,
2299 /// the vast majority of nodes should eventually agree on their best valid block chain
2300 /// up to that height."
2301 ///
2302 /// <https://zips.z.cash/protocol/protocol.pdf#blockchain>
2303 ///
2304 /// # Correctness
2305 ///
2306 /// `Chain::cmp` is used in a `BTreeSet`, so the fields accessed by `cmp` must not have
2307 /// interior mutability.
2308 ///
2309 /// # Panics
2310 ///
2311 /// If two chains compare equal.
2312 ///
2313 /// This panic enforces the [`NonFinalizedState::chain_set`][2] unique chain invariant.
2314 ///
2315 /// If the chain set contains duplicate chains, the non-finalized state might
2316 /// handle new blocks or block finalization incorrectly.
2317 ///
2318 /// [1]: super::NonFinalizedState
2319 /// [2]: super::NonFinalizedState::chain_set
2320 fn cmp(&self, other: &Self) -> Ordering {
2321 if self.partial_cumulative_work != other.partial_cumulative_work {
2322 self.partial_cumulative_work
2323 .cmp(&other.partial_cumulative_work)
2324 } else {
2325 let self_hash = self
2326 .blocks
2327 .values()
2328 .last()
2329 .expect("always at least 1 element")
2330 .hash;
2331
2332 let other_hash = other
2333 .blocks
2334 .values()
2335 .last()
2336 .expect("always at least 1 element")
2337 .hash;
2338
2339 // This comparison is a tie-breaker within the local node, so it does not need to
2340 // be consistent with the ordering on `ExpandedDifficulty` and `block::Hash`.
2341 match self_hash.0.cmp(&other_hash.0) {
2342 Ordering::Equal => unreachable!("Chain tip block hashes are always unique"),
2343 ordering => ordering,
2344 }
2345 }
2346 }
2347}
2348
2349impl PartialOrd for Chain {
2350 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
2351 Some(self.cmp(other))
2352 }
2353}
2354
2355impl PartialEq for Chain {
2356 /// Chain equality for [`NonFinalizedState::chain_set`][1], using proof of
2357 /// work, then the tip block hash as a tie-breaker.
2358 ///
2359 /// # Panics
2360 ///
2361 /// If two chains compare equal.
2362 ///
2363 /// See [`Chain::cmp`] for details.
2364 ///
2365 /// [1]: super::NonFinalizedState::chain_set
2366 fn eq(&self, other: &Self) -> bool {
2367 self.partial_cmp(other) == Some(Ordering::Equal)
2368 }
2369}
2370
2371impl Eq for Chain {}
2372
2373#[cfg(test)]
2374impl Chain {
2375 /// Inserts the supplied Sapling note commitment subtree into the chain.
2376 pub(crate) fn insert_sapling_subtree(
2377 &mut self,
2378 subtree: NoteCommitmentSubtree<sapling::tree::Node>,
2379 ) {
2380 self.inner
2381 .sapling_subtrees
2382 .insert(subtree.index, subtree.into_data());
2383 }
2384
2385 /// Inserts the supplied Orchard note commitment subtree into the chain.
2386 pub(crate) fn insert_orchard_subtree(
2387 &mut self,
2388 subtree: NoteCommitmentSubtree<orchard::tree::Node>,
2389 ) {
2390 self.inner
2391 .orchard_subtrees
2392 .insert(subtree.index, subtree.into_data());
2393 }
2394}