Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

zcash_client_sqlite: Ensure that Orchard and Sapling checkpoints are always available for the same block heights. #1262

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,5 @@ codegen-units = 1

[patch.crates-io]
orchard = { git = "https://github.com/zcash/orchard", rev = "e74879dd0ad0918f4ffe0826e03905cd819981bd" }
incrementalmerkletree = { git = "https://github.com/nuttycom/incrementalmerkletree", rev = "fa147c89c6c98a03bba745538f4e68d4eaed5146" }
shardtree = { git = "https://github.com/nuttycom/incrementalmerkletree", rev = "fa147c89c6c98a03bba745538f4e68d4eaed5146" }
26 changes: 19 additions & 7 deletions zcash_client_backend/src/data_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ use secrecy::SecretVec;
use shardtree::{error::ShardTreeError, store::ShardStore, ShardTree};
use zcash_keys::keys::HdSeedFingerprint;

use self::{chain::CommitmentTreeRoot, scanning::ScanRange};
use self::{
chain::{ChainState, CommitmentTreeRoot},
scanning::ScanRange,
};
use crate::{
address::UnifiedAddress,
decrypt::DecryptedOutput,
Expand Down Expand Up @@ -1300,9 +1303,15 @@ pub trait WalletWrite: WalletRead {
/// along with the note commitments that were detected when scanning the block for transactions
/// pertaining to this wallet.
///
/// `blocks` must be sequential, in order of increasing block height
fn put_blocks(&mut self, blocks: Vec<ScannedBlock<Self::AccountId>>)
-> Result<(), Self::Error>;
/// ### Arguments
/// - `from_state` must be the chain state for the block height prior to the first
/// block in `blocks`.
/// - `blocks` must be sequential, in order of increasing block height.
fn put_blocks(
&mut self,
from_state: &ChainState,
blocks: Vec<ScannedBlock<Self::AccountId>>,
) -> Result<(), Self::Error>;

/// Updates the wallet's view of the blockchain.
///
Expand Down Expand Up @@ -1442,9 +1451,11 @@ pub mod testing {
};

use super::{
chain::CommitmentTreeRoot, scanning::ScanRange, AccountBirthday, BlockMetadata,
DecryptedTransaction, InputSource, NullifierQuery, ScannedBlock, SentTransaction,
WalletCommitmentTrees, WalletRead, WalletSummary, WalletWrite, SAPLING_SHARD_HEIGHT,
chain::{ChainState, CommitmentTreeRoot},
scanning::ScanRange,
AccountBirthday, BlockMetadata, DecryptedTransaction, InputSource, NullifierQuery,
ScannedBlock, SentTransaction, WalletCommitmentTrees, WalletRead, WalletSummary,
WalletWrite, SAPLING_SHARD_HEIGHT,
};

#[cfg(feature = "transparent-inputs")]
Expand Down Expand Up @@ -1684,6 +1695,7 @@ pub mod testing {
#[allow(clippy::type_complexity)]
fn put_blocks(
&mut self,
_from_state: &ChainState,
_blocks: Vec<ScannedBlock<Self::AccountId>>,
) -> Result<(), Self::Error> {
Ok(())
Expand Down
86 changes: 83 additions & 3 deletions zcash_client_backend/src/data_api/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,20 @@
//! // the first element of the vector of suggested ranges.
//! match scan_ranges.first() {
//! Some(scan_range) if scan_range.priority() == ScanPriority::Verify => {
//! // Download the chain state for the block prior to the start of the range you want
//! // to scan.
//! let chain_state = unimplemented!("get_chain_state(scan_range.block_range().start - 1)?;");
//! // Download the blocks in `scan_range` into the block source, overwriting any
//! // existing blocks in this range.
//! unimplemented!();
//! unimplemented!("cache_blocks(scan_range)?;");
//!
//! // Scan the downloaded blocks
//! let scan_result = scan_cached_blocks(
//! &network,
//! &block_source,
//! &mut wallet_db,
//! scan_range.block_range().start,
//! chain_state,
//! scan_range.len()
//! );
//!
Expand Down Expand Up @@ -118,21 +122,25 @@
//! // encountered, this process should be repeated starting at step (3).
//! let scan_ranges = wallet_db.suggest_scan_ranges().map_err(Error::Wallet)?;
//! for scan_range in scan_ranges {
//! // Download the chain state for the block prior to the start of the range you want
//! // to scan.
//! let chain_state = unimplemented!("get_chain_state(scan_range.block_range().start - 1)?;");
//! // Download the blocks in `scan_range` into the block source. While in this example this
//! // step is performed in-line, it's fine for the download of scan ranges to be asynchronous
//! // and for the scanner to process the downloaded ranges as they become available in a
//! // separate thread. The scan ranges should also be broken down into smaller chunks as
//! // appropriate, and for ranges with priority `Historic` it can be useful to download and
//! // scan the range in reverse order (to discover more recent unspent notes sooner), or from
//! // the start and end of the range inwards.
//! unimplemented!();
//! unimplemented!("cache_blocks(scan_range)?;");
//!
//! // Scan the downloaded blocks.
//! let scan_result = scan_cached_blocks(
//! &network,
//! &block_source,
//! &mut wallet_db,
//! scan_range.block_range().start,
//! chain_state,
//! scan_range.len()
//! )?;
//!
Expand All @@ -145,6 +153,7 @@

use std::ops::Range;

use incrementalmerkletree::frontier::Frontier;
use subtle::ConditionallySelectable;
use zcash_primitives::consensus::{self, BlockHeight};

Expand Down Expand Up @@ -278,19 +287,86 @@ impl ScanSummary {
}
}

/// The final note commitment tree state for each shielded pool, as of a particular block height.
#[derive(Debug, Clone)]
pub struct ChainState {
block_height: BlockHeight,
final_sapling_tree: Frontier<sapling::Node, { sapling::NOTE_COMMITMENT_TREE_DEPTH }>,
#[cfg(feature = "orchard")]
final_orchard_tree:
Frontier<orchard::tree::MerkleHashOrchard, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }>,
}

impl ChainState {
/// Construct a new empty chain state.
pub fn empty(block_height: BlockHeight) -> Self {
Self {
block_height,
final_sapling_tree: Frontier::empty(),
#[cfg(feature = "orchard")]
final_orchard_tree: Frontier::empty(),
}
}

/// Construct a new [`ChainState`] from its constituent parts.
pub fn new(
block_height: BlockHeight,
final_sapling_tree: Frontier<sapling::Node, { sapling::NOTE_COMMITMENT_TREE_DEPTH }>,
#[cfg(feature = "orchard")] final_orchard_tree: Frontier<
orchard::tree::MerkleHashOrchard,
{ orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
>,
) -> Self {
Self {
block_height,
final_sapling_tree,
#[cfg(feature = "orchard")]
final_orchard_tree,
}
}

/// Returns the block height to which this chain state applies.
pub fn block_height(&self) -> BlockHeight {
self.block_height
}

/// Returns the frontier of the Sapling note commitment tree as of the end of the block at
/// [`Self::block_height`].
pub fn final_sapling_tree(
&self,
) -> &Frontier<sapling::Node, { sapling::NOTE_COMMITMENT_TREE_DEPTH }> {
&self.final_sapling_tree
}

/// Returns the frontier of the Orchard note commitment tree as of the end of the block at
/// [`Self::block_height`].
#[cfg(feature = "orchard")]
pub fn final_orchard_tree(
&self,
) -> &Frontier<orchard::tree::MerkleHashOrchard, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }>
{
&self.final_orchard_tree
}
}

/// Scans at most `limit` blocks from the provided block source for in order to find transactions
/// received by the accounts tracked in the provided wallet database.
///
/// This function will return after scanning at most `limit` new blocks, to enable the caller to
/// update their UI with scanning progress. Repeatedly calling this function with `from_height ==
/// None` will process sequential ranges of blocks.
///
/// ## Panics
///
/// This method will panic if `from_height != from_state.block_height() + 1`.
#[tracing::instrument(skip(params, block_source, data_db))]
#[allow(clippy::type_complexity)]
pub fn scan_cached_blocks<ParamsT, DbT, BlockSourceT>(
params: &ParamsT,
block_source: &BlockSourceT,
data_db: &mut DbT,
from_height: BlockHeight,
from_state: &ChainState,
limit: usize,
) -> Result<ScanSummary, Error<DbT::Error, BlockSourceT::Error>>
where
Expand All @@ -299,6 +375,8 @@ where
DbT: WalletWrite,
<DbT as WalletRead>::AccountId: ConditionallySelectable + Default + Send + 'static,
{
assert_eq!(from_height, from_state.block_height + 1);

// Fetch the UnifiedFullViewingKeys we are tracking
let account_ufvks = data_db
.get_unified_full_viewing_keys()
Expand Down Expand Up @@ -392,7 +470,9 @@ where
},
)?;

data_db.put_blocks(scanned_blocks).map_err(Error::Wallet)?;
data_db
.put_blocks(from_state, scanned_blocks)
.map_err(Error::Wallet)?;
Ok(scan_summary)
}

Expand Down
3 changes: 2 additions & 1 deletion zcash_client_backend/src/data_api/wallet/input_selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,9 @@ where
::sapling::builder::BundleType::DEFAULT,
&shielded_inputs
.iter()
.cloned()
.filter_map(|i| {
i.clone().traverse_opt(|wn| match wn {
i.traverse_opt(|wn| match wn {
Note::Sapling(n) => Some(n),
#[cfg(feature = "orchard")]
_ => None,
Expand Down
2 changes: 1 addition & 1 deletion zcash_client_backend/src/scanning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ where
self.orchard.add_outputs(
block_hash,
txid,
|action| OrchardDomain::for_compact_action(action),
OrchardDomain::for_compact_action,
&tx.actions
.iter()
.enumerate()
Expand Down
2 changes: 2 additions & 0 deletions zcash_client_sqlite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,12 @@ maybe-rayon.workspace = true

[dev-dependencies]
assert_matches.workspace = true
bls12_381.workspace = true
incrementalmerkletree = { workspace = true, features = ["test-dependencies"] }
pasta_curves.workspace = true
shardtree = { workspace = true, features = ["legacy-api", "test-dependencies"] }
nonempty.workspace = true
orchard = { workspace = true, features = ["test-dependencies"] }
proptest.workspace = true
rand_chacha.workspace = true
rand_core.workspace = true
Expand Down
4 changes: 3 additions & 1 deletion zcash_client_sqlite/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,14 +338,16 @@ mod tests {
testing::pool::valid_chain_states::<OrchardPoolTester>()
}

// FIXME: This requires test framework fixes to pass.
#[test]
#[cfg(feature = "orchard")]
fn invalid_chain_cache_disconnected_sapling() {
testing::pool::invalid_chain_cache_disconnected::<SaplingPoolTester>()
}

#[test]
#[cfg(feature = "orchard")]
fn invalid_chain_cache_disconnected_orchard() {
#[cfg(feature = "orchard")]
testing::pool::invalid_chain_cache_disconnected::<OrchardPoolTester>()
}

Expand Down
Loading
Loading