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}