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}