Skip to content

Commit

Permalink
Merge pull request #1254 from zcash/orchard-tables
Browse files Browse the repository at this point in the history
zcash_client_sqlite: Add database tables for Orchard
  • Loading branch information
str4d authored Mar 11, 2024
2 parents cc3f05a + ae9dd25 commit 0780272
Show file tree
Hide file tree
Showing 12 changed files with 957 additions and 202 deletions.
3 changes: 3 additions & 0 deletions zcash_client_backend/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this library adheres to Rust's notion of
- `SentTransaction::new`
- `ORCHARD_SHARD_HEIGHT`
- `BlockMetadata::orchard_tree_size`
- `WalletSummary::next_orchard_subtree_index`
- `chain::ScanSummary::{spent_orchard_note_count, received_orchard_note_count}`
- `zcash_client_backend::fees`:
- `orchard`
Expand Down Expand Up @@ -64,6 +65,8 @@ and this library adheres to Rust's notion of
- `fn put_orchard_subtree_roots`
- Added method `WalletRead::validate_seed`
- Removed `Error::AccountNotFound` variant.
- `WalletSummary::new` now takes an additional `next_orchard_subtree_index`
argument when the `orchard` feature flag is enabled.
- `zcash_client_backend::decrypt`:
- Fields of `DecryptedOutput` are now private. Use `DecryptedOutput::new`
and the newly provided accessors instead.
Expand Down
16 changes: 14 additions & 2 deletions zcash_client_backend/src/data_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,8 @@ pub struct WalletSummary<AccountId: Eq + Hash> {
fully_scanned_height: BlockHeight,
scan_progress: Option<Ratio<u64>>,
next_sapling_subtree_index: u64,
#[cfg(feature = "orchard")]
next_orchard_subtree_index: u64,
}

impl<AccountId: Eq + Hash> WalletSummary<AccountId> {
Expand All @@ -362,14 +364,17 @@ impl<AccountId: Eq + Hash> WalletSummary<AccountId> {
chain_tip_height: BlockHeight,
fully_scanned_height: BlockHeight,
scan_progress: Option<Ratio<u64>>,
next_sapling_subtree_idx: u64,
next_sapling_subtree_index: u64,
#[cfg(feature = "orchard")] next_orchard_subtree_index: u64,
) -> Self {
Self {
account_balances,
chain_tip_height,
fully_scanned_height,
scan_progress,
next_sapling_subtree_index: next_sapling_subtree_idx,
next_sapling_subtree_index,
#[cfg(feature = "orchard")]
next_orchard_subtree_index,
}
}

Expand Down Expand Up @@ -405,6 +410,13 @@ impl<AccountId: Eq + Hash> WalletSummary<AccountId> {
self.next_sapling_subtree_index
}

/// Returns the Orchard subtree index that should start the next range of subtree
/// roots passed to [`WalletCommitmentTrees::put_orchard_subtree_roots`].
#[cfg(feature = "orchard")]
pub fn next_orchard_subtree_index(&self) -> u64 {
self.next_orchard_subtree_index
}

/// Returns whether or not wallet scanning is complete.
pub fn is_synced(&self) -> bool {
self.chain_tip_height == self.fully_scanned_height
Expand Down
1 change: 1 addition & 0 deletions zcash_client_sqlite/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ and this library adheres to Rust's notion of
- `init::WalletMigrationError` has added variants:
- `WalletMigrationError::AddressGeneration`
- `WalletMigrationError::CannotRevert`
- The `v_transactions` and `v_tx_outputs` views now include Orchard notes.

## [0.9.1] - 2024-03-09

Expand Down
57 changes: 47 additions & 10 deletions zcash_client_sqlite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -956,7 +956,7 @@ impl<P: consensus::Parameters> WalletCommitmentTrees for WalletDb<rusqlite::Conn
>;

#[cfg(feature = "orchard")]
fn with_orchard_tree_mut<F, A, E>(&mut self, _callback: F) -> Result<A, E>
fn with_orchard_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
where
for<'a> F: FnMut(
&'a mut ShardTree<
Expand All @@ -967,16 +967,41 @@ impl<P: consensus::Parameters> WalletCommitmentTrees for WalletDb<rusqlite::Conn
) -> Result<A, E>,
E: From<ShardTreeError<Self::Error>>,
{
todo!()
let tx = self
.conn
.transaction()
.map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
let shard_store = SqliteShardStore::from_connection(&tx, ORCHARD_TABLES_PREFIX)
.map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
let result = {
let mut shardtree = ShardTree::new(shard_store, PRUNING_DEPTH.try_into().unwrap());
callback(&mut shardtree)?
};

tx.commit()
.map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
Ok(result)
}

#[cfg(feature = "orchard")]
fn put_orchard_subtree_roots(
&mut self,
_start_index: u64,
_roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
start_index: u64,
roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
) -> Result<(), ShardTreeError<Self::Error>> {
todo!()
let tx = self
.conn
.transaction()
.map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
put_shard_roots::<_, { ORCHARD_SHARD_HEIGHT * 2 }, ORCHARD_SHARD_HEIGHT>(
&tx,
ORCHARD_TABLES_PREFIX,
start_index,
roots,
)?;
tx.commit()
.map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
Ok(())
}
}

Expand Down Expand Up @@ -1027,7 +1052,7 @@ impl<'conn, P: consensus::Parameters> WalletCommitmentTrees for WalletDb<SqlTran
>;

#[cfg(feature = "orchard")]
fn with_orchard_tree_mut<F, A, E>(&mut self, _callback: F) -> Result<A, E>
fn with_orchard_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
where
for<'a> F: FnMut(
&'a mut ShardTree<
Expand All @@ -1038,16 +1063,28 @@ impl<'conn, P: consensus::Parameters> WalletCommitmentTrees for WalletDb<SqlTran
) -> Result<A, E>,
E: From<ShardTreeError<Self::Error>>,
{
todo!()
let mut shardtree = ShardTree::new(
SqliteShardStore::from_connection(self.conn.0, ORCHARD_TABLES_PREFIX)
.map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?,
PRUNING_DEPTH.try_into().unwrap(),
);
let result = callback(&mut shardtree)?;

Ok(result)
}

#[cfg(feature = "orchard")]
fn put_orchard_subtree_roots(
&mut self,
_start_index: u64,
_roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
start_index: u64,
roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
) -> Result<(), ShardTreeError<Self::Error>> {
todo!()
put_shard_roots::<_, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }, ORCHARD_SHARD_HEIGHT>(
self.conn.0,
ORCHARD_TABLES_PREFIX,
start_index,
roots,
)
}
}

Expand Down
23 changes: 10 additions & 13 deletions zcash_client_sqlite/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,7 @@ impl TestFvk for orchard::keys::FullViewingKey {
fn add_spend<R: RngCore + CryptoRng>(
&self,
ctx: &mut CompactTx,
nf: Self::Nullifier,
revealed_spent_note_nullifier: Self::Nullifier,
rng: &mut R,
) {
// Generate a dummy recipient.
Expand All @@ -977,7 +977,7 @@ impl TestFvk for orchard::keys::FullViewingKey {
};

let (cact, _) = compact_orchard_action(
nf,
revealed_spent_note_nullifier,
recipient,
NonNegativeAmount::ZERO,
self.orchard_ovk(zip32::Scope::Internal),
Expand All @@ -997,7 +997,7 @@ impl TestFvk for orchard::keys::FullViewingKey {
mut rng: &mut R,
) -> Self::Nullifier {
// Generate a dummy nullifier
let nullifier =
let revealed_spent_note_nullifier =
orchard::note::Nullifier::from_bytes(&pallas::Base::random(&mut rng).to_repr())
.unwrap();

Expand All @@ -1008,7 +1008,7 @@ impl TestFvk for orchard::keys::FullViewingKey {
};

let (cact, note) = compact_orchard_action(
nullifier,
revealed_spent_note_nullifier,
self.address_at(j, scope),
value,
self.orchard_ovk(scope),
Expand All @@ -1025,7 +1025,7 @@ impl TestFvk for orchard::keys::FullViewingKey {
ctx: &mut CompactTx,
_: &P,
_: BlockHeight,
nf: Self::Nullifier,
revealed_spent_note_nullifier: Self::Nullifier,
req: AddressType,
value: NonNegativeAmount,
_: u32,
Expand All @@ -1038,14 +1038,15 @@ impl TestFvk for orchard::keys::FullViewingKey {
};

let (cact, note) = compact_orchard_action(
nf,
revealed_spent_note_nullifier,
self.address_at(j, scope),
value,
self.orchard_ovk(scope),
rng,
);
ctx.actions.push(cact);

// Return the nullifier of the newly created output note
note.nullifier(self)
}
}
Expand Down Expand Up @@ -1100,8 +1101,6 @@ fn compact_orchard_action<R: RngCore + CryptoRng>(
ovk: Option<orchard::keys::OutgoingViewingKey>,
rng: &mut R,
) -> (CompactOrchardAction, orchard::Note) {
let nf = nullifier.to_bytes().to_vec();

let rseed = {
loop {
let mut bytes = [0; 32];
Expand All @@ -1120,16 +1119,14 @@ fn compact_orchard_action<R: RngCore + CryptoRng>(
)
.unwrap();
let encryptor = OrchardNoteEncryption::new(ovk, note, *MemoBytes::empty().as_array());
let cmx = orchard::note::ExtractedNoteCommitment::from(note.commitment())
.to_bytes()
.to_vec();
let cmx = orchard::note::ExtractedNoteCommitment::from(note.commitment());
let ephemeral_key = OrchardDomain::epk_bytes(encryptor.epk()).0.to_vec();
let enc_ciphertext = encryptor.encrypt_note_plaintext();

(
CompactOrchardAction {
nullifier: nf,
cmx,
nullifier: nullifier.to_bytes().to_vec(),
cmx: cmx.to_bytes().to_vec(),
ephemeral_key,
ciphertext: enc_ciphertext.as_ref()[..52].to_vec(),
},
Expand Down
28 changes: 28 additions & 0 deletions zcash_client_sqlite/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ use crate::{

use self::scanning::{parse_priority_code, priority_code, replace_queue_entries};

#[cfg(feature = "orchard")]
use {crate::ORCHARD_TABLES_PREFIX, zcash_client_backend::data_api::ORCHARD_SHARD_HEIGHT};

#[cfg(feature = "transparent-inputs")]
use {
crate::UtxoId,
Expand Down Expand Up @@ -948,6 +951,9 @@ pub(crate) fn get_wallet_summary<P: consensus::Parameters>(
drop(transparent_trace);
}

// The approach used here for Sapling and Orchard subtree indexing was a quick hack
// that has not yet been replaced. TODO: Make less hacky.
// https://github.com/zcash/librustzcash/issues/1249
let next_sapling_subtree_index = {
let shard_store =
SqliteShardStore::<_, ::sapling::Node, SAPLING_SHARD_HEIGHT>::from_connection(
Expand All @@ -967,12 +973,34 @@ pub(crate) fn get_wallet_summary<P: consensus::Parameters>(
.unwrap_or(0)
};

#[cfg(feature = "orchard")]
let next_orchard_subtree_index = {
let shard_store = SqliteShardStore::<
_,
::orchard::tree::MerkleHashOrchard,
ORCHARD_SHARD_HEIGHT,
>::from_connection(tx, ORCHARD_TABLES_PREFIX)?;

// The last shard will be incomplete, and we want the next range to overlap with
// the last complete shard, so return the index of the second-to-last shard root.
shard_store
.get_shard_roots()
.map_err(ShardTreeError::Storage)?
.iter()
.rev()
.nth(1)
.map(|addr| addr.index())
.unwrap_or(0)
};

let summary = WalletSummary::new(
account_balances,
chain_tip_height,
fully_scanned_height,
sapling_scan_progress,
next_sapling_subtree_index,
#[cfg(feature = "orchard")]
next_orchard_subtree_index,
);

Ok(Some(summary))
Expand Down
Loading

0 comments on commit 0780272

Please sign in to comment.