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}