feat(scanner): Add a very basic RAM database to store keys and scan results (#7942)

* add a basic RAM database for the scanner

* specify a crate version for zebra-chain dependency

* add a type for sapling keys

* rename everything to reflect sapling

* change some storage methods
This commit is contained in:
Alfredo Garcia 2023-11-15 20:06:23 -03:00 committed by GitHub
parent 461c5aa680
commit 732ee01443
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 122 additions and 2 deletions

View File

@ -19,6 +19,7 @@ categories = ["cryptography::cryptocurrencies"]
# Production features that activate extra dependencies, or extra features in dependencies
[dependencies]
zebra-chain = { path = "../zebra-chain", version = "1.0.0-beta.31" }
[dev-dependencies]
@ -35,5 +36,4 @@ group = "0.13.0"
tokio = { version = "1.34.0", features = ["test-util"] }
zebra-state = { path = "../zebra-state" }
zebra-chain = { path = "../zebra-chain" }
zebra-test = { path = "../zebra-test" }

View File

@ -4,5 +4,7 @@
#![doc(html_logo_url = "https://zfnd.org/wp-content/uploads/2022/03/zebra-icon.png")]
#![doc(html_root_url = "https://docs.rs/zebra_scan")]
mod storage;
#[cfg(test)]
mod tests;

54
zebra-scan/src/storage.rs Normal file
View File

@ -0,0 +1,54 @@
//! Store viewing keys and results of the scan.
use std::collections::HashMap;
use zebra_chain::{block::Height, transaction::Hash};
/// 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;
/// Store key info and results of the scan.
#[allow(dead_code)]
pub struct Storage {
/// The sapling key and an optional birthday for it.
sapling_keys: HashMap<SaplingScanningKey, Option<Height>>,
/// The sapling key and the related transaction id.
sapling_results: HashMap<SaplingScanningKey, Vec<Hash>>,
}
#[allow(dead_code)]
impl Storage {
/// Create a new storage.
pub fn new() -> Self {
Self {
sapling_keys: HashMap::new(),
sapling_results: HashMap::new(),
}
}
/// Add a sapling key to the storage.
pub fn add_sapling_key(&mut self, key: SaplingScanningKey, birthday: Option<Height>) {
self.sapling_keys.insert(key, birthday);
}
/// Add a sapling result to the storage.
pub fn add_sapling_result(&mut self, key: SaplingScanningKey, txid: Hash) {
if let Some(results) = self.sapling_results.get_mut(&key) {
results.push(txid);
} else {
self.sapling_results.insert(key, vec![txid]);
}
}
/// Get the results of a sapling key.
pub fn get_sapling_results(&self, key: &str) -> Vec<Hash> {
self.sapling_results.get(key).cloned().unwrap_or_default()
}
/// Get all keys and their birthdays.
pub fn get_sapling_keys(&self) -> HashMap<String, Option<Height>> {
self.sapling_keys.clone()
}
}

View File

@ -39,7 +39,7 @@ use zebra_chain::{
block::Block,
chain_tip::ChainTip,
serialization::{ZcashDeserializeInto, ZcashSerialize},
transaction::Transaction,
transaction::{Hash, Transaction},
};
/// Prove that Zebra blocks can be scanned using the `zcash_client_backend::scanning::scan_block` function:
@ -283,6 +283,70 @@ async fn scanning_zecpages_from_populated_zebra_state() -> Result<()> {
Ok(())
}
/// In this test we generate a viewing key and manually add it to the database. Also we send results to the Storage database.
/// The purpose of this test is to check if our database and our scanning code are compatible.
#[tokio::test]
#[allow(deprecated)]
async fn scanning_fake_blocks_store_key_and_results() -> Result<()> {
// Generate a key
let account = AccountId::from(12);
let extsk = ExtendedSpendingKey::master(&[]);
// TODO: find out how to do it with `to_diversifiable_full_viewing_key` as `to_extended_full_viewing_key` is deprecated.
let extfvk = extsk.to_extended_full_viewing_key();
let dfvk: DiversifiableFullViewingKey = extsk.to_diversifiable_full_viewing_key();
let key_to_be_stored =
zcash_client_backend::encoding::encode_extended_full_viewing_key("zxviews", &extfvk);
// Create a database
let mut s = crate::storage::Storage::new();
// Insert the generated key to the database
s.add_sapling_key(key_to_be_stored.clone(), None);
// Check key was added
assert_eq!(s.get_sapling_keys().len(), 1);
assert_eq!(s.get_sapling_keys().get(&key_to_be_stored), Some(&None));
let vks: Vec<(&AccountId, &SaplingIvk)> = vec![];
let nf = Nullifier([7; 32]);
// Add key to fake block
let cb = fake_compact_block(
1u32.into(),
BlockHash([0; 32]),
nf,
&dfvk,
1,
false,
Some(0),
);
// Scan with our key
let res = scan_block(
&zcash_primitives::consensus::MainNetwork,
cb.clone(),
&vks[..],
&[(account, nf)],
None,
)
.unwrap();
// Get transaction hash
let found_transaction = res.transactions()[0].txid.as_ref();
let found_transaction_hash = Hash::from_bytes_in_display_order(found_transaction);
// Add result to database
s.add_sapling_result(key_to_be_stored.clone(), found_transaction_hash);
// Check the result was added
assert_eq!(
s.get_sapling_results(key_to_be_stored.as_str())[0],
found_transaction_hash
);
Ok(())
}
/// Convert a zebra block and meta data into a compact block.
fn block_to_compact(block: Arc<Block>, chain_metadata: ChainMetadata) -> CompactBlock {
CompactBlock {