Skip to content

Commit 5f9bcc2

Browse files
committed
zcash_client_backend: Require the tree state for the start of each scanned 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.
1 parent d68a01a commit 5f9bcc2

File tree

8 files changed

+445
-137
lines changed

8 files changed

+445
-137
lines changed

Cargo.lock

+4-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,8 @@ zip32 = "0.1"
120120
lto = true
121121
panic = 'abort'
122122
codegen-units = 1
123+
124+
[patch.crates-io]
125+
incrementalmerkletree = { git = "https://github.com/nuttycom/incrementalmerkletree", rev = "fa147c89c6c98a03bba745538f4e68d4eaed5146" }
126+
shardtree = { git = "https://github.com/nuttycom/incrementalmerkletree", rev = "fa147c89c6c98a03bba745538f4e68d4eaed5146" }
127+
orchard = { git = "https://github.com/nuttycom/orchard", rev = "7ef1feaf1672980095f424be42fd5f79ba01a5aa" }

zcash_client_backend/src/data_api.rs

+15-6
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ use incrementalmerkletree::{frontier::Frontier, Retention};
6767
use secrecy::SecretVec;
6868
use shardtree::{error::ShardTreeError, store::ShardStore, ShardTree};
6969

70-
use self::{chain::CommitmentTreeRoot, scanning::ScanRange};
70+
use self::{
71+
chain::{ChainState, CommitmentTreeRoot},
72+
scanning::ScanRange,
73+
};
7174
use crate::{
7275
address::UnifiedAddress,
7376
decrypt::DecryptedOutput,
@@ -1260,8 +1263,11 @@ pub trait WalletWrite: WalletRead {
12601263
/// pertaining to this wallet.
12611264
///
12621265
/// `blocks` must be sequential, in order of increasing block height
1263-
fn put_blocks(&mut self, blocks: Vec<ScannedBlock<Self::AccountId>>)
1264-
-> Result<(), Self::Error>;
1266+
fn put_blocks(
1267+
&mut self,
1268+
from_state: &ChainState,
1269+
blocks: Vec<ScannedBlock<Self::AccountId>>,
1270+
) -> Result<(), Self::Error>;
12651271

12661272
/// Updates the wallet's view of the blockchain.
12671273
///
@@ -1400,9 +1406,11 @@ pub mod testing {
14001406
};
14011407

14021408
use super::{
1403-
chain::CommitmentTreeRoot, scanning::ScanRange, AccountBirthday, BlockMetadata,
1404-
DecryptedTransaction, InputSource, NullifierQuery, ScannedBlock, SentTransaction,
1405-
WalletCommitmentTrees, WalletRead, WalletSummary, WalletWrite, SAPLING_SHARD_HEIGHT,
1409+
chain::{ChainState, CommitmentTreeRoot},
1410+
scanning::ScanRange,
1411+
AccountBirthday, BlockMetadata, DecryptedTransaction, InputSource, NullifierQuery,
1412+
ScannedBlock, SentTransaction, WalletCommitmentTrees, WalletRead, WalletSummary,
1413+
WalletWrite, SAPLING_SHARD_HEIGHT,
14061414
};
14071415

14081416
#[cfg(feature = "transparent-inputs")]
@@ -1633,6 +1641,7 @@ pub mod testing {
16331641
#[allow(clippy::type_complexity)]
16341642
fn put_blocks(
16351643
&mut self,
1644+
_from_state: &ChainState,
16361645
_blocks: Vec<ScannedBlock<Self::AccountId>>,
16371646
) -> Result<(), Self::Error> {
16381647
Ok(())

zcash_client_backend/src/data_api/chain.rs

+68-2
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
146146
use std::ops::Range;
147147

148+
use incrementalmerkletree::frontier::Frontier;
148149
use subtle::ConditionallySelectable;
149150
use zcash_primitives::consensus::{self, BlockHeight};
150151

@@ -278,6 +279,68 @@ impl ScanSummary {
278279
}
279280
}
280281

282+
/// The final note commitment tree state for each shielded pool, as of a particular block height.
283+
#[derive(Debug, Clone)]
284+
pub struct ChainState {
285+
block_height: BlockHeight,
286+
final_sapling_tree: Frontier<sapling::Node, { sapling::NOTE_COMMITMENT_TREE_DEPTH }>,
287+
#[cfg(feature = "orchard")]
288+
final_orchard_tree:
289+
Frontier<orchard::tree::MerkleHashOrchard, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }>,
290+
}
291+
292+
impl ChainState {
293+
/// Construct a new empty chain state.
294+
pub fn empty(block_height: BlockHeight) -> Self {
295+
Self {
296+
block_height,
297+
final_sapling_tree: Frontier::empty(),
298+
#[cfg(feature = "orchard")]
299+
final_orchard_tree: Frontier::empty(),
300+
}
301+
}
302+
303+
/// Construct a new [`ChainState`] from its constituent parts.
304+
pub fn new(
305+
block_height: BlockHeight,
306+
final_sapling_tree: Frontier<sapling::Node, { sapling::NOTE_COMMITMENT_TREE_DEPTH }>,
307+
#[cfg(feature = "orchard")] final_orchard_tree: Frontier<
308+
orchard::tree::MerkleHashOrchard,
309+
{ orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
310+
>,
311+
) -> Self {
312+
Self {
313+
block_height,
314+
final_sapling_tree,
315+
#[cfg(feature = "orchard")]
316+
final_orchard_tree,
317+
}
318+
}
319+
320+
/// Returns the block height to which this chain state applies.
321+
pub fn block_height(&self) -> BlockHeight {
322+
self.block_height
323+
}
324+
325+
/// Returns the frontier of the Sapling note commitment tree as of the end of the block at
326+
/// [`Self::block_height`].
327+
pub fn final_sapling_tree(
328+
&self,
329+
) -> &Frontier<sapling::Node, { sapling::NOTE_COMMITMENT_TREE_DEPTH }> {
330+
&self.final_sapling_tree
331+
}
332+
333+
/// Returns the frontier of the Orchard note commitment tree as of the end of the block at
334+
/// [`Self::block_height`].
335+
#[cfg(feature = "orchard")]
336+
pub fn final_orchard_tree(
337+
&self,
338+
) -> &Frontier<orchard::tree::MerkleHashOrchard, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }>
339+
{
340+
&self.final_orchard_tree
341+
}
342+
}
343+
281344
/// Scans at most `limit` blocks from the provided block source for in order to find transactions
282345
/// received by the accounts tracked in the provided wallet database.
283346
///
@@ -290,7 +353,7 @@ pub fn scan_cached_blocks<ParamsT, DbT, BlockSourceT>(
290353
params: &ParamsT,
291354
block_source: &BlockSourceT,
292355
data_db: &mut DbT,
293-
from_height: BlockHeight,
356+
from_state: &ChainState,
294357
limit: usize,
295358
) -> Result<ScanSummary, Error<DbT::Error, BlockSourceT::Error>>
296359
where
@@ -299,6 +362,7 @@ where
299362
DbT: WalletWrite,
300363
<DbT as WalletRead>::AccountId: ConditionallySelectable + Default + Send + 'static,
301364
{
365+
let from_height = from_state.block_height + 1;
302366
// Fetch the UnifiedFullViewingKeys we are tracking
303367
let account_ufvks = data_db
304368
.get_unified_full_viewing_keys()
@@ -392,7 +456,9 @@ where
392456
},
393457
)?;
394458

395-
data_db.put_blocks(scanned_blocks).map_err(Error::Wallet)?;
459+
data_db
460+
.put_blocks(from_state, scanned_blocks)
461+
.map_err(Error::Wallet)?;
396462
Ok(scan_summary)
397463
}
398464

zcash_client_sqlite/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,12 @@ maybe-rayon.workspace = true
7878

7979
[dev-dependencies]
8080
assert_matches.workspace = true
81+
bls12_381.workspace = true
8182
incrementalmerkletree = { workspace = true, features = ["test-dependencies"] }
8283
pasta_curves.workspace = true
8384
shardtree = { workspace = true, features = ["legacy-api", "test-dependencies"] }
8485
nonempty.workspace = true
86+
orchard = { workspace = true, features = ["test-dependencies"] }
8587
proptest.workspace = true
8688
rand_chacha.workspace = true
8789
rand_core.workspace = true

0 commit comments

Comments
 (0)