Merge pull request #867 from nuttycom/feature/pre_dag_sync-require_scan_range

zcash_client_backend: Make scan range bounds required in `data_api::chain::scan_cached_blocks`
This commit is contained in:
str4d 2023-07-06 18:59:17 +01:00 committed by GitHub
commit 82705a4ae4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 440 additions and 147 deletions

View File

@ -11,14 +11,15 @@ and this library adheres to Rust's notion of
- `impl Eq for zcash_client_backend::zip321::{Payment, TransactionRequest}` - `impl Eq for zcash_client_backend::zip321::{Payment, TransactionRequest}`
- `impl Debug` for `zcash_client_backend::{data_api::wallet::input_selection::Proposal, wallet::ReceivedSaplingNote}` - `impl Debug` for `zcash_client_backend::{data_api::wallet::input_selection::Proposal, wallet::ReceivedSaplingNote}`
- `zcash_client_backend::data_api`: - `zcash_client_backend::data_api`:
- `BlockMetadata`
- `NullifierQuery` for use with `WalletRead::get_sapling_nullifiers`
- `ScannedBlock`
- `ShieldedProtocol`
- `WalletRead::{block_metadata, block_fully_scanned, suggest_scan_ranges}` - `WalletRead::{block_metadata, block_fully_scanned, suggest_scan_ranges}`
- `WalletWrite::put_block` - `WalletWrite::put_block`
- `WalletCommitmentTrees` - `WalletCommitmentTrees`
- `testing::MockWalletDb::new`
- `NullifierQuery` for use with `WalletRead::get_sapling_nullifiers`
- `BlockMetadata`
- `ScannedBlock`
- `chain::CommitmentTreeRoot` - `chain::CommitmentTreeRoot`
- `testing::MockWalletDb::new`
- `wallet::input_sellection::Proposal::{min_target_height, min_anchor_height}`: - `wallet::input_sellection::Proposal::{min_target_height, min_anchor_height}`:
- `zcash_client_backend::wallet::WalletSaplingOutput::note_commitment_tree_position` - `zcash_client_backend::wallet::WalletSaplingOutput::note_commitment_tree_position`
- `zcash_client_backend::scanning::ScanError` - `zcash_client_backend::scanning::ScanError`
@ -35,7 +36,8 @@ and this library adheres to Rust's notion of
and its signature has changed; it now subsumes the removed `WalletRead::get_all_nullifiers`. and its signature has changed; it now subsumes the removed `WalletRead::get_all_nullifiers`.
- `WalletRead::get_target_and_anchor_heights` now takes its argument as a `NonZeroU32` - `WalletRead::get_target_and_anchor_heights` now takes its argument as a `NonZeroU32`
- `chain::scan_cached_blocks` now takes a `from_height` argument that - `chain::scan_cached_blocks` now takes a `from_height` argument that
permits the caller to control the starting position of the scan range. permits the caller to control the starting position of the scan range
In addition, the `limit` parameter is now required.
- A new `CommitmentTree` variant has been added to `data_api::error::Error` - A new `CommitmentTree` variant has been added to `data_api::error::Error`
- `data_api::wallet::{create_spend_to_address, create_proposed_transaction, - `data_api::wallet::{create_spend_to_address, create_proposed_transaction,
shield_transparent_funds}` all now require that `WalletCommitmentTrees` be shield_transparent_funds}` all now require that `WalletCommitmentTrees` be
@ -49,6 +51,8 @@ and this library adheres to Rust's notion of
now take their respective `min_confirmations` arguments as `NonZeroU32` now take their respective `min_confirmations` arguments as `NonZeroU32`
- A new `Scan` variant has been added to `data_api::chain::error::Error`. - A new `Scan` variant has been added to `data_api::chain::error::Error`.
- A new `SyncRequired` variant has been added to `data_api::wallet::input_selection::InputSelectorError`. - A new `SyncRequired` variant has been added to `data_api::wallet::input_selection::InputSelectorError`.
- The variants of the `PoolType` enum have changed; the `PoolType::Sapling` variant has been
removed in favor of a `PoolType::Shielded` variant that wraps a `ShieldedProtocol` value.
- `zcash_client_backend::wallet`: - `zcash_client_backend::wallet`:
- `SpendableNote` has been renamed to `ReceivedSaplingNote`. - `SpendableNote` has been renamed to `ReceivedSaplingNote`.
- Arguments to `WalletSaplingOutput::from_parts` have changed. - Arguments to `WalletSaplingOutput::from_parts` have changed.

View File

@ -372,14 +372,21 @@ pub struct SentTransaction<'a> {
pub utxos_spent: Vec<OutPoint>, pub utxos_spent: Vec<OutPoint>,
} }
/// A shielded transfer protocol supported by the wallet.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ShieldedProtocol {
/// The Sapling protocol
Sapling,
// TODO: Orchard
}
/// A value pool to which the wallet supports sending transaction outputs. /// A value pool to which the wallet supports sending transaction outputs.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PoolType { pub enum PoolType {
/// The transparent value pool /// The transparent value pool
Transparent, Transparent,
/// The Sapling value pool /// A shielded value pool.
Sapling, Shielded(ShieldedProtocol),
// TODO: Orchard
} }
/// A type that represents the recipient of a transaction output; a recipient address (and, for /// A type that represents the recipient of a transaction output; a recipient address (and, for
@ -487,7 +494,6 @@ pub trait WalletWrite: WalletRead {
/// Updates the state of the wallet database by persisting the provided block information, /// Updates the state of the wallet database by persisting the provided block information,
/// along with the note commitments that were detected when scanning the block for transactions /// along with the note commitments that were detected when scanning the block for transactions
/// pertaining to this wallet. /// pertaining to this wallet.
#[allow(clippy::type_complexity)]
fn put_block( fn put_block(
&mut self, &mut self,
block: ScannedBlock<sapling::Nullifier>, block: ScannedBlock<sapling::Nullifier>,

View File

@ -45,12 +45,13 @@
//! // At this point, the cache and scanned data are locally consistent (though not //! // At this point, the cache and scanned data are locally consistent (though not
//! // necessarily consistent with the latest chain tip - this would be discovered the //! // necessarily consistent with the latest chain tip - this would be discovered the
//! // next time this codepath is executed after new blocks are received). //! // next time this codepath is executed after new blocks are received).
//! scan_cached_blocks(&network, &block_source, &mut db_data, None, None) //! scan_cached_blocks(&network, &block_source, &mut db_data, BlockHeight::from(0), 10)
//! # } //! # }
//! # } //! # }
//! ``` //! ```
use zcash_primitives::{ use zcash_primitives::{
block::BlockHash,
consensus::{self, BlockHeight}, consensus::{self, BlockHeight},
sapling::{self, note_encryption::PreparedIncomingViewingKey}, sapling::{self, note_encryption::PreparedIncomingViewingKey},
zip32::Scope, zip32::Scope,
@ -60,9 +61,11 @@ use crate::{
data_api::{NullifierQuery, WalletWrite}, data_api::{NullifierQuery, WalletWrite},
proto::compact_formats::CompactBlock, proto::compact_formats::CompactBlock,
scan::BatchRunner, scan::BatchRunner,
scanning::{add_block_to_runner, scan_block_with_runner}, scanning::{add_block_to_runner, check_continuity, scan_block_with_runner},
}; };
use super::BlockMetadata;
pub mod error; pub mod error;
use error::Error; use error::Error;
@ -141,8 +144,8 @@ pub fn scan_cached_blocks<ParamsT, DbT, BlockSourceT>(
params: &ParamsT, params: &ParamsT,
block_source: &BlockSourceT, block_source: &BlockSourceT,
data_db: &mut DbT, data_db: &mut DbT,
from_height: Option<BlockHeight>, from_height: BlockHeight,
limit: Option<u32>, limit: u32,
) -> Result<(), Error<DbT::Error, BlockSourceT::Error>> ) -> Result<(), Error<DbT::Error, BlockSourceT::Error>>
where where
ParamsT: consensus::Parameters + Send + 'static, ParamsT: consensus::Parameters + Send + 'static,
@ -178,34 +181,66 @@ where
.map(|(tag, ivk)| (tag, PreparedIncomingViewingKey::new(&ivk))), .map(|(tag, ivk)| (tag, PreparedIncomingViewingKey::new(&ivk))),
); );
// Start at either the provided height, or where we synced up to previously. let mut prior_block_metadata = if from_height > BlockHeight::from(0) {
let (scan_from, mut prior_block_metadata) = match from_height { data_db
Some(h) => { .block_metadata(from_height - 1)
// if we are provided with a starting height, obtain the metadata for the previous .map_err(Error::Wallet)?
// block (if any is available)
(
Some(h),
if h > BlockHeight::from(0) {
data_db.block_metadata(h - 1).map_err(Error::Wallet)?
} else { } else {
None None
},
)
}
None => {
let last_scanned = data_db.block_fully_scanned().map_err(Error::Wallet)?;
last_scanned.map_or_else(|| (None, None), |m| (Some(m.block_height + 1), Some(m)))
}
}; };
block_source.with_blocks::<_, DbT::Error>(scan_from, limit, |block: CompactBlock| { let mut continuity_check_metadata = prior_block_metadata;
block_source.with_blocks::<_, DbT::Error>(
Some(from_height),
Some(limit),
|block: CompactBlock| {
// check block continuity
if let Some(scan_error) = check_continuity(&block, continuity_check_metadata.as_ref()) {
return Err(Error::Scan(scan_error));
}
if from_height == BlockHeight::from(0) {
// We can always derive a valid `continuity_check_metadata` for the
// genesis block, even if the block source doesn't have
// `sapling_commitment_tree_size`. So briefly set it to a dummy value that
// ensures the `map` below produces the correct genesis block value.
assert!(continuity_check_metadata.is_none());
continuity_check_metadata = Some(BlockMetadata::from_parts(
BlockHeight::from(0),
BlockHash([0; 32]),
0,
));
}
continuity_check_metadata = continuity_check_metadata.as_ref().map(|m| {
BlockMetadata::from_parts(
block.height(),
block.hash(),
block
.chain_metadata
.as_ref()
.map(|m| m.sapling_commitment_tree_size)
.unwrap_or_else(|| {
m.sapling_tree_size()
+ u32::try_from(
block.vtx.iter().map(|tx| tx.outputs.len()).sum::<usize>(),
)
.unwrap()
}),
)
});
add_block_to_runner(params, block, &mut batch_runner); add_block_to_runner(params, block, &mut batch_runner);
Ok(()) Ok(())
})?; },
)?;
batch_runner.flush(); batch_runner.flush();
block_source.with_blocks::<_, DbT::Error>(scan_from, limit, |block: CompactBlock| { block_source.with_blocks::<_, DbT::Error>(
Some(from_height),
Some(limit),
|block: CompactBlock| {
let scanned_block = scan_block_with_runner( let scanned_block = scan_block_with_runner(
params, params,
block, block,
@ -232,7 +267,8 @@ where
prior_block_metadata = Some(*scanned_block.metadata()); prior_block_metadata = Some(*scanned_block.metadata());
data_db.put_block(scanned_block).map_err(Error::Wallet)?; data_db.put_block(scanned_block).map_err(Error::Wallet)?;
Ok(()) Ok(())
})?; },
)?;
Ok(()) Ok(())
} }

View File

@ -37,6 +37,8 @@ use crate::{
pub mod input_selection; pub mod input_selection;
use input_selection::{GreedyInputSelector, GreedyInputSelectorError, InputSelector}; use input_selection::{GreedyInputSelector, GreedyInputSelectorError, InputSelector};
use super::ShieldedProtocol;
#[cfg(feature = "transparent-inputs")] #[cfg(feature = "transparent-inputs")]
use { use {
crate::wallet::WalletTransparentOutput, crate::wallet::WalletTransparentOutput,
@ -550,7 +552,7 @@ where
payment.memo.clone().unwrap_or_else(MemoBytes::empty), payment.memo.clone().unwrap_or_else(MemoBytes::empty),
)?; )?;
sapling_output_meta.push(( sapling_output_meta.push((
Recipient::Unified(ua.clone(), PoolType::Sapling), Recipient::Unified(ua.clone(), PoolType::Shielded(ShieldedProtocol::Sapling)),
payment.amount, payment.amount,
payment.memo.clone(), payment.memo.clone(),
)); ));
@ -589,7 +591,10 @@ where
MemoBytes::empty(), MemoBytes::empty(),
)?; )?;
sapling_output_meta.push(( sapling_output_meta.push((
Recipient::InternalAccount(account, PoolType::Sapling), Recipient::InternalAccount(
account,
PoolType::Shielded(ShieldedProtocol::Sapling),
),
*amount, *amount,
change_memo.clone(), change_memo.clone(),
)) ))
@ -610,8 +615,11 @@ where
.output_index(i) .output_index(i)
.expect("An output should exist in the transaction for each shielded payment."); .expect("An output should exist in the transaction for each shielded payment.");
let received_as = let received_as = if let Recipient::InternalAccount(
if let Recipient::InternalAccount(account, PoolType::Sapling) = recipient { account,
PoolType::Shielded(ShieldedProtocol::Sapling),
) = recipient
{
tx.sapling_bundle().and_then(|bundle| { tx.sapling_bundle().and_then(|bundle| {
try_sapling_note_decryption( try_sapling_note_decryption(
params, params,

View File

@ -21,7 +21,7 @@ use zcash_primitives::{
zip32::{sapling::DiversifiableFullViewingKey, AccountId, Scope}, zip32::{sapling::DiversifiableFullViewingKey, AccountId, Scope},
}; };
use crate::data_api::{BlockMetadata, ScannedBlock}; use crate::data_api::{BlockMetadata, ScannedBlock, ShieldedProtocol};
use crate::{ use crate::{
proto::compact_formats::CompactBlock, proto::compact_formats::CompactBlock,
scan::{Batch, BatchRunner, Tasks}, scan::{Batch, BatchRunner, Tasks},
@ -116,17 +116,29 @@ pub enum ScanError {
/// the current chain tip. /// the current chain tip.
PrevHashMismatch { at_height: BlockHeight }, PrevHashMismatch { at_height: BlockHeight },
/// The block height field of the proposed new chain tip is not equal to the height of the /// The block height field of the proposed new block is not equal to the height of the previous
/// previous chain tip + 1. This variant stores a copy of the incorrect height value for /// block + 1.
/// reporting purposes.
BlockHeightDiscontinuity { BlockHeightDiscontinuity {
previous_tip: BlockHeight, prev_height: BlockHeight,
new_height: BlockHeight, new_height: BlockHeight,
}, },
/// The size of the Sapling note commitment tree was not provided as part of a [`CompactBlock`] /// The note commitment tree size for the given protocol at the proposed new block is not equal
/// being scanned, making it impossible to construct the nullifier for a detected note. /// to the size at the previous block plus the count of this block's outputs.
SaplingTreeSizeUnknown { at_height: BlockHeight }, TreeSizeMismatch {
protocol: ShieldedProtocol,
at_height: BlockHeight,
given: u32,
computed: u32,
},
/// The size of the note commitment tree for the given protocol was not provided as part of a
/// [`CompactBlock`] being scanned, making it impossible to construct the nullifier for a
/// detected note.
TreeSizeUnknown {
protocol: ShieldedProtocol,
at_height: BlockHeight,
},
} }
impl fmt::Display for ScanError { impl fmt::Display for ScanError {
@ -137,11 +149,14 @@ impl fmt::Display for ScanError {
"The parent hash of proposed block does not correspond to the block hash at height {}.", "The parent hash of proposed block does not correspond to the block hash at height {}.",
at_height at_height
), ),
ScanError::BlockHeightDiscontinuity { previous_tip, new_height } => { ScanError::BlockHeightDiscontinuity { prev_height, new_height } => {
write!(f, "Block height discontinuity at height {}; next height is : {}", previous_tip, new_height) write!(f, "Block height discontinuity at height {}; next height is : {}", prev_height, new_height)
} }
ScanError::SaplingTreeSizeUnknown { at_height } => { ScanError::TreeSizeMismatch { protocol, at_height, given, computed } => {
write!(f, "Unable to determine Sapling note commitment tree size at height {}", at_height) write!(f, "The {:?} note commitment tree size provided by a compact block did not match the expected size at height {}; given {}, expected {}", protocol, at_height, given, computed)
}
ScanError::TreeSizeUnknown { protocol, at_height } => {
write!(f, "Unable to determine {:?} note commitment tree size at height {}", protocol, at_height)
} }
} }
} }
@ -224,6 +239,46 @@ pub(crate) fn add_block_to_runner<P, S, T>(
} }
} }
pub(crate) fn check_continuity(
block: &CompactBlock,
prior_block_metadata: Option<&BlockMetadata>,
) -> Option<ScanError> {
if let Some(prev) = prior_block_metadata {
if block.height() != prev.block_height() + 1 {
return Some(ScanError::BlockHeightDiscontinuity {
prev_height: prev.block_height(),
new_height: block.height(),
});
}
if block.prev_hash() != prev.block_hash() {
return Some(ScanError::PrevHashMismatch {
at_height: block.height(),
});
}
if let Some(given) = block
.chain_metadata
.as_ref()
.map(|m| m.sapling_commitment_tree_size)
{
let computed = prev.sapling_tree_size()
+ u32::try_from(block.vtx.iter().map(|tx| tx.outputs.len()).sum::<usize>())
.unwrap();
if given != computed {
return Some(ScanError::TreeSizeMismatch {
protocol: ShieldedProtocol::Sapling,
at_height: block.height(),
given,
computed,
});
}
}
}
None
}
#[tracing::instrument(skip_all, fields(height = block.height))] #[tracing::instrument(skip_all, fields(height = block.height))]
pub(crate) fn scan_block_with_runner< pub(crate) fn scan_block_with_runner<
P: consensus::Parameters + Send + 'static, P: consensus::Parameters + Send + 'static,
@ -237,26 +292,13 @@ pub(crate) fn scan_block_with_runner<
prior_block_metadata: Option<&BlockMetadata>, prior_block_metadata: Option<&BlockMetadata>,
mut batch_runner: Option<&mut TaggedBatchRunner<P, K::Scope, T>>, mut batch_runner: Option<&mut TaggedBatchRunner<P, K::Scope, T>>,
) -> Result<ScannedBlock<K::Nf>, ScanError> { ) -> Result<ScannedBlock<K::Nf>, ScanError> {
let mut wtxs: Vec<WalletTx<K::Nf>> = vec![]; if let Some(scan_error) = check_continuity(&block, prior_block_metadata) {
let mut sapling_note_commitments: Vec<(sapling::Node, Retention<BlockHeight>)> = vec![]; return Err(scan_error);
}
let cur_height = block.height(); let cur_height = block.height();
let cur_hash = block.hash(); let cur_hash = block.hash();
if let Some(prev) = prior_block_metadata {
if cur_height != prev.block_height() + 1 {
return Err(ScanError::BlockHeightDiscontinuity {
previous_tip: prev.block_height(),
new_height: cur_height,
});
}
if block.prev_hash() != prev.block_hash() {
return Err(ScanError::PrevHashMismatch {
at_height: cur_height,
});
}
}
// It's possible to make progress without a Sapling tree position if we don't have any Sapling // It's possible to make progress without a Sapling tree position if we don't have any Sapling
// notes in the block, since we only use the position for constructing nullifiers for our own // notes in the block, since we only use the position for constructing nullifiers for our own
// received notes. Thus, we allow it to be optional here, and only produce an error if we try // received notes. Thus, we allow it to be optional here, and only produce an error if we try
@ -281,11 +323,14 @@ pub(crate) fn scan_block_with_runner<
} }
}) })
.or_else(|| prior_block_metadata.map(|m| m.sapling_tree_size())) .or_else(|| prior_block_metadata.map(|m| m.sapling_tree_size()))
.ok_or(ScanError::SaplingTreeSizeUnknown { .ok_or(ScanError::TreeSizeUnknown {
protocol: ShieldedProtocol::Sapling,
at_height: cur_height, at_height: cur_height,
})?; })?;
let compact_block_tx_count = block.vtx.len(); let compact_block_tx_count = block.vtx.len();
let mut wtxs: Vec<WalletTx<K::Nf>> = vec![];
let mut sapling_note_commitments: Vec<(sapling::Node, Retention<BlockHeight>)> = vec![];
for (tx_idx, tx) in block.vtx.into_iter().enumerate() { for (tx_idx, tx) in block.vtx.into_iter().enumerate() {
let txid = tx.txid(); let txid = tx.txid();

View File

@ -330,7 +330,14 @@ mod tests {
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
// Scan the cache // Scan the cache
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
// Create a second fake CompactBlock sending more value to the address // Create a second fake CompactBlock sending more value to the address
let (cb2, _) = fake_compact_block( let (cb2, _) = fake_compact_block(
@ -344,7 +351,14 @@ mod tests {
insert_into_cache(&db_cache, &cb2); insert_into_cache(&db_cache, &cb2);
// Scan the cache again // Scan the cache again
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 1,
1,
)
.unwrap();
} }
#[test] #[test]
@ -381,7 +395,14 @@ mod tests {
insert_into_cache(&db_cache, &cb2); insert_into_cache(&db_cache, &cb2);
// Scan the cache // Scan the cache
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
2,
)
.unwrap();
// Create more fake CompactBlocks that don't connect to the scanned ones // Create more fake CompactBlocks that don't connect to the scanned ones
let (cb3, _) = fake_compact_block( let (cb3, _) = fake_compact_block(
@ -405,7 +426,13 @@ mod tests {
// Data+cache chain should be invalid at the data/cache boundary // Data+cache chain should be invalid at the data/cache boundary
assert_matches!( assert_matches!(
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None), scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 2,
2
),
Err(_) // FIXME: check error result more closely Err(_) // FIXME: check error result more closely
); );
} }
@ -444,7 +471,14 @@ mod tests {
insert_into_cache(&db_cache, &cb2); insert_into_cache(&db_cache, &cb2);
// Scan the cache // Scan the cache
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
2,
)
.unwrap();
// Create more fake CompactBlocks that contain a reorg // Create more fake CompactBlocks that contain a reorg
let (cb3, _) = fake_compact_block( let (cb3, _) = fake_compact_block(
@ -468,7 +502,13 @@ mod tests {
// Data+cache chain should be invalid inside the cache // Data+cache chain should be invalid inside the cache
assert_matches!( assert_matches!(
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None), scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 2,
2
),
Err(_) // FIXME: check error result more closely Err(_) // FIXME: check error result more closely
); );
} }
@ -516,7 +556,14 @@ mod tests {
insert_into_cache(&db_cache, &cb2); insert_into_cache(&db_cache, &cb2);
// Scan the cache // Scan the cache
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
2,
)
.unwrap();
// Account balance should reflect both received notes // Account balance should reflect both received notes
assert_eq!( assert_eq!(
@ -551,7 +598,14 @@ mod tests {
); );
// Scan the cache again // Scan the cache again
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
2,
)
.unwrap();
// Account balance should again reflect both received notes // Account balance should again reflect both received notes
assert_eq!( assert_eq!(
@ -586,7 +640,14 @@ mod tests {
0, 0,
); );
insert_into_cache(&db_cache, &cb1); insert_into_cache(&db_cache, &cb1);
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
assert_eq!( assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(), get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
value value
@ -617,8 +678,8 @@ mod tests {
&tests::network(), &tests::network(),
&db_cache, &db_cache,
&mut db_data, &mut db_data,
Some(sapling_activation_height() + 2), sapling_activation_height() + 2,
None 1
), ),
Ok(_) Ok(_)
); );
@ -629,8 +690,8 @@ mod tests {
&tests::network(), &tests::network(),
&db_cache, &db_cache,
&mut db_data, &mut db_data,
Some(sapling_activation_height() + 1), sapling_activation_height() + 1,
Some(1), 1,
) )
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
@ -699,7 +760,14 @@ mod tests {
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
// Scan the cache // Scan the cache
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
// Account balance should reflect the received note // Account balance should reflect the received note
assert_eq!( assert_eq!(
@ -720,7 +788,14 @@ mod tests {
insert_into_cache(&db_cache, &cb2); insert_into_cache(&db_cache, &cb2);
// Scan the cache again // Scan the cache again
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 1,
1,
)
.unwrap();
// Account balance should reflect both received notes // Account balance should reflect both received notes
assert_eq!( assert_eq!(
@ -761,7 +836,14 @@ mod tests {
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
// Scan the cache // Scan the cache
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
// Account balance should reflect the received note // Account balance should reflect the received note
assert_eq!( assert_eq!(
@ -787,7 +869,14 @@ mod tests {
); );
// Scan the cache again // Scan the cache again
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 1,
1,
)
.unwrap();
// Account balance should equal the change // Account balance should equal the change
assert_eq!( assert_eq!(

View File

@ -59,7 +59,8 @@ use zcash_client_backend::{
self, self,
chain::{BlockSource, CommitmentTreeRoot}, chain::{BlockSource, CommitmentTreeRoot},
BlockMetadata, DecryptedTransaction, NullifierQuery, PoolType, Recipient, ScannedBlock, BlockMetadata, DecryptedTransaction, NullifierQuery, PoolType, Recipient, ScannedBlock,
SentTransaction, WalletCommitmentTrees, WalletRead, WalletWrite, SAPLING_SHARD_HEIGHT, SentTransaction, ShieldedProtocol, WalletCommitmentTrees, WalletRead, WalletWrite,
SAPLING_SHARD_HEIGHT,
}, },
keys::{UnifiedFullViewingKey, UnifiedSpendingKey}, keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
proto::compact_formats::CompactBlock, proto::compact_formats::CompactBlock,
@ -459,7 +460,10 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
let recipient = if output.transfer_type == TransferType::Outgoing { let recipient = if output.transfer_type == TransferType::Outgoing {
Recipient::Sapling(output.note.recipient()) Recipient::Sapling(output.note.recipient())
} else { } else {
Recipient::InternalAccount(output.account, PoolType::Sapling) Recipient::InternalAccount(
output.account,
PoolType::Shielded(ShieldedProtocol::Sapling)
)
}; };
wallet::put_sent_output( wallet::put_sent_output(

View File

@ -65,9 +65,10 @@
//! - `memo` the shielded memo associated with the output, if any. //! - `memo` the shielded memo associated with the output, if any.
use rusqlite::{self, named_params, OptionalExtension, ToSql}; use rusqlite::{self, named_params, OptionalExtension, ToSql};
use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::io::Cursor; use std::io::{self, Cursor};
use std::{collections::HashMap, io}; use zcash_client_backend::data_api::ShieldedProtocol;
use zcash_primitives::{ use zcash_primitives::{
block::BlockHash, block::BlockHash,
@ -116,7 +117,7 @@ pub(crate) fn pool_code(pool_type: PoolType) -> i64 {
// implementation detail. // implementation detail.
match pool_type { match pool_type {
PoolType::Transparent => 0i64, PoolType::Transparent => 0i64,
PoolType::Sapling => 2i64, PoolType::Shielded(ShieldedProtocol::Sapling) => 2i64,
} }
} }
@ -1119,7 +1120,11 @@ fn recipient_params<P: consensus::Parameters>(
) -> (Option<String>, Option<u32>, PoolType) { ) -> (Option<String>, Option<u32>, PoolType) {
match to { match to {
Recipient::Transparent(addr) => (Some(addr.encode(params)), None, PoolType::Transparent), Recipient::Transparent(addr) => (Some(addr.encode(params)), None, PoolType::Transparent),
Recipient::Sapling(addr) => (Some(addr.encode(params)), None, PoolType::Sapling), Recipient::Sapling(addr) => (
Some(addr.encode(params)),
None,
PoolType::Shielded(ShieldedProtocol::Sapling),
),
Recipient::Unified(addr, pool) => (Some(addr.encode(params)), None, *pool), Recipient::Unified(addr, pool) => (Some(addr.encode(params)), None, *pool),
Recipient::InternalAccount(id, pool) => (None, Some(u32::from(*id)), *pool), Recipient::InternalAccount(id, pool) => (None, Some(u32::from(*id)), *pool),
} }

View File

@ -8,7 +8,9 @@ use secrecy::{ExposeSecret, SecretVec};
use uuid::Uuid; use uuid::Uuid;
use zcash_client_backend::{ use zcash_client_backend::{
address::RecipientAddress, data_api::PoolType, keys::UnifiedSpendingKey, address::RecipientAddress,
data_api::{PoolType, ShieldedProtocol},
keys::UnifiedSpendingKey,
}; };
use zcash_primitives::{consensus, zip32::AccountId}; use zcash_primitives::{consensus, zip32::AccountId};
@ -229,7 +231,9 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
)) ))
})?; })?;
let output_pool = match decoded_address { let output_pool = match decoded_address {
RecipientAddress::Shielded(_) => Ok(pool_code(PoolType::Sapling)), RecipientAddress::Shielded(_) => {
Ok(pool_code(PoolType::Shielded(ShieldedProtocol::Sapling)))
}
RecipientAddress::Transparent(_) => Ok(pool_code(PoolType::Transparent)), RecipientAddress::Transparent(_) => Ok(pool_code(PoolType::Transparent)),
RecipientAddress::Unified(_) => Err(WalletMigrationError::CorruptedData( RecipientAddress::Unified(_) => Err(WalletMigrationError::CorruptedData(
"Unified addresses should not yet appear in the sent_notes table." "Unified addresses should not yet appear in the sent_notes table."

View File

@ -6,9 +6,10 @@ use rusqlite::{self, named_params};
use schemer; use schemer;
use schemer_rusqlite::RusqliteMigration; use schemer_rusqlite::RusqliteMigration;
use uuid::Uuid; use uuid::Uuid;
use zcash_client_backend::data_api::{PoolType, ShieldedProtocol};
use super::add_transaction_views; use super::add_transaction_views;
use crate::wallet::{init::WalletMigrationError, pool_code, PoolType}; use crate::wallet::{init::WalletMigrationError, pool_code};
pub(super) const MIGRATION_ID: Uuid = Uuid::from_fields( pub(super) const MIGRATION_ID: Uuid = Uuid::from_fields(
0x2aa4d24f, 0x2aa4d24f,
@ -48,7 +49,7 @@ impl RusqliteMigration for Migration {
SELECT tx, :output_pool, output_index, from_account, from_account, value SELECT tx, :output_pool, output_index, from_account, from_account, value
FROM sent_notes", FROM sent_notes",
named_params![ named_params![
":output_pool": &pool_code(PoolType::Sapling) ":output_pool": &pool_code(PoolType::Shielded(ShieldedProtocol::Sapling))
] ]
)?; )?;

View File

@ -571,7 +571,14 @@ pub(crate) mod tests {
0, 0,
); );
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
// Verified balance matches total balance // Verified balance matches total balance
let (_, anchor_height) = db_data let (_, anchor_height) = db_data
@ -598,7 +605,14 @@ pub(crate) mod tests {
) )
.0; .0;
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 1,
1,
)
.unwrap();
// Verified balance does not include the second note // Verified balance does not include the second note
let (_, anchor_height2) = db_data let (_, anchor_height2) = db_data
@ -651,7 +665,14 @@ pub(crate) mod tests {
.0; .0;
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
} }
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 2,
8,
)
.unwrap();
// Second spend still fails // Second spend still fails
assert_matches!( assert_matches!(
@ -681,11 +702,18 @@ pub(crate) mod tests {
&dfvk, &dfvk,
AddressType::DefaultExternal, AddressType::DefaultExternal,
value, value,
11, 10,
) )
.0; .0;
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 10,
1,
)
.unwrap();
// Second spend should now succeed // Second spend should now succeed
assert_matches!( assert_matches!(
@ -730,7 +758,14 @@ pub(crate) mod tests {
0, 0,
); );
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
assert_eq!( assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(), get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
value value
@ -788,7 +823,14 @@ pub(crate) mod tests {
.0; .0;
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
} }
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 1,
41,
)
.unwrap();
// Second spend still fails // Second spend still fails
assert_matches!( assert_matches!(
@ -821,7 +863,14 @@ pub(crate) mod tests {
) )
.0; .0;
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 42,
1,
)
.unwrap();
// Second spend should now succeed // Second spend should now succeed
create_spend_to_address( create_spend_to_address(
@ -865,7 +914,14 @@ pub(crate) mod tests {
0, 0,
); );
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
assert_eq!( assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(), get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
value value
@ -938,7 +994,14 @@ pub(crate) mod tests {
.0; .0;
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
} }
scan_cached_blocks(&network, &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&network,
&db_cache,
&mut db_data,
sapling_activation_height() + 1,
42,
)
.unwrap();
// Send the funds again, discarding history. // Send the funds again, discarding history.
// Neither transaction output is decryptable by the sender. // Neither transaction output is decryptable by the sender.
@ -971,7 +1034,14 @@ pub(crate) mod tests {
0, 0,
); );
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
// Verified balance matches total balance // Verified balance matches total balance
let (_, anchor_height) = db_data let (_, anchor_height) = db_data
@ -1030,7 +1100,14 @@ pub(crate) mod tests {
0, 0,
); );
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
// Verified balance matches total balance // Verified balance matches total balance
let (_, anchor_height) = db_data let (_, anchor_height) = db_data
@ -1103,7 +1180,14 @@ pub(crate) mod tests {
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
} }
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
11,
)
.unwrap();
// Verified balance matches total balance // Verified balance matches total balance
let total = Amount::from_u64(60000).unwrap(); let total = Amount::from_u64(60000).unwrap();
@ -1225,7 +1309,14 @@ pub(crate) mod tests {
0, 0,
); );
insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb);
scan_cached_blocks(&tests::network(), &db_cache, &mut db_data, None, None).unwrap(); scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
assert_matches!( assert_matches!(
shield_transparent_funds( shield_transparent_funds(