use std::cmp;
use std::collections::HashMap;
use std::fmt::Debug;
use secrecy::SecretVec;
use zcash_primitives::{
block::BlockHash,
consensus::BlockHeight,
legacy::TransparentAddress,
memo::{Memo, MemoBytes},
merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::{Node, Nullifier, PaymentAddress},
transaction::{
components::{amount::Amount, OutPoint},
Transaction, TxId,
},
zip32::{AccountId, ExtendedFullViewingKey},
};
use crate::{
address::{AddressMetadata, UnifiedAddress},
decrypt::DecryptedOutput,
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
wallet::{SpendableNote, WalletTransparentOutput, WalletTx},
};
pub mod chain;
pub mod error;
pub mod wallet;
pub trait WalletRead {
type Error;
type NoteRef: Copy + Debug + Eq + Ord;
type TxRef: Copy + Debug + Eq + Ord;
fn block_height_extrema(&self) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error>;
fn get_target_and_anchor_heights(
&self,
min_confirmations: u32,
) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
self.block_height_extrema().map(|heights| {
heights.map(|(min_height, max_height)| {
let target_height = max_height + 1;
let anchor_height = BlockHeight::from(cmp::max(
u32::from(target_height).saturating_sub(min_confirmations),
u32::from(min_height),
));
(target_height, anchor_height)
})
})
}
fn get_block_hash(&self, block_height: BlockHeight) -> Result<Option<BlockHash>, Self::Error>;
fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error> {
self.block_height_extrema()
.and_then(|extrema_opt| {
extrema_opt
.map(|(_, max_height)| {
self.get_block_hash(max_height)
.map(|hash_opt| hash_opt.map(move |hash| (max_height, hash)))
})
.transpose()
})
.map(|oo| oo.flatten())
}
fn get_tx_height(&self, txid: TxId) -> Result<Option<BlockHeight>, Self::Error>;
fn get_current_address(
&self,
account: AccountId,
) -> Result<Option<UnifiedAddress>, Self::Error>;
fn get_unified_full_viewing_keys(
&self,
) -> Result<HashMap<AccountId, UnifiedFullViewingKey>, Self::Error>;
fn get_account_for_ufvk(
&self,
ufvk: &UnifiedFullViewingKey,
) -> Result<Option<AccountId>, Self::Error>;
fn is_valid_account_extfvk(
&self,
account: AccountId,
extfvk: &ExtendedFullViewingKey,
) -> Result<bool, Self::Error>;
fn get_balance_at(
&self,
account: AccountId,
anchor_height: BlockHeight,
) -> Result<Amount, Self::Error>;
fn get_memo(&self, id_note: Self::NoteRef) -> Result<Memo, Self::Error>;
fn get_transaction(&self, id_tx: Self::TxRef) -> Result<Transaction, Self::Error>;
fn get_commitment_tree(
&self,
block_height: BlockHeight,
) -> Result<Option<CommitmentTree<Node>>, Self::Error>;
#[allow(clippy::type_complexity)]
fn get_witnesses(
&self,
block_height: BlockHeight,
) -> Result<Vec<(Self::NoteRef, IncrementalWitness<Node>)>, Self::Error>;
fn get_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error>;
fn get_all_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error>;
fn get_spendable_sapling_notes(
&self,
account: AccountId,
anchor_height: BlockHeight,
exclude: &[Self::NoteRef],
) -> Result<Vec<SpendableNote<Self::NoteRef>>, Self::Error>;
fn select_spendable_sapling_notes(
&self,
account: AccountId,
target_value: Amount,
anchor_height: BlockHeight,
exclude: &[Self::NoteRef],
) -> Result<Vec<SpendableNote<Self::NoteRef>>, Self::Error>;
fn get_transparent_receivers(
&self,
account: AccountId,
) -> Result<HashMap<TransparentAddress, AddressMetadata>, Self::Error>;
fn get_unspent_transparent_outputs(
&self,
address: &TransparentAddress,
max_height: BlockHeight,
exclude: &[OutPoint],
) -> Result<Vec<WalletTransparentOutput>, Self::Error>;
fn get_transparent_balances(
&self,
account: AccountId,
max_height: BlockHeight,
) -> Result<HashMap<TransparentAddress, Amount>, Self::Error>;
}
pub struct PrunedBlock<'a> {
pub block_height: BlockHeight,
pub block_hash: BlockHash,
pub block_time: u32,
pub commitment_tree: &'a CommitmentTree<Node>,
pub transactions: &'a Vec<WalletTx<Nullifier>>,
}
pub struct DecryptedTransaction<'a> {
pub tx: &'a Transaction,
pub sapling_outputs: &'a Vec<DecryptedOutput>,
}
pub struct SentTransaction<'a> {
pub tx: &'a Transaction,
pub created: time::OffsetDateTime,
pub account: AccountId,
pub outputs: Vec<SentTransactionOutput>,
pub fee_amount: Amount,
#[cfg(feature = "transparent-inputs")]
pub utxos_spent: Vec<OutPoint>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PoolType {
Transparent,
Sapling,
}
#[derive(Debug, Clone)]
pub enum Recipient {
Transparent(TransparentAddress),
Sapling(PaymentAddress),
Unified(UnifiedAddress, PoolType),
InternalAccount(AccountId, PoolType),
}
pub struct SentTransactionOutput {
pub output_index: usize,
pub recipient: Recipient,
pub value: Amount,
pub memo: Option<MemoBytes>,
}
pub trait WalletWrite: WalletRead {
type UtxoRef;
fn create_account(
&mut self,
seed: &SecretVec<u8>,
) -> Result<(AccountId, UnifiedSpendingKey), Self::Error>;
fn get_next_available_address(
&mut self,
account: AccountId,
) -> Result<Option<UnifiedAddress>, Self::Error>;
#[allow(clippy::type_complexity)]
fn advance_by_block(
&mut self,
block: &PrunedBlock,
updated_witnesses: &[(Self::NoteRef, IncrementalWitness<Node>)],
) -> Result<Vec<(Self::NoteRef, IncrementalWitness<Node>)>, Self::Error>;
fn store_decrypted_tx(
&mut self,
received_tx: &DecryptedTransaction,
) -> Result<Self::TxRef, Self::Error>;
fn store_sent_tx(&mut self, sent_tx: &SentTransaction) -> Result<Self::TxRef, Self::Error>;
fn rewind_to_height(&mut self, block_height: BlockHeight) -> Result<(), Self::Error>;
fn put_received_transparent_utxo(
&mut self,
output: &WalletTransparentOutput,
) -> Result<Self::UtxoRef, Self::Error>;
}
#[cfg(feature = "test-dependencies")]
pub mod testing {
use secrecy::{ExposeSecret, SecretVec};
use std::collections::HashMap;
use zcash_primitives::{
block::BlockHash,
consensus::{BlockHeight, Network},
legacy::TransparentAddress,
memo::Memo,
merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::{Node, Nullifier},
transaction::{
components::{Amount, OutPoint},
Transaction, TxId,
},
zip32::{AccountId, ExtendedFullViewingKey},
};
use crate::{
address::{AddressMetadata, UnifiedAddress},
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
wallet::{SpendableNote, WalletTransparentOutput},
};
use super::{DecryptedTransaction, PrunedBlock, SentTransaction, WalletRead, WalletWrite};
pub struct MockWalletDb {
pub network: Network,
}
impl WalletRead for MockWalletDb {
type Error = ();
type NoteRef = u32;
type TxRef = TxId;
fn block_height_extrema(&self) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
Ok(None)
}
fn get_block_hash(
&self,
_block_height: BlockHeight,
) -> Result<Option<BlockHash>, Self::Error> {
Ok(None)
}
fn get_tx_height(&self, _txid: TxId) -> Result<Option<BlockHeight>, Self::Error> {
Ok(None)
}
fn get_current_address(
&self,
_account: AccountId,
) -> Result<Option<UnifiedAddress>, Self::Error> {
Ok(None)
}
fn get_unified_full_viewing_keys(
&self,
) -> Result<HashMap<AccountId, UnifiedFullViewingKey>, Self::Error> {
Ok(HashMap::new())
}
fn get_account_for_ufvk(
&self,
_ufvk: &UnifiedFullViewingKey,
) -> Result<Option<AccountId>, Self::Error> {
Ok(None)
}
fn is_valid_account_extfvk(
&self,
_account: AccountId,
_extfvk: &ExtendedFullViewingKey,
) -> Result<bool, Self::Error> {
Ok(false)
}
fn get_balance_at(
&self,
_account: AccountId,
_anchor_height: BlockHeight,
) -> Result<Amount, Self::Error> {
Ok(Amount::zero())
}
fn get_memo(&self, _id_note: Self::NoteRef) -> Result<Memo, Self::Error> {
Ok(Memo::Empty)
}
fn get_transaction(&self, _id_tx: Self::TxRef) -> Result<Transaction, Self::Error> {
Err(())
}
fn get_commitment_tree(
&self,
_block_height: BlockHeight,
) -> Result<Option<CommitmentTree<Node>>, Self::Error> {
Ok(None)
}
#[allow(clippy::type_complexity)]
fn get_witnesses(
&self,
_block_height: BlockHeight,
) -> Result<Vec<(Self::NoteRef, IncrementalWitness<Node>)>, Self::Error> {
Ok(Vec::new())
}
fn get_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error> {
Ok(Vec::new())
}
fn get_all_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error> {
Ok(Vec::new())
}
fn get_spendable_sapling_notes(
&self,
_account: AccountId,
_anchor_height: BlockHeight,
_exclude: &[Self::NoteRef],
) -> Result<Vec<SpendableNote<Self::NoteRef>>, Self::Error> {
Ok(Vec::new())
}
fn select_spendable_sapling_notes(
&self,
_account: AccountId,
_target_value: Amount,
_anchor_height: BlockHeight,
_exclude: &[Self::NoteRef],
) -> Result<Vec<SpendableNote<Self::NoteRef>>, Self::Error> {
Ok(Vec::new())
}
fn get_transparent_receivers(
&self,
_account: AccountId,
) -> Result<HashMap<TransparentAddress, AddressMetadata>, Self::Error> {
Ok(HashMap::new())
}
fn get_unspent_transparent_outputs(
&self,
_address: &TransparentAddress,
_anchor_height: BlockHeight,
_exclude: &[OutPoint],
) -> Result<Vec<WalletTransparentOutput>, Self::Error> {
Ok(Vec::new())
}
fn get_transparent_balances(
&self,
_account: AccountId,
_max_height: BlockHeight,
) -> Result<HashMap<TransparentAddress, Amount>, Self::Error> {
Ok(HashMap::new())
}
}
impl WalletWrite for MockWalletDb {
type UtxoRef = u32;
fn create_account(
&mut self,
seed: &SecretVec<u8>,
) -> Result<(AccountId, UnifiedSpendingKey), Self::Error> {
let account = AccountId::from(0);
UnifiedSpendingKey::from_seed(&self.network, seed.expose_secret(), account)
.map(|k| (account, k))
.map_err(|_| ())
}
fn get_next_available_address(
&mut self,
_account: AccountId,
) -> Result<Option<UnifiedAddress>, Self::Error> {
Ok(None)
}
#[allow(clippy::type_complexity)]
fn advance_by_block(
&mut self,
_block: &PrunedBlock,
_updated_witnesses: &[(Self::NoteRef, IncrementalWitness<Node>)],
) -> Result<Vec<(Self::NoteRef, IncrementalWitness<Node>)>, Self::Error> {
Ok(vec![])
}
fn store_decrypted_tx(
&mut self,
_received_tx: &DecryptedTransaction,
) -> Result<Self::TxRef, Self::Error> {
Ok(TxId::from_bytes([0u8; 32]))
}
fn store_sent_tx(
&mut self,
_sent_tx: &SentTransaction,
) -> Result<Self::TxRef, Self::Error> {
Ok(TxId::from_bytes([0u8; 32]))
}
fn rewind_to_height(&mut self, _block_height: BlockHeight) -> Result<(), Self::Error> {
Ok(())
}
fn put_received_transparent_utxo(
&mut self,
_output: &WalletTransparentOutput,
) -> Result<Self::UtxoRef, Self::Error> {
Ok(0)
}
}
}