Skip to content

Commit

Permalink
Try bumping zcash_client_backend
Browse files Browse the repository at this point in the history
  • Loading branch information
upbqdn committed May 20, 2024
1 parent 9a6cdfc commit 779b1b8
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 96 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6315,11 +6315,13 @@ dependencies = [
"proptest",
"proptest-derive",
"rand 0.8.5",
"sapling-crypto",
"semver 1.0.23",
"serde",
"tokio",
"tower",
"tracing",
"zcash_address",
"zcash_client_backend",
"zcash_note_encryption",
"zcash_primitives 0.13.0",
Expand Down
6 changes: 4 additions & 2 deletions zebra-chain/src/primitives/viewing_key/sapling.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! Defines types and implements methods for parsing Sapling viewing keys and converting them to `zebra-chain` types
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
use zcash_client_backend::{
encoding::decode_extended_full_viewing_key,
keys::sapling::DiversifiableFullViewingKey as SaplingDfvk,
};
use zcash_primitives::{
constants::*,
sapling::keys::{FullViewingKey as SaplingFvk, SaplingIvk},
zip32::DiversifiableFullViewingKey as SaplingDfvk,
};

use crate::parameters::Network;
Expand Down
2 changes: 2 additions & 0 deletions zebra-scan/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ futures = "0.3.30"

zcash_client_backend = "0.12.0"
zcash_primitives = "0.13.0"
sapling-crypto = "0.1.0"
zcash_address = "0.3.0"

zebra-chain = { path = "../zebra-chain", version = "1.0.0-beta.37", features = ["shielded-scan"] }
zebra-state = { path = "../zebra-state", version = "1.0.0-beta.37", features = ["shielded-scan"] }
Expand Down
22 changes: 7 additions & 15 deletions zebra-scan/src/service/scan_task/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use zebra_chain::{block::Height, parameters::Network};
use zebra_node_services::scan_service::response::ScanResult;
use zebra_state::SaplingScanningKey;

use crate::scan::sapling_key_to_scan_block_keys;
use crate::scan::{scanning_key, ScanningKey};

use super::ScanTask;

Expand Down Expand Up @@ -57,17 +57,11 @@ impl ScanTask {
/// Returns newly registered keys for scanning.
pub fn process_messages(
cmd_receiver: &mut tokio::sync::mpsc::Receiver<ScanTaskCommand>,
registered_keys: &mut HashMap<
SaplingScanningKey,
(Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>),
>,
registered_keys: &mut HashMap<SaplingScanningKey, ScanningKey>,
network: &Network,
) -> Result<
(
HashMap<
SaplingScanningKey,
(Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>, Height),
>,
HashMap<SaplingScanningKey, (ScanningKey, Height)>,
HashMap<SaplingScanningKey, Sender<ScanResult>>,
Vec<(Receiver<ScanResult>, oneshot::Sender<Receiver<ScanResult>>)>,
),
Expand Down Expand Up @@ -117,9 +111,9 @@ impl ScanTask {
sapling_activation_height
};

sapling_key_to_scan_block_keys(&key.0, network)
scanning_key(&key.0, network)
.ok()
.map(|parsed| (key.0, (parsed.0, parsed.1, birth_height)))
.map(|decoded| (key.0, (decoded, birth_height)))
})
.collect();

Expand All @@ -128,10 +122,8 @@ impl ScanTask {

new_keys.extend(keys.clone());

registered_keys.extend(
keys.into_iter()
.map(|(key, (dfvks, ivks, _))| (key, (dfvks, ivks))),
);
registered_keys
.extend(keys.into_iter().map(|(key, (decoded, _))| (key, decoded)));
}

ScanTaskCommand::RemoveKeys { done_tx, keys } => {
Expand Down
104 changes: 39 additions & 65 deletions zebra-scan/src/service/scan_task/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ use tokio::{
use tower::{buffer::Buffer, util::BoxService, Service, ServiceExt};

use tracing::Instrument;
use zcash_address::unified::{Encoding, Fvk, Ufvk};
use zcash_client_backend::{
data_api::ScannedBlock,
encoding::decode_extended_full_viewing_key,
keys::UnifiedFullViewingKey,
proto::compact_formats::{
ChainMetadata, CompactBlock, CompactSaplingOutput, CompactSaplingSpend, CompactTx,
},
scanning::{ScanError, ScanningKey},
};
use zcash_primitives::{
sapling::SaplingIvk,
zip32::{AccountId, DiversifiableFullViewingKey, Scope},
scanning::{Nullifiers, ScanError, ScanningKeys},
};

use zcash_primitives::zip32::{AccountId, Scope};

use zebra_chain::{
block::{Block, Height},
chain_tip::ChainTip,
Expand Down Expand Up @@ -56,6 +56,11 @@ pub type State = Buffer<
zebra_state::Request,
>;

/// The key for blockchain scanning.
///
/// Currently contains a single key.
pub type ScanningKey = ScanningKeys<AccountId, (AccountId, Scope)>;

/// Wait a few seconds at startup for some blocks to get verified.
///
/// But sometimes the state might be empty if the network is slow.
Expand Down Expand Up @@ -105,15 +110,9 @@ pub async fn start(

// Parse and convert keys once, then use them to scan all blocks.
// There is some cryptography here, but it should be fast even with thousands of keys.
let mut parsed_keys: HashMap<
SaplingScanningKey,
(Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>),
> = key_heights
let mut parsed_keys: HashMap<SaplingScanningKey, ScanningKey> = key_heights
.keys()
.map(|key| {
let parsed_keys = sapling_key_to_scan_block_keys(key, &network)?;
Ok::<_, Report>((key.clone(), parsed_keys))
})
.map(|key| Ok::<_, Report>((key.clone(), scanning_key(key, &network)?)))
.try_collect()?;

let mut subscribed_keys: HashMap<SaplingScanningKey, Sender<ScanResult>> = HashMap::new();
Expand Down Expand Up @@ -165,7 +164,7 @@ pub async fn start(

let start_height = new_keys
.iter()
.map(|(_, (_, _, height))| *height)
.map(|(_, (_, height))| *height)
.min()
.unwrap_or(sapling_activation_height);

Expand Down Expand Up @@ -251,7 +250,7 @@ pub async fn scan_height_and_store_results(
chain_tip_change: Option<ChainTipChange>,
storage: Storage,
key_last_scanned_heights: Arc<HashMap<SaplingScanningKey, Height>>,
parsed_keys: HashMap<SaplingScanningKey, (Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>)>,
parsed_keys: HashMap<SaplingScanningKey, ScanningKey>,
subscribed_keys_receiver: watch::Receiver<Arc<HashMap<String, Sender<ScanResult>>>>,
) -> Result<Option<Height>, Report> {
let network = storage.network();
Expand All @@ -277,7 +276,7 @@ pub async fn scan_height_and_store_results(
_ => unreachable!("unmatched response to a state::Block request"),
};

for (key_index_in_task, (sapling_key, (dfvks, ivks))) in parsed_keys.into_iter().enumerate() {
for (key_index_in_task, (sapling_key, scanning_key)) in parsed_keys.into_iter().enumerate() {
match key_last_scanned_heights.get(&sapling_key) {
// Only scan what was not scanned for each key
Some(last_scanned_height) if height <= *last_scanned_height => continue,
Expand Down Expand Up @@ -326,19 +325,14 @@ pub async fn scan_height_and_store_results(
let sapling_tree_size = 1 << 16;

tokio::task::spawn_blocking(move || {
let dfvk_res =
scan_block(&network, &block, sapling_tree_size, &dfvks).map_err(|e| eyre!(e))?;
let ivk_res =
scan_block(&network, &block, sapling_tree_size, &ivks).map_err(|e| eyre!(e))?;
let scanned = scan_block(&network, &block, sapling_tree_size, &scanning_key)
.map_err(|e| eyre!(e))?;

let dfvk_res = scanned_block_to_db_result(dfvk_res);
let ivk_res = scanned_block_to_db_result(ivk_res);
let results = scanned_block_to_db_result(scanned);

let latest_subscribed_keys = subscribed_keys_receiver.borrow().clone();
if let Some(results_sender) = latest_subscribed_keys.get(&sapling_key).cloned() {
let results = dfvk_res.iter().chain(ivk_res.iter());

for (_tx_index, &tx_id) in results {
for (_tx_index, &tx_id) in results.iter() {
// TODO: Handle `SendErrors` by dropping sender from `subscribed_keys`
let _ = results_sender.try_send(ScanResult {
key: sapling_key.clone(),
Expand All @@ -348,8 +342,7 @@ pub async fn scan_height_and_store_results(
}
}

storage.add_sapling_results(&sapling_key, height, dfvk_res);
storage.add_sapling_results(&sapling_key, height, ivk_res);
storage.add_sapling_results(&sapling_key, height, results);

Ok::<_, Report>(())
})
Expand All @@ -360,27 +353,22 @@ pub async fn scan_height_and_store_results(
Ok(Some(height))
}

/// Returns the transactions from `block` belonging to the given `scanning_keys`.
/// This list of keys should come from a single configured `SaplingScanningKey`.
///
/// For example, there are two individual viewing keys for most shielded transfers:
/// - the payment (external) key, and
/// - the change (internal) key.
/// Returns the transactions from `block` belonging to the given `scanning_key`.
///
/// # Performance / Hangs
///
/// This method can block while reading database files, so it must be inside spawn_blocking()
/// in async code.
///
/// TODO:
/// - Pass the real `sapling_tree_size` parameter from the state.
/// - Add other prior block metadata.
pub fn scan_block<K: ScanningKey>(
// TODO:
// - Pass the real `sapling_tree_size` parameter from the state.
// - Add other prior block metadata.
pub fn scan_block(
network: &Network,
block: &Block,
sapling_tree_size: u32,
scanning_keys: &[K],
) -> Result<ScannedBlock<K::Nf>, ScanError> {
scanning_key: &ScanningKey,
) -> Result<ScannedBlock<AccountId>, ScanError> {
// TODO: Implement a check that returns early when the block height is below the Sapling
// activation height.

Expand All @@ -390,45 +378,31 @@ pub fn scan_block<K: ScanningKey>(
orchard_commitment_tree_size: 0,
};

// 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.as_slice(),
&scanning_key,
// Ignore whether notes are change from a viewer's own spends for now.
&[],
&Nullifiers::empty(),
// Ignore previous blocks for now.
None,
)
}

/// Converts a Zebra-format scanning key into some `scan_block()` keys.
/// Turns a [`SaplingScanningKey`] into [`ScanningKey`] .
///
/// Currently only accepts extended full viewing keys, and returns both their diversifiable full
/// viewing key and their individual viewing key, for testing purposes.
/// Currently only accepts a string-encoded extended full viewing key.
///
// TODO: work out what string format is used for SaplingIvk, if any, and support it here
// 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(
key: &SaplingScanningKey,
network: &Network,
) -> Result<(Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>), Report> {
pub fn scanning_key(key: &SaplingScanningKey, network: &Network) -> Result<ScanningKey, Report> {
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();
let eivk = dfvk.to_ivk(Scope::External);
let iivk = dfvk.to_ivk(Scope::Internal);

Ok((vec![dfvk], vec![eivk, iivk]))
let dfvk = efvk.to_diversifiable_full_viewing_key().to_bytes();
let fvk = Fvk::try_from((2, &dfvk[..]))?;
let ufvk = Ufvk::try_from_items(vec![fvk])?;
let ufvk = UnifiedFullViewingKey::parse(&ufvk).map_err(|e| eyre!(e))?;
Ok(ScanningKeys::from_account_ufvks(vec![(0.into(), ufvk)]))
}

/// Converts a zebra block and meta data into a compact block.
Expand Down Expand Up @@ -525,8 +499,8 @@ fn scanned_block_to_db_result<Nf>(
.iter()
.map(|tx| {
(
TransactionIndex::from_usize(tx.index),
SaplingScannedResult::from_bytes_in_display_order(*tx.txid.as_ref()),
TransactionIndex::from_usize(tx.block_index()),
SaplingScannedResult::from_bytes_in_display_order(*tx.txid().as_ref()),
)
})
.collect()
Expand Down
20 changes: 8 additions & 12 deletions zebra-scan/src/service/scan_task/scan/scan_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ use zebra_chain::block::Height;
use zebra_node_services::scan_service::response::ScanResult;
use zebra_state::SaplingScanningKey;

use super::ScanningKey;

/// A builder for a scan until task
pub struct ScanRangeTaskBuilder {
/// The range of block heights that should be scanned for these keys
// TODO: Remove start heights from keys and require that all keys per task use the same start height
height_range: std::ops::Range<Height>,

/// The keys to be used for scanning blocks in this task
keys: HashMap<SaplingScanningKey, (Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>, Height)>,
keys: HashMap<SaplingScanningKey, (ScanningKey, Height)>,

/// A handle to the state service for reading the blocks and the chain tip height
state: State,
Expand All @@ -37,10 +39,7 @@ impl ScanRangeTaskBuilder {
/// Creates a new [`ScanRangeTaskBuilder`]
pub fn new(
stop_height: Height,
keys: HashMap<
SaplingScanningKey,
(Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>, Height),
>,
keys: HashMap<SaplingScanningKey, (ScanningKey, Height)>,
state: State,
storage: Storage,
) -> Self {
Expand Down Expand Up @@ -83,7 +82,7 @@ impl ScanRangeTaskBuilder {
// TODO: update the first parameter to `std::ops::Range<Height>`
pub async fn scan_range(
stop_before_height: Height,
keys: HashMap<SaplingScanningKey, (Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>, Height)>,
keys: HashMap<SaplingScanningKey, (ScanningKey, Height)>,
state: State,
storage: Storage,
subscribed_keys_receiver: watch::Receiver<Arc<HashMap<String, Sender<ScanResult>>>>,
Expand All @@ -99,20 +98,17 @@ pub async fn scan_range(

let key_heights: HashMap<String, Height> = keys
.iter()
.map(|(key, (_, _, height))| (key.clone(), *height))
.map(|(key, (_, height))| (key.clone(), *height))
.collect();

let mut height = get_min_height(&key_heights).unwrap_or(sapling_activation_height);

let key_heights = Arc::new(key_heights);

// Parse and convert keys once, then use them to scan all blocks.
let parsed_keys: HashMap<
SaplingScanningKey,
(Vec<DiversifiableFullViewingKey>, Vec<SaplingIvk>),
> = keys
let parsed_keys: HashMap<SaplingScanningKey, ScanningKey> = keys
.into_iter()
.map(|(key, (decoded_dfvks, decoded_ivks, _h))| (key, (decoded_dfvks, decoded_ivks)))
.map(|(key, (scanning_key, _h))| (key, scanning_key))
.collect();

while height < stop_before_height {
Expand Down
4 changes: 2 additions & 2 deletions zebra-utils/src/bin/scanning-results-reader/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use zcash_primitives::consensus::{BlockHeight, BranchId};
use zcash_primitives::transaction::Transaction;
use zcash_primitives::zip32::AccountId;

use zebra_scan::scan::sapling_key_to_scan_block_keys;
use zebra_scan::scan::scanning_key;
use zebra_scan::{storage::Storage, Config};

/// Prints the memos of transactions from Zebra's scanning results storage.
Expand Down Expand Up @@ -47,7 +47,7 @@ pub fn main() {
let mut prev_memo = "".to_owned();

for (key, _) in storage.sapling_keys_last_heights().iter() {
let dfvk = sapling_key_to_scan_block_keys(key, &network)
let dfvk = scanning_key(key, &network)
.expect("Scanning key from the storage should be valid")
.0
.into_iter()
Expand Down

0 comments on commit 779b1b8

Please sign in to comment.