test(scan): Add typed database format snapshots to the scanner (#8083)

* Refactor test data into functions

* Add a typed snapshot test for scanner storage

* Use standard hex serialization with SaplingScannedResult

* Simplify transaction::Hash hex serialization

* Sort HashMaps before snapshotting

* Add typed snapshot data
This commit is contained in:
teor 2023-12-13 08:34:59 +10:00 committed by GitHub
parent fa4c80f35a
commit 9ace6f8a4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 427 additions and 64 deletions

View File

@ -85,10 +85,9 @@ impl FromHex for Hash {
type Error = <[u8; 32] as FromHex>::Error; type Error = <[u8; 32] as FromHex>::Error;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> { fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let mut hash = <[u8; 32]>::from_hex(hex)?; let hash = <[u8; 32]>::from_hex(hex)?;
hash.reverse();
Ok(hash.into()) Ok(Self::from_bytes_in_display_order(&hash))
} }
} }
@ -148,12 +147,6 @@ impl ZcashDeserialize for Hash {
impl std::str::FromStr for Hash { impl std::str::FromStr for Hash {
type Err = SerializationError; type Err = SerializationError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut bytes = [0; 32]; Ok(Self::from_hex(s)?)
if hex::decode_to_slice(s, &mut bytes[..]).is_err() {
Err(SerializationError::Parse("hex decoding error"))
} else {
bytes.reverse();
Ok(Hash(bytes))
}
} }
} }

View File

@ -2,6 +2,7 @@
use std::{array::TryFromSliceError, io, num::TryFromIntError, str::Utf8Error}; use std::{array::TryFromSliceError, io, num::TryFromIntError, str::Utf8Error};
use hex::FromHexError;
use thiserror::Error; use thiserror::Error;
/// A serialization error. /// A serialization error.
@ -31,6 +32,10 @@ pub enum SerializationError {
#[error("CompactSize too large: {0}")] #[error("CompactSize too large: {0}")]
TryFromIntError(#[from] TryFromIntError), TryFromIntError(#[from] TryFromIntError),
/// A string was not valid hexadecimal.
#[error("string was not hex: {0}")]
FromHexError(#[from] FromHexError),
/// An error caused when validating a zatoshi `Amount` /// An error caused when validating a zatoshi `Amount`
#[error("input couldn't be parsed as a zatoshi `Amount`: {source}")] #[error("input couldn't be parsed as a zatoshi `Amount`: {source}")]
Amount { Amount {

View File

@ -25,6 +25,7 @@ proptest-impl = [
"proptest-derive", "proptest-derive",
"zebra-state/proptest-impl", "zebra-state/proptest-impl",
"zebra-chain/proptest-impl", "zebra-chain/proptest-impl",
"zebra-test",
"bls12_381", "bls12_381",
"ff", "ff",
"group", "group",
@ -63,6 +64,8 @@ jubjub = { version = "0.10.0", optional = true }
rand = { version = "0.8.5", optional = true } rand = { version = "0.8.5", optional = true }
zcash_note_encryption = { version = "0.4.0", optional = true } zcash_note_encryption = { version = "0.4.0", optional = true }
zebra-test = { path = "../zebra-test", version = "1.0.0-beta.31", optional = true }
[dev-dependencies] [dev-dependencies]
insta = { version = "1.33.0", features = ["ron", "redactions"] } insta = { version = "1.33.0", features = ["ron", "redactions"] }
@ -78,4 +81,4 @@ rand = "0.8.5"
zcash_note_encryption = "0.4.0" zcash_note_encryption = "0.4.0"
zebra-state = { path = "../zebra-state", version = "1.0.0-beta.31", features = ["proptest-impl"] } zebra-state = { path = "../zebra-state", version = "1.0.0-beta.31", features = ["proptest-impl"] }
zebra-test = { path = "../zebra-test" } zebra-test = { path = "../zebra-test", version = "1.0.0-beta.31" }

View File

@ -19,8 +19,8 @@ pub use zebra_state::{
pub mod sapling; pub mod sapling;
#[cfg(test)] #[cfg(any(test, feature = "proptest-impl"))]
mod tests; pub mod tests;
/// The directory name used to distinguish the scanner database from Zebra's other databases or /// The directory name used to distinguish the scanner database from Zebra's other databases or
/// flat files. /// flat files.

View File

@ -1,3 +1,58 @@
//! General scanner database tests. //! General scanner database tests.
use std::sync::Arc;
use zebra_chain::{
block::{Block, Height},
parameters::Network::{self, *},
serialization::ZcashDeserializeInto,
};
use zebra_state::TransactionIndex;
use crate::{
storage::Storage,
tests::{FAKE_SAPLING_VIEWING_KEY, ZECPAGES_SAPLING_VIEWING_KEY},
Config,
};
#[cfg(test)]
mod snapshot; mod snapshot;
/// Returns an empty `Storage` suitable for testing.
pub fn new_test_storage(network: Network) -> Storage {
Storage::new(&Config::ephemeral(), network)
}
/// Add fake keys to `storage` for testing purposes.
pub fn add_fake_keys(storage: &mut Storage) {
// Snapshot a birthday that is automatically set to activation height
storage.add_sapling_key(&ZECPAGES_SAPLING_VIEWING_KEY.to_string(), None);
// Snapshot a birthday above activation height
storage.add_sapling_key(&FAKE_SAPLING_VIEWING_KEY.to_string(), Height(1_000_000));
}
/// Add fake results to `storage` for testing purposes.
pub fn add_fake_results(storage: &mut Storage, network: Network, height: Height) {
let blocks = match network {
Mainnet => &*zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS,
Testnet => &*zebra_test::vectors::CONTINUOUS_TESTNET_BLOCKS,
};
let block: Arc<Block> = blocks
.get(&height.0)
.expect("block height has test data")
.zcash_deserialize_into()
.expect("test data deserializes");
// Fake results from the first few blocks
storage.add_sapling_results(
&ZECPAGES_SAPLING_VIEWING_KEY.to_string(),
height,
block
.transactions
.iter()
.enumerate()
.map(|(index, tx)| (TransactionIndex::from_usize(index), tx.hash().into()))
.collect(),
);
}

View File

@ -24,40 +24,39 @@
//! Due to `serde` limitations, some object types can't be represented exactly, //! Due to `serde` limitations, some object types can't be represented exactly,
//! so RON uses the closest equivalent structure. //! so RON uses the closest equivalent structure.
use std::{collections::BTreeMap, sync::Arc}; use std::collections::BTreeMap;
use itertools::Itertools;
use zebra_chain::{ use zebra_chain::{
block::{Block, Height}, block::Height,
parameters::Network::{self, *}, parameters::Network::{self, *},
serialization::ZcashDeserializeInto,
}; };
use zebra_state::{RawBytes, ReadDisk, TransactionIndex, KV}; use zebra_state::{RawBytes, ReadDisk, SaplingScannedDatabaseIndex, TransactionLocation, KV};
use crate::{ use crate::storage::{db::ScannerDb, Storage};
storage::{db::ScannerDb, Storage},
tests::{FAKE_SAPLING_VIEWING_KEY, ZECPAGES_SAPLING_VIEWING_KEY},
Config,
};
/// Snapshot test for RocksDB column families, and their key-value data. /// Snapshot test for:
/// - RocksDB column families, and their raw key-value data, and
/// - typed scanner result data using high-level storage methods.
/// ///
/// These snapshots contain the `default` column family, but it is not used by Zebra. /// These snapshots contain the `default` column family, but it is not used by Zebra.
#[test] #[test]
fn test_raw_rocksdb_column_families() { fn test_database_format() {
let _init_guard = zebra_test::init(); let _init_guard = zebra_test::init();
test_raw_rocksdb_column_families_with_network(Mainnet); test_database_format_with_network(Mainnet);
test_raw_rocksdb_column_families_with_network(Testnet); test_database_format_with_network(Testnet);
} }
/// Snapshot raw column families for `network`. /// Snapshot raw and typed database formats for `network`.
/// ///
/// See [`test_raw_rocksdb_column_families`]. /// See [`test_database_format()`] for details.
fn test_raw_rocksdb_column_families_with_network(network: Network) { fn test_database_format_with_network(network: Network) {
let mut net_suffix = network.to_string(); let mut net_suffix = network.to_string();
net_suffix.make_ascii_lowercase(); net_suffix.make_ascii_lowercase();
let mut storage = Storage::new(&Config::ephemeral(), network); let mut storage = super::new_test_storage(network);
// Snapshot the column family names // Snapshot the column family names
let mut cf_names = storage.db.list_cf().expect("empty database is valid"); let mut cf_names = storage.db.list_cf().expect("empty database is valid");
@ -75,48 +74,32 @@ fn test_raw_rocksdb_column_families_with_network(network: Network) {
settings.set_snapshot_suffix("empty"); settings.set_snapshot_suffix("empty");
settings.bind(|| snapshot_raw_rocksdb_column_family_data(&storage.db, &cf_names)); settings.bind(|| snapshot_raw_rocksdb_column_family_data(&storage.db, &cf_names));
settings.bind(|| snapshot_typed_result_data(&storage));
// Snapshot a birthday that is automatically set to activation height super::add_fake_keys(&mut storage);
storage.add_sapling_key(&ZECPAGES_SAPLING_VIEWING_KEY.to_string(), None);
// Snapshot a birthday above activation height
storage.add_sapling_key(&FAKE_SAPLING_VIEWING_KEY.to_string(), Height(1_000_000));
// Assert that the key format doesn't change.
settings.set_snapshot_suffix(format!("{net_suffix}_keys")); settings.set_snapshot_suffix(format!("{net_suffix}_keys"));
settings.bind(|| snapshot_raw_rocksdb_column_family_data(&storage.db, &cf_names)); settings.bind(|| snapshot_raw_rocksdb_column_family_data(&storage.db, &cf_names));
settings.bind(|| snapshot_typed_result_data(&storage));
// Snapshot raw database data for: // Snapshot raw database data for:
// - mainnet and testnet // - mainnet and testnet
// - genesis, block 1, and block 2 // - genesis, block 1, and block 2
let blocks = match network { //
Mainnet => &*zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS, // We limit the number of blocks, because we create 2 snapshots per block, one for each network.
Testnet => &*zebra_test::vectors::CONTINUOUS_TESTNET_BLOCKS,
};
// We limit the number of blocks, because the serialized data is a few kilobytes per block.
for height in 0..=2 { for height in 0..=2 {
let block: Arc<Block> = blocks super::add_fake_results(&mut storage, network, Height(height));
.get(&height)
.expect("block height has test data")
.zcash_deserialize_into()
.expect("test data deserializes");
// Fake results from the first few blocks
storage.add_sapling_results(
&ZECPAGES_SAPLING_VIEWING_KEY.to_string(),
Height(height),
block
.transactions
.iter()
.enumerate()
.map(|(index, tx)| (TransactionIndex::from_usize(index), tx.hash().into()))
.collect(),
);
let mut settings = insta::Settings::clone_current(); let mut settings = insta::Settings::clone_current();
settings.set_snapshot_suffix(format!("{net_suffix}_{height}")); settings.set_snapshot_suffix(format!("{net_suffix}_{height}"));
// Assert that the result format doesn't change.
settings.bind(|| snapshot_raw_rocksdb_column_family_data(&storage.db, &cf_names)); settings.bind(|| snapshot_raw_rocksdb_column_family_data(&storage.db, &cf_names));
settings.bind(|| snapshot_typed_result_data(&storage));
} }
// TODO: add an empty marker result after PR #8080 merges
} }
/// Snapshot the data in each column family, using `cargo insta` and RON serialization. /// Snapshot the data in each column family, using `cargo insta` and RON serialization.
@ -150,15 +133,70 @@ fn snapshot_raw_rocksdb_column_family_data(db: &ScannerDb, original_cf_names: &[
if cf_name == "default" { if cf_name == "default" {
assert_eq!(cf_data.len(), 0, "default column family is never used"); assert_eq!(cf_data.len(), 0, "default column family is never used");
} else if cf_data.is_empty() { } else if cf_data.is_empty() {
// distinguish column family names from empty column families // Distinguish column family names from empty column families
empty_column_families.push(format!("{cf_name}: no entries")); empty_column_families.push(format!("{cf_name}: no entries"));
} else { } else {
// The note commitment tree snapshots will change if the trees do not have cached roots. // Make sure the raw format doesn't accidentally change.
// But we expect them to always have cached roots,
// because those roots are used to populate the anchor column families.
insta::assert_ron_snapshot!(format!("{cf_name}_raw_data"), cf_data); insta::assert_ron_snapshot!(format!("{cf_name}_raw_data"), cf_data);
} }
} }
insta::assert_ron_snapshot!("empty_column_families", empty_column_families); insta::assert_ron_snapshot!("empty_column_families", empty_column_families);
} }
/// Snapshot typed scanner result data using high-level storage methods,
/// using `cargo insta` and RON serialization.
fn snapshot_typed_result_data(storage: &Storage) {
// TODO: snapshot the latest scanned heights after PR #8080 merges
//insta::assert_ron_snapshot!("latest_heights", latest_scanned_heights);
// Make sure the typed key format doesn't accidentally change.
//
// TODO: update this after PR #8080
let sapling_keys_and_birthday_heights = storage.sapling_keys();
// HashMap has an unstable order across Rust releases, so we need to sort it here.
insta::assert_ron_snapshot!(
"sapling_keys",
sapling_keys_and_birthday_heights,
{
"." => insta::sorted_redaction()
}
);
// HashMap has an unstable order across Rust releases, so we need to sort it here as well.
for (key_index, (sapling_key, _birthday_height)) in sapling_keys_and_birthday_heights
.iter()
.sorted()
.enumerate()
{
let sapling_results = storage.sapling_results(sapling_key);
// Check internal database method consistency
for (height, results) in sapling_results.iter() {
let sapling_index_and_results =
storage.sapling_results_for_key_and_height(sapling_key, *height);
// The list of results for each height must match the results queried by that height.
let sapling_results_for_height: Vec<_> = sapling_index_and_results
.values()
.flatten()
.cloned()
.collect();
assert_eq!(results, &sapling_results_for_height);
for (index, result) in sapling_index_and_results {
let index = SaplingScannedDatabaseIndex {
sapling_key: sapling_key.clone(),
tx_loc: TransactionLocation::from_parts(*height, index),
};
// The result for each index must match the result queried by that index.
let sapling_result_for_index = storage.sapling_result_for_index(&index);
assert_eq!(result, sapling_result_for_index);
}
}
// Make sure the typed result format doesn't accidentally change.
insta::assert_ron_snapshot!(format!("sapling_key_{key_index}_results"), sapling_results);
}
}

View File

@ -0,0 +1,10 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(0): [
SaplingScannedResult("c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb"),
],
Height(419199): [],
}

View File

@ -0,0 +1,13 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(0): [
SaplingScannedResult("c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb"),
],
Height(1): [
SaplingScannedResult("851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609"),
],
Height(419199): [],
}

View File

@ -0,0 +1,16 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(0): [
SaplingScannedResult("c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb"),
],
Height(1): [
SaplingScannedResult("851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609"),
],
Height(2): [
SaplingScannedResult("8974d08d1c5f9c860d8b629d582a56659a4a1dcb2b5f98a25a5afcc2a784b0f4"),
],
Height(419199): [],
}

View File

@ -0,0 +1,7 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(419199): [],
}

View File

@ -0,0 +1,10 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(0): [
SaplingScannedResult("c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb"),
],
Height(279999): [],
}

View File

@ -0,0 +1,13 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(0): [
SaplingScannedResult("c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb"),
],
Height(1): [
SaplingScannedResult("f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75"),
],
Height(279999): [],
}

View File

@ -0,0 +1,16 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(0): [
SaplingScannedResult("c4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb"),
],
Height(1): [
SaplingScannedResult("f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75"),
],
Height(2): [
SaplingScannedResult("5822c0532da8a008259ac39933d3210e508c17e3ba21d2b2c428785efdccb3d5"),
],
Height(279999): [],
}

View File

@ -0,0 +1,7 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(279999): [],
}

View File

@ -0,0 +1,7 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(999999): [],
}

View File

@ -0,0 +1,7 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(999999): [],
}

View File

@ -0,0 +1,7 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(999999): [],
}

View File

@ -0,0 +1,7 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(999999): [],
}

View File

@ -0,0 +1,7 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(999999): [],
}

View File

@ -0,0 +1,7 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(999999): [],
}

View File

@ -0,0 +1,7 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(999999): [],
}

View File

@ -0,0 +1,7 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_results
---
{
Height(999999): [],
}

View File

@ -0,0 +1,5 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_keys_and_birthday_heights
---
{}

View File

@ -0,0 +1,8 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_keys_and_birthday_heights
---
{
"zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz": Height(0),
"zxviewsfake": Height(1000000),
}

View File

@ -0,0 +1,8 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_keys_and_birthday_heights
---
{
"zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz": Height(0),
"zxviewsfake": Height(1000000),
}

View File

@ -0,0 +1,8 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_keys_and_birthday_heights
---
{
"zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz": Height(0),
"zxviewsfake": Height(1000000),
}

View File

@ -0,0 +1,8 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_keys_and_birthday_heights
---
{
"zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz": Height(419200),
"zxviewsfake": Height(1000000),
}

View File

@ -0,0 +1,8 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_keys_and_birthday_heights
---
{
"zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz": Height(0),
"zxviewsfake": Height(1000000),
}

View File

@ -0,0 +1,8 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_keys_and_birthday_heights
---
{
"zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz": Height(0),
"zxviewsfake": Height(1000000),
}

View File

@ -0,0 +1,8 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_keys_and_birthday_heights
---
{
"zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz": Height(0),
"zxviewsfake": Height(1000000),
}

View File

@ -0,0 +1,8 @@
---
source: zebra-scan/src/storage/db/tests/snapshot.rs
expression: sapling_keys_and_birthday_heights
---
{
"zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz": Height(280000),
"zxviewsfake": Height(1000000),
}

View File

@ -7,6 +7,9 @@
//! `zebra_scan::Storage::database_format_version_in_code()` must be incremented //! `zebra_scan::Storage::database_format_version_in_code()` must be incremented
//! each time the database format (column, serialization, etc) changes. //! each time the database format (column, serialization, etc) changes.
use std::fmt;
use hex::{FromHex, ToHex};
use zebra_chain::{block::Height, transaction}; use zebra_chain::{block::Height, transaction};
use crate::{FromDisk, IntoDisk, TransactionLocation}; use crate::{FromDisk, IntoDisk, TransactionLocation};
@ -27,9 +30,58 @@ pub type SaplingScanningKey = String;
/// ///
/// Currently contains a TXID in "display order", which is big-endian byte order following the u256 /// Currently contains a TXID in "display order", which is big-endian byte order following the u256
/// convention set by Bitcoin and zcashd. /// convention set by Bitcoin and zcashd.
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Default))] #[cfg_attr(
pub struct SaplingScannedResult([u8; 32]); any(test, feature = "proptest-impl"),
derive(Arbitrary, Default, serde::Serialize, serde::Deserialize)
)]
pub struct SaplingScannedResult(
#[cfg_attr(any(test, feature = "proptest-impl"), serde(with = "hex"))] [u8; 32],
);
impl fmt::Display for SaplingScannedResult {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.encode_hex::<String>())
}
}
impl fmt::Debug for SaplingScannedResult {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("SaplingScannedResult")
.field(&self.encode_hex::<String>())
.finish()
}
}
impl ToHex for &SaplingScannedResult {
fn encode_hex<T: FromIterator<char>>(&self) -> T {
self.bytes_in_display_order().encode_hex()
}
fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
self.bytes_in_display_order().encode_hex_upper()
}
}
impl ToHex for SaplingScannedResult {
fn encode_hex<T: FromIterator<char>>(&self) -> T {
(&self).encode_hex()
}
fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
(&self).encode_hex_upper()
}
}
impl FromHex for SaplingScannedResult {
type Error = <[u8; 32] as FromHex>::Error;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let result = <[u8; 32]>::from_hex(hex)?;
Ok(Self::from_bytes_in_display_order(result))
}
}
impl From<SaplingScannedResult> for transaction::Hash { impl From<SaplingScannedResult> for transaction::Hash {
fn from(scanned_result: SaplingScannedResult) -> Self { fn from(scanned_result: SaplingScannedResult) -> Self {