diff --git a/Cargo.lock b/Cargo.lock index 764656b84..455ff429b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4582,7 +4582,6 @@ dependencies = [ "base58", "crossbeam-channel", "futures", - "getset", "incrementalmerkletree", "memuse", "orchard", diff --git a/Cargo.toml b/Cargo.toml index 19b8c1cb6..8e11d028b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,6 @@ enum_dispatch = "0.3" ff = "0.13" futures = "0.3" futures-util = "0.3" -getset = "0.1" group = "0.13" hex = "0.4" http = "1" diff --git a/libtonode-tests/tests/concrete.rs b/libtonode-tests/tests/concrete.rs index 341e4272a..b3b60a682 100644 --- a/libtonode-tests/tests/concrete.rs +++ b/libtonode-tests/tests/concrete.rs @@ -2224,24 +2224,24 @@ mod slow { dbg!(wallet.sync_state.wallet_height()); dbg!(wallet .shard_trees - .sapling() + .sapling .store() .max_checkpoint_id() .unwrap()); dbg!(wallet .shard_trees - .orchard() + .orchard .store() .max_checkpoint_id() .unwrap()); dbg!(wallet .shard_trees - .sapling() + .sapling .root_at_checkpoint_id(&3.into()) .unwrap()); dbg!(wallet .shard_trees - .orchard() + .orchard .root_at_checkpoint_id(&3.into()) .unwrap()); drop(wallet); diff --git a/zingo-sync/Cargo.toml b/zingo-sync/Cargo.toml index 86e15350c..c2dad1d3a 100644 --- a/zingo-sync/Cargo.toml +++ b/zingo-sync/Cargo.toml @@ -42,9 +42,6 @@ memuse.workspace = true crossbeam-channel.workspace = true rayon.workspace = true -# Minimise boilerplate -getset.workspace = true - # Error handling thiserror.workspace = true diff --git a/zingo-sync/src/client.rs b/zingo-sync/src/client.rs index 5acc2282e..f108288c8 100644 --- a/zingo-sync/src/client.rs +++ b/zingo-sync/src/client.rs @@ -19,13 +19,13 @@ use zcash_primitives::{ transaction::{Transaction, TxId}, }; -pub mod fetch; +pub(crate) mod fetch; /// Fetch requests are created and sent to the [`crate::client::fetch::fetch`] task when a connection to the server is required. /// /// Each variant includes a [`tokio::sync::oneshot::Sender`] for returning the fetched data to the requester. #[derive(Debug)] -pub enum FetchRequest { +pub(crate) enum FetchRequest { /// Gets the height of the blockchain from the server. ChainTip(oneshot::Sender), /// Gets the specified range of compact blocks from the server (end exclusive). @@ -38,6 +38,7 @@ pub enum FetchRequest { /// Get a full transaction by txid. Transaction(oneshot::Sender<(Transaction, BlockHeight)>, TxId), /// Get a list of unspent transparent output metadata for a given list of transparent addresses and start height. + #[allow(dead_code)] UtxoMetadata( oneshot::Sender>, (Vec, BlockHeight), @@ -59,7 +60,7 @@ pub enum FetchRequest { /// Gets the height of the blockchain from the server. /// /// Requires [`crate::client::fetch::fetch`] to be running concurrently, connected via the `fetch_request` channel. -pub async fn get_chain_height( +pub(crate) async fn get_chain_height( fetch_request_sender: UnboundedSender, ) -> Result { let (reply_sender, reply_receiver) = oneshot::channel(); @@ -74,7 +75,7 @@ pub async fn get_chain_height( /// Gets the specified range of compact blocks from the server (end exclusive). /// /// Requires [`crate::client::fetch::fetch`] to be running concurrently, connected via the `fetch_request` channel. -pub async fn get_compact_block_range( +pub(crate) async fn get_compact_block_range( fetch_request_sender: UnboundedSender, block_range: Range, ) -> Result, ()> { @@ -91,7 +92,7 @@ pub async fn get_compact_block_range( /// from the server. /// /// Requires [`crate::client::fetch::fetch`] to be running concurrently, connected via the `fetch_request` channel. -pub async fn get_subtree_roots( +pub(crate) async fn get_subtree_roots( fetch_request_sender: UnboundedSender, start_index: u32, shielded_protocol: i32, @@ -118,7 +119,7 @@ pub async fn get_subtree_roots( /// Gets the frontiers for a specified block height. /// /// Requires [`crate::client::fetch::fetch`] to be running concurrently, connected via the `fetch_request` channel. -pub async fn get_frontiers( +pub(crate) async fn get_frontiers( fetch_request_sender: UnboundedSender, block_height: BlockHeight, ) -> Result { @@ -135,7 +136,7 @@ pub async fn get_frontiers( /// Gets a full transaction for a specified txid. /// /// Requires [`crate::client::fetch::fetch`] to be running concurrently, connected via the `fetch_request` channel. -pub async fn get_transaction_and_block_height( +pub(crate) async fn get_transaction_and_block_height( fetch_request_sender: UnboundedSender, txid: TxId, ) -> Result<(Transaction, BlockHeight), ()> { @@ -151,7 +152,8 @@ pub async fn get_transaction_and_block_height( /// Gets unspent transparent output metadata for a list of `transparent addresses` from the specified `start_height`. /// /// Requires [`crate::client::fetch::fetch`] to be running concurrently, connected via the `fetch_request` channel. -pub async fn get_utxo_metadata( +#[allow(dead_code)] +pub(crate) async fn get_utxo_metadata( fetch_request_sender: UnboundedSender, transparent_addresses: Vec, start_height: BlockHeight, @@ -175,7 +177,7 @@ pub async fn get_utxo_metadata( /// Gets transactions relevant to a given `transparent address` in the specified `block_range`. /// /// Requires [`crate::client::fetch::fetch`] to be running concurrently, connected via the `fetch_request` channel. -pub async fn get_transparent_address_transactions( +pub(crate) async fn get_transparent_address_transactions( fetch_request_sender: UnboundedSender, transparent_address: String, block_range: Range, @@ -193,7 +195,7 @@ pub async fn get_transparent_address_transactions( } /// Gets stream of mempool transactions until the next block is mined. -pub async fn get_mempool_transaction_stream( +pub(crate) async fn get_mempool_transaction_stream( client: &mut CompactTxStreamerClient, ) -> Result, ()> { tracing::debug!("Fetching mempool stream"); diff --git a/zingo-sync/src/client/fetch.rs b/zingo-sync/src/client/fetch.rs index 61d9bdc08..f679e490b 100644 --- a/zingo-sync/src/client/fetch.rs +++ b/zingo-sync/src/client/fetch.rs @@ -25,7 +25,7 @@ use crate::client::FetchRequest; /// /// Allows all requests to the server to be handled from a single task for efficiency and also enables /// request prioritisation for further performance enhancement -pub async fn fetch( +pub(crate) async fn fetch( mut fetch_request_receiver: UnboundedReceiver, mut client: CompactTxStreamerClient, consensus_parameters: impl consensus::Parameters, diff --git a/zingo-sync/src/keys.rs b/zingo-sync/src/keys.rs index 0758dfbd6..e6e977cb5 100644 --- a/zingo-sync/src/keys.rs +++ b/zingo-sync/src/keys.rs @@ -2,7 +2,6 @@ use std::collections::HashMap; -use getset::Getters; use incrementalmerkletree::Position; use orchard::{ keys::{FullViewingKey, IncomingViewingKey}, @@ -13,6 +12,7 @@ use sapling_crypto::{ }; use zcash_keys::keys::UnifiedFullViewingKey; use zcash_note_encryption::Domain; +use zcash_primitives::consensus; use zip32::Scope; /// Child index for the `address_index` path level in the BIP44 hierarchy. @@ -135,11 +135,9 @@ impl ScanningKeyOps } /// A set of keys to be used in scanning for decryptable transaction outputs. -#[derive(Getters)] -#[getset(get = "pub(crate)")] pub(crate) struct ScanningKeys { - sapling: HashMap>, - orchard: HashMap>, + pub(crate) sapling: HashMap>, + pub(crate) orchard: HashMap>, } impl ScanningKeys { @@ -188,3 +186,18 @@ impl ScanningKeys { Self { sapling, orchard } } } + +pub(crate) fn encode_orchard_receiver( + parameters: &impl consensus::Parameters, + orchard_address: &orchard::Address, +) -> Result { + Ok(zcash_address::unified::Encoding::encode( + &::try_from_items( + vec![zcash_address::unified::Receiver::Orchard( + orchard_address.to_raw_address_bytes(), + )], + ) + .unwrap(), + ¶meters.network_type(), + )) +} diff --git a/zingo-sync/src/keys/transparent.rs b/zingo-sync/src/keys/transparent.rs index e88719627..d60da7640 100644 --- a/zingo-sync/src/keys/transparent.rs +++ b/zingo-sync/src/keys/transparent.rs @@ -24,7 +24,7 @@ pub struct TransparentAddressId { impl TransparentAddressId { /// Construct from parts - pub fn from_parts( + pub fn new( account_id: zcash_primitives::zip32::AccountId, scope: TransparentScope, address_index: AddressIndex, diff --git a/zingo-sync/src/lib.rs b/zingo-sync/src/lib.rs index 267464ca0..2313bdaf4 100644 --- a/zingo-sync/src/lib.rs +++ b/zingo-sync/src/lib.rs @@ -15,16 +15,13 @@ //! of the previous sync, before the server is contacted to update the wallet height to the new chain height. //! Fully scanned height - block height in which the wallet has completed scanning all blocks equal to and below this height. -pub mod client; +pub(crate) mod client; pub mod error; pub mod keys; -#[allow(missing_docs)] -pub mod primitives; pub(crate) mod scan; pub mod sync; -pub mod traits; -pub(crate) mod utils; -pub mod witness; +pub mod wallet; +pub(crate) mod witness; pub use sync::scan_pending_transaction; pub use sync::sync; diff --git a/zingo-sync/src/primitives.rs b/zingo-sync/src/primitives.rs deleted file mode 100644 index f923eb807..000000000 --- a/zingo-sync/src/primitives.rs +++ /dev/null @@ -1,901 +0,0 @@ -//! Module for primitive wallet structs generated by the sync engine from block chain data or to track the wallet's -//! sync status. -//! The structs will be (or be transposed into) the fundamental wallet components for the wallet interfacing with this -//! sync engine. - -use std::{ - collections::{BTreeMap, BTreeSet}, - fmt::Debug, - ops::Range, -}; - -use incrementalmerkletree::Position; -use zcash_client_backend::{ - data_api::scanning::{ScanPriority, ScanRange}, - PoolType, ShieldedProtocol, -}; -use zcash_keys::{address::UnifiedAddress, encoding::encode_payment_address}; -use zcash_primitives::{ - block::BlockHash, - consensus::{BlockHeight, NetworkConstants, Parameters}, - legacy::Script, - memo::Memo, - transaction::{ - components::{amount::NonNegativeAmount, OutPoint}, - TxId, - }, -}; - -use zingo_status::confirmation_status::ConfirmationStatus; - -use crate::{ - keys::{transparent::TransparentAddressId, KeyId}, - utils, -}; - -/// Block height and txid of relevant transactions that have yet to be scanned. These may be added due to transparent -/// output/spend discovery or for targetted rescan. -pub type Locator = (BlockHeight, TxId); - -/// Initial sync state. -/// -/// All fields will be reset when a new sync session starts. -#[derive(Debug, Clone)] -pub struct InitialSyncState { - /// One block above the fully scanned wallet height at start of sync session. - pub(crate) sync_start_height: BlockHeight, - /// The tree sizes of the fully scanned height and chain tip at start of sync session. - pub(crate) sync_tree_bounds: TreeBounds, - /// Total number of blocks to scan. - pub(crate) total_blocks_to_scan: u32, - /// Total number of sapling outputs to scan. - pub(crate) total_sapling_outputs_to_scan: u32, - /// Total number of orchard outputs to scan. - pub(crate) total_orchard_outputs_to_scan: u32, -} - -impl InitialSyncState { - /// Create new InitialSyncState - pub fn new() -> Self { - InitialSyncState { - sync_start_height: 0.into(), - sync_tree_bounds: TreeBounds { - sapling_initial_tree_size: 0, - sapling_final_tree_size: 0, - orchard_initial_tree_size: 0, - orchard_final_tree_size: 0, - }, - total_blocks_to_scan: 0, - total_sapling_outputs_to_scan: 0, - total_orchard_outputs_to_scan: 0, - } - } -} - -impl Default for InitialSyncState { - fn default() -> Self { - Self::new() - } -} - -/// Encapsulates the current state of sync -#[derive(Debug, Clone)] -pub struct SyncState { - /// A vec of block ranges with scan priorities from wallet birthday to chain tip. - /// In block height order with no overlaps or gaps. - pub(crate) scan_ranges: Vec, - /// The block ranges that contain all sapling outputs of complete sapling shards. - /// - /// There is an edge case where a range may include two (or more) shards. However, this only occurs when the lower - /// shards are already scanned so will cause no issues when punching in the higher scan priorites. - pub(crate) sapling_shard_ranges: Vec>, - /// The block ranges that contain all orchard outputs of complete orchard shards. - /// - /// There is an edge case where a range may include two (or more) shards. However, this only occurs when the lower - /// shards are already scanned so will cause no issues when punching in the higher scan priorites. - pub(crate) orchard_shard_ranges: Vec>, - /// Locators for relevant transactions to the wallet. - pub(crate) locators: BTreeSet, - /// Initial sync state. - pub(crate) initial_sync_state: InitialSyncState, -} - -impl SyncState { - /// Create new SyncState - pub fn new() -> Self { - SyncState { - scan_ranges: Vec::new(), - sapling_shard_ranges: Vec::new(), - orchard_shard_ranges: Vec::new(), - locators: BTreeSet::new(), - initial_sync_state: InitialSyncState::new(), - } - } - - pub fn scan_ranges(&self) -> &[ScanRange] { - &self.scan_ranges - } - - /// Returns true if all scan ranges are scanned. - pub(crate) fn scan_complete(&self) -> bool { - self.scan_ranges - .iter() - .all(|scan_range| scan_range.priority() == ScanPriority::Scanned) - } - - /// Returns the block height at which all blocks equal to and below this height are scanned. - /// - /// Will panic if called before scan ranges are updated for the first time. - pub fn fully_scanned_height(&self) -> BlockHeight { - if let Some(scan_range) = self - .scan_ranges - .iter() - .find(|scan_range| scan_range.priority() != ScanPriority::Scanned) - { - scan_range.block_range().start - 1 - } else { - self.scan_ranges - .last() - .expect("scan ranges always non-empty") - .block_range() - .end - - 1 - } - } - - /// Returns the highest block height that has been scanned. - /// - /// If no scan ranges have been scanned, returns the block below the wallet birthday. - /// Will panic if called before scan ranges are updated for the first time. - pub fn highest_scanned_height(&self) -> BlockHeight { - if let Some(last_scanned_range) = self - .scan_ranges - .iter() - .filter(|scan_range| scan_range.priority() == ScanPriority::Scanned) - .last() - { - last_scanned_range.block_range().end - 1 - } else { - self.wallet_birthday() - .expect("scan ranges always non-empty") - - 1 - } - } - - /// Returns the wallet birthday or `None` if `self.scan_ranges` is empty. - /// - /// If the wallet birthday is below the sapling activation height, returns the sapling activation height instead. - pub fn wallet_birthday(&self) -> Option { - self.scan_ranges - .first() - .map(|range| range.block_range().start) - } - - /// Returns the last known chain height to the wallet or `None` if `self.scan_ranges` is empty. - pub fn wallet_height(&self) -> Option { - self.scan_ranges - .last() - .map(|range| range.block_range().end - 1) - } -} - -impl Default for SyncState { - fn default() -> Self { - Self::new() - } -} - -#[derive(Debug, Clone, Copy)] -pub struct TreeBounds { - pub sapling_initial_tree_size: u32, - pub sapling_final_tree_size: u32, - pub orchard_initial_tree_size: u32, - pub orchard_final_tree_size: u32, -} - -/// A snapshot of the current state of sync. Useful for displaying the status of sync to a user / consumer. -/// -/// `percentage_outputs_scanned` is a much more accurate indicator of sync completion than `percentage_blocks_scanned`. -#[derive(Debug, Clone)] -pub struct SyncStatus { - pub scan_ranges: Vec, - pub scanned_blocks: u32, - pub unscanned_blocks: u32, - pub percentage_blocks_scanned: f32, - pub scanned_sapling_outputs: u32, - pub unscanned_sapling_outputs: u32, - pub scanned_orchard_outputs: u32, - pub unscanned_orchard_outputs: u32, - pub percentage_outputs_scanned: f32, -} - -impl std::fmt::Display for SyncStatus { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{{ - scanned blocks: {} - percentage complete: {} - }}", - self.scanned_blocks, self.percentage_outputs_scanned - ) - } -} - -/// Output ID for a given pool type -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub struct OutputId { - /// ID of associated transaction - txid: TxId, - /// Index of output within the transactions bundle of the given pool type. - output_index: u16, -} - -impl OutputId { - /// Creates new OutputId from parts - pub fn new(txid: TxId, output_index: u16) -> Self { - OutputId { txid, output_index } - } - - pub fn txid(&self) -> TxId { - self.txid - } - - pub fn output_index(&self) -> u16 { - self.output_index - } -} - -impl From<&OutPoint> for OutputId { - fn from(value: &OutPoint) -> Self { - OutputId::new(*value.txid(), value.n() as u16) - } -} - -impl From for OutPoint { - fn from(value: OutputId) -> Self { - OutPoint::new(value.txid.into(), value.output_index as u32) - } -} - -/// Binary tree map of nullifiers from transaction spends or actions -#[derive(Debug)] -pub struct NullifierMap { - pub(crate) sapling: BTreeMap, - pub(crate) orchard: BTreeMap, -} - -impl NullifierMap { - pub fn new() -> Self { - Self { - sapling: BTreeMap::new(), - orchard: BTreeMap::new(), - } - } - - pub fn clear(&mut self) { - self.sapling.clear(); - self.orchard.clear(); - } -} - -impl Default for NullifierMap { - fn default() -> Self { - Self::new() - } -} - -/// Wallet block data -#[derive(Debug, Clone)] -pub struct WalletBlock { - pub(crate) block_height: BlockHeight, - pub(crate) block_hash: BlockHash, - pub(crate) prev_hash: BlockHash, - pub(crate) time: u32, - pub(crate) txids: Vec, - pub(crate) tree_bounds: TreeBounds, -} - -impl WalletBlock { - pub fn block_height(&self) -> BlockHeight { - self.block_height - } - - pub fn block_hash(&self) -> BlockHash { - self.block_hash - } - - pub fn prev_hash(&self) -> BlockHash { - self.prev_hash - } - - pub fn time(&self) -> u32 { - self.time - } - - pub fn txids(&self) -> &[TxId] { - &self.txids - } - - pub fn tree_bounds(&self) -> TreeBounds { - self.tree_bounds - } -} - -/// Wallet transaction -pub struct WalletTransaction { - pub(crate) txid: TxId, - pub(crate) transaction: zcash_primitives::transaction::Transaction, - pub(crate) status: ConfirmationStatus, - pub(crate) datetime: u32, - pub(crate) transparent_coins: Vec, - pub(crate) sapling_notes: Vec, - pub(crate) orchard_notes: Vec, - pub(crate) outgoing_sapling_notes: Vec, - pub(crate) outgoing_orchard_notes: Vec, -} - -impl WalletTransaction { - /// Transaction ID - pub fn txid(&self) -> TxId { - self.txid - } - - /// [`zcash_primitives::transaction::Transaction`] - pub fn transaction(&self) -> &zcash_primitives::transaction::Transaction { - &self.transaction - } - - /// Confirmation status - pub fn status(&self) -> ConfirmationStatus { - self.status - } - - /// Datetime. In form of seconds since unix epoch. - pub fn datetime(&self) -> u32 { - self.datetime - } - - /// Transparent coins - pub fn transparent_coins(&self) -> &[TransparentCoin] { - &self.transparent_coins - } - - /// Transparent coins mutable - pub(crate) fn transparent_coins_mut(&mut self) -> Vec<&mut TransparentCoin> { - self.transparent_coins.iter_mut().collect() - } - - /// Sapling notes - pub fn sapling_notes(&self) -> &[SaplingNote] { - &self.sapling_notes - } - - /// Sapling notes mutable - pub(crate) fn sapling_notes_mut(&mut self) -> Vec<&mut SaplingNote> { - self.sapling_notes.iter_mut().collect() - } - - /// Orchard notes - pub fn orchard_notes(&self) -> &[OrchardNote] { - &self.orchard_notes - } - - /// Orchard notes mutable - pub(crate) fn orchard_notes_mut(&mut self) -> Vec<&mut OrchardNote> { - self.orchard_notes.iter_mut().collect() - } - - /// Outgoing sapling notes - pub fn outgoing_sapling_notes(&self) -> &[OutgoingSaplingNote] { - &self.outgoing_sapling_notes - } - - /// Outgoing orchard notes - pub fn outgoing_orchard_notes(&self) -> &[OutgoingOrchardNote] { - &self.outgoing_orchard_notes - } - - /// Returns nullifers from sapling bundle. - /// Returns empty vec if bundle is `None`. - pub fn sapling_nullifiers(&self) -> Vec<&sapling_crypto::Nullifier> { - self.transaction - .sapling_bundle() - .map_or_else(Vec::new, |bundle| { - bundle - .shielded_spends() - .iter() - .map(|spend| spend.nullifier()) - .collect::>() - }) - } - - /// Returns nullifers from orchard bundle. - /// Returns empty vec if bundle is `None`. - pub fn orchard_nullifiers(&self) -> Vec<&orchard::note::Nullifier> { - self.transaction - .orchard_bundle() - .map_or_else(Vec::new, |bundle| { - bundle - .actions() - .iter() - .map(|action| action.nullifier()) - .collect::>() - }) - } - - /// Returns outpoints from transparent bundle. - /// Returns empty vec if bundle is `None`. - pub fn outpoints(&self) -> Vec<&OutPoint> { - self.transaction - .transparent_bundle() - .map_or_else(Vec::new, |bundle| { - bundle - .vin - .iter() - .map(|txin| &txin.prevout) - .collect::>() - }) - } -} - -#[cfg(feature = "wallet_essentials")] -impl WalletTransaction { - /// Returns the total value sent to receivers, including value explicitly sent to the wallet own addresses but - /// excluding change. - pub fn total_value_sent(&self) -> u64 { - let transparent_value_sent = self.transaction.transparent_bundle().map_or(0, |bundle| { - bundle - .vout - .iter() - .map(|output| output.value.into_u64()) - .sum() - }) - self.total_output_value::(); - - transparent_value_sent - + self.total_outgoing_note_value::() - + self.total_outgoing_note_value::() - } - - /// Returns total sum of all output values. - pub fn total_value_received(&self) -> u64 { - self.total_output_value::() - + self.total_output_value::() - + self.total_output_value::() - } - - /// Returns total sum of output values for a given pool. - pub fn total_output_value(&self) -> u64 { - Op::transaction_outputs(self) - .iter() - .map(|output| output.value()) - .sum() - } - - /// Returns total sum of outgoing note values for a given shielded pool. - pub fn total_outgoing_note_value(&self) -> u64 { - Op::transaction_outgoing_notes(self) - .iter() - .map(|note| note.value()) - .sum() - } -} - -impl std::fmt::Debug for WalletTransaction { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.debug_struct("WalletTransaction") - .field("confirmation_status", &self.status) - .field("transparent_coins", &self.transparent_coins) - .field("sapling_notes", &self.sapling_notes) - .field("orchard_notes", &self.orchard_notes) - .field("outgoing_sapling_notes", &self.outgoing_sapling_notes) - .field("outgoing_orchard_notes", &self.outgoing_orchard_notes) - .finish() - } -} - -/// Wallet note, shielded output with metadata relevant to the wallet -#[derive(Debug, Clone)] -pub struct WalletNote { - /// Output ID - pub(crate) output_id: OutputId, - /// Identifier for key used to decrypt output - pub(crate) key_id: KeyId, - /// Decrypted note with recipient and value - pub(crate) note: N, - /// Derived nullifier - pub(crate) nullifier: Option, //TODO: syncing without nullifier deriving key - /// Commitment tree leaf position - pub(crate) position: Option, - /// Memo - pub(crate) memo: Memo, - /// Txid of transaction this output was spent. - /// If `None`, output is not spent. - pub(crate) spending_transaction: Option, -} - -pub trait OutputInterface: Sized { - type KeyId; - type Input: Clone + Debug + PartialEq + Eq + PartialOrd + Ord; - - const POOL_TYPE: PoolType; - - /// Output ID - fn output_id(&self) -> OutputId; - - /// Identifier for key used to decrypt output - fn key_id(&self) -> Self::KeyId; - - /// Txid of transaction this output was spent. - /// If `None`, output is not spent. - fn spending_transaction(&self) -> Option; - - /// Note value. - fn value(&self) -> u64; - - /// Returns the type used to link with transaction inputs for spend detection. - /// Returns `None` in the case the nullifier is not available for shielded outputs. - /// - /// Nullifier for shielded outputs. - /// Outpoint for transparent outputs. - fn spend_link(&self) -> Option; - - /// Inputs within `transaction` used to detect an output's spend status. - /// - /// Nullifiers for shielded outputs. - /// Out points for transparent outputs. - fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input>; - - /// Outputs within `transaction`. - fn transaction_outputs(transaction: &WalletTransaction) -> &[Self]; -} - -/// Transparent coin (output) with metadata relevant to the wallet -#[derive(Debug, Clone)] -pub struct TransparentCoin { - /// Output ID - pub(crate) output_id: OutputId, - /// Identifier for key used to derive address - pub(crate) key_id: TransparentAddressId, - /// Encoded transparent address - pub(crate) address: String, - /// Script - pub(crate) script: Script, - /// Coin value - pub(crate) value: NonNegativeAmount, - /// Txid of transaction this output was spent. - /// If `None`, output is not spent. - pub(crate) spending_transaction: Option, -} - -impl TransparentCoin { - pub fn address(&self) -> &str { - &self.address - } - - pub fn script(&self) -> &Script { - &self.script - } -} - -impl OutputInterface for TransparentCoin { - type KeyId = TransparentAddressId; - type Input = OutPoint; - - const POOL_TYPE: PoolType = PoolType::Transparent; - - fn output_id(&self) -> OutputId { - self.output_id - } - - fn key_id(&self) -> Self::KeyId { - self.key_id - } - - fn spending_transaction(&self) -> Option { - self.spending_transaction - } - - fn value(&self) -> u64 { - self.value.into_u64() - } - - fn spend_link(&self) -> Option { - Some(self.output_id.into()) - } - - fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> { - transaction.outpoints() - } - - fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] { - &transaction.transparent_coins - } -} - -pub trait NoteInterface: OutputInterface + Sized { - type ZcashNote; - type Nullifier: Copy; - - const SHIELDED_PROTOCOL: ShieldedProtocol; - - /// Decrypted note with recipient and value - fn note(&self) -> &Self::ZcashNote; - - /// Derived nullifier - fn nullifier(&self) -> Option; - - /// Commitment tree leaf position - fn position(&self) -> Option; - - /// Memo - fn memo(&self) -> &Memo; -} - -pub type SaplingNote = WalletNote; - -impl OutputInterface for SaplingNote { - type KeyId = KeyId; - type Input = sapling_crypto::Nullifier; - - const POOL_TYPE: PoolType = PoolType::Shielded(ShieldedProtocol::Sapling); - - fn output_id(&self) -> OutputId { - self.output_id - } - - fn key_id(&self) -> KeyId { - self.key_id - } - - fn spending_transaction(&self) -> Option { - self.spending_transaction - } - - fn value(&self) -> u64 { - self.note.value().inner() - } - - fn spend_link(&self) -> Option { - self.nullifier - } - - fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> { - transaction.sapling_nullifiers() - } - - fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] { - &transaction.sapling_notes - } -} - -impl NoteInterface for SaplingNote { - type ZcashNote = sapling_crypto::Note; - type Nullifier = sapling_crypto::Nullifier; - - const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Sapling; - - fn note(&self) -> &Self::ZcashNote { - &self.note - } - - fn nullifier(&self) -> Option { - self.nullifier - } - - fn position(&self) -> Option { - self.position - } - - fn memo(&self) -> &Memo { - &self.memo - } -} - -pub type OrchardNote = WalletNote; - -impl OutputInterface for OrchardNote { - type KeyId = KeyId; - type Input = orchard::note::Nullifier; - - const POOL_TYPE: PoolType = PoolType::Shielded(ShieldedProtocol::Orchard); - - fn output_id(&self) -> OutputId { - self.output_id - } - - fn key_id(&self) -> KeyId { - self.key_id - } - - fn spending_transaction(&self) -> Option { - self.spending_transaction - } - - fn value(&self) -> u64 { - self.note.value().inner() - } - - fn spend_link(&self) -> Option { - self.nullifier - } - - fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> { - transaction.orchard_nullifiers() - } - - fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] { - &transaction.orchard_notes - } -} - -impl NoteInterface for OrchardNote { - type ZcashNote = orchard::Note; - type Nullifier = Self::Input; - - const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard; - - fn note(&self) -> &Self::ZcashNote { - &self.note - } - - fn nullifier(&self) -> Option { - self.spend_link() - } - - fn position(&self) -> Option { - self.position - } - - fn memo(&self) -> &Memo { - &self.memo - } -} - -pub trait OutgoingNoteInterface: Sized { - type ZcashNote; - - const SHIELDED_PROTOCOL: ShieldedProtocol; - - /// Output ID - fn output_id(&self) -> OutputId; - - /// Identifier for key used to decrypt outgoing note - fn key_id(&self) -> KeyId; - - /// Note value - fn value(&self) -> u64; - - /// Decrypted note with recipient and value - fn note(&self) -> &Self::ZcashNote; - - /// Memo - fn memo(&self) -> &Memo; - - /// Encoded recipient address recorded in note on chain (single receiver). - fn encoded_recipient

(&self, parameters: &P) -> String - where - P: Parameters + NetworkConstants; - - /// Encoded recipient unified address as given by recipient and recorded in an encoded memo (all original receivers). - fn encoded_recipient_unified_address

(&self, consensus_parameters: &P) -> Option - where - P: Parameters + NetworkConstants; - - /// Outgoing notes within `transaction`. - fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self]; -} - -/// Note sent from this capability to a recipient -#[derive(Debug, Clone)] -pub struct OutgoingNote { - /// Output ID - pub(crate) output_id: OutputId, - /// Identifier for key used to decrypt output - pub(crate) key_id: KeyId, - /// Decrypted note with recipient and value - pub(crate) note: N, - /// Memo - pub(crate) memo: Memo, - /// Recipient's full unified address from encoded memo - pub(crate) recipient_unified_address: Option, -} - -pub type OutgoingSaplingNote = OutgoingNote; - -impl OutgoingNoteInterface for OutgoingSaplingNote { - type ZcashNote = sapling_crypto::Note; - - const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Sapling; - - fn output_id(&self) -> OutputId { - self.output_id - } - - fn key_id(&self) -> KeyId { - self.key_id - } - - fn value(&self) -> u64 { - self.note.value().inner() - } - - fn note(&self) -> &Self::ZcashNote { - &self.note - } - - fn memo(&self) -> &Memo { - &self.memo - } - - fn encoded_recipient

(&self, consensus_parameters: &P) -> String - where - P: Parameters + NetworkConstants, - { - encode_payment_address( - consensus_parameters.hrp_sapling_payment_address(), - &self.note().recipient(), - ) - } - - fn encoded_recipient_unified_address

(&self, consensus_parameters: &P) -> Option - where - P: Parameters + NetworkConstants, - { - self.recipient_unified_address - .as_ref() - .map(|unified_address| unified_address.encode(consensus_parameters)) - } - - fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self] { - &transaction.outgoing_sapling_notes - } -} - -pub type OutgoingOrchardNote = OutgoingNote; - -impl OutgoingNoteInterface for OutgoingOrchardNote { - type ZcashNote = orchard::Note; - - const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard; - - fn output_id(&self) -> OutputId { - self.output_id - } - - fn key_id(&self) -> KeyId { - self.key_id - } - - fn value(&self) -> u64 { - self.note.value().inner() - } - - fn note(&self) -> &Self::ZcashNote { - &self.note - } - - fn memo(&self) -> &Memo { - &self.memo - } - - fn encoded_recipient

(&self, parameters: &P) -> String - where - P: Parameters + NetworkConstants, - { - utils::encode_orchard_receiver(parameters, &self.note().recipient()).unwrap() - } - - fn encoded_recipient_unified_address

(&self, consensus_parameters: &P) -> Option - where - P: Parameters + NetworkConstants, - { - self.recipient_unified_address - .as_ref() - .map(|unified_address| unified_address.encode(consensus_parameters)) - } - - fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self] { - &transaction.outgoing_orchard_notes - } -} diff --git a/zingo-sync/src/scan.rs b/zingo-sync/src/scan.rs index 8b8810b20..35290d17b 100644 --- a/zingo-sync/src/scan.rs +++ b/zingo-sync/src/scan.rs @@ -15,7 +15,7 @@ use zcash_primitives::{ use crate::{ client::FetchRequest, - primitives::{Locator, NullifierMap, OutputId, WalletBlock, WalletTransaction}, + wallet::{Locator, NullifierMap, OutputId, WalletBlock, WalletTransaction}, witness::{self, LocatedTreeData, WitnessData}, }; diff --git a/zingo-sync/src/scan/compact_blocks.rs b/zingo-sync/src/scan/compact_blocks.rs index 502fa156a..8b3043997 100644 --- a/zingo-sync/src/scan/compact_blocks.rs +++ b/zingo-sync/src/scan/compact_blocks.rs @@ -21,7 +21,7 @@ use zcash_primitives::{ use crate::{ client::{self, FetchRequest}, keys::{KeyId, ScanningKeyOps, ScanningKeys}, - primitives::{NullifierMap, OutputId, TreeBounds, WalletBlock}, + wallet::{NullifierMap, OutputId, TreeBounds, WalletBlock}, witness::WitnessData, MAX_BATCH_OUTPUTS, }; @@ -116,13 +116,13 @@ where calculate_nullifiers_and_positions( sapling_final_tree_size, - scanning_keys.sapling(), + &scanning_keys.sapling, &incoming_sapling_outputs, &mut decrypted_note_data.sapling_nullifiers_and_positions, ); calculate_nullifiers_and_positions( orchard_final_tree_size, - scanning_keys.orchard(), + &scanning_keys.orchard, &incoming_orchard_outputs, &mut decrypted_note_data.orchard_nullifiers_and_positions, ); @@ -277,9 +277,8 @@ fn calculate_nullifiers_and_positions( incoming_decrypted_outputs .iter() .for_each(|(output_id, incoming_output)| { - let position = Position::from(u64::from( - tree_size + u32::try_from(output_id.output_index()).unwrap(), - )); + let position = + Position::from(u64::from(tree_size + u32::from(output_id.output_index()))); let key = keys .get(&incoming_output.ivk_tag) .expect("key should be available as it was used to decrypt output"); diff --git a/zingo-sync/src/scan/compact_blocks/runners.rs b/zingo-sync/src/scan/compact_blocks/runners.rs index c01da370a..ef8e136d9 100644 --- a/zingo-sync/src/scan/compact_blocks/runners.rs +++ b/zingo-sync/src/scan/compact_blocks/runners.rs @@ -25,7 +25,7 @@ use memuse::DynamicUsage; use crate::keys::KeyId; use crate::keys::ScanningKeyOps as _; use crate::keys::ScanningKeys; -use crate::primitives::OutputId; +use crate::wallet::OutputId; type TaggedSaplingBatch = Batch< SaplingDomain, @@ -65,14 +65,14 @@ where sapling: BatchRunner::new( batch_size_threshold, scanning_keys - .sapling() + .sapling .iter() .map(|(id, key)| (*id, key.prepare())), ), orchard: BatchRunner::new( batch_size_threshold, scanning_keys - .orchard() + .orchard .iter() .map(|(id, key)| (*id, key.prepare())), ), diff --git a/zingo-sync/src/scan/task.rs b/zingo-sync/src/scan/task.rs index 447b3afb8..b239b95f9 100644 --- a/zingo-sync/src/scan/task.rs +++ b/zingo-sync/src/scan/task.rs @@ -25,9 +25,9 @@ use zcash_primitives::{ use crate::{ client::{self, FetchRequest}, keys::transparent::TransparentAddressId, - primitives::{Locator, WalletBlock}, sync, - traits::{SyncBlocks, SyncWallet}, + wallet::traits::{SyncBlocks, SyncWallet}, + wallet::{Locator, WalletBlock}, MAX_BATCH_OUTPUTS, }; diff --git a/zingo-sync/src/scan/transactions.rs b/zingo-sync/src/scan/transactions.rs index 827896fce..988eb45f0 100644 --- a/zingo-sync/src/scan/transactions.rs +++ b/zingo-sync/src/scan/transactions.rs @@ -13,9 +13,7 @@ use sapling_crypto::{ }; use tokio::sync::mpsc; -use zcash_keys::{ - address::UnifiedAddress, encoding::encode_payment_address, keys::UnifiedFullViewingKey, -}; +use zcash_keys::{address::UnifiedAddress, keys::UnifiedFullViewingKey}; use zcash_note_encryption::{BatchDomain, Domain, ShieldedOutput, ENC_CIPHERTEXT_SIZE}; use zcash_primitives::{ consensus::{self, BlockHeight, NetworkConstants}, @@ -33,13 +31,12 @@ use crate::{ transparent::{self, TransparentAddressId}, KeyId, }, - primitives::{ + wallet::traits::{SyncBlocks, SyncNullifiers, SyncTransactions}, + wallet::{ Locator, NullifierMap, OrchardNote, OutgoingNote, OutgoingNoteInterface, OutgoingOrchardNote, OutgoingSaplingNote, OutputId, SaplingNote, TransparentCoin, WalletBlock, WalletNote, WalletTransaction, }, - traits::{SyncBlocks, SyncNullifiers, SyncTransactions}, - utils, }; use super::DecryptedNoteData; @@ -466,8 +463,8 @@ fn add_recipient_unified_address( { for ua in unified_addresses { let ua_receivers = [ - utils::encode_orchard_receiver(parameters, ua.orchard().unwrap()).unwrap(), - encode_payment_address( + keys::encode_orchard_receiver(parameters, ua.orchard().unwrap()).unwrap(), + zcash_keys::encoding::encode_payment_address( parameters.hrp_sapling_payment_address(), ua.sapling().unwrap(), ), diff --git a/zingo-sync/src/sync.rs b/zingo-sync/src/sync.rs index da61be0b2..10bc88331 100644 --- a/zingo-sync/src/sync.rs +++ b/zingo-sync/src/sync.rs @@ -24,14 +24,14 @@ use zingo_status::confirmation_status::ConfirmationStatus; use crate::client::{self, FetchRequest}; use crate::error::SyncError; use crate::keys::transparent::TransparentAddressId; -use crate::primitives::{NullifierMap, SyncStatus}; use crate::scan::error::{ContinuityError, ScanError}; use crate::scan::task::Scanner; use crate::scan::transactions::scan_transaction; use crate::scan::ScanResults; -use crate::traits::{ +use crate::wallet::traits::{ SyncBlocks, SyncNullifiers, SyncOutPoints, SyncShardTrees, SyncTransactions, SyncWallet, }; +use crate::wallet::{NullifierMap, SyncStatus}; use crate::witness; pub(crate) mod spend; @@ -604,7 +604,7 @@ async fn update_subtree_roots( let sapling_start_index = wallet .get_shard_trees() .unwrap() - .sapling() + .sapling .store() .get_shard_roots() .unwrap() @@ -612,7 +612,7 @@ async fn update_subtree_roots( let orchard_start_index = wallet .get_shard_trees() .unwrap() - .orchard() + .orchard .store() .get_shard_roots() .unwrap() @@ -640,8 +640,8 @@ async fn update_subtree_roots( ); let shard_trees = wallet.get_shard_trees_mut().unwrap(); - witness::add_subtree_roots(sapling_subtree_roots, shard_trees.sapling_mut()); - witness::add_subtree_roots(orchard_subtree_roots, shard_trees.orchard_mut()); + witness::add_subtree_roots(sapling_subtree_roots, &mut shard_trees.sapling); + witness::add_subtree_roots(orchard_subtree_roots, &mut shard_trees.orchard); } /// Sets up mempool stream. diff --git a/zingo-sync/src/sync/spend.rs b/zingo-sync/src/sync/spend.rs index 2fbe6b00f..c6004e222 100644 --- a/zingo-sync/src/sync/spend.rs +++ b/zingo-sync/src/sync/spend.rs @@ -13,9 +13,9 @@ use zip32::AccountId; use crate::{ client::FetchRequest, - primitives::{Locator, NullifierMap, OutputId, WalletTransaction}, scan::transactions::scan_spending_transactions, - traits::{SyncBlocks, SyncNullifiers, SyncOutPoints, SyncTransactions}, + wallet::traits::{SyncBlocks, SyncNullifiers, SyncOutPoints, SyncTransactions}, + wallet::{Locator, NullifierMap, OutputId, WalletTransaction}, }; use super::state; diff --git a/zingo-sync/src/sync/state.rs b/zingo-sync/src/sync/state.rs index 7ecff3847..64e7e38e7 100644 --- a/zingo-sync/src/sync/state.rs +++ b/zingo-sync/src/sync/state.rs @@ -21,9 +21,9 @@ use zcash_primitives::{ use crate::{ client::{self, FetchRequest}, keys::transparent::TransparentAddressId, - primitives::{Locator, SyncState, TreeBounds, WalletTransaction}, scan::task::ScanTask, - traits::{SyncBlocks, SyncWallet}, + wallet::traits::{SyncBlocks, SyncWallet}, + wallet::{Locator, SyncState, TreeBounds, WalletTransaction}, }; use super::VERIFY_BLOCK_RANGE_SIZE; diff --git a/zingo-sync/src/sync/transparent.rs b/zingo-sync/src/sync/transparent.rs index ec3bb7054..7857ed627 100644 --- a/zingo-sync/src/sync/transparent.rs +++ b/zingo-sync/src/sync/transparent.rs @@ -10,8 +10,8 @@ use zcash_primitives::zip32::AccountId; use crate::client::{self, FetchRequest}; use crate::keys; use crate::keys::transparent::{TransparentAddressId, TransparentScope}; -use crate::primitives::Locator; -use crate::traits::SyncWallet; +use crate::wallet::traits::SyncWallet; +use crate::wallet::Locator; use super::MAX_VERIFICATION_WINDOW; @@ -93,8 +93,7 @@ pub(crate) async fn update_addresses_and_locators( let mut addresses: Vec<(TransparentAddressId, String)> = Vec::new(); while unused_address_count < ADDRESS_GAP_LIMIT { - let address_id = - TransparentAddressId::from_parts(*account_id, scope, address_index); + let address_id = TransparentAddressId::new(*account_id, scope, address_index); let address = keys::transparent::derive_address( consensus_parameters, account_pubkey, diff --git a/zingo-sync/src/traits.rs b/zingo-sync/src/traits.rs deleted file mode 100644 index 1ce66e849..000000000 --- a/zingo-sync/src/traits.rs +++ /dev/null @@ -1,275 +0,0 @@ -//! Traits for interfacing a wallet with the sync engine - -use std::collections::{BTreeMap, HashMap}; -use std::fmt::Debug; - -use orchard::tree::MerkleHashOrchard; -use zcash_client_backend::keys::UnifiedFullViewingKey; -use zcash_primitives::consensus::BlockHeight; -use zcash_primitives::transaction::TxId; -use zcash_primitives::zip32::AccountId; - -use crate::keys::transparent::TransparentAddressId; -use crate::primitives::{ - Locator, NullifierMap, OutputId, SyncState, WalletBlock, WalletTransaction, -}; -use crate::witness::{LocatedTreeData, ShardTrees}; - -// TODO: clean up interface and move many default impls out of traits. consider merging to a simplified SyncWallet interface. - -/// Trait for interfacing wallet with the sync engine. -pub trait SyncWallet { - /// Errors associated with interfacing the sync engine with wallet data - type Error: Debug; - - /// Returns the block height wallet was created. - fn get_birthday(&self) -> Result; - - /// Returns a reference to wallet sync state. - fn get_sync_state(&self) -> Result<&SyncState, Self::Error>; - - /// Returns a mutable reference to wallet sync state. - fn get_sync_state_mut(&mut self) -> Result<&mut SyncState, Self::Error>; - - /// Returns all unified full viewing keys known to this wallet. - fn get_unified_full_viewing_keys( - &self, - ) -> Result, Self::Error>; - - /// Returns a reference to all the transparent addresses known to this wallet. - fn get_transparent_addresses( - &self, - ) -> Result<&BTreeMap, Self::Error>; - - /// Returns a mutable reference to all the transparent addresses known to this wallet. - fn get_transparent_addresses_mut( - &mut self, - ) -> Result<&mut BTreeMap, Self::Error>; -} - -/// Trait for interfacing [`crate::primitives::WalletBlock`]s with wallet data -pub trait SyncBlocks: SyncWallet { - /// Get a stored wallet compact block from wallet data by block height - /// Must return error if block is not found - fn get_wallet_block(&self, block_height: BlockHeight) -> Result; - - /// Get mutable reference to wallet blocks - fn get_wallet_blocks_mut( - &mut self, - ) -> Result<&mut BTreeMap, Self::Error>; - - /// Append wallet compact blocks to wallet data - fn append_wallet_blocks( - &mut self, - mut wallet_blocks: BTreeMap, - ) -> Result<(), Self::Error> { - self.get_wallet_blocks_mut()?.append(&mut wallet_blocks); - - Ok(()) - } - - /// Removes all wallet blocks above the given `block_height`. - fn truncate_wallet_blocks(&mut self, truncate_height: BlockHeight) -> Result<(), Self::Error> { - self.get_wallet_blocks_mut()? - .retain(|block_height, _| *block_height <= truncate_height); - - Ok(()) - } -} - -/// Trait for interfacing [`crate::primitives::WalletTransaction`]s with wallet data -pub trait SyncTransactions: SyncWallet { - /// Get reference to wallet transactions - fn get_wallet_transactions(&self) -> Result<&HashMap, Self::Error>; - - /// Get mutable reference to wallet transactions - fn get_wallet_transactions_mut( - &mut self, - ) -> Result<&mut HashMap, Self::Error>; - - /// Insert wallet transaction - fn insert_wallet_transaction( - &mut self, - wallet_transaction: WalletTransaction, - ) -> Result<(), Self::Error> { - self.get_wallet_transactions_mut()? - .insert(wallet_transaction.txid(), wallet_transaction); - - Ok(()) - } - - /// Extend wallet transaction map with new wallet transactions - fn extend_wallet_transactions( - &mut self, - wallet_transactions: HashMap, - ) -> Result<(), Self::Error> { - self.get_wallet_transactions_mut()? - .extend(wallet_transactions); - - Ok(()) - } - - /// Removes all confirmed wallet transactions above the given `block_height`. - /// Also sets any output's spending_transaction field to `None` if it's spending transaction was removed. - fn truncate_wallet_transactions( - &mut self, - truncate_height: BlockHeight, - ) -> Result<(), Self::Error> { - // TODO: Replace with `extract_if()` when it's in stable rust - let invalid_txids: Vec = self - .get_wallet_transactions()? - .values() - .filter(|tx| tx.status().is_confirmed_after(&truncate_height)) - .map(|tx| tx.transaction().txid()) - .collect(); - - let wallet_transactions = self.get_wallet_transactions_mut()?; - wallet_transactions - .values_mut() - .flat_map(|tx| tx.sapling_notes_mut()) - .filter(|note| { - note.spending_transaction.map_or_else( - || false, - |spending_txid| invalid_txids.contains(&spending_txid), - ) - }) - .for_each(|note| { - note.spending_transaction = None; - }); - wallet_transactions - .values_mut() - .flat_map(|tx| tx.orchard_notes_mut()) - .filter(|note| { - note.spending_transaction.map_or_else( - || false, - |spending_txid| invalid_txids.contains(&spending_txid), - ) - }) - .for_each(|note| { - note.spending_transaction = None; - }); - - invalid_txids.iter().for_each(|invalid_txid| { - wallet_transactions.remove(invalid_txid); - }); - - Ok(()) - } -} - -/// Trait for interfacing nullifiers with wallet data -pub trait SyncNullifiers: SyncWallet { - /// Get wallet nullifier map - fn get_nullifiers(&self) -> Result<&NullifierMap, Self::Error>; - - /// Get mutable reference to wallet nullifier map - fn get_nullifiers_mut(&mut self) -> Result<&mut NullifierMap, Self::Error>; - - /// Append nullifiers to wallet nullifier map - fn append_nullifiers(&mut self, mut nullifiers: NullifierMap) -> Result<(), Self::Error> { - self.get_nullifiers_mut()? - .sapling - .append(&mut nullifiers.sapling); - self.get_nullifiers_mut()? - .orchard - .append(&mut nullifiers.orchard); - - Ok(()) - } - - /// Removes all mapped nullifiers above the given `block_height`. - fn truncate_nullifiers(&mut self, truncate_height: BlockHeight) -> Result<(), Self::Error> { - let nullifier_map = self.get_nullifiers_mut()?; - nullifier_map - .sapling - .retain(|_, (block_height, _)| *block_height <= truncate_height); - nullifier_map - .orchard - .retain(|_, (block_height, _)| *block_height <= truncate_height); - - Ok(()) - } -} - -/// Trait for interfacing outpoints with wallet data -pub trait SyncOutPoints: SyncWallet { - /// Get wallet outpoint map - fn get_outpoints(&self) -> Result<&BTreeMap, Self::Error>; - - /// Get mutable reference to wallet outpoint map - fn get_outpoints_mut(&mut self) -> Result<&mut BTreeMap, Self::Error>; - - /// Append outpoints to wallet outpoint map - fn append_outpoints( - &mut self, - outpoints: &mut BTreeMap, - ) -> Result<(), Self::Error> { - self.get_outpoints_mut()?.append(outpoints); - - Ok(()) - } - - /// Removes all mapped outpoints above the given `block_height`. - fn truncate_outpoints(&mut self, truncate_height: BlockHeight) -> Result<(), Self::Error> { - self.get_outpoints_mut()? - .retain(|_, (block_height, _)| *block_height <= truncate_height); - - Ok(()) - } -} - -/// Trait for interfacing shard tree data with wallet data -pub trait SyncShardTrees: SyncWallet { - /// Get reference to shard trees - fn get_shard_trees(&self) -> Result<&ShardTrees, Self::Error>; - - /// Get mutable reference to shard trees - fn get_shard_trees_mut(&mut self) -> Result<&mut ShardTrees, Self::Error>; - - /// Update wallet shard trees with new shard tree data - fn update_shard_trees( - &mut self, - sapling_located_trees: Vec>, - orchard_located_trees: Vec>, - ) -> Result<(), Self::Error> { - let shard_trees = self.get_shard_trees_mut()?; - - for tree in sapling_located_trees.into_iter() { - shard_trees - .sapling_mut() - .insert_tree(tree.subtree, tree.checkpoints) - .unwrap(); - } - for tree in orchard_located_trees.into_iter() { - shard_trees - .orchard_mut() - .insert_tree(tree.subtree, tree.checkpoints) - .unwrap(); - } - - Ok(()) - } - - /// Removes all shard tree data above the given `block_height`. - fn truncate_shard_trees(&mut self, truncate_height: BlockHeight) -> Result<(), Self::Error> { - // TODO: investigate resetting the shard completely when truncate height is 0 - if !self - .get_shard_trees_mut()? - .sapling_mut() - .truncate_to_checkpoint(&truncate_height) - .unwrap() - { - panic!("max checkpoints should always be higher than verification window!"); - } - if !self - .get_shard_trees_mut()? - .orchard_mut() - .truncate_to_checkpoint(&truncate_height) - .unwrap() - { - panic!("max checkpoints should always be higher than verification window!"); - } - - Ok(()) - } -} diff --git a/zingo-sync/src/utils.rs b/zingo-sync/src/utils.rs deleted file mode 100644 index f59ea8d47..000000000 --- a/zingo-sync/src/utils.rs +++ /dev/null @@ -1,16 +0,0 @@ -use zcash_primitives::consensus::Parameters; - -pub(crate) fn encode_orchard_receiver( - parameters: &P, - orchard_address: &orchard::Address, -) -> Result { - Ok(zcash_address::unified::Encoding::encode( - &::try_from_items( - vec![zcash_address::unified::Receiver::Orchard( - orchard_address.to_raw_address_bytes(), - )], - ) - .unwrap(), - ¶meters.network_type(), - )) -} diff --git a/zingo-sync/src/witness.rs b/zingo-sync/src/witness.rs index eb25087bf..3436f62b8 100644 --- a/zingo-sync/src/witness.rs +++ b/zingo-sync/src/witness.rs @@ -2,54 +2,17 @@ use std::collections::BTreeMap; -use getset::{Getters, MutGetters}; use incrementalmerkletree::{Position, Retention}; use orchard::tree::MerkleHashOrchard; -use shardtree::{ - store::{memory::MemoryShardStore, ShardStore}, - LocatedPrunableTree, ShardTree, -}; +use shardtree::{store::ShardStore, LocatedPrunableTree}; use zcash_client_backend::proto::service::SubtreeRoot; use zcash_primitives::consensus::BlockHeight; -use crate::{sync::MAX_VERIFICATION_WINDOW, MAX_BATCH_OUTPUTS}; +use crate::MAX_BATCH_OUTPUTS; -const NOTE_COMMITMENT_TREE_DEPTH: u8 = 32; -const SHARD_HEIGHT: u8 = 16; +pub(crate) const SHARD_HEIGHT: u8 = 16; const LOCATED_TREE_SIZE: usize = MAX_BATCH_OUTPUTS / 16; -/// Type alias for sapling memory shard store -pub type SaplingShardStore = MemoryShardStore; - -/// Type alias for orchard memory shard store -pub type OrchardShardStore = MemoryShardStore; - -/// Shard tree wallet data struct -#[derive(Debug, Getters, MutGetters)] -#[getset(get = "pub", get_mut = "pub")] -pub struct ShardTrees { - /// Sapling shard tree - sapling: ShardTree, - /// Orchard shard tree - orchard: ShardTree, -} - -impl ShardTrees { - /// Create new ShardTrees - pub fn new() -> Self { - Self { - sapling: ShardTree::new(MemoryShardStore::empty(), MAX_VERIFICATION_WINDOW as usize), - orchard: ShardTree::new(MemoryShardStore::empty(), MAX_VERIFICATION_WINDOW as usize), - } - } -} - -impl Default for ShardTrees { - fn default() -> Self { - Self::new() - } -} - /// Required data for updating [`shardtree::ShardTree`] pub(crate) struct WitnessData { pub(crate) sapling_initial_position: Position, @@ -60,7 +23,10 @@ pub(crate) struct WitnessData { impl WitnessData { /// Creates new ShardTreeData - pub fn new(sapling_initial_position: Position, orchard_initial_position: Position) -> Self { + pub(crate) fn new( + sapling_initial_position: Position, + orchard_initial_position: Position, + ) -> Self { WitnessData { sapling_initial_position, orchard_initial_position, @@ -73,9 +39,9 @@ impl WitnessData { /// Located prunable tree data built from nodes and retentions during scanning for insertion into the shard store. pub struct LocatedTreeData { /// Located prunable tree - pub subtree: LocatedPrunableTree, + pub(crate) subtree: LocatedPrunableTree, /// Checkpoints - pub checkpoints: BTreeMap, + pub(crate) checkpoints: BTreeMap, } pub(crate) fn build_located_trees( diff --git a/zingolib/src/lightclient/describe.rs b/zingolib/src/lightclient/describe.rs index c54227e98..95843f7a6 100644 --- a/zingolib/src/lightclient/describe.rs +++ b/zingolib/src/lightclient/describe.rs @@ -2,7 +2,7 @@ use json::{object, JsonValue}; use std::collections::HashMap; use tokio::runtime::Runtime; -use zingo_sync::primitives::{OrchardNote, SaplingNote, TransparentCoin}; +use zingo_sync::wallet::{OrchardNote, SaplingNote, TransparentCoin}; use crate::{ lightclient::{AccountBackupInfo, LightClient, PoolBalances}, diff --git a/zingolib/src/testutils/assertions.rs b/zingolib/src/testutils/assertions.rs index 6fc928dfa..be2d043a6 100644 --- a/zingolib/src/testutils/assertions.rs +++ b/zingolib/src/testutils/assertions.rs @@ -4,7 +4,7 @@ use nonempty::NonEmpty; use zcash_client_backend::proposal::{Proposal, Step}; use zcash_primitives::transaction::TxId; -use zingo_sync::primitives::WalletTransaction; +use zingo_sync::wallet::WalletTransaction; use crate::{lightclient::LightClient, wallet::LightWallet}; diff --git a/zingolib/src/wallet.rs b/zingolib/src/wallet.rs index d63c22ba0..a0791f4ea 100644 --- a/zingolib/src/wallet.rs +++ b/zingolib/src/wallet.rs @@ -15,10 +15,10 @@ use rand::rngs::OsRng; use rand::Rng; use zingo_sync::keys::transparent::{self, TransparentScope}; +use zingo_sync::wallet::ShardTrees; use zingo_sync::{ keys::transparent::TransparentAddressId, - primitives::{Locator, NullifierMap, OutputId, SyncState, WalletBlock, WalletTransaction}, - witness::ShardTrees, + wallet::{Locator, NullifierMap, OutputId, SyncState, WalletBlock, WalletTransaction}, }; use bip0039::Mnemonic; @@ -332,7 +332,7 @@ impl LightWallet { unified_addresses.iter().for_each(|unified_address| { if let Some(transparent_address) = unified_address.transparent() { transparent_addresses.insert( - TransparentAddressId::from_parts( + TransparentAddressId::new( zip32::AccountId::ZERO, TransparentScope::External, 0, @@ -351,10 +351,10 @@ impl LightWallet { price: Arc::new(RwLock::new(WalletZecPriceInfo::default())), wallet_blocks: BTreeMap::new(), wallet_transactions: HashMap::new(), - nullifier_map: zingo_sync::primitives::NullifierMap::new(), + nullifier_map: zingo_sync::wallet::NullifierMap::new(), outpoint_map: BTreeMap::new(), - shard_trees: zingo_sync::witness::ShardTrees::new(), - sync_state: zingo_sync::primitives::SyncState::new(), + shard_trees: zingo_sync::wallet::ShardTrees::new(), + sync_state: zingo_sync::wallet::SyncState::new(), transparent_addresses, unified_addresses, network, diff --git a/zingolib/src/wallet/describe.rs b/zingolib/src/wallet/describe.rs index 4f289c4cf..60356c797 100644 --- a/zingolib/src/wallet/describe.rs +++ b/zingolib/src/wallet/describe.rs @@ -11,13 +11,13 @@ use zcash_primitives::legacy::TransparentAddress; use zcash_primitives::memo::Memo; use zcash_primitives::transaction::components::amount::NonNegativeAmount; use zcash_primitives::transaction::fees::zip317::MARGINAL_FEE; -use zingo_sync::primitives::NoteInterface as _; -use zingo_sync::primitives::OrchardNote; -use zingo_sync::primitives::OutgoingNoteInterface; -use zingo_sync::primitives::OutputInterface; -use zingo_sync::primitives::SaplingNote; -use zingo_sync::primitives::TransparentCoin; -use zingo_sync::primitives::WalletTransaction; +use zingo_sync::wallet::NoteInterface as _; +use zingo_sync::wallet::OrchardNote; +use zingo_sync::wallet::OutgoingNoteInterface; +use zingo_sync::wallet::OutputInterface; +use zingo_sync::wallet::SaplingNote; +use zingo_sync::wallet::TransparentCoin; +use zingo_sync::wallet::WalletTransaction; use std::cmp::Ordering; diff --git a/zingolib/src/wallet/disk.rs b/zingolib/src/wallet/disk.rs index 6903bfda7..96ca2bdcc 100644 --- a/zingolib/src/wallet/disk.rs +++ b/zingolib/src/wallet/disk.rs @@ -294,10 +294,10 @@ impl LightWallet { price: Arc::new(RwLock::new(price)), wallet_blocks: BTreeMap::new(), wallet_transactions: HashMap::new(), - nullifier_map: zingo_sync::primitives::NullifierMap::new(), + nullifier_map: zingo_sync::wallet::NullifierMap::new(), outpoint_map: BTreeMap::new(), - shard_trees: zingo_sync::witness::ShardTrees::new(), - sync_state: zingo_sync::primitives::SyncState::new(), + shard_trees: zingo_sync::wallet::ShardTrees::new(), + sync_state: zingo_sync::wallet::SyncState::new(), transparent_addresses: BTreeMap::new(), unified_addresses: AppendOnlyVec::new(), network, diff --git a/zingolib/src/wallet/keys.rs b/zingolib/src/wallet/keys.rs index 1c33c2360..085c39db0 100644 --- a/zingolib/src/wallet/keys.rs +++ b/zingolib/src/wallet/keys.rs @@ -36,7 +36,7 @@ impl LightWallet { if let Some(transparent_address) = unified_address.transparent() { self.transparent_addresses.insert( - TransparentAddressId::from_parts( + TransparentAddressId::new( zip32::AccountId::ZERO, TransparentScope::External, self.unified_addresses.len() as u32, @@ -63,7 +63,7 @@ impl LightWallet { (refund_address_count..(refund_address_count + n)) .map(|address_index| { - let transparent_address_id = TransparentAddressId::from_parts( + let transparent_address_id = TransparentAddressId::new( zip32::AccountId::ZERO, TransparentScope::Refund, address_index as u32, diff --git a/zingolib/src/wallet/output.rs b/zingolib/src/wallet/output.rs index f5959cce9..c3c37569c 100644 --- a/zingolib/src/wallet/output.rs +++ b/zingolib/src/wallet/output.rs @@ -6,11 +6,11 @@ use zcash_primitives::consensus::BlockHeight; use zcash_primitives::transaction::components::amount::NonNegativeAmount; use zcash_primitives::transaction::fees::zip317::MARGINAL_FEE; use zcash_primitives::transaction::TxId; -use zingo_sync::primitives::NoteInterface; -use zingo_sync::primitives::OutputId; -use zingo_sync::primitives::OutputInterface; -use zingo_sync::primitives::TransparentCoin; -use zingo_sync::primitives::WalletTransaction; +use zingo_sync::wallet::NoteInterface; +use zingo_sync::wallet::OutputId; +use zingo_sync::wallet::OutputInterface; +use zingo_sync::wallet::TransparentCoin; +use zingo_sync::wallet::WalletTransaction; use query::OutputQuery; use query::OutputSpendStatusQuery; diff --git a/zingolib/src/wallet/send.rs b/zingolib/src/wallet/send.rs index 23cd6f975..8dfffc003 100644 --- a/zingolib/src/wallet/send.rs +++ b/zingolib/src/wallet/send.rs @@ -18,7 +18,7 @@ use zcash_primitives::memo::MemoBytes; use zcash_primitives::transaction::fees::zip317; use zingo_memo::create_wallet_internal_memo_version_1; use zingo_status::confirmation_status::ConfirmationStatus; -use zingo_sync::traits::SyncWallet as _; +use zingo_sync::wallet::traits::SyncWallet as _; use crate::lightclient::send::send_with_proposal::BroadcastTransactionsError; use crate::wallet::now; diff --git a/zingolib/src/wallet/sync.rs b/zingolib/src/wallet/sync.rs index 1ba879733..9faad78a1 100644 --- a/zingolib/src/wallet/sync.rs +++ b/zingolib/src/wallet/sync.rs @@ -6,11 +6,10 @@ use zcash_keys::keys::UnifiedFullViewingKey; use zcash_primitives::consensus::BlockHeight; use zingo_sync::{ keys::transparent::TransparentAddressId, - primitives::{Locator, NullifierMap, OutputId, SyncState, WalletBlock}, - traits::{ + wallet::traits::{ SyncBlocks, SyncNullifiers, SyncOutPoints, SyncShardTrees, SyncTransactions, SyncWallet, }, - witness::ShardTrees, + wallet::{Locator, NullifierMap, OutputId, ShardTrees, SyncState, WalletBlock}, }; use zip32::AccountId; @@ -74,7 +73,7 @@ impl SyncTransactions for LightWallet { fn get_wallet_transactions( &self, ) -> Result< - &HashMap, + &HashMap, Self::Error, > { Ok(&self.wallet_transactions) @@ -83,10 +82,7 @@ impl SyncTransactions for LightWallet { fn get_wallet_transactions_mut( &mut self, ) -> Result< - &mut HashMap< - zcash_primitives::transaction::TxId, - zingo_sync::primitives::WalletTransaction, - >, + &mut HashMap, Self::Error, > { Ok(&mut self.wallet_transactions) diff --git a/zingolib/src/wallet/transaction.rs b/zingolib/src/wallet/transaction.rs index 897da1e2e..15d55013e 100644 --- a/zingolib/src/wallet/transaction.rs +++ b/zingolib/src/wallet/transaction.rs @@ -1,5 +1,5 @@ use zcash_primitives::transaction::components::Amount; -use zingo_sync::primitives::{OutputId, OutputInterface, TransparentCoin, WalletTransaction}; +use zingo_sync::wallet::{OutputId, OutputInterface, TransparentCoin, WalletTransaction}; use super::{ error::{FeeError, KindError}, diff --git a/zingolib/src/wallet/zcb_traits.rs b/zingolib/src/wallet/zcb_traits.rs index b3cc6d92c..a0714d80d 100644 --- a/zingolib/src/wallet/zcb_traits.rs +++ b/zingolib/src/wallet/zcb_traits.rs @@ -28,9 +28,11 @@ use zcash_primitives::{ use zingo_status::confirmation_status::ConfirmationStatus; use zingo_sync::{ keys::transparent::{self, TransparentScope}, - primitives::{NoteInterface as _, OrchardNote, OutputId, OutputInterface, SaplingNote}, - traits::SyncWallet, - witness::{OrchardShardStore, SaplingShardStore}, + wallet::traits::SyncWallet, + wallet::{ + NoteInterface as _, OrchardNote, OrchardShardStore, OutputId, OutputInterface, SaplingNote, + SaplingShardStore, + }, }; use crate::wallet::output::RemainingNeeded; @@ -425,7 +427,7 @@ impl WalletCommitmentTrees for LightWallet { ) -> Result, E: From>, { - callback(self.shard_trees.sapling_mut()) + callback(&mut self.shard_trees.sapling) } fn put_sapling_subtree_roots( @@ -458,7 +460,7 @@ impl WalletCommitmentTrees for LightWallet { ) -> Result, E: From>, { - callback(self.shard_trees.orchard_mut()) + callback(&mut self.shard_trees.orchard) } fn put_orchard_subtree_roots( @@ -581,10 +583,10 @@ impl InputSource for LightWallet { NoteId::new( note.output_id().txid(), ShieldedProtocol::Sapling, - note.output_id().output_index() as u16, + note.output_id().output_index(), ), note.output_id().txid(), - note.output_id().output_index() as u16, + note.output_id().output_index(), note.note().clone(), note.key_id().scope, note.position() @@ -599,10 +601,10 @@ impl InputSource for LightWallet { NoteId::new( note.output_id().txid(), ShieldedProtocol::Orchard, - note.output_id().output_index() as u16, + note.output_id().output_index(), ), note.output_id().txid(), - note.output_id().output_index() as u16, + note.output_id().output_index(), *note.note(), note.key_id().scope, note.position()