//! Serialization formats for the shielded scanner results database. //! //! Due to Rust's orphan rule, these serializations must be implemented in this crate. //! //! # Correctness //! //! Once format versions are implemented for the scanner database, //! `zebra_scan::Storage::database_format_version_in_code()` must be incremented //! each time the database format (column, serialization, etc) changes. use zebra_chain::{block::Height, transaction}; use crate::{FromDisk, IntoDisk}; use super::block::HEIGHT_DISK_BYTES; /// The fixed length of the scanning result. /// /// TODO: If the scanning result doesn't have a fixed length, either: /// - deserialize using internal length or end markers, /// - prefix it with a length, or /// - stop storing vectors of results on disk, instead store each result with a unique key. pub const SAPLING_SCANNING_RESULT_LENGTH: usize = 32; /// The type used in Zebra to store Sapling scanning keys. /// It can represent a full viewing key or an individual viewing key. pub type SaplingScanningKey = String; /// The type used in Zebra to store Sapling scanning results. pub type SaplingScannedResult = transaction::Hash; /// A database column family entry for a block scanned with a Sapling vieweing key. #[derive(Clone, Debug, Eq, PartialEq)] pub struct SaplingScannedDatabaseEntry { /// The database column family key. Must be unique for each scanning key and scanned block. pub index: SaplingScannedDatabaseIndex, /// The database column family value. pub value: Vec, } /// A database column family key for a block scanned with a Sapling vieweing key. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct SaplingScannedDatabaseIndex { /// The Sapling viewing key used to scan the block. pub sapling_key: SaplingScanningKey, /// The height of the block. pub height: Height, } impl SaplingScannedDatabaseIndex { /// The minimum value of a sapling scanned database index. /// This value is guarateed to be the minimum, and not correspond to a valid key. pub const fn min() -> Self { Self { // The empty string is the minimum value in RocksDB lexicographic order. sapling_key: String::new(), // Genesis is the minimum height, and never has valid shielded transfers. height: Height(0), } } /// The minimum value of a sapling scanned database index for `sapling_key`. /// This value is guarateed to be the minimum, and not correspond to a valid entry. pub fn min_for_key(sapling_key: &SaplingScanningKey) -> Self { Self { sapling_key: sapling_key.clone(), // Genesis is the minimum height, and never has valid shielded transfers. height: Height(0), } } /// The maximum value of a sapling scanned database index for `sapling_key`. /// This value is guarateed to be the maximum, and not correspond to a valid entry. pub fn max_for_key(sapling_key: &SaplingScanningKey) -> Self { Self { sapling_key: sapling_key.clone(), // The maximum height will never be mined - we'll increase it before that happens. height: Height::MAX, } } } impl IntoDisk for SaplingScanningKey { type Bytes = Vec; fn as_bytes(&self) -> Self::Bytes { SaplingScanningKey::as_bytes(self).to_vec() } } impl FromDisk for SaplingScanningKey { fn from_bytes(bytes: impl AsRef<[u8]>) -> Self { SaplingScanningKey::from_utf8(bytes.as_ref().to_vec()) .expect("only valid UTF-8 strings are written to the database") } } impl IntoDisk for SaplingScannedDatabaseIndex { type Bytes = Vec; fn as_bytes(&self) -> Self::Bytes { let mut bytes = Vec::new(); bytes.extend(self.sapling_key.as_bytes()); bytes.extend(self.height.as_bytes()); bytes } } impl FromDisk for SaplingScannedDatabaseIndex { fn from_bytes(bytes: impl AsRef<[u8]>) -> Self { let bytes = bytes.as_ref(); let (sapling_key, height) = bytes.split_at(bytes.len() - HEIGHT_DISK_BYTES); Self { sapling_key: SaplingScanningKey::from_bytes(sapling_key), height: Height::from_bytes(height), } } } impl IntoDisk for Vec { type Bytes = Vec; fn as_bytes(&self) -> Self::Bytes { self.iter() .flat_map(SaplingScannedResult::as_bytes) .collect() } } impl FromDisk for Vec { fn from_bytes(bytes: impl AsRef<[u8]>) -> Self { bytes .as_ref() .chunks(SAPLING_SCANNING_RESULT_LENGTH) .map(SaplingScannedResult::from_bytes) .collect() } }