zebra_scan/storage/db.rs
1//! Persistent storage for scanner results.
2
3use std::path::Path;
4
5use semver::Version;
6
7use zebra_chain::parameters::Network;
8
9use crate::Config;
10
11use super::Storage;
12
13// Public types and APIs
14pub use zebra_state::{
15 SaplingScannedDatabaseEntry, SaplingScannedDatabaseIndex, SaplingScannedResult,
16 SaplingScanningKey, ZebraDb as ScannerDb,
17};
18
19pub mod sapling;
20
21#[cfg(any(test, feature = "proptest-impl"))]
22pub mod tests;
23
24/// The directory name used to distinguish the scanner database from Zebra's other databases or
25/// flat files.
26///
27/// We use "private" in the name to warn users not to share this data.
28pub const SCANNER_DATABASE_KIND: &str = "private-scan";
29
30/// The column families supported by the running `zebra-scan` database code.
31///
32/// Existing column families that aren't listed here are preserved when the database is opened.
33pub const SCANNER_COLUMN_FAMILIES_IN_CODE: &[&str] = &[
34 // Sapling
35 sapling::SAPLING_TX_IDS,
36 // Orchard
37 // TODO: add Orchard support
38];
39
40/// The major version number of the scanner database. This must be updated whenever the database
41/// format changes.
42const SCANNER_DATABASE_FORMAT_MAJOR_VERSION: u64 = 1;
43
44impl Storage {
45 // Creation
46
47 /// Opens and returns an on-disk scanner results database instance for `config` and `network`.
48 /// If there is no existing database, creates a new database on disk.
49 ///
50 /// New keys in `config` are not inserted into the database.
51 pub(crate) fn new_db(config: &Config, network: &Network, read_only: bool) -> Self {
52 Self::new_with_debug(
53 config, network,
54 // TODO: make format upgrades work with any database, then change debug_skip_format_upgrades to `false`
55 true, read_only,
56 )
57 }
58
59 /// Returns an on-disk database instance with the supplied production and debug settings.
60 /// If there is no existing database, creates a new database on disk.
61 ///
62 /// New keys in `config` are not inserted into the database.
63 ///
64 /// This method is intended for use in tests.
65 pub(crate) fn new_with_debug(
66 config: &Config,
67 network: &Network,
68 debug_skip_format_upgrades: bool,
69 read_only: bool,
70 ) -> Self {
71 let db = ScannerDb::new(
72 config.db_config(),
73 SCANNER_DATABASE_KIND,
74 &Self::database_format_version_in_code(),
75 network,
76 debug_skip_format_upgrades,
77 SCANNER_COLUMN_FAMILIES_IN_CODE
78 .iter()
79 .map(ToString::to_string),
80 read_only,
81 );
82
83 let new_storage = Self { db };
84
85 // Report where we are for each key in the database.
86 let keys = new_storage.sapling_keys_last_heights();
87 for (key_num, (_key, height)) in keys.iter().enumerate() {
88 info!(
89 "Last scanned height for key number {} is {}, resuming at {}",
90 key_num,
91 height.as_usize(),
92 height.next().expect("height is not maximum").as_usize(),
93 );
94 }
95
96 info!("loaded Zebra scanner cache");
97
98 new_storage
99 }
100
101 // Config
102
103 /// Returns the configured network for this database.
104 pub fn network(&self) -> Network {
105 self.db.network()
106 }
107
108 /// Returns the `Path` where the files used by this database are located.
109 pub fn path(&self) -> &Path {
110 self.db.path()
111 }
112
113 // Versioning & Upgrades
114
115 /// The database format version in the running scanner code.
116 pub fn database_format_version_in_code() -> Version {
117 // TODO: implement in-place scanner database format upgrades
118 Version::new(SCANNER_DATABASE_FORMAT_MAJOR_VERSION, 0, 0)
119 }
120
121 /// Check for panics in code running in spawned threads.
122 /// If a thread exited with a panic, resume that panic.
123 ///
124 /// This method should be called regularly, so that panics are detected as soon as possible.
125 //
126 // TODO: when we implement format changes, call this method regularly
127 pub fn check_for_panics(&mut self) {
128 self.db.check_for_panics()
129 }
130
131 // General database status
132
133 /// Returns true if the database is empty.
134 pub fn is_empty(&self) -> bool {
135 // Any column family that is populated at (or near) startup can be used here.
136 self.sapling_tx_ids_cf().zs_is_empty()
137 }
138}