Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a scan_block function to use across scanning tasks #7994

Merged
merged 8 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions zebra-chain/src/primitives/zcash_primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,3 +336,12 @@ pub(crate) fn transparent_output_address(
None => None,
}
}

impl From<Network> for zcash_primitives::consensus::Network {
fn from(network: Network) -> Self {
match network {
Network::Mainnet => zcash_primitives::consensus::Network::MainNetwork,
Network::Testnet => zcash_primitives::consensus::Network::TestNetwork,
}
}
}
173 changes: 87 additions & 86 deletions zebra-scan/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@
use std::sync::Arc;

use zcash_client_backend::{
data_api::BlockMetadata,
data_api::ScannedBlock,
encoding::decode_extended_full_viewing_key,
proto::compact_formats::{
self as compact, ChainMetadata, CompactBlock, CompactSaplingOutput, CompactSaplingSpend,
CompactTx,
},
scanning::scan_block,
scanning::{ScanError, ScanningKey},
};
use zcash_note_encryption::Domain;
use zcash_primitives::{
block::BlockHash,
consensus::{BlockHeight, Network},
consensus::BlockHeight,
constants::{mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, SPENDING_KEY_GENERATOR},
memo::MemoBytes,
sapling::{
Expand All @@ -35,24 +35,22 @@ use rand::{rngs::OsRng, RngCore};

use ff::{Field, PrimeField};
use group::GroupEncoding;

use zebra_chain::{
block::Block,
chain_tip::ChainTip,
parameters::Network,
serialization::{ZcashDeserializeInto, ZcashSerialize},
transaction::{Hash, Transaction},
};

/// Prove that Zebra blocks can be scanned using the `zcash_client_backend::scanning::scan_block` function:
/// - Populates the state with a continuous chain of mainnet blocks from genesis.
/// - Scan the chain from the tip going backwards down to genesis.
/// - Verifies that no relevant transaction is found in the chain when scanning for a fake account's nullifier.
/// Scans a continuous chain of Mainnet blocks from tip to genesis.
///
/// Also verifies that no relevant transaction is found in the chain when scanning for a fake
/// account's nullifier.
#[tokio::test]
async fn scanning_from_populated_zebra_state() -> Result<()> {
let account = AccountId::from(12);
let vks: Vec<(&AccountId, &SaplingIvk)> = vec![];
let nf = Nullifier([7; 32]);

let network = zebra_chain::parameters::Network::default();
let network = Network::default();

// Create a continuous chain of mainnet blocks from genesis
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
Expand All @@ -75,41 +73,32 @@ async fn scanning_from_populated_zebra_state() -> Result<()> {
// TODO: Accessing the state database directly is ok in the tests, but not in production code.
// Use `Request::Block` if the code is copied to production.
while let Some(block) = db.block(height.into()) {
// We fake the sapling tree size to 1 because we are not in Sapling heights.
let sapling_tree_size = 1;
let orchard_tree_size = db
.orchard_tree_by_hash_or_height(height.into())
.expect("each state block must have a sapling tree")
.count();
// 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: 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"),
sapling_commitment_tree_size,
orchard_commitment_tree_size,
};

let compact_block = block_to_compact(block, chain_metadata);
let compact_block = block_to_compact(block.clone(), chain_metadata);

let res = scan_block(
&zcash_primitives::consensus::MainNetwork,
compact_block.clone(),
&vks[..],
&[(account, nf)],
None,
)
.unwrap();
let res =
scan_block::<SaplingIvk>(network, block, sapling_commitment_tree_size, &[]).unwrap();

transactions_found += res.transactions().len();
transactions_scanned += compact_block.vtx.len();
blocks_scanned += 1;

// scan backwards
if height.is_min() {
break;
}

// scan backwards
height = height.previous()?;
}

Expand Down Expand Up @@ -150,7 +139,7 @@ async fn scanning_from_fake_generated_blocks() -> Result<()> {
// The fake block function will have our transaction and a random one.
assert_eq!(cb.vtx.len(), 2);

let res = scan_block(
let res = zcash_client_backend::scanning::scan_block(
upbqdn marked this conversation as resolved.
Show resolved Hide resolved
&zcash_primitives::consensus::MainNetwork,
cb.clone(),
&vks[..],
Expand Down Expand Up @@ -185,12 +174,9 @@ async fn scanning_zecpages_from_populated_zebra_state() -> Result<()> {
)
.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;

Expand All @@ -213,54 +199,22 @@ async fn scanning_zecpages_from_populated_zebra_state() -> Result<()> {
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()
};
// 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_tree_size = db
.orchard_tree_by_hash_or_height(height.into())
.expect("each state block must have a orchard tree")
.count();
let orchard_commitment_tree_size = 0;

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"),
))
sapling_commitment_tree_size,
orchard_commitment_tree_size,
};

let compact_block = block_to_compact(block, chain_metadata);
let compact_block = block_to_compact(block.clone(), 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");
let res = scan_block(network, block, sapling_commitment_tree_size, &[&ivk])
.expect("scanning block for the ZECpages viewing key should work");

transactions_found += res.transactions().len();
transactions_scanned += compact_block.vtx.len();
Expand Down Expand Up @@ -322,7 +276,7 @@ async fn scanning_fake_blocks_store_key_and_results() -> Result<()> {
);

// Scan with our key
let res = scan_block(
let res = zcash_client_backend::scanning::scan_block(
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
&zcash_primitives::consensus::MainNetwork,
cb.clone(),
&vks[..],
Expand Down Expand Up @@ -448,18 +402,23 @@ fn fake_compact_block(

// Create a fake Note for the account
let mut rng = OsRng;
let rseed = generate_random_rseed(&Network::TestNetwork, height, &mut rng);
let rseed = generate_random_rseed(
&zcash_primitives::consensus::Network::TestNetwork,
height,
&mut rng,
);
let note = Note::from_parts(to, NoteValue::from_raw(value), rseed);
let encryptor = sapling_note_encryption::<_, Network>(
let encryptor = sapling_note_encryption::<_, zcash_primitives::consensus::Network>(
Some(dfvk.fvk().ovk),
note.clone(),
MemoBytes::empty(),
&mut rng,
);
let cmu = note.cmu().to_bytes().to_vec();
let ephemeral_key = SaplingDomain::<Network>::epk_bytes(encryptor.epk())
.0
.to_vec();
let ephemeral_key =
SaplingDomain::<zcash_primitives::consensus::Network>::epk_bytes(encryptor.epk())
.0
.to_vec();
let enc_ciphertext = encryptor.encrypt_note_plaintext();

// Create a fake CompactBlock containing the note
Expand Down Expand Up @@ -550,3 +509,45 @@ fn random_compact_tx(mut rng: impl RngCore) -> CompactTx {
ctx.outputs.push(cout);
ctx
}

/// Returns transactions belonging to any of the given [`ScanningKey`]s.
///
/// TODO:
/// - Remove the `sapling_tree_size` parameter or turn it into an `Option` once we have access to
/// Zebra's state, and we can retrieve the tree size ourselves.
/// - Add prior block metadata once we have access to Zebra's state.
fn scan_block<K: ScanningKey>(
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
upbqdn marked this conversation as resolved.
Show resolved Hide resolved
network: Network,
block: Arc<Block>,
sapling_tree_size: u32,
scanning_keys: &[&K],
) -> Result<ScannedBlock<K::Nf>, ScanError> {
// TODO: Implement a check that returns early when the block height is below the Sapling
// activation height.

let network: zcash_primitives::consensus::Network = network.into();

let chain_metadata = ChainMetadata {
sapling_commitment_tree_size: sapling_tree_size,
// Orchard is not supported at the moment so the tree size can be 0.
orchard_commitment_tree_size: 0,
upbqdn marked this conversation as resolved.
Show resolved Hide resolved
};

// Use a dummy `AccountId` as we don't use accounts yet.
let dummy_account = AccountId::from(0);

let scanning_keys: Vec<_> = scanning_keys
.iter()
.map(|key| (&dummy_account, key))
.collect();

zcash_client_backend::scanning::scan_block(
&network,
block_to_compact(block, chain_metadata),
&scanning_keys,
// Ignore whether notes are change from a viewer's own spends for now.
&[],
// Ignore previous blocks for now.
None,
)
}
Loading