Skip to content

Commit

Permalink
zcash_client_backend: Require the tree state for the start of each sc…
Browse files Browse the repository at this point in the history
…anned range.

In order to support constructing the anchor for multiple pools with a
common anchor height, we must be able to checkpoint each note commitment
tree (and consequently compute the root) at that height. Since we may
not have the information in the tree needed to do so, we require that it
be provided.

As a bonus, this change makes it possible to improve the UX around
spendability, because we will no longer require subtree ranges below
received notes to be fully scanned; the inserted frontier provides
sufficient information to make them spendable.
  • Loading branch information
nuttycom committed Mar 11, 2024
1 parent d68a01a commit 3448b25
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 57 deletions.
6 changes: 2 additions & 4 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,7 @@ zip32 = "0.1"
lto = true
panic = 'abort'
codegen-units = 1

[patch.crates-io]
incrementalmerkletree = { git = "https://github.com/nuttycom/incrementalmerkletree", rev = "fa147c89c6c98a03bba745538f4e68d4eaed5146" }
shardtree = { git = "https://github.com/nuttycom/incrementalmerkletree", rev = "fa147c89c6c98a03bba745538f4e68d4eaed5146" }
21 changes: 15 additions & 6 deletions zcash_client_backend/src/data_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ use incrementalmerkletree::{frontier::Frontier, Retention};
use secrecy::SecretVec;
use shardtree::{error::ShardTreeError, store::ShardStore, ShardTree};

use self::{chain::CommitmentTreeRoot, scanning::ScanRange};
use self::{
chain::{ChainState, CommitmentTreeRoot},
scanning::ScanRange,
};
use crate::{
address::UnifiedAddress,
decrypt::DecryptedOutput,
Expand Down Expand Up @@ -1260,8 +1263,11 @@ pub trait WalletWrite: WalletRead {
/// 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>;
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 @@ -1400,9 +1406,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 @@ -1633,6 +1641,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
60 changes: 58 additions & 2 deletions zcash_client_backend/src/data_api/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
use std::ops::Range;

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

Expand Down Expand Up @@ -278,6 +279,58 @@ impl ScanSummary {
}
}

/// The final note commitment tree state for each shielded pool, as of a particular block height.
#[derive(Debug)]
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 [`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.
///
Expand All @@ -290,7 +343,7 @@ 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 +352,7 @@ where
DbT: WalletWrite,
<DbT as WalletRead>::AccountId: ConditionallySelectable + Default + Send + 'static,
{
let 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 +446,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
Loading

0 comments on commit 3448b25

Please sign in to comment.