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, history_tree::HistoryTree, transparent,
21    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,
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 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
65impl ZebraDb {
66    // Column family convenience methods
67
68    /// Returns a typed handle to the `history_tree` column family.
69    pub(crate) fn history_tree_cf(&self) -> HistoryTreePartsCf {
70        HistoryTreePartsCf::new(&self.db, HISTORY_TREE)
71            .expect("column family was created when database was created")
72    }
73
74    /// Returns a legacy typed handle to the `history_tree` column family.
75    /// This should not be used in new code.
76    pub(crate) fn legacy_history_tree_cf(&self) -> LegacyHistoryTreePartsCf {
77        LegacyHistoryTreePartsCf::new(&self.db, HISTORY_TREE)
78            .expect("column family was created when database was created")
79    }
80
81    /// Returns a generic raw key typed handle to the `history_tree` column family.
82    /// This should not be used in new code.
83    pub(crate) fn raw_history_tree_cf(&self) -> RawHistoryTreePartsCf {
84        RawHistoryTreePartsCf::new(&self.db, HISTORY_TREE)
85            .expect("column family was created when database was created")
86    }
87
88    /// Returns a typed handle to the chain value pools column family.
89    pub(crate) fn chain_value_pools_cf(&self) -> ChainValuePoolsCf {
90        ChainValuePoolsCf::new(&self.db, CHAIN_VALUE_POOLS)
91            .expect("column family was created when database was created")
92    }
93
94    // History tree methods
95
96    /// Returns the ZIP-221 history tree of the finalized tip.
97    ///
98    /// If history trees have not been activated yet (pre-Heartwood), or the state is empty,
99    /// returns an empty history tree.
100    pub fn history_tree(&self) -> Arc<HistoryTree> {
101        let history_tree_cf = self.history_tree_cf();
102
103        // # Backwards Compatibility
104        //
105        // This code can read the column family format in 1.2.0 and earlier (tip height key),
106        // and after PR #7392 is merged (empty key). The height-based code can be removed when
107        // versions 1.2.0 and earlier are no longer supported.
108        //
109        // # Concurrency
110        //
111        // There is only one entry in this column family, which is atomically updated by a block
112        // write batch (database transaction). If we used a height as the key in this column family,
113        // any updates between reading the tip height and reading the tree could cause panics.
114        //
115        // So we use the empty key `()`. Since the key has a constant value, we will always read
116        // the latest tree.
117        let mut history_tree_parts = history_tree_cf.zs_get(&());
118
119        if history_tree_parts.is_none() {
120            let legacy_history_tree_cf = self.legacy_history_tree_cf();
121
122            // In Zebra 1.4.0 and later, we only update the history tip tree when it has changed (for every block after heartwood).
123            // But we write with a `()` key, not a height key.
124            // So we need to look for the most recent update height if the `()` key has never been written.
125            history_tree_parts = legacy_history_tree_cf
126                .zs_last_key_value()
127                .map(|(_height_key, tree_value)| tree_value);
128        }
129
130        let history_tree = history_tree_parts.map(|parts| {
131            parts.with_network(&self.db.network()).expect(
132                "deserialization format should match the serialization format used by IntoDisk",
133            )
134        });
135        Arc::new(HistoryTree::from(history_tree))
136    }
137
138    /// Returns all the history tip trees.
139    /// We only store the history tree for the tip, so this method is only used in tests and
140    /// upgrades.
141    pub(crate) fn history_trees_full_tip(&self) -> BTreeMap<RawBytes, Arc<HistoryTree>> {
142        let raw_history_tree_cf = self.raw_history_tree_cf();
143
144        raw_history_tree_cf
145            .zs_forward_range_iter(..)
146            .map(|(raw_key, history_tree_parts)| {
147                let history_tree = history_tree_parts.with_network(&self.db.network()).expect(
148                    "deserialization format should match the serialization format used by IntoDisk",
149                );
150                (raw_key, Arc::new(HistoryTree::from(history_tree)))
151            })
152            .collect()
153    }
154
155    // Value pool methods
156
157    /// Returns the stored `ValueBalance` for the best chain at the finalized tip height.
158    pub fn finalized_value_pool(&self) -> ValueBalance<NonNegative> {
159        let chain_value_pools_cf = self.chain_value_pools_cf();
160
161        chain_value_pools_cf
162            .zs_get(&())
163            .unwrap_or_else(ValueBalance::zero)
164    }
165}
166
167impl DiskWriteBatch {
168    // History tree methods
169
170    /// Updates the history tree for the tip, if it is not empty.
171    ///
172    /// The batch must be written to the database by the caller.
173    pub fn update_history_tree(&mut self, db: &ZebraDb, tree: &HistoryTree) {
174        let history_tree_cf = db.history_tree_cf().with_batch_for_writing(self);
175
176        if let Some(tree) = tree.as_ref() {
177            // The batch is modified by this method and written by the caller.
178            let _ = history_tree_cf.zs_insert(&(), &HistoryTreeParts::from(tree));
179        }
180    }
181
182    /// Legacy method: Deletes the range of history trees at the given [`Height`]s.
183    /// Doesn't delete the upper bound.
184    ///
185    /// From state format 25.3.0 onwards, the history trees are indexed by an empty key,
186    /// so this method does nothing.
187    ///
188    /// The batch must be written to the database by the caller.
189    pub fn delete_range_history_tree(
190        &mut self,
191        db: &ZebraDb,
192        from: &Height,
193        until_strictly_before: &Height,
194    ) {
195        let history_tree_cf = db.legacy_history_tree_cf().with_batch_for_writing(self);
196
197        // The batch is modified by this method and written by the caller.
198        //
199        // TODO: convert zs_delete_range() to take std::ops::RangeBounds
200        let _ = history_tree_cf.zs_delete_range(from, until_strictly_before);
201    }
202
203    // Value pool methods
204
205    /// Prepares a database batch containing the chain value pool update from `finalized.block`, and
206    /// returns it without actually writing anything.
207    ///
208    /// The batch is modified by this method and written by the caller. The caller should not write
209    /// the batch if this method returns an error.
210    ///
211    /// The parameter `utxos_spent_by_block` must contain the [`transparent::Utxo`]s of every input
212    /// in this block, including UTXOs created by earlier transactions in this block.
213    ///
214    /// Note that the chain value pool has the opposite sign to the transaction value pool. See the
215    /// [`chain_value_pool_change`] and [`add_chain_value_pool_change`] methods for more details.
216    ///
217    /// # Errors
218    ///
219    /// - Propagates any errors from updating value pools
220    ///
221    /// [`chain_value_pool_change`]: zebra_chain::block::Block::chain_value_pool_change
222    /// [`add_chain_value_pool_change`]: ValueBalance::add_chain_value_pool_change
223    pub fn prepare_chain_value_pools_batch(
224        &mut self,
225        db: &ZebraDb,
226        finalized: &FinalizedBlock,
227        utxos_spent_by_block: HashMap<transparent::OutPoint, transparent::Utxo>,
228        value_pool: ValueBalance<NonNegative>,
229    ) -> Result<(), BoxError> {
230        let _ = db
231            .chain_value_pools_cf()
232            .with_batch_for_writing(self)
233            .zs_insert(
234                &(),
235                &value_pool.add_chain_value_pool_change(
236                    finalized.block.chain_value_pool_change(
237                        &utxos_spent_by_block,
238                        finalized.deferred_balance,
239                    )?,
240                )?,
241            );
242
243        Ok(())
244    }
245}