diff --git a/zcash_client_backend/src/scanning.rs b/zcash_client_backend/src/scanning.rs
index faee0d8f6..dee90840f 100644
--- a/zcash_client_backend/src/scanning.rs
+++ b/zcash_client_backend/src/scanning.rs
@@ -376,7 +376,7 @@ pub(crate) fn scan_block_with_runner
(
sapling_keys: &[(&A, SK)],
sapling_nullifiers: &[(A, sapling::Nullifier)],
prior_block_metadata: Option<&BlockMetadata>,
- mut batch_runner: Option<&mut TaggedBatchRunner>,
+ mut sapling_batch_runner: Option<&mut TaggedBatchRunner>,
) -> Result, ScanError>
where
P: consensus::Parameters + Send + 'static,
@@ -494,42 +494,22 @@ where
let tx_index =
u16::try_from(tx.index).expect("Cannot fit more than 2^16 transactions in a block");
- // Check for spent notes. The comparison against known-unspent nullifiers is done
- // in constant time.
- // TODO: However, this is O(|nullifiers| * |notes|); does using
- // constant-time operations here really make sense?
- let mut shielded_spends = vec![];
- let mut sapling_unlinked_nullifiers = Vec::with_capacity(tx.spends.len());
- for (index, spend) in tx.spends.into_iter().enumerate() {
- let spend_nf = spend
- .nf()
- .expect("Could not deserialize nullifier for spend from protobuf representation.");
+ let (sapling_spends, sapling_unlinked_nullifiers) = check_nullifiers(
+ &tx.spends,
+ sapling_nullifiers,
+ |spend| {
+ spend.nf().expect(
+ "Could not deserialize nullifier for spend from protobuf representation.",
+ )
+ },
+ WalletSaplingSpend::from_parts,
+ );
- // Find the first tracked nullifier that matches this spend, and produce
- // a WalletShieldedSpend if there is a match, in constant time.
- let spend = sapling_nullifiers
- .iter()
- .map(|&(account, nf)| CtOption::new(account, nf.ct_eq(&spend_nf)))
- .fold(CtOption::new(A::default(), 0.into()), |first, next| {
- CtOption::conditional_select(&next, &first, first.is_some())
- })
- .map(|account| WalletSaplingSpend::from_parts(index, spend_nf, account));
-
- if spend.is_some().into() {
- shielded_spends.push(spend.unwrap());
- } else {
- // This nullifier didn't match any we are currently tracking; save it in
- // case it matches an earlier block range we haven't scanned yet.
- sapling_unlinked_nullifiers.push(spend_nf);
- }
- }
sapling_nullifier_map.push((txid, tx_index, sapling_unlinked_nullifiers));
// Collect the set of accounts that were spent from in this transaction
- let spent_from_accounts: HashSet<_> = shielded_spends
- .iter()
- .map(|spend| spend.account())
- .collect();
+ let spent_from_accounts: HashSet<_> =
+ sapling_spends.iter().map(|spend| spend.account()).collect();
// We keep track of the number of outputs and actions here because tx.outputs
// and tx.actions end up being moved.
@@ -554,7 +534,7 @@ where
})
.collect::>();
- let decrypted: Vec<_> = if let Some(runner) = batch_runner.as_mut() {
+ let decrypted: Vec<_> = if let Some(runner) = sapling_batch_runner.as_mut() {
let sapling_keys = sapling_keys
.iter()
.flat_map(|(a, k)| {
@@ -648,11 +628,11 @@ where
}
}
- if !(shielded_spends.is_empty() && shielded_outputs.is_empty()) {
+ if !(sapling_spends.is_empty() && shielded_outputs.is_empty()) {
wtxs.push(WalletTx {
txid,
index: tx_index as usize,
- sapling_spends: shielded_spends,
+ sapling_spends,
sapling_outputs: shielded_outputs,
});
}
@@ -704,6 +684,42 @@ where
))
}
+// Check for spent notes. The comparison against known-unspent nullifiers is done
+// in constant time.
+fn check_nullifiers(
+ spends: &[Spend],
+ nullifiers: &[(A, Nf)],
+ extract_nf: impl Fn(&Spend) -> Nf,
+ construct_wallet_spend: impl Fn(usize, Nf, A) -> WS,
+) -> (Vec, Vec) {
+ // TODO: this is O(|nullifiers| * |notes|); does using constant-time operations here really
+ // make sense?
+ let mut found_spent = vec![];
+ let mut unlinked_nullifiers = Vec::with_capacity(spends.len());
+ for (index, spend) in spends.iter().enumerate() {
+ let spend_nf = extract_nf(spend);
+
+ // Find the first tracked nullifier that matches this spend, and produce
+ // a WalletShieldedSpend if there is a match, in constant time.
+ let spend = nullifiers
+ .iter()
+ .map(|&(account, nf)| CtOption::new(account, nf.ct_eq(&spend_nf)))
+ .fold(CtOption::new(A::default(), 0.into()), |first, next| {
+ CtOption::conditional_select(&next, &first, first.is_some())
+ })
+ .map(|account| construct_wallet_spend(index, spend_nf, account));
+
+ if let Some(spend) = spend.into() {
+ found_spent.push(spend);
+ } else {
+ // This nullifier didn't match any we are currently tracking; save it in
+ // case it matches an earlier block range we haven't scanned yet.
+ unlinked_nullifiers.push(spend_nf);
+ }
+ }
+ (found_spent, unlinked_nullifiers)
+}
+
#[cfg(test)]
mod tests {
use group::{