zebra_scan/
storage.rs

1//! Store viewing keys and results of the scan.
2
3use std::collections::{BTreeMap, HashMap};
4
5use zebra_chain::{block::Height, parameters::Network};
6use zebra_state::TransactionIndex;
7
8use crate::config::Config;
9
10pub mod db;
11
12// Public types and APIs
13pub use db::{SaplingScannedResult, SaplingScanningKey};
14
15/// We insert an empty results entry to the database every this interval for each stored key,
16/// so we can track progress.
17pub const INSERT_CONTROL_INTERVAL: u32 = 1_000;
18
19/// Store key info and results of the scan.
20///
21/// `rocksdb` allows concurrent writes through a shared reference,
22/// so clones of the scanner storage represent the same database instance.
23/// When the final clone is dropped, the database is closed.
24#[derive(Clone, Debug)]
25pub struct Storage {
26    // Configuration
27    //
28    // This configuration cannot be modified after the database is initialized,
29    // because some clones would have different values.
30    //
31    // TODO: add config if needed?
32
33    // Owned State
34    //
35    // Everything contained in this state must be shared by all clones, or read-only.
36    //
37    /// The underlying database.
38    ///
39    /// `rocksdb` allows reads and writes via a shared reference,
40    /// so this database object can be freely cloned.
41    /// The last instance that is dropped will close the underlying database.
42    db: db::ScannerDb,
43}
44
45impl Storage {
46    /// Opens and returns the on-disk scanner results storage for `config` and `network`.
47    /// If there is no existing storage, creates a new storage on disk.
48    ///
49    /// Birthdays and scanner progress are marked by inserting an empty result for that height.
50    ///
51    /// # Performance / Hangs
52    ///
53    /// This method can block while creating or reading database files, so it must be inside
54    /// spawn_blocking() in async code.
55    pub fn new(config: &Config, network: &Network, read_only: bool) -> Self {
56        let mut storage = Self::new_db(config, network, read_only);
57
58        for (sapling_key, birthday) in config.sapling_keys_to_scan.iter() {
59            storage.add_sapling_key(sapling_key, Some(zebra_chain::block::Height(*birthday)));
60        }
61
62        storage
63    }
64
65    /// Add a sapling key to the storage.
66    ///
67    /// # Performance / Hangs
68    ///
69    /// This method can block while writing database files, so it must be inside spawn_blocking()
70    /// in async code.
71    pub fn add_sapling_key(
72        &mut self,
73        sapling_key: &SaplingScanningKey,
74        birthday: impl Into<Option<Height>>,
75    ) {
76        let birthday = birthday.into();
77
78        // It's ok to write some keys and not others during shutdown, so each key can get its own
79        // batch. (They will be re-written on startup anyway.)
80        self.insert_sapling_key(sapling_key, birthday);
81    }
82
83    /// Returns all the keys and their last scanned heights.
84    ///
85    /// # Performance / Hangs
86    ///
87    /// This method can block while reading database files, so it must be inside spawn_blocking()
88    /// in async code.
89    pub fn sapling_keys_last_heights(&self) -> HashMap<SaplingScanningKey, Height> {
90        self.sapling_keys_and_last_scanned_heights()
91    }
92
93    /// Add the sapling results for `height` to the storage. The results can be any map of
94    /// [`TransactionIndex`] to [`SaplingScannedResult`].
95    ///
96    /// All the results for the same height must be written at the same time, to avoid partial
97    /// writes during shutdown.
98    ///
99    /// Also adds empty progress tracking entries every `INSERT_CONTROL_INTERVAL` blocks if needed.
100    ///
101    /// # Performance / Hangs
102    ///
103    /// This method can block while writing database files, so it must be inside spawn_blocking()
104    /// in async code.
105    pub fn add_sapling_results(
106        &mut self,
107        sapling_key: &SaplingScanningKey,
108        height: Height,
109        sapling_results: BTreeMap<TransactionIndex, SaplingScannedResult>,
110    ) {
111        self.insert_sapling_results(sapling_key, height, sapling_results)
112    }
113
114    /// Returns all the results for a sapling key, for every scanned block height.
115    ///
116    /// # Performance / Hangs
117    ///
118    /// This method can block while reading database files, so it must be inside spawn_blocking()
119    /// in async code.
120    pub fn sapling_results(
121        &self,
122        sapling_key: &SaplingScanningKey,
123    ) -> BTreeMap<Height, Vec<SaplingScannedResult>> {
124        self.sapling_results_for_key(sapling_key)
125    }
126}