zebra_state/service/finalized_state/zebra_db/
chain.rs

1//! Provides high-level access to database whole-chain:
2//! - history trees
3//! - chain value pools
4//!
5//! This module makes sure that:
6//! - all disk writes happen inside a RocksDB transaction, and
7//! - format-specific invariants are maintained.
8//!
9//! # Correctness
10//!
11//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
12//! each time the database format (column, serialization, etc) changes.
13
14use std::{
15    collections::{BTreeMap, HashMap},
16    sync::Arc,
17};
18
19use zebra_chain::{
20    amount::NonNegative, block::Height, block_info::BlockInfo, history_tree::HistoryTree,
21    serialization::ZcashSerialize as _, transparent, value_balance::ValueBalance,
22};
23
24use crate::{
25    request::FinalizedBlock,
26    service::finalized_state::{
27        disk_db::DiskWriteBatch,
28        disk_format::{chain::HistoryTreeParts, RawBytes},
29        zebra_db::ZebraDb,
30        TypedColumnFamily,
31    },
32    BoxError, HashOrHeight,
33};
34
35/// The name of the History Tree column family.
36///
37/// This constant should be used so the compiler can detect typos.
38pub const HISTORY_TREE: &str = "history_tree";
39
40/// The type for reading history trees from the database.
41///
42/// This constant should be used so the compiler can detect incorrectly typed accesses to the
43/// column family.
44pub type HistoryTreePartsCf<'cf> = TypedColumnFamily<'cf, (), HistoryTreeParts>;
45
46/// The legacy (1.3.0 and earlier) type for reading history trees from the database.
47/// This type should not be used in new code.
48pub type LegacyHistoryTreePartsCf<'cf> = TypedColumnFamily<'cf, Height, HistoryTreeParts>;
49
50/// A generic raw key type for reading history trees from the database, regardless of the database version.
51/// This type should not be used in new code.
52pub type RawHistoryTreePartsCf<'cf> = TypedColumnFamily<'cf, RawBytes, HistoryTreeParts>;
53
54/// The name of the tip-only chain value pools column family.
55///
56/// This constant should be used so the compiler can detect typos.
57pub const CHAIN_VALUE_POOLS: &str = "tip_chain_value_pool";
58
59/// The type for reading value pools from the database.
60///
61/// This constant should be used so the compiler can detect incorrectly typed accesses to the
62/// column family.
63pub type ChainValuePoolsCf<'cf> = TypedColumnFamily<'cf, (), ValueBalance<NonNegative>>;
64
65/// The name of the block info column family.
66///
67/// This constant should be used so the compiler can detect typos.
68pub const BLOCK_INFO: &str = "block_info";
69
70/// The type for reading value pools from the database.
71///
72/// This constant should be used so the compiler can detect incorrectly typed accesses to the
73/// column family.
74pub type BlockInfoCf<'cf> = TypedColumnFamily<'cf, Height, BlockInfo>;
75
76impl ZebraDb {
77    // Column family convenience methods
78
79    /// Returns a typed handle to the `history_tree` column family.
80    pub(crate) fn history_tree_cf(&self) -> HistoryTreePartsCf {
81        HistoryTreePartsCf::new(&self.db, HISTORY_TREE)
82            .expect("column family was created when database was created")
83    }
84
85    /// Returns a legacy typed handle to the `history_tree` column family.
86    /// This should not be used in new code.
87    pub(crate) fn legacy_history_tree_cf(&self) -> LegacyHistoryTreePartsCf {
88        LegacyHistoryTreePartsCf::new(&self.db, HISTORY_TREE)
89            .expect("column family was created when database was created")
90    }
91
92    /// Returns a generic raw key typed handle to the `history_tree` column family.
93    /// This should not be used in new code.
94    pub(crate) fn raw_history_tree_cf(&self) -> RawHistoryTreePartsCf {
95        RawHistoryTreePartsCf::new(&self.db, HISTORY_TREE)
96            .expect("column family was created when database was created")
97    }
98
99    /// Returns a typed handle to the chain value pools column family.
100    pub(crate) fn chain_value_pools_cf(&self) -> ChainValuePoolsCf {
101        ChainValuePoolsCf::new(&self.db, CHAIN_VALUE_POOLS)
102            .expect("column family was created when database was created")
103    }
104
105    /// Returns a typed handle to the block data column family.
106    pub(crate) fn block_info_cf(&self) -> BlockInfoCf {
107        BlockInfoCf::new(&self.db, BLOCK_INFO)
108            .expect("column family was created when database was created")
109    }
110
111    // History tree methods
112
113    /// Returns the ZIP-221 history tree of the finalized tip.
114    ///
115    /// If history trees have not been activated yet (pre-Heartwood), or the state is empty,
116    /// returns an empty history tree.
117    pub fn history_tree(&self) -> Arc<HistoryTree> {
118        let history_tree_cf = self.history_tree_cf();
119
120        // # Backwards Compatibility
121        //
122        // This code can read the column family format in 1.2.0 and earlier (tip height key),
123        // and after PR #7392 is merged (empty key). The height-based code can be removed when
124        // versions 1.2.0 and earlier are no longer supported.
125        //
126        // # Concurrency
127        //
128        // There is only one entry in this column family, which is atomically updated by a block
129        // write batch (database transaction). If we used a height as the key in this column family,
130        // any updates between reading the tip height and reading the tree could cause panics.
131        //
132        // So we use the empty key `()`. Since the key has a constant value, we will always read
133        // the latest tree.
134        let mut history_tree_parts = history_tree_cf.zs_get(&());
135
136        if history_tree_parts.is_none() {
137            let legacy_history_tree_cf = self.legacy_history_tree_cf();
138
139            // In Zebra 1.4.0 and later, we only update the history tip tree when it has changed (for every block after heartwood).
140            // But we write with a `()` key, not a height key.
141            // So we need to look for the most recent update height if the `()` key has never been written.
142            history_tree_parts = legacy_history_tree_cf
143                .zs_last_key_value()
144                .map(|(_height_key, tree_value)| tree_value);
145        }
146
147        let history_tree = history_tree_parts.map(|parts| {
148            parts.with_network(&self.db.network()).expect(
149                "deserialization format should match the serialization format used by IntoDisk",
150            )
151        });
152        Arc::new(HistoryTree::from(history_tree))
153    }
154
155    /// Returns all the history tip trees.
156    /// We only store the history tree for the tip, so this method is only used in tests and
157    /// upgrades.
158    pub(crate) fn history_trees_full_tip(&self) -> BTreeMap<RawBytes, Arc<HistoryTree>> {
159        let raw_history_tree_cf = self.raw_history_tree_cf();
160
161        raw_history_tree_cf
162            .zs_forward_range_iter(..)
163            .map(|(raw_key, history_tree_parts)| {
164                let history_tree = history_tree_parts.with_network(&self.db.network()).expect(
165                    "deserialization format should match the serialization format used by IntoDisk",
166                );
167                (raw_key, Arc::new(HistoryTree::from(history_tree)))
168            })
169            .collect()
170    }
171
172    // Value pool methods
173
174    /// Returns the stored `ValueBalance` for the best chain at the finalized tip height.
175    pub fn finalized_value_pool(&self) -> ValueBalance<NonNegative> {
176        let chain_value_pools_cf = self.chain_value_pools_cf();
177
178        chain_value_pools_cf
179            .zs_get(&())
180            .unwrap_or_else(ValueBalance::zero)
181    }
182
183    /// Returns the stored `BlockInfo` for the given block.
184    pub fn block_info(&self, hash_or_height: HashOrHeight) -> Option<BlockInfo> {
185        let height = hash_or_height.height_or_else(|hash| self.height(hash))?;
186
187        let block_info_cf = self.block_info_cf();
188
189        block_info_cf.zs_get(&height)
190    }
191}
192
193impl DiskWriteBatch {
194    // History tree methods
195
196    /// Updates the history tree for the tip, if it is not empty.
197    ///
198    /// The batch must be written to the database by the caller.
199    pub fn update_history_tree(&mut self, db: &ZebraDb, tree: &HistoryTree) {
200        let history_tree_cf = db.history_tree_cf().with_batch_for_writing(self);
201
202        if let Some(tree) = tree.as_ref() {
203            // The batch is modified by this method and written by the caller.
204            let _ = history_tree_cf.zs_insert(&(), &HistoryTreeParts::from(tree));
205        }
206    }
207
208    /// Legacy method: Deletes the range of history trees at the given [`Height`]s.
209    /// Doesn't delete the upper bound.
210    ///
211    /// From state format 25.3.0 onwards, the history trees are indexed by an empty key,
212    /// so this method does nothing.
213    ///
214    /// The batch must be written to the database by the caller.
215    pub fn delete_range_history_tree(
216        &mut self,
217        db: &ZebraDb,
218        from: &Height,
219        until_strictly_before: &Height,
220    ) {
221        let history_tree_cf = db.legacy_history_tree_cf().with_batch_for_writing(self);
222
223        // The batch is modified by this method and written by the caller.
224        //
225        // TODO: convert zs_delete_range() to take std::ops::RangeBounds
226        let _ = history_tree_cf.zs_delete_range(from, until_strictly_before);
227    }
228
229    // Value pool methods
230
231    /// Prepares a database batch containing the chain value pool update from `finalized.block`, and
232    /// returns it without actually writing anything.
233    ///
234    /// The batch is modified by this method and written by the caller. The caller should not write
235    /// the batch if this method returns an error.
236    ///
237    /// The parameter `utxos_spent_by_block` must contain the [`transparent::Utxo`]s of every input
238    /// in this block, including UTXOs created by earlier transactions in this block.
239    ///
240    /// Note that the chain value pool has the opposite sign to the transaction value pool. See the
241    /// [`chain_value_pool_change`] and [`add_chain_value_pool_change`] methods for more details.
242    ///
243    /// # Errors
244    ///
245    /// - Propagates any errors from updating value pools
246    ///
247    /// [`chain_value_pool_change`]: zebra_chain::block::Block::chain_value_pool_change
248    /// [`add_chain_value_pool_change`]: ValueBalance::add_chain_value_pool_change
249    pub fn prepare_chain_value_pools_batch(
250        &mut self,
251        db: &ZebraDb,
252        finalized: &FinalizedBlock,
253        utxos_spent_by_block: HashMap<transparent::OutPoint, transparent::Utxo>,
254        value_pool: ValueBalance<NonNegative>,
255    ) -> Result<(), BoxError> {
256        let new_value_pool = value_pool.add_chain_value_pool_change(
257            finalized
258                .block
259                .chain_value_pool_change(&utxos_spent_by_block, finalized.deferred_balance)?,
260        )?;
261        let _ = db
262            .chain_value_pools_cf()
263            .with_batch_for_writing(self)
264            .zs_insert(&(), &new_value_pool);
265
266        // Get the block size to store with the BlockInfo. This is a bit wasteful
267        // since the block header and txs were serialized previously when writing
268        // them to the DB, and we could get the size if we modified the database
269        // code to return the size of data written; but serialization should be cheap.
270        let block_size = finalized.block.zcash_serialized_size();
271
272        let _ = db.block_info_cf().with_batch_for_writing(self).zs_insert(
273            &finalized.height,
274            &BlockInfo::new(new_value_pool, block_size as u32),
275        );
276
277        Ok(())
278    }
279}