From a0b517a93a7cf420960eff6eb23ed40a562c35ae Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 9 Mar 2024 20:18:02 +0000 Subject: [PATCH] zcash_client_sqlite: Add `OrchardPoolTester`, run tests with Orchard --- zcash_client_sqlite/src/lib.rs | 2 + zcash_client_sqlite/src/testing.rs | 8 + zcash_client_sqlite/src/wallet.rs | 2 + .../src/wallet/commitment_tree.rs | 46 ++++ zcash_client_sqlite/src/wallet/orchard.rs | 238 ++++++++++++++++++ zcash_client_sqlite/src/wallet/scanning.rs | 39 +++ 6 files changed, 335 insertions(+) create mode 100644 zcash_client_sqlite/src/wallet/orchard.rs diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index b04ea3e5a..8c1dae2aa 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -111,6 +111,8 @@ pub(crate) const PRUNING_DEPTH: u32 = 100; pub(crate) const VERIFY_LOOKAHEAD: u32 = 10; pub(crate) const SAPLING_TABLES_PREFIX: &str = "sapling"; +#[cfg(feature = "orchard")] +pub(crate) const ORCHARD_TABLES_PREFIX: &str = "orchard"; #[cfg(not(feature = "transparent-inputs"))] pub(crate) const UA_TRANSPARENT: bool = false; diff --git a/zcash_client_sqlite/src/testing.rs b/zcash_client_sqlite/src/testing.rs index 0cf71a6df..66f0dcab3 100644 --- a/zcash_client_sqlite/src/testing.rs +++ b/zcash_client_sqlite/src/testing.rs @@ -501,6 +501,14 @@ impl TestState { .and_then(|(_, _, usk, _)| usk.to_unified_full_viewing_key().sapling().cloned()) } + /// Exposes the test account's Sapling DFVK, if enabled via [`TestBuilder::with_test_account`]. + #[cfg(feature = "orchard")] + pub(crate) fn test_account_orchard(&self) -> Option { + self.test_account + .as_ref() + .and_then(|(_, _, usk, _)| usk.to_unified_full_viewing_key().orchard().cloned()) + } + /// Invokes [`create_spend_to_address`] with the given arguments. #[allow(deprecated)] #[allow(clippy::type_complexity)] diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 6648c7fb3..ff0c7cf6a 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -127,6 +127,8 @@ use { pub mod commitment_tree; pub mod init; +#[cfg(feature = "orchard")] +pub(crate) mod orchard; pub(crate) mod sapling; pub(crate) mod scanning; diff --git a/zcash_client_sqlite/src/wallet/commitment_tree.rs b/zcash_client_sqlite/src/wallet/commitment_tree.rs index 28b26c757..8dde47a53 100644 --- a/zcash_client_sqlite/src/wallet/commitment_tree.rs +++ b/zcash_client_sqlite/src/wallet/commitment_tree.rs @@ -1111,6 +1111,52 @@ mod tests { ShardTree::new(store, m) } + #[cfg(feature = "orchard")] + mod orchard { + use super::new_tree; + use crate::wallet::orchard::tests::OrchardPoolTester; + + #[test] + fn append() { + super::check_append(new_tree::); + } + + #[test] + fn root_hashes() { + super::check_root_hashes(new_tree::); + } + + #[test] + fn witnesses() { + super::check_witnesses(new_tree::); + } + + #[test] + fn witness_consistency() { + super::check_witness_consistency(new_tree::); + } + + #[test] + fn checkpoint_rewind() { + super::check_checkpoint_rewind(new_tree::); + } + + #[test] + fn remove_mark() { + super::check_remove_mark(new_tree::); + } + + #[test] + fn rewind_remove_mark() { + super::check_rewind_remove_mark(new_tree::); + } + + #[test] + fn put_shard_roots() { + super::put_shard_roots::() + } + } + #[test] fn sapling_append() { check_append(new_tree::); diff --git a/zcash_client_sqlite/src/wallet/orchard.rs b/zcash_client_sqlite/src/wallet/orchard.rs new file mode 100644 index 000000000..e9251ef01 --- /dev/null +++ b/zcash_client_sqlite/src/wallet/orchard.rs @@ -0,0 +1,238 @@ +#[cfg(test)] +pub(crate) mod tests { + use incrementalmerkletree::{Hashable, Level}; + use orchard::{ + keys::{FullViewingKey, SpendingKey}, + note_encryption::OrchardDomain, + tree::MerkleHashOrchard, + }; + use shardtree::error::ShardTreeError; + use zcash_client_backend::{ + data_api::{ + chain::CommitmentTreeRoot, DecryptedTransaction, WalletCommitmentTrees, WalletSummary, + }, + wallet::{Note, ReceivedNote}, + }; + use zcash_keys::{ + address::{Address, UnifiedAddress}, + keys::UnifiedSpendingKey, + }; + use zcash_note_encryption::try_output_recovery_with_ovk; + use zcash_primitives::transaction::Transaction; + use zcash_protocol::{consensus::BlockHeight, memo::MemoBytes, ShieldedProtocol}; + + use crate::{ + error::SqliteClientError, + testing::{ + self, + pool::{OutputRecoveryError, ShieldedPoolTester}, + TestState, + }, + wallet::commitment_tree, + ORCHARD_TABLES_PREFIX, + }; + + pub(crate) struct OrchardPoolTester; + impl ShieldedPoolTester for OrchardPoolTester { + const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard; + const TABLES_PREFIX: &'static str = ORCHARD_TABLES_PREFIX; + + type Sk = SpendingKey; + type Fvk = FullViewingKey; + type MerkleTreeHash = MerkleHashOrchard; + + fn test_account_fvk(st: &TestState) -> Self::Fvk { + st.test_account_orchard().unwrap() + } + + fn usk_to_sk(usk: &UnifiedSpendingKey) -> &Self::Sk { + usk.orchard() + } + + fn sk(seed: &[u8]) -> Self::Sk { + let mut account = zip32::AccountId::ZERO; + loop { + if let Ok(sk) = SpendingKey::from_zip32_seed(seed, 1, account) { + break sk; + } + account = account.next().unwrap(); + } + } + + fn sk_to_fvk(sk: &Self::Sk) -> Self::Fvk { + sk.into() + } + + fn sk_default_address(sk: &Self::Sk) -> Address { + Self::fvk_default_address(&Self::sk_to_fvk(sk)) + } + + fn fvk_default_address(fvk: &Self::Fvk) -> Address { + UnifiedAddress::from_receivers( + Some(fvk.address_at(0u32, zip32::Scope::External)), + None, + None, + ) + .unwrap() + .into() + } + + fn fvks_equal(a: &Self::Fvk, b: &Self::Fvk) -> bool { + a == b + } + + fn empty_tree_leaf() -> Self::MerkleTreeHash { + MerkleHashOrchard::empty_leaf() + } + + fn empty_tree_root(level: Level) -> Self::MerkleTreeHash { + MerkleHashOrchard::empty_root(level) + } + + fn put_subtree_roots( + st: &mut TestState, + start_index: u64, + roots: &[CommitmentTreeRoot], + ) -> Result<(), ShardTreeError> { + st.wallet_mut() + .put_orchard_subtree_roots(start_index, roots) + } + + fn next_subtree_index(s: &WalletSummary) -> u64 { + todo!() + } + + fn select_spendable_notes( + st: &TestState, + account: crate::AccountId, + target_value: zcash_protocol::value::Zatoshis, + anchor_height: BlockHeight, + exclude: &[crate::ReceivedNoteId], + ) -> Result>, SqliteClientError> { + todo!() + } + + fn decrypted_pool_outputs_count( + d_tx: &DecryptedTransaction<'_, crate::AccountId>, + ) -> usize { + d_tx.orchard_outputs().len() + } + + fn with_decrypted_pool_memos( + d_tx: &DecryptedTransaction<'_, crate::AccountId>, + mut f: impl FnMut(&MemoBytes), + ) { + for output in d_tx.orchard_outputs() { + f(output.memo()); + } + } + + fn try_output_recovery( + _: &TestState, + _: BlockHeight, + tx: &Transaction, + fvk: &Self::Fvk, + ) -> Result, OutputRecoveryError> { + for action in tx.orchard_bundle().unwrap().actions() { + // Find the output that decrypts with the external OVK + let result = try_output_recovery_with_ovk( + &OrchardDomain::for_action(action), + &fvk.to_ovk(zip32::Scope::External), + action, + action.cv_net(), + &action.encrypted_note().out_ciphertext, + ); + + if result.is_some() { + return Ok(result.map(|(note, addr, memo)| { + ( + Note::Orchard(note), + UnifiedAddress::from_receivers(Some(addr), None, None) + .unwrap() + .into(), + MemoBytes::from_bytes(&memo).expect("correct length"), + ) + })); + } + } + + Ok(None) + } + } + + #[test] + fn send_single_step_proposed_transfer() { + testing::pool::send_single_step_proposed_transfer::() + } + + #[test] + #[cfg(feature = "transparent-inputs")] + fn send_multi_step_proposed_transfer() { + testing::pool::send_multi_step_proposed_transfer::() + } + + #[test] + #[allow(deprecated)] + fn create_to_address_fails_on_incorrect_usk() { + testing::pool::create_to_address_fails_on_incorrect_usk::() + } + + #[test] + #[allow(deprecated)] + fn proposal_fails_with_no_blocks() { + testing::pool::proposal_fails_with_no_blocks::() + } + + #[test] + fn spend_fails_on_unverified_notes() { + testing::pool::spend_fails_on_unverified_notes::() + } + + #[test] + fn spend_fails_on_locked_notes() { + testing::pool::spend_fails_on_locked_notes::() + } + + #[test] + fn ovk_policy_prevents_recovery_from_chain() { + testing::pool::ovk_policy_prevents_recovery_from_chain::() + } + + #[test] + fn spend_succeeds_to_t_addr_zero_change() { + testing::pool::spend_succeeds_to_t_addr_zero_change::() + } + + #[test] + fn change_note_spends_succeed() { + testing::pool::change_note_spends_succeed::() + } + + #[test] + fn external_address_change_spends_detected_in_restore_from_seed() { + testing::pool::external_address_change_spends_detected_in_restore_from_seed::< + OrchardPoolTester, + >() + } + + #[test] + fn zip317_spend() { + testing::pool::zip317_spend::() + } + + #[test] + #[cfg(feature = "transparent-inputs")] + fn shield_transparent() { + testing::pool::shield_transparent::() + } + + #[test] + fn birthday_in_anchor_shard() { + testing::pool::birthday_in_anchor_shard::() + } + + #[test] + fn checkpoint_gaps() { + testing::pool::checkpoint_gaps::() + } +} diff --git a/zcash_client_sqlite/src/wallet/scanning.rs b/zcash_client_sqlite/src/wallet/scanning.rs index 528fbf152..13c43bda7 100644 --- a/zcash_client_sqlite/src/wallet/scanning.rs +++ b/zcash_client_sqlite/src/wallet/scanning.rs @@ -565,11 +565,20 @@ pub(crate) mod tests { VERIFY_LOOKAHEAD, }; + #[cfg(feature = "orchard")] + use crate::wallet::orchard::tests::OrchardPoolTester; + #[test] fn sapling_scan_complete() { scan_complete::(); } + #[cfg(feature = "orchard")] + #[test] + fn orchard_scan_complete() { + scan_complete::(); + } + fn scan_complete() { use ScanPriority::*; @@ -736,6 +745,12 @@ pub(crate) mod tests { create_account_creates_ignored_range::(); } + #[cfg(feature = "orchard")] + #[test] + fn orchard_create_account_creates_ignored_range() { + create_account_creates_ignored_range::(); + } + fn create_account_creates_ignored_range() { use ScanPriority::*; @@ -799,6 +814,12 @@ pub(crate) mod tests { update_chain_tip_with_no_subtree_roots::(); } + #[cfg(feature = "orchard")] + #[test] + fn orchard_update_chain_tip_with_no_subtree_roots() { + update_chain_tip_with_no_subtree_roots::(); + } + fn update_chain_tip_with_no_subtree_roots() { use ScanPriority::*; @@ -834,6 +855,12 @@ pub(crate) mod tests { update_chain_tip_when_never_scanned::(); } + #[cfg(feature = "orchard")] + #[test] + fn orchard_update_chain_tip_when_never_scanned() { + update_chain_tip_when_never_scanned::(); + } + fn update_chain_tip_when_never_scanned() { use ScanPriority::*; @@ -883,6 +910,12 @@ pub(crate) mod tests { update_chain_tip_unstable_max_scanned::(); } + #[cfg(feature = "orchard")] + #[test] + fn orchard_update_chain_tip_unstable_max_scanned() { + update_chain_tip_unstable_max_scanned::(); + } + fn update_chain_tip_unstable_max_scanned() { use ScanPriority::*; @@ -1015,6 +1048,12 @@ pub(crate) mod tests { update_chain_tip_stable_max_scanned::(); } + #[cfg(feature = "orchard")] + #[test] + fn orchard_update_chain_tip_stable_max_scanned() { + update_chain_tip_stable_max_scanned::(); + } + fn update_chain_tip_stable_max_scanned() { use ScanPriority::*;