From e5e89ec549c57325c26cbf2cc8478e1b2db9d65c Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Wed, 8 Nov 2023 20:46:47 -0300 Subject: [PATCH] poc(scanner): add a populated state test for ZECpages viewing key (#7916) * get started with the blockchain scanner poc * rustfmt * fix the tests * Reads blocks from db * Adds conversion functions * scans blocks and counts transactions * fix bug * split into 2 tests * add duplicated dependencies to deny.toml * upgrade zebra-scanner version * try removing ecc duplicated dependencies * try fix deny.toml * remove unintended paste from deny.toml * remove duplicated code from the other test * remove strict version of `zcash_primitives` crate * change description * remove feture * remove tokio features * change lib doc * add more documentation * change expect * do not use default in compact block creation * more docs * add more checks to test * remove zebra-consensus dependency * move all deps to dev-deps * change crate version * rename crate to zebra-scan * lock file * add test for zecpages populated state * scans all cached blocks for zecpages viewing key expecting Ok results. * use test blocks * fixes test * fix expect messages * Discard changes to Cargo.lock * Discard changes to deny.toml --------- Co-authored-by: arya2 Co-authored-by: teor --- zebra-scan/src/tests.rs | 117 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/zebra-scan/src/tests.rs b/zebra-scan/src/tests.rs index 19069e731..ec792f445 100644 --- a/zebra-scan/src/tests.rs +++ b/zebra-scan/src/tests.rs @@ -6,6 +6,8 @@ use std::sync::Arc; use zcash_client_backend::{ + data_api::BlockMetadata, + encoding::decode_extended_full_viewing_key, proto::compact_formats::{ self as compact, ChainMetadata, CompactBlock, CompactSaplingOutput, CompactSaplingSpend, CompactTx, @@ -16,7 +18,7 @@ use zcash_note_encryption::Domain; use zcash_primitives::{ block::BlockHash, consensus::{BlockHeight, Network}, - constants::SPENDING_KEY_GENERATOR, + constants::{mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, SPENDING_KEY_GENERATOR}, memo::MemoBytes, sapling::{ note_encryption::{sapling_note_encryption, SaplingDomain}, @@ -168,6 +170,119 @@ async fn scanning_from_fake_generated_blocks() -> Result<()> { Ok(()) } +/// Scan a populated state for the ZECpages viewing key. +/// This test is very similar to `scanning_from_populated_zebra_state` but with the ZECpages key. +/// There are no zechub transactions in the test data so we should get empty related transactions. +#[tokio::test] +async fn scanning_zecpages_from_populated_zebra_state() -> Result<()> { + /// The extended Sapling viewing key of [ZECpages](https://zecpages.com/boardinfo) + const ZECPAGES_VIEWING_KEY: &str = "zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz"; + + // Parse the key from ZECpages + let efvk = decode_extended_full_viewing_key( + HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + ZECPAGES_VIEWING_KEY, + ) + .unwrap(); + + let account = AccountId::from(1); + + // Build a vector of viewing keys `vks` to scan for. + let fvk = efvk.fvk; + let ivk = fvk.vk.ivk(); + let vks: Vec<(&AccountId, &SaplingIvk)> = vec![(&account, &ivk)]; + + let network = zebra_chain::parameters::Network::Mainnet; + + // Create a continuous chain of mainnet blocks from genesis + let blocks: Vec> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS + .iter() + .map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap()) + .collect(); + + // Create a populated state service. + let (_state_service, read_only_state_service, latest_chain_tip, _chain_tip_change) = + zebra_state::populated_state(blocks.clone(), network).await; + + let db = read_only_state_service.db(); + + // use the tip as starting height + let mut height = latest_chain_tip.best_tip_height().unwrap(); + + let mut transactions_found = 0; + let mut transactions_scanned = 0; + let mut blocks_scanned = 0; + while let Some(block) = db.block(height.into()) { + // zcash_client_backend doesn't support scanning the genesis block, but that's ok, because + // Sapling activates at height 419,200. So we'll never scan these blocks in production code. + let sapling_tree_size = if height.is_min() { + 1 + } else { + db.sapling_tree_by_hash_or_height(height.into()) + .expect("each state block must have a sapling tree") + .count() + }; + + let orchard_tree_size = db + .orchard_tree_by_hash_or_height(height.into()) + .expect("each state block must have a orchard tree") + .count(); + + let chain_metadata = ChainMetadata { + sapling_commitment_tree_size: sapling_tree_size + .try_into() + .expect("sapling position is limited to u32::MAX"), + orchard_commitment_tree_size: orchard_tree_size + .try_into() + .expect("orchard position is limited to u32::MAX"), + }; + + let block_metadata = if height.is_min() { + None + } else { + Some(BlockMetadata::from_parts( + height.previous()?.0.into(), + BlockHash(block.header.previous_block_hash.0), + db.sapling_tree_by_hash_or_height(block.header.previous_block_hash.into()) + .expect("each state block must have a sapling tree") + .count() + .try_into() + .expect("sapling position is limited to u32::MAX"), + )) + }; + + let compact_block = block_to_compact(block, chain_metadata); + + let res = scan_block( + &zcash_primitives::consensus::MainNetwork, + compact_block.clone(), + &vks[..], + &[], + block_metadata.as_ref(), + ) + .expect("scanning block for the ZECpages viewing key should work"); + + transactions_found += res.transactions().len(); + transactions_scanned += compact_block.vtx.len(); + blocks_scanned += 1; + + // scan backwards + if height.is_min() { + break; + } + height = height.previous()?; + } + + // make sure all blocks and transactions were scanned + assert_eq!(blocks_scanned, 11); + assert_eq!(transactions_scanned, 11); + + // no relevant transactions should be found + assert_eq!(transactions_found, 0); + + Ok(()) +} + /// Convert a zebra block and meta data into a compact block. fn block_to_compact(block: Arc, chain_metadata: ChainMetadata) -> CompactBlock { CompactBlock {