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}