203 lines
6.7 KiB
Rust
203 lines
6.7 KiB
Rust
//! Fixed integration test vectors for the scanner.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use color_eyre::Result;
|
|
|
|
use zcash_client_backend::{
|
|
encoding::{decode_extended_full_viewing_key, encode_extended_full_viewing_key},
|
|
proto::compact_formats::ChainMetadata,
|
|
};
|
|
use zcash_primitives::{
|
|
constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
|
|
sapling::Nullifier,
|
|
zip32::{DiversifiableFullViewingKey, ExtendedSpendingKey},
|
|
};
|
|
|
|
use zebra_chain::{
|
|
block::{Block, Height},
|
|
chain_tip::ChainTip,
|
|
parameters::Network,
|
|
serialization::ZcashDeserializeInto,
|
|
};
|
|
use zebra_state::{SaplingScannedResult, TransactionIndex};
|
|
|
|
use crate::{
|
|
scan::{block_to_compact, scan_block},
|
|
storage::db::tests::new_test_storage,
|
|
tests::{fake_block, mock_sapling_efvk, ZECPAGES_SAPLING_VIEWING_KEY},
|
|
};
|
|
|
|
/// This test:
|
|
/// - Creates a viewing key and a fake block containing a Sapling output decryptable by the key.
|
|
/// - Scans the block.
|
|
/// - Checks that the result contains the txid of the tx containing the Sapling output.
|
|
#[tokio::test]
|
|
async fn scanning_from_fake_generated_blocks() -> Result<()> {
|
|
let extsk = ExtendedSpendingKey::master(&[]);
|
|
let dfvk: DiversifiableFullViewingKey = extsk.to_diversifiable_full_viewing_key();
|
|
let nf = Nullifier([7; 32]);
|
|
|
|
let (block, sapling_tree_size) = fake_block(1u32.into(), nf, &dfvk, 1, true, Some(0));
|
|
|
|
assert_eq!(block.transactions.len(), 4);
|
|
|
|
let res = scan_block(Network::Mainnet, &block, sapling_tree_size, &[&dfvk]).unwrap();
|
|
|
|
// The response should have one transaction relevant to the key we provided.
|
|
assert_eq!(res.transactions().len(), 1);
|
|
|
|
// Check that the original block contains the txid in the scanning result.
|
|
assert!(block
|
|
.transactions
|
|
.iter()
|
|
.map(|tx| tx.hash().bytes_in_display_order())
|
|
.any(|txid| &txid == res.transactions()[0].txid.as_ref()));
|
|
|
|
// Check that the txid in the scanning result matches the third tx in the original block.
|
|
assert_eq!(
|
|
res.transactions()[0].txid.as_ref(),
|
|
&block.transactions[2].hash().bytes_in_display_order()
|
|
);
|
|
|
|
// The block hash of the response should be the same as the one provided.
|
|
assert_eq!(res.block_hash().0, block.hash().0);
|
|
|
|
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<()> {
|
|
// Parse the key from ZECpages
|
|
let efvk = decode_extended_full_viewing_key(
|
|
HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
|
|
ZECPAGES_SAPLING_VIEWING_KEY,
|
|
)
|
|
.unwrap();
|
|
|
|
// Build a vector of viewing keys `vks` to scan for.
|
|
let fvk = efvk.fvk;
|
|
let ivk = fvk.vk.ivk();
|
|
let ivks = vec![ivk];
|
|
|
|
let network = Network::Mainnet;
|
|
|
|
// Create a continuous chain of mainnet blocks from genesis
|
|
let blocks: Vec<Arc<Block>> = 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()) {
|
|
// We use a dummy size of the Sapling note commitment tree. We can't set the size to zero
|
|
// because the underlying scanning function would return
|
|
// `zcash_client_backeng::scanning::ScanError::TreeSizeUnknown`.
|
|
let sapling_commitment_tree_size = 1;
|
|
|
|
let orchard_commitment_tree_size = 0;
|
|
|
|
let chain_metadata = ChainMetadata {
|
|
sapling_commitment_tree_size,
|
|
orchard_commitment_tree_size,
|
|
};
|
|
|
|
let compact_block = block_to_compact(&block, chain_metadata);
|
|
|
|
let res = scan_block(network, &block, sapling_commitment_tree_size, &ivks)
|
|
.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(())
|
|
}
|
|
|
|
/// Creates a viewing key and a fake block containing a Sapling output decryptable by the key, scans
|
|
/// the block using the key, and adds the results to the database.
|
|
///
|
|
/// The purpose of this test is to check if our database and our scanning code are compatible.
|
|
#[test]
|
|
fn scanning_fake_blocks_store_key_and_results() -> Result<()> {
|
|
let network = Network::Mainnet;
|
|
|
|
// Generate a key
|
|
let efvk = mock_sapling_efvk(&[]);
|
|
let dfvk = efvk.to_diversifiable_full_viewing_key();
|
|
let key_to_be_stored = encode_extended_full_viewing_key("zxviews", &efvk);
|
|
|
|
// Create a database
|
|
let mut storage = new_test_storage(network);
|
|
|
|
// Insert the generated key to the database
|
|
storage.add_sapling_key(&key_to_be_stored, None);
|
|
|
|
// Check key was added
|
|
assert_eq!(storage.sapling_keys_last_heights().len(), 1);
|
|
assert_eq!(
|
|
storage
|
|
.sapling_keys_last_heights()
|
|
.get(&key_to_be_stored)
|
|
.expect("height is stored")
|
|
.next()
|
|
.expect("height is not maximum"),
|
|
network.sapling_activation_height()
|
|
);
|
|
|
|
let nf = Nullifier([7; 32]);
|
|
|
|
let (block, sapling_tree_size) = fake_block(1u32.into(), nf, &dfvk, 1, true, Some(0));
|
|
|
|
let result = scan_block(Network::Mainnet, &block, sapling_tree_size, &[&dfvk]).unwrap();
|
|
|
|
// The response should have one transaction relevant to the key we provided.
|
|
assert_eq!(result.transactions().len(), 1);
|
|
|
|
let result =
|
|
SaplingScannedResult::from_bytes_in_display_order(*result.transactions()[0].txid.as_ref());
|
|
|
|
// Add result to database
|
|
storage.add_sapling_results(
|
|
&key_to_be_stored,
|
|
Height(1),
|
|
[(TransactionIndex::from_usize(0), result)].into(),
|
|
);
|
|
|
|
// Check the result was added
|
|
assert_eq!(
|
|
storage.sapling_results(&key_to_be_stored).get(&Height(1)),
|
|
Some(&vec![result])
|
|
);
|
|
|
|
Ok(())
|
|
}
|