add(scan): Implement `RegisterKeys` service request to register new keys (#8251)

* Refactor obtaining of activation heights

* Impl the `RegisterKeys` service request

* Mock viewing keys for tests

* Refactor tests

* Apply suggestions from code review

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

* Remove a redundant comment

I don't think we need to assume the genesis block doesn't contain
shielded data as the comment says.

* Avoid using a single-letter variable

* Refactor mocking Sapling scanning keys

---------

Co-authored-by: Arya <aryasolhi@gmail.com>
This commit is contained in:
Marek 2024-02-09 16:23:19 +01:00 committed by GitHub
parent 0c2c421bec
commit 6b8cbf9904
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 166 additions and 139 deletions

View File

@ -129,6 +129,13 @@ impl Network {
pub fn is_a_test_network(&self) -> bool { pub fn is_a_test_network(&self) -> bool {
*self != Network::Mainnet *self != Network::Mainnet
} }
/// Returns the Sapling activation height for this network.
pub fn sapling_activation_height(self) -> Height {
super::NetworkUpgrade::Sapling
.activation_height(self)
.expect("Sapling activation height needs to be set")
}
} }
impl FromStr for Network { impl FromStr for Network {

View File

@ -14,8 +14,8 @@ pub enum Request {
/// TODO: Accept `KeyHash`es and return key hashes that are registered /// TODO: Accept `KeyHash`es and return key hashes that are registered
CheckKeyHashes(Vec<()>), CheckKeyHashes(Vec<()>),
/// TODO: Accept `ViewingKeyWithHash`es and return Ok(()) if successful or an error /// Submits viewing keys with their optional birth-heights for scanning.
RegisterKeys(Vec<()>), RegisterKeys(Vec<(String, Option<u32>)>),
/// Deletes viewing keys and their results from the database. /// Deletes viewing keys and their results from the database.
DeleteKeys(Vec<String>), DeleteKeys(Vec<String>),

View File

@ -10,23 +10,28 @@ use zebra_chain::{block::Height, transaction::Hash};
#[derive(Debug)] #[derive(Debug)]
/// Response types for `zebra_scan::service::ScanService` /// Response types for `zebra_scan::service::ScanService`
pub enum Response { pub enum Response {
/// Response to the `Info` request /// Response to the [`Info`](super::request::Request::Info) request
Info { Info {
/// The minimum sapling birthday height for the shielded scanner /// The minimum sapling birthday height for the shielded scanner
min_sapling_birthday_height: Height, min_sapling_birthday_height: Height,
}, },
/// Response to Results request /// Response to [`RegisterKeys`](super::request::Request::RegisterKeys) request
///
/// Contains the keys that the scanner accepted.
RegisteredKeys(Vec<String>),
/// Response to [`Results`](super::request::Request::Results) request
/// ///
/// We use the nested `BTreeMap` so we don't repeat any piece of response data. /// We use the nested `BTreeMap` so we don't repeat any piece of response data.
Results(BTreeMap<String, BTreeMap<Height, Vec<Hash>>>), Results(BTreeMap<String, BTreeMap<Height, Vec<Hash>>>),
/// Response to DeleteKeys request /// Response to [`DeleteKeys`](super::request::Request::DeleteKeys) request
DeletedKeys, DeletedKeys,
/// Response to ClearResults request /// Response to [`ClearResults`](super::request::Request::ClearResults) request
ClearedResults, ClearedResults,
/// Response to SubscribeResults request /// Response to `SubscribeResults` request
SubscribeResults(mpsc::Receiver<Arc<Hash>>), SubscribeResults(mpsc::Receiver<Arc<Hash>>),
} }

View File

@ -91,7 +91,7 @@ impl Service<Request> for ScanService {
return async move { return async move {
Ok(Response::Info { Ok(Response::Info {
min_sapling_birthday_height: db.min_sapling_birthday_height(), min_sapling_birthday_height: db.network().sapling_activation_height(),
}) })
} }
.boxed(); .boxed();
@ -101,10 +101,15 @@ impl Service<Request> for ScanService {
// TODO: check that these entries exist in db // TODO: check that these entries exist in db
} }
Request::RegisterKeys(_viewing_key_with_hashes) => { Request::RegisterKeys(keys) => {
// TODO: let mut scan_task = self.scan_task.clone();
// - add these keys as entries in db
// - send new keys to scan task return async move {
Ok(Response::RegisteredKeys(
scan_task.register_keys(keys)?.await?,
))
}
.boxed();
} }
Request::DeleteKeys(keys) => { Request::DeleteKeys(keys) => {

View File

@ -12,9 +12,11 @@ use color_eyre::{eyre::eyre, Report};
use tokio::sync::oneshot; use tokio::sync::oneshot;
use zcash_primitives::{sapling::SaplingIvk, zip32::DiversifiableFullViewingKey}; use zcash_primitives::{sapling::SaplingIvk, zip32::DiversifiableFullViewingKey};
use zebra_chain::{block::Height, transaction::Transaction}; use zebra_chain::{block::Height, parameters::Network, transaction::Transaction};
use zebra_state::SaplingScanningKey; use zebra_state::SaplingScanningKey;
use crate::scan::sapling_key_to_scan_block_keys;
use super::ScanTask; use super::ScanTask;
#[derive(Debug)] #[derive(Debug)]
@ -23,10 +25,9 @@ pub enum ScanTaskCommand {
/// Start scanning for new viewing keys /// Start scanning for new viewing keys
RegisterKeys { RegisterKeys {
/// New keys to start scanning for /// New keys to start scanning for
keys: HashMap< keys: Vec<(String, Option<u32>)>,
SaplingScanningKey, /// Returns the set of keys the scanner accepted.
(Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>, Height), rsp_tx: oneshot::Sender<Vec<SaplingScanningKey>>,
>,
}, },
/// Stop scanning for deleted viewing keys /// Stop scanning for deleted viewing keys
@ -61,11 +62,13 @@ impl ScanTask {
SaplingScanningKey, SaplingScanningKey,
(Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>), (Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>),
>, >,
network: Network,
) -> Result< ) -> Result<
HashMap<SaplingScanningKey, (Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>, Height)>, HashMap<SaplingScanningKey, (Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>, Height)>,
Report, Report,
> { > {
let mut new_keys = HashMap::new(); let mut new_keys = HashMap::new();
let sapling_activation_height = network.sapling_activation_height();
loop { loop {
let cmd = match cmd_receiver.try_recv() { let cmd = match cmd_receiver.try_recv() {
@ -79,25 +82,44 @@ impl ScanTask {
}; };
match cmd { match cmd {
ScanTaskCommand::RegisterKeys { keys } => { ScanTaskCommand::RegisterKeys { keys, rsp_tx } => {
// Determine what keys we pass to the scanner.
let keys: Vec<_> = keys let keys: Vec<_> = keys
.into_iter() .into_iter()
.filter(|(key, _)| { .filter_map(|key| {
!parsed_keys.contains_key(key) || new_keys.contains_key(key) // Don't accept keys that:
// 1. the scanner already has, and
// 2. were already submitted.
if parsed_keys.contains_key(&key.0) && !new_keys.contains_key(&key.0) {
return None;
}
let birth_height = if let Some(height) = key.1 {
match Height::try_from(height) {
Ok(height) => height,
// Don't accept the key if its birth height is not a valid height.
Err(_) => return None,
}
} else {
// Use the Sapling activation height if the key has no birth height.
sapling_activation_height
};
sapling_key_to_scan_block_keys(&key.0, network)
.ok()
.map(|parsed| (key.0, (parsed.0, parsed.1, birth_height)))
}) })
.collect(); .collect();
if !keys.is_empty() { // Send the accepted keys back.
new_keys.extend(keys.clone()); let _ = rsp_tx.send(keys.iter().map(|key| key.0.clone()).collect());
let keys = new_keys.extend(keys.clone());
keys.into_iter()
.map(|(key, (decoded_dfvks, decoded_ivks, _h))| {
(key, (decoded_dfvks, decoded_ivks))
});
parsed_keys.extend(keys); parsed_keys.extend(
} keys.into_iter()
.map(|(key, (dfvks, ivks, _))| (key, (dfvks, ivks))),
);
} }
ScanTaskCommand::RemoveKeys { done_tx, keys } => { ScanTaskCommand::RemoveKeys { done_tx, keys } => {
@ -143,11 +165,12 @@ impl ScanTask {
/// Sends a message to the scan task to start scanning for the provided viewing keys. /// Sends a message to the scan task to start scanning for the provided viewing keys.
pub fn register_keys( pub fn register_keys(
&mut self, &mut self,
keys: HashMap< keys: Vec<(String, Option<u32>)>,
SaplingScanningKey, ) -> Result<oneshot::Receiver<Vec<String>>, mpsc::SendError<ScanTaskCommand>> {
(Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>, Height), let (rsp_tx, rsp_rx) = oneshot::channel();
>,
) -> Result<(), mpsc::SendError<ScanTaskCommand>> { self.send(ScanTaskCommand::RegisterKeys { keys, rsp_tx })?;
self.send(ScanTaskCommand::RegisterKeys { keys })
Ok(rsp_rx)
} }
} }

View File

@ -75,7 +75,7 @@ pub async fn start(
cmd_receiver: Receiver<ScanTaskCommand>, cmd_receiver: Receiver<ScanTaskCommand>,
) -> Result<(), Report> { ) -> Result<(), Report> {
let network = storage.network(); let network = storage.network();
let sapling_activation_height = storage.min_sapling_birthday_height(); let sapling_activation_height = network.sapling_activation_height();
// Do not scan and notify if we are below sapling activation height. // Do not scan and notify if we are below sapling activation height.
wait_for_height( wait_for_height(
@ -125,7 +125,7 @@ pub async fn start(
} }
} }
let new_keys = ScanTask::process_messages(&cmd_receiver, &mut parsed_keys)?; let new_keys = ScanTask::process_messages(&cmd_receiver, &mut parsed_keys, network)?;
// TODO: Check if the `start_height` is at or above the current height // TODO: Check if the `start_height` is at or above the current height
if !new_keys.is_empty() { if !new_keys.is_empty() {

View File

@ -71,7 +71,7 @@ pub async fn scan_range(
state: State, state: State,
storage: Storage, storage: Storage,
) -> Result<(), BoxError> { ) -> Result<(), BoxError> {
let sapling_activation_height = storage.min_sapling_birthday_height(); let sapling_activation_height = storage.network().sapling_activation_height();
// Do not scan and notify if we are below sapling activation height. // Do not scan and notify if we are below sapling activation height.
wait_for_height( wait_for_height(
sapling_activation_height, sapling_activation_height,

View File

@ -4,25 +4,23 @@ use std::collections::HashMap;
use color_eyre::Report; use color_eyre::Report;
use zebra_chain::block::Height; use crate::{service::ScanTask, tests::mock_sapling_scanning_keys};
use crate::service::ScanTask;
/// Test that [`ScanTask::process_messages`] adds and removes keys as expected for `RegisterKeys` and `DeleteKeys` command /// Test that [`ScanTask::process_messages`] adds and removes keys as expected for `RegisterKeys` and `DeleteKeys` command
#[tokio::test] #[tokio::test]
async fn scan_task_processes_messages_correctly() -> Result<(), Report> { async fn scan_task_processes_messages_correctly() -> Result<(), Report> {
let (mut mock_scan_task, cmd_receiver) = ScanTask::mock(); let (mut mock_scan_task, cmd_receiver) = ScanTask::mock();
let mut parsed_keys = HashMap::new(); let mut parsed_keys = HashMap::new();
let network = Default::default();
// Send some keys to be registered // Send some keys to be registered
let num_keys = 10; let num_keys = 10;
mock_scan_task.register_keys( let sapling_keys = mock_sapling_scanning_keys(num_keys.try_into().expect("should fit in u8"));
(0..num_keys) let sapling_keys_with_birth_heights: Vec<(String, Option<u32>)> =
.map(|i| (i.to_string(), (vec![], vec![], Height::MIN))) sapling_keys.into_iter().zip((0..).map(Some)).collect();
.collect(), mock_scan_task.register_keys(sapling_keys_with_birth_heights.clone())?;
)?;
let new_keys = ScanTask::process_messages(&cmd_receiver, &mut parsed_keys)?; let new_keys = ScanTask::process_messages(&cmd_receiver, &mut parsed_keys, network)?;
// Check that it updated parsed_keys correctly and returned the right new keys when starting with an empty state // Check that it updated parsed_keys correctly and returned the right new keys when starting with an empty state
@ -38,15 +36,11 @@ async fn scan_task_processes_messages_correctly() -> Result<(), Report> {
"should add all received keys to parsed keys" "should add all received keys to parsed keys"
); );
mock_scan_task.register_keys( mock_scan_task.register_keys(sapling_keys_with_birth_heights.clone())?;
(0..num_keys)
.map(|i| (i.to_string(), (vec![], vec![], Height::MIN)))
.collect(),
)?;
// Check that no key should be added if they are all already known and the heights are the same // Check that no key should be added if they are all already known and the heights are the same
let new_keys = ScanTask::process_messages(&cmd_receiver, &mut parsed_keys)?; let new_keys = ScanTask::process_messages(&cmd_receiver, &mut parsed_keys, network)?;
assert_eq!( assert_eq!(
parsed_keys.len(), parsed_keys.len(),
@ -59,21 +53,19 @@ async fn scan_task_processes_messages_correctly() -> Result<(), Report> {
"should not return known keys as new keys" "should not return known keys as new keys"
); );
// Check that it returns the last seen start height for a key as the new key when receiving 2 register key messages // Check that keys can't be overridden.
mock_scan_task.register_keys( let sapling_keys = mock_sapling_scanning_keys(20);
(10..20) let sapling_keys_with_birth_heights: Vec<(String, Option<u32>)> = sapling_keys
.map(|i| (i.to_string(), (vec![], vec![], Height::MIN))) .clone()
.collect(), .into_iter()
)?; .map(|key| (key, Some(0)))
.collect();
mock_scan_task.register_keys( mock_scan_task.register_keys(sapling_keys_with_birth_heights[10..20].to_vec())?;
(10..15) mock_scan_task.register_keys(sapling_keys_with_birth_heights[10..15].to_vec())?;
.map(|i| (i.to_string(), (vec![], vec![], Height::MAX)))
.collect(),
)?;
let new_keys = ScanTask::process_messages(&cmd_receiver, &mut parsed_keys)?; let new_keys = ScanTask::process_messages(&cmd_receiver, &mut parsed_keys, network)?;
assert_eq!( assert_eq!(
parsed_keys.len(), parsed_keys.len(),
@ -87,22 +79,13 @@ async fn scan_task_processes_messages_correctly() -> Result<(), Report> {
"should add 10 of received keys to new keys" "should add 10 of received keys to new keys"
); );
for (new_key, (_, _, start_height)) in new_keys {
if (10..15).contains(&new_key.parse::<i32>().expect("should parse into int")) {
assert_eq!(
start_height,
Height::MAX,
"these key heights should have been overwritten by the second message"
);
}
}
// Check that it removes keys correctly // Check that it removes keys correctly
let done_rx = let sapling_keys = mock_sapling_scanning_keys(30);
mock_scan_task.remove_keys(&(0..200).map(|i| i.to_string()).collect::<Vec<_>>())?;
let new_keys = ScanTask::process_messages(&cmd_receiver, &mut parsed_keys)?; let done_rx = mock_scan_task.remove_keys(&sapling_keys)?;
let new_keys = ScanTask::process_messages(&cmd_receiver, &mut parsed_keys, network)?;
// Check that it sends the done notification successfully before returning and dropping `done_tx` // Check that it sends the done notification successfully before returning and dropping `done_tx`
done_rx.await?; done_rx.await?;
@ -116,15 +99,11 @@ async fn scan_task_processes_messages_correctly() -> Result<(), Report> {
// Check that it doesn't return removed keys as new keys when processing a batch of messages // Check that it doesn't return removed keys as new keys when processing a batch of messages
mock_scan_task.register_keys( mock_scan_task.register_keys(sapling_keys_with_birth_heights.clone())?;
(0..200)
.map(|i| (i.to_string(), (vec![], vec![], Height::MAX)))
.collect(),
)?;
mock_scan_task.remove_keys(&(0..200).map(|i| i.to_string()).collect::<Vec<_>>())?; mock_scan_task.remove_keys(&sapling_keys)?;
let new_keys = ScanTask::process_messages(&cmd_receiver, &mut parsed_keys)?; let new_keys = ScanTask::process_messages(&cmd_receiver, &mut parsed_keys, network)?;
assert!( assert!(
new_keys.is_empty(), new_keys.is_empty(),
@ -133,21 +112,13 @@ async fn scan_task_processes_messages_correctly() -> Result<(), Report> {
// Check that it does return registered keys if they were removed in a prior message when processing a batch of messages // Check that it does return registered keys if they were removed in a prior message when processing a batch of messages
mock_scan_task.register_keys( mock_scan_task.register_keys(sapling_keys_with_birth_heights.clone())?;
(0..200)
.map(|i| (i.to_string(), (vec![], vec![], Height::MAX)))
.collect(),
)?;
mock_scan_task.remove_keys(&(0..200).map(|i| i.to_string()).collect::<Vec<_>>())?; mock_scan_task.remove_keys(&sapling_keys)?;
mock_scan_task.register_keys( mock_scan_task.register_keys(sapling_keys_with_birth_heights[..2].to_vec())?;
(0..2)
.map(|i| (i.to_string(), (vec![], vec![], Height::MAX)))
.collect(),
)?;
let new_keys = ScanTask::process_messages(&cmd_receiver, &mut parsed_keys)?; let new_keys = ScanTask::process_messages(&cmd_receiver, &mut parsed_keys, network)?;
assert_eq!( assert_eq!(
new_keys.len(), new_keys.len(),

View File

@ -2,10 +2,7 @@
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use zebra_chain::{ use zebra_chain::{block::Height, parameters::Network};
block::Height,
parameters::{Network, NetworkUpgrade},
};
use zebra_state::TransactionIndex; use zebra_state::TransactionIndex;
use crate::config::Config; use crate::config::Config;
@ -126,19 +123,4 @@ impl Storage {
) -> BTreeMap<Height, Vec<SaplingScannedResult>> { ) -> BTreeMap<Height, Vec<SaplingScannedResult>> {
self.sapling_results_for_key(sapling_key) self.sapling_results_for_key(sapling_key)
} }
// Parameters
/// Returns the minimum sapling birthday height for the configured network.
pub fn min_sapling_birthday_height(&self) -> Height {
// Assume that the genesis block never contains shielded inputs or outputs.
//
// # Consensus
//
// For Zcash mainnet and the public testnet, Sapling activates above genesis,
// so this is always true.
NetworkUpgrade::Sapling
.activation_height(self.network())
.unwrap_or(Height(0))
}
} }

View File

@ -213,7 +213,7 @@ impl Storage {
sapling_key: &SaplingScanningKey, sapling_key: &SaplingScanningKey,
birthday_height: Option<Height>, birthday_height: Option<Height>,
) { ) {
let min_birthday_height = self.min_sapling_birthday_height(); let min_birthday_height = self.network().sapling_activation_height();
// The birthday height must be at least the minimum height for that pool. // The birthday height must be at least the minimum height for that pool.
let birthday_height = birthday_height let birthday_height = birthday_height

View File

@ -12,8 +12,11 @@ use ff::{Field, PrimeField};
use group::GroupEncoding; use group::GroupEncoding;
use rand::{rngs::OsRng, thread_rng, RngCore}; use rand::{rngs::OsRng, thread_rng, RngCore};
use zcash_client_backend::proto::compact_formats::{ use zcash_client_backend::{
ChainMetadata, CompactBlock, CompactSaplingOutput, CompactSaplingSpend, CompactTx, encoding::encode_extended_full_viewing_key,
proto::compact_formats::{
ChainMetadata, CompactBlock, CompactSaplingOutput, CompactSaplingSpend, CompactTx,
},
}; };
use zcash_note_encryption::Domain; use zcash_note_encryption::Domain;
use zcash_primitives::{ use zcash_primitives::{
@ -27,7 +30,7 @@ use zcash_primitives::{
value::NoteValue, value::NoteValue,
Note, Nullifier, Note, Nullifier,
}, },
zip32::DiversifiableFullViewingKey, zip32,
}; };
use zebra_chain::{ use zebra_chain::{
@ -41,6 +44,7 @@ use zebra_chain::{
transparent::{CoinbaseData, Input}, transparent::{CoinbaseData, Input},
work::{difficulty::CompactDifficulty, equihash::Solution}, work::{difficulty::CompactDifficulty, equihash::Solution},
}; };
use zebra_state::SaplingScanningKey;
#[cfg(test)] #[cfg(test)]
mod vectors; mod vectors;
@ -51,6 +55,31 @@ pub const ZECPAGES_SAPLING_VIEWING_KEY: &str = "zxviews1q0duytgcqqqqpqre26wkl45g
/// A fake viewing key in an incorrect format. /// A fake viewing key in an incorrect format.
pub const FAKE_SAPLING_VIEWING_KEY: &str = "zxviewsfake"; pub const FAKE_SAPLING_VIEWING_KEY: &str = "zxviewsfake";
/// Generates `num_keys` of [`SaplingScanningKey`]s for tests.
///
/// 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> {
let mut keys: Vec<SaplingScanningKey> = vec![];
for seed in 0..num_keys {
keys.push(encode_extended_full_viewing_key(
"zxviews",
&mock_sapling_efvk(&[seed]),
));
}
keys
}
/// Generates an [`zip32::sapling::ExtendedFullViewingKey`] from `seed` for tests.
#[allow(deprecated)]
pub fn mock_sapling_efvk(seed: &[u8]) -> zip32::sapling::ExtendedFullViewingKey {
// TODO: Use `to_diversifiable_full_viewing_key` since `to_extended_full_viewing_key` is
// deprecated.
zip32::sapling::ExtendedSpendingKey::master(seed).to_extended_full_viewing_key()
}
/// Generates a fake block containing a Sapling output decryptable by `dfvk`. /// Generates a fake block containing a Sapling output decryptable by `dfvk`.
/// ///
/// The fake block has the following transactions in this order: /// The fake block has the following transactions in this order:
@ -61,7 +90,7 @@ pub const FAKE_SAPLING_VIEWING_KEY: &str = "zxviewsfake";
pub fn fake_block( pub fn fake_block(
height: BlockHeight, height: BlockHeight,
nf: Nullifier, nf: Nullifier,
dfvk: &DiversifiableFullViewingKey, dfvk: &zip32::sapling::DiversifiableFullViewingKey,
value: u64, value: u64,
tx_after: bool, tx_after: bool,
initial_sapling_tree_size: Option<u32>, initial_sapling_tree_size: Option<u32>,
@ -135,7 +164,7 @@ pub fn fake_compact_block(
height: BlockHeight, height: BlockHeight,
prev_hash: BlockHash, prev_hash: BlockHash,
nf: Nullifier, nf: Nullifier,
dfvk: &DiversifiableFullViewingKey, dfvk: &zip32::sapling::DiversifiableFullViewingKey,
value: u64, value: u64,
tx_after: bool, tx_after: bool,
initial_sapling_tree_size: Option<u32>, initial_sapling_tree_size: Option<u32>,

View File

@ -5,7 +5,8 @@ use std::sync::Arc;
use color_eyre::Result; use color_eyre::Result;
use zcash_client_backend::{ use zcash_client_backend::{
encoding::decode_extended_full_viewing_key, proto::compact_formats::ChainMetadata, encoding::{decode_extended_full_viewing_key, encode_extended_full_viewing_key},
proto::compact_formats::ChainMetadata,
}; };
use zcash_primitives::{ use zcash_primitives::{
constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
@ -24,7 +25,7 @@ use zebra_state::{SaplingScannedResult, TransactionIndex};
use crate::{ use crate::{
scan::{block_to_compact, scan_block}, scan::{block_to_compact, scan_block},
storage::db::tests::new_test_storage, storage::db::tests::new_test_storage,
tests::{fake_block, ZECPAGES_SAPLING_VIEWING_KEY}, tests::{fake_block, mock_sapling_efvk, ZECPAGES_SAPLING_VIEWING_KEY},
}; };
/// This test: /// This test:
@ -146,31 +147,30 @@ async fn scanning_zecpages_from_populated_zebra_state() -> Result<()> {
/// ///
/// The purpose of this test is to check if our database and our scanning code are compatible. /// The purpose of this test is to check if our database and our scanning code are compatible.
#[test] #[test]
#[allow(deprecated)]
fn scanning_fake_blocks_store_key_and_results() -> Result<()> { fn scanning_fake_blocks_store_key_and_results() -> Result<()> {
let network = Network::Mainnet;
// Generate a key // Generate a key
let extsk = ExtendedSpendingKey::master(&[]); let efvk = mock_sapling_efvk(&[]);
// TODO: find out how to do it with `to_diversifiable_full_viewing_key` as `to_extended_full_viewing_key` is deprecated. let dfvk = efvk.to_diversifiable_full_viewing_key();
let extfvk = extsk.to_extended_full_viewing_key(); let key_to_be_stored = encode_extended_full_viewing_key("zxviews", &efvk);
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 // Create a database
let mut s = new_test_storage(Network::Mainnet); let mut storage = new_test_storage(network);
// Insert the generated key to the database // Insert the generated key to the database
s.add_sapling_key(&key_to_be_stored, None); storage.add_sapling_key(&key_to_be_stored, None);
// Check key was added // Check key was added
assert_eq!(s.sapling_keys_last_heights().len(), 1); assert_eq!(storage.sapling_keys_last_heights().len(), 1);
assert_eq!( assert_eq!(
s.sapling_keys_last_heights() storage
.sapling_keys_last_heights()
.get(&key_to_be_stored) .get(&key_to_be_stored)
.expect("height is stored") .expect("height is stored")
.next() .next()
.expect("height is not maximum"), .expect("height is not maximum"),
s.min_sapling_birthday_height() network.sapling_activation_height()
); );
let nf = Nullifier([7; 32]); let nf = Nullifier([7; 32]);
@ -186,7 +186,7 @@ fn scanning_fake_blocks_store_key_and_results() -> Result<()> {
SaplingScannedResult::from_bytes_in_display_order(*result.transactions()[0].txid.as_ref()); SaplingScannedResult::from_bytes_in_display_order(*result.transactions()[0].txid.as_ref());
// Add result to database // Add result to database
s.add_sapling_results( storage.add_sapling_results(
&key_to_be_stored, &key_to_be_stored,
Height(1), Height(1),
[(TransactionIndex::from_usize(0), result)].into(), [(TransactionIndex::from_usize(0), result)].into(),
@ -194,7 +194,7 @@ fn scanning_fake_blocks_store_key_and_results() -> Result<()> {
// Check the result was added // Check the result was added
assert_eq!( assert_eq!(
s.sapling_results(&key_to_be_stored).get(&Height(1)), storage.sapling_results(&key_to_be_stored).get(&Height(1)),
Some(&vec![result]) Some(&vec![result])
); );

View File

@ -105,7 +105,12 @@ pub(crate) async fn run() -> Result<()> {
tracing::info!("started scan task, sending register keys message with zecpages key to start scanning for a new key",); tracing::info!("started scan task, sending register keys message with zecpages key to start scanning for a new key",);
scan_task.register_keys(parsed_keys)?; scan_task.register_keys(
parsed_keys
.into_iter()
.map(|(key, (_, _, Height(h)))| (key, Some(h)))
.collect(),
)?;
tracing::info!( tracing::info!(
?WAIT_FOR_RESULTS_DURATION, ?WAIT_FOR_RESULTS_DURATION,