add(scan): Test the `RegisterKeys` scan service call (#8281)

* Impl generating continuous deserialized blocks

* Make `sapling_efvk_hrp` `pub`

* Don't wait for Sapling activation height in tests

* Set the sleep interval for scan service to 10 secs

* Simplify `sapling_key_to_scan_block_keys`

* Enable mocking Sapling scanning keys for Testnet

* Test the `RegisterKeys` scan service call

* Enable `shielded-scan` for `zebra-chain`

* Use an ephemeral database so results don't persist

* Don't generate blocks when mocking the state

* Improve error messages

* Simplify seeding mocked Sapling viewing keys

* Apply suggestions from code review

Co-authored-by: Arya <aryasolhi@gmail.com>

* Use a manual iterator over `Network`

---------

Co-authored-by: Arya <aryasolhi@gmail.com>
This commit is contained in:
Marek 2024-02-20 00:45:38 +01:00 committed by GitHub
parent a8dcd98b6e
commit 663c57700b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 101 additions and 25 deletions

View File

@ -85,6 +85,12 @@ impl fmt::Display for Network {
}
impl Network {
/// Returns an iterator over [`Network`] variants.
pub fn iter() -> impl Iterator<Item = Self> {
// TODO: Use default values of `Testnet` variant when adding fields for #7845.
[Self::Mainnet, Self::Testnet].into_iter()
}
/// Get the default port associated to this network.
pub fn default_port(&self) -> u16 {
match self {

View File

@ -74,7 +74,7 @@ impl SaplingViewingKey {
impl Network {
/// Returns the human-readable prefix for an Zcash Sapling extended full viewing key
/// for this network.
fn sapling_efvk_hrp(&self) -> &'static str {
pub fn sapling_efvk_hrp(&self) -> &'static str {
if self.is_a_test_network() {
// Assume custom testnets have the same HRP
//

View File

@ -54,7 +54,7 @@ futures = "0.3.30"
zcash_client_backend = "0.10.0-rc.1"
zcash_primitives = "0.13.0-rc.1"
zebra-chain = { path = "../zebra-chain", version = "1.0.0-beta.34" }
zebra-chain = { path = "../zebra-chain", version = "1.0.0-beta.34", features = ["shielded-scan"] }
zebra-state = { path = "../zebra-state", version = "1.0.0-beta.34", features = ["shielded-scan"] }
zebra-node-services = { path = "../zebra-node-services", version = "1.0.0-beta.34", features = ["shielded-scan"] }
zebra-grpc = { path = "../zebra-grpc", version = "0.1.0-alpha.1" }
@ -75,7 +75,6 @@ zcash_note_encryption = { version = "0.4.0", optional = true }
zebra-test = { path = "../zebra-test", version = "1.0.0-beta.34", optional = true }
[dev-dependencies]
insta = { version = "1.33.0", features = ["ron", "redactions"] }
tokio = { version = "1.36.0", features = ["test-util"] }

View File

@ -21,7 +21,6 @@ use zcash_client_backend::{
scanning::{ScanError, ScanningKey},
};
use zcash_primitives::{
constants::*,
sapling::SaplingIvk,
zip32::{AccountId, DiversifiableFullViewingKey, Scope},
};
@ -61,8 +60,9 @@ const INITIAL_WAIT: Duration = Duration::from_secs(15);
/// The amount of time between checking for new blocks and starting new scans.
///
/// This is just under half the target block interval.
pub const CHECK_INTERVAL: Duration = Duration::from_secs(30);
/// TODO: The current value is set to 10 so that tests don't sleep for too long and finish faster.
/// Set it to 30 after #8250 gets addressed or remove this const completely in the refactor.
pub const CHECK_INTERVAL: Duration = Duration::from_secs(10);
/// We log an info log with progress after this many blocks.
const INFO_LOG_INTERVAL: u32 = 10_000;
@ -81,6 +81,7 @@ pub async fn start(
info!(?network, "starting scan task");
// Do not scan and notify if we are below sapling activation height.
#[cfg(not(test))]
wait_for_height(
sapling_activation_height,
"Sapling activation",
@ -405,19 +406,11 @@ pub fn scan_block<K: ScanningKey>(
// performance: stop returning both the dfvk and ivk for the same key
// TODO: use `ViewingKey::parse` from zebra-chain instead
pub fn sapling_key_to_scan_block_keys(
sapling_key: &SaplingScanningKey,
key: &SaplingScanningKey,
network: Network,
) -> Result<(Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>), Report> {
let hrp = if network.is_a_test_network() {
// Assume custom testnets have the same HRP
//
// TODO: add the regtest HRP here
testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY
} else {
mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY
};
let efvk = decode_extended_full_viewing_key(hrp, sapling_key).map_err(|e| eyre!(e))?;
let efvk =
decode_extended_full_viewing_key(network.sapling_efvk_hrp(), key).map_err(|e| eyre!(e))?;
// Just return all the keys for now, so we can be sure our code supports them.
let dfvk = efvk.to_diversifiable_full_viewing_key();

View File

@ -18,7 +18,8 @@ async fn scan_task_processes_messages_correctly() -> Result<(), Report> {
// Send some keys to be registered
let num_keys = 10;
let sapling_keys = mock_sapling_scanning_keys(num_keys.try_into().expect("should fit in u8"));
let sapling_keys =
mock_sapling_scanning_keys(num_keys.try_into().expect("should fit in u8"), network);
let sapling_keys_with_birth_heights: Vec<(String, Option<u32>)> =
sapling_keys.into_iter().zip((0..).map(Some)).collect();
mock_scan_task.register_keys(sapling_keys_with_birth_heights.clone())?;
@ -60,7 +61,7 @@ async fn scan_task_processes_messages_correctly() -> Result<(), Report> {
// Check that keys can't be overridden.
let sapling_keys = mock_sapling_scanning_keys(20);
let sapling_keys = mock_sapling_scanning_keys(20, network);
let sapling_keys_with_birth_heights: Vec<(String, Option<u32>)> = sapling_keys
.clone()
.into_iter()
@ -87,7 +88,7 @@ async fn scan_task_processes_messages_correctly() -> Result<(), Report> {
// Check that it removes keys correctly
let sapling_keys = mock_sapling_scanning_keys(30);
let sapling_keys = mock_sapling_scanning_keys(30, network);
let done_rx = mock_scan_task.remove_keys(&sapling_keys)?;
let (new_keys, _new_results_senders) =

View File

@ -1,7 +1,7 @@
//! Tests for ScanService.
use tokio::sync::mpsc::error::TryRecvError;
use tower::{Service, ServiceExt};
use tower::{Service, ServiceBuilder, ServiceExt};
use color_eyre::{eyre::eyre, Result};
@ -12,7 +12,8 @@ use zebra_state::TransactionIndex;
use crate::{
service::{scan_task::ScanTaskCommand, ScanService},
storage::db::tests::{fake_sapling_results, new_test_storage},
tests::ZECPAGES_SAPLING_VIEWING_KEY,
tests::{mock_sapling_scanning_keys, ZECPAGES_SAPLING_VIEWING_KEY},
Config,
};
/// Tests that keys are deleted correctly
@ -254,3 +255,78 @@ pub async fn scan_service_get_results_for_key_correctly() -> Result<()> {
Ok(())
}
/// Tests that the scan service registers keys correctly.
#[tokio::test]
pub async fn scan_service_registers_keys_correctly() -> Result<()> {
for network in Network::iter() {
scan_service_registers_keys_correctly_for(network).await?;
}
Ok(())
}
async fn scan_service_registers_keys_correctly_for(network: Network) -> Result<()> {
// Mock the state.
let (state, _, _, chain_tip_change) = zebra_state::populated_state(vec![], network).await;
// Instantiate the scan service.
let mut scan_service = ServiceBuilder::new()
.buffer(2)
.service(ScanService::new(&Config::ephemeral(), network, state, chain_tip_change).await);
// Mock three Sapling keys.
let mocked_keys = mock_sapling_scanning_keys(3, network);
// Add birth heights to the mocked keys.
let keys_to_register: Vec<_> = mocked_keys
.clone()
.into_iter()
.zip((0u32..).map(Some))
.collect();
// Register the first key.
match scan_service
.ready()
.await
.map_err(|err| eyre!(err))?
.call(Request::RegisterKeys(keys_to_register[..1].to_vec()))
.await
.map_err(|err| eyre!(err))?
{
Response::RegisteredKeys(registered_keys) => {
// The key should be registered.
assert_eq!(
registered_keys,
mocked_keys[..1],
"response should match newly registered key"
);
}
_ => panic!("scan service should have responded with the `RegisteredKeys` response"),
}
// Try registering all three keys.
match scan_service
.ready()
.await
.map_err(|err| eyre!(err))?
.call(Request::RegisterKeys(keys_to_register))
.await
.map_err(|err| eyre!(err))?
{
Response::RegisteredKeys(registered_keys) => {
// Only the last two keys should be registered in this service call since the first one
// was registered in the previous call.
assert_eq!(
registered_keys,
mocked_keys[1..3],
"response should match newly registered keys"
);
}
_ => panic!("scan service should have responded with the `RegisteredKeys` response"),
}
Ok(())
}

View File

@ -37,6 +37,7 @@ use zebra_chain::{
amount::{Amount, NegativeAllowed},
block::{self, merkle, Block, Header, Height},
fmt::HexDebug,
parameters::Network,
primitives::{redjubjub, Groth16Proof},
sapling::{self, PerSpendAnchor, Spend, TransferData},
serialization::AtLeastOne,
@ -55,16 +56,16 @@ pub const ZECPAGES_SAPLING_VIEWING_KEY: &str = "zxviews1q0duytgcqqqqpqre26wkl45g
/// A fake viewing key in an incorrect format.
pub const FAKE_SAPLING_VIEWING_KEY: &str = "zxviewsfake";
/// Generates `num_keys` of [`SaplingScanningKey`]s for tests.
/// Generates `num_keys` of [`SaplingScanningKey`]s for tests for the given [`Network`].
///
/// The keys are seeded only from their index in the returned `Vec`, so repeated calls return same
/// keys at a particular index.
pub fn mock_sapling_scanning_keys(num_keys: u8) -> Vec<SaplingScanningKey> {
pub fn mock_sapling_scanning_keys(num_keys: u8, network: Network) -> Vec<SaplingScanningKey> {
let mut keys: Vec<SaplingScanningKey> = vec![];
for seed in 0..num_keys {
keys.push(encode_extended_full_viewing_key(
"zxviews",
network.sapling_efvk_hrp(),
&mock_sapling_efvk(&[seed]),
));
}