Merge pull request #852 from nuttycom/wallet/sapling_cleanup

Consolidate sapling functionality in zcash_client_sqlite in a dedicated module.
This commit is contained in:
Kris Nuttycombe 2023-06-02 13:11:32 -06:00 committed by GitHub
commit de771b7f22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 350 additions and 341 deletions

View File

@ -21,5 +21,5 @@ codegen-units = 1
[patch.crates-io]
zcash_encoding = { path = "components/zcash_encoding" }
zcash_note_encryption = { path = "components/zcash_note_encryption" }
incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree.git", rev = "62f0c9039b0bee94c16c40c272e19c5922290664" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "2a4f27c937fbcbdb66163e1bb426ce1fcb5bc4f8" }
incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree.git", rev = "2dbdd345670ea22337a0efa6734272d54551285f" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "35054e85b85dc144b4572ed0fd57ea164f50c26a" }

View File

@ -20,6 +20,7 @@ exclude = ["*.proto"]
development = ["zcash_proofs"]
[dependencies]
incrementalmerkletree = { version = "0.3", features = ["legacy-api"] }
zcash_address = { version = "0.2", path = "../components/zcash_address" }
zcash_encoding = { version = "0.2", path = "../components/zcash_encoding" }
zcash_note_encryption = "0.3"
@ -94,6 +95,7 @@ test-dependencies = [
"proptest",
"orchard/test-dependencies",
"zcash_primitives/test-dependencies",
"incrementalmerkletree/test-dependencies"
]
unstable = ["byteorder"]

View File

@ -10,7 +10,7 @@ use zcash_primitives::{
consensus::BlockHeight,
legacy::TransparentAddress,
memo::{Memo, MemoBytes},
sapling::{self, Nullifier, PaymentAddress},
sapling,
transaction::{
components::{amount::Amount, OutPoint},
Transaction, TxId,
@ -22,13 +22,18 @@ use crate::{
address::{AddressMetadata, UnifiedAddress},
decrypt::DecryptedOutput,
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
wallet::{SpendableNote, WalletTransparentOutput, WalletTx},
wallet::{ReceivedSaplingNote, WalletTransparentOutput, WalletTx},
};
pub mod chain;
pub mod error;
pub mod wallet;
pub enum NullifierQuery {
Unspent,
All,
}
/// Read-only operations required for light wallet functions.
///
/// This trait defines the read-only portion of the storage interface atop which
@ -107,15 +112,15 @@ pub trait WalletRead {
.map(|oo| oo.flatten())
}
/// Returns the block height in which the specified transaction was mined,
/// or `Ok(None)` if the transaction is not mined in the main chain.
/// Returns the block height in which the specified transaction was mined, or `Ok(None)` if the
/// transaction is not in the main chain.
fn get_tx_height(&self, txid: TxId) -> Result<Option<BlockHeight>, Self::Error>;
/// Returns the most recently generated unified address for the specified account, if the
/// account identifier specified refers to a valid account for this wallet.
///
/// This will return `Ok(None)` if the account identifier does not correspond
/// to a known account.
/// This will return `Ok(None)` if the account identifier does not correspond to a known
/// account.
fn get_current_address(
&self,
account: AccountId,
@ -126,26 +131,23 @@ pub trait WalletRead {
&self,
) -> Result<HashMap<AccountId, UnifiedFullViewingKey>, Self::Error>;
/// Returns the account id corresponding to a given [`UnifiedFullViewingKey`],
/// if any.
/// Returns the account id corresponding to a given [`UnifiedFullViewingKey`], if any.
fn get_account_for_ufvk(
&self,
ufvk: &UnifiedFullViewingKey,
) -> Result<Option<AccountId>, Self::Error>;
/// Checks whether the specified extended full viewing key is
/// associated with the account.
/// Checks whether the specified extended full viewing key is associated with the account.
fn is_valid_account_extfvk(
&self,
account: AccountId,
extfvk: &ExtendedFullViewingKey,
) -> Result<bool, Self::Error>;
/// Returns the wallet balance for an account as of the specified block
/// height.
/// Returns the wallet balance for an account as of the specified block height.
///
/// This may be used to obtain a balance that ignores notes that have been
/// received so recently that they are not yet deemed spendable.
/// This may be used to obtain a balance that ignores notes that have been received so recently
/// that they are not yet deemed spendable.
fn get_balance_at(
&self,
account: AccountId,
@ -176,15 +178,13 @@ pub trait WalletRead {
block_height: BlockHeight,
) -> Result<Vec<(Self::NoteRef, sapling::IncrementalWitness)>, Self::Error>;
/// Returns the nullifiers for notes that the wallet is tracking, along with their
/// associated account IDs, that are either unspent or have not yet been confirmed as
/// spent (in that the spending transaction has not yet been included in a block).
fn get_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error>;
/// Returns all nullifiers for notes that the wallet is tracking
/// (including those for notes that have been previously spent),
/// along with the account identifiers with which they are associated.
fn get_all_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error>;
/// Returns the nullifiers for notes that the wallet is tracking, along with their associated
/// account IDs, that are either unspent or have not yet been confirmed as spent (in that a
/// spending transaction known to the wallet has not yet been included in a block).
fn get_sapling_nullifiers(
&self,
query: NullifierQuery,
) -> Result<Vec<(AccountId, sapling::Nullifier)>, Self::Error>;
/// Return all unspent Sapling notes.
fn get_spendable_sapling_notes(
@ -192,17 +192,17 @@ pub trait WalletRead {
account: AccountId,
anchor_height: BlockHeight,
exclude: &[Self::NoteRef],
) -> Result<Vec<SpendableNote<Self::NoteRef>>, Self::Error>;
) -> Result<Vec<ReceivedSaplingNote<Self::NoteRef>>, Self::Error>;
/// Returns a list of spendable Sapling notes sufficient to cover the specified
/// target value, if possible.
/// Returns a list of spendable Sapling notes sufficient to cover the specified target value,
/// if possible.
fn select_spendable_sapling_notes(
&self,
account: AccountId,
target_value: Amount,
anchor_height: BlockHeight,
exclude: &[Self::NoteRef],
) -> Result<Vec<SpendableNote<Self::NoteRef>>, Self::Error>;
) -> Result<Vec<ReceivedSaplingNote<Self::NoteRef>>, Self::Error>;
/// Returns the set of all transparent receivers associated with the given account.
///
@ -241,7 +241,7 @@ pub struct PrunedBlock<'a> {
pub block_hash: BlockHash,
pub block_time: u32,
pub commitment_tree: &'a sapling::CommitmentTree,
pub transactions: &'a Vec<WalletTx<Nullifier>>,
pub transactions: &'a Vec<WalletTx<sapling::Nullifier>>,
}
/// A transaction that was detected during scanning of the blockchain,
@ -286,7 +286,7 @@ pub enum PoolType {
#[derive(Debug, Clone)]
pub enum Recipient {
Transparent(TransparentAddress),
Sapling(PaymentAddress),
Sapling(sapling::PaymentAddress),
Unified(UnifiedAddress, PoolType),
InternalAccount(AccountId, PoolType),
}
@ -395,7 +395,7 @@ pub trait WalletWrite: WalletRead {
/// Caches a decrypted transaction in the persistent wallet store.
fn store_decrypted_tx(
&mut self,
received_tx: &DecryptedTransaction,
received_tx: DecryptedTransaction,
) -> Result<Self::TxRef, Self::Error>;
/// Saves information about a transaction that was constructed and sent by the wallet to the
@ -434,7 +434,7 @@ pub mod testing {
consensus::{BlockHeight, Network},
legacy::TransparentAddress,
memo::Memo,
sapling::{self, Nullifier},
sapling,
transaction::{
components::{Amount, OutPoint},
Transaction, TxId,
@ -445,10 +445,12 @@ pub mod testing {
use crate::{
address::{AddressMetadata, UnifiedAddress},
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
wallet::{SpendableNote, WalletTransparentOutput},
wallet::{ReceivedSaplingNote, WalletTransparentOutput},
};
use super::{DecryptedTransaction, PrunedBlock, SentTransaction, WalletRead, WalletWrite};
use super::{
DecryptedTransaction, NullifierQuery, PrunedBlock, SentTransaction, WalletRead, WalletWrite,
};
pub struct MockWalletDb {
pub network: Network,
@ -537,11 +539,10 @@ pub mod testing {
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> {
fn get_sapling_nullifiers(
&self,
_query: NullifierQuery,
) -> Result<Vec<(AccountId, sapling::Nullifier)>, Self::Error> {
Ok(Vec::new())
}
@ -550,7 +551,7 @@ pub mod testing {
_account: AccountId,
_anchor_height: BlockHeight,
_exclude: &[Self::NoteRef],
) -> Result<Vec<SpendableNote<Self::NoteRef>>, Self::Error> {
) -> Result<Vec<ReceivedSaplingNote<Self::NoteRef>>, Self::Error> {
Ok(Vec::new())
}
@ -560,7 +561,7 @@ pub mod testing {
_target_value: Amount,
_anchor_height: BlockHeight,
_exclude: &[Self::NoteRef],
) -> Result<Vec<SpendableNote<Self::NoteRef>>, Self::Error> {
) -> Result<Vec<ReceivedSaplingNote<Self::NoteRef>>, Self::Error> {
Ok(Vec::new())
}
@ -620,7 +621,7 @@ pub mod testing {
fn store_decrypted_tx(
&mut self,
_received_tx: &DecryptedTransaction,
_received_tx: DecryptedTransaction,
) -> Result<Self::TxRef, Self::Error> {
Ok(TxId::from_bytes([0u8; 32]))
}

View File

@ -104,6 +104,8 @@ use crate::{
pub mod error;
use error::{ChainError, Error};
use super::NullifierQuery;
/// This trait provides sequential access to raw blockchain data via a callback-oriented
/// API.
pub trait BlockSource {
@ -252,7 +254,9 @@ where
)?;
// Get the nullifiers for the notes we are tracking
let mut nullifiers = data_db.get_nullifiers().map_err(Error::Wallet)?;
let mut nullifiers = data_db
.get_sapling_nullifiers(NullifierQuery::Unspent)
.map_err(Error::Wallet)?;
let mut batch_runner = BatchRunner::<_, _, _, ()>::new(
100,

View File

@ -28,7 +28,7 @@ use crate::{
decrypt_transaction,
fees::{self, ChangeValue, DustOutputPolicy},
keys::UnifiedSpendingKey,
wallet::{OvkPolicy, SpendableNote},
wallet::{OvkPolicy, ReceivedSaplingNote},
zip321::{self, Payment},
};
@ -68,7 +68,7 @@ where
.or_else(|| params.activation_height(NetworkUpgrade::Sapling))
.expect("Sapling activation height must be known.");
data.store_decrypted_tx(&DecryptedTransaction {
data.store_decrypted_tx(DecryptedTransaction {
tx,
sapling_outputs: &decrypt_transaction(params, height, tx, &ufvks),
})?;
@ -701,7 +701,7 @@ where
}
fn select_key_for_note<N>(
selected: &SpendableNote<N>,
selected: &ReceivedSaplingNote<N>,
extsk: &ExtendedSpendingKey,
dfvk: &DiversifiableFullViewingKey,
) -> Option<(sapling::Note, ExtendedSpendingKey, sapling::MerklePath)> {

View File

@ -22,7 +22,7 @@ use crate::{
address::{RecipientAddress, UnifiedAddress},
data_api::WalletRead,
fees::{ChangeError, ChangeStrategy, DustOutputPolicy, TransactionBalance},
wallet::{SpendableNote, WalletTransparentOutput},
wallet::{ReceivedSaplingNote, WalletTransparentOutput},
zip321::TransactionRequest,
};
@ -68,7 +68,7 @@ impl<DE: fmt::Display, SE: fmt::Display> fmt::Display for InputSelectorError<DE,
pub struct Proposal<FeeRuleT, NoteRef> {
transaction_request: TransactionRequest,
transparent_inputs: Vec<WalletTransparentOutput>,
sapling_inputs: Vec<SpendableNote<NoteRef>>,
sapling_inputs: Vec<ReceivedSaplingNote<NoteRef>>,
balance: TransactionBalance,
fee_rule: FeeRuleT,
target_height: BlockHeight,
@ -85,7 +85,7 @@ impl<FeeRuleT, NoteRef> Proposal<FeeRuleT, NoteRef> {
&self.transparent_inputs
}
/// Returns the Sapling inputs that have been selected to fund the transaction.
pub fn sapling_inputs(&self) -> &[SpendableNote<NoteRef>] {
pub fn sapling_inputs(&self) -> &[ReceivedSaplingNote<NoteRef>] {
&self.sapling_inputs
}
/// Returns the change outputs to be added to the transaction and the fee to be paid.
@ -336,7 +336,7 @@ where
}
}
let mut sapling_inputs: Vec<SpendableNote<DbT::NoteRef>> = vec![];
let mut sapling_inputs: Vec<ReceivedSaplingNote<DbT::NoteRef>> = vec![];
let mut prior_available = Amount::zero();
let mut amount_required = Amount::zero();
let mut exclude: Vec<DbT::NoteRef> = vec![];
@ -425,7 +425,7 @@ where
target_height,
&transparent_inputs,
&Vec::<TxOut>::new(),
&Vec::<SpendableNote<DbT::NoteRef>>::new(),
&Vec::<ReceivedSaplingNote<DbT::NoteRef>>::new(),
&Vec::<SaplingPayment>::new(),
&self.dust_output_policy,
);
@ -441,7 +441,7 @@ where
target_height,
&transparent_inputs,
&Vec::<TxOut>::new(),
&Vec::<SpendableNote<DbT::NoteRef>>::new(),
&Vec::<ReceivedSaplingNote<DbT::NoteRef>>::new(),
&Vec::<SaplingPayment>::new(),
&self.dust_output_policy,
)?

View File

@ -177,7 +177,7 @@ impl<N> WalletSaplingOutput<N> {
/// Information about a note that is tracked by the wallet that is available for spending,
/// with sufficient information for use in note selection.
pub struct SpendableNote<NoteRef> {
pub struct ReceivedSaplingNote<NoteRef> {
pub note_id: NoteRef,
pub diversifier: sapling::Diversifier,
pub note_value: Amount,
@ -185,7 +185,7 @@ pub struct SpendableNote<NoteRef> {
pub witness: sapling::IncrementalWitness,
}
impl<NoteRef> sapling_fees::InputView<NoteRef> for SpendableNote<NoteRef> {
impl<NoteRef> sapling_fees::InputView<NoteRef> for ReceivedSaplingNote<NoteRef> {
fn note_id(&self) -> &NoteRef {
&self.note_id
}

View File

@ -91,7 +91,7 @@ impl ScanningKey for DiversifiableFullViewingKey {
) -> Self::Nf {
note.nf(
key,
u64::try_from(witness.position())
u64::try_from(witness.tip_position())
.expect("Sapling note commitment tree position must fit into a u64"),
)
}

View File

@ -10,6 +10,9 @@ and this library adheres to Rust's notion of
- MSRV is now 1.65.0.
- Bumped dependencies to `hdwallet 0.4`.
### Removed
- The empty `wallet::transact` module has been removed.
## [0.7.1] - 2023-05-17
### Fixed

View File

@ -43,7 +43,7 @@ use zcash_primitives::{
consensus::{self, BlockHeight},
legacy::TransparentAddress,
memo::{Memo, MemoBytes},
sapling::{self, Nullifier},
sapling::{self},
transaction::{
components::{amount::Amount, OutPoint},
Transaction, TxId,
@ -54,12 +54,12 @@ use zcash_primitives::{
use zcash_client_backend::{
address::{AddressMetadata, UnifiedAddress},
data_api::{
self, chain::BlockSource, DecryptedTransaction, PoolType, PrunedBlock, Recipient,
SentTransaction, WalletRead, WalletWrite,
self, chain::BlockSource, DecryptedTransaction, NullifierQuery, PoolType, PrunedBlock,
Recipient, SentTransaction, WalletRead, WalletWrite,
},
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
proto::compact_formats::CompactBlock,
wallet::{SpendableNote, WalletTransparentOutput},
wallet::{ReceivedSaplingNote, WalletTransparentOutput},
DecryptedOutput, TransferType,
};
@ -201,7 +201,7 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
&self,
block_height: BlockHeight,
) -> Result<Option<sapling::CommitmentTree>, Self::Error> {
wallet::get_sapling_commitment_tree(self, block_height)
wallet::sapling::get_sapling_commitment_tree(self, block_height)
}
#[allow(clippy::type_complexity)]
@ -209,15 +209,17 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
&self,
block_height: BlockHeight,
) -> Result<Vec<(Self::NoteRef, sapling::IncrementalWitness)>, Self::Error> {
wallet::get_sapling_witnesses(self, block_height)
wallet::sapling::get_sapling_witnesses(self, block_height)
}
fn get_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error> {
wallet::get_sapling_nullifiers(self)
}
fn get_all_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error> {
wallet::get_all_sapling_nullifiers(self)
fn get_sapling_nullifiers(
&self,
query: data_api::NullifierQuery,
) -> Result<Vec<(AccountId, sapling::Nullifier)>, Self::Error> {
match query {
NullifierQuery::Unspent => wallet::sapling::get_sapling_nullifiers(self),
NullifierQuery::All => wallet::sapling::get_all_sapling_nullifiers(self),
}
}
fn get_spendable_sapling_notes(
@ -225,8 +227,8 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
account: AccountId,
anchor_height: BlockHeight,
exclude: &[Self::NoteRef],
) -> Result<Vec<SpendableNote<Self::NoteRef>>, Self::Error> {
wallet::transact::get_spendable_sapling_notes(self, account, anchor_height, exclude)
) -> Result<Vec<ReceivedSaplingNote<Self::NoteRef>>, Self::Error> {
wallet::sapling::get_spendable_sapling_notes(self, account, anchor_height, exclude)
}
fn select_spendable_sapling_notes(
@ -235,8 +237,8 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
target_value: Amount,
anchor_height: BlockHeight,
exclude: &[Self::NoteRef],
) -> Result<Vec<SpendableNote<Self::NoteRef>>, Self::Error> {
wallet::transact::select_spendable_sapling_notes(
) -> Result<Vec<ReceivedSaplingNote<Self::NoteRef>>, Self::Error> {
wallet::sapling::select_spendable_sapling_notes(
self,
account,
target_value,
@ -368,12 +370,11 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> {
self.wallet_db.get_witnesses(block_height)
}
fn get_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error> {
self.wallet_db.get_nullifiers()
}
fn get_all_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error> {
self.wallet_db.get_all_nullifiers()
fn get_sapling_nullifiers(
&self,
query: data_api::NullifierQuery,
) -> Result<Vec<(AccountId, sapling::Nullifier)>, Self::Error> {
self.wallet_db.get_sapling_nullifiers(query)
}
fn get_spendable_sapling_notes(
@ -381,7 +382,7 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> {
account: AccountId,
anchor_height: BlockHeight,
exclude: &[Self::NoteRef],
) -> Result<Vec<SpendableNote<Self::NoteRef>>, Self::Error> {
) -> Result<Vec<ReceivedSaplingNote<Self::NoteRef>>, Self::Error> {
self.wallet_db
.get_spendable_sapling_notes(account, anchor_height, exclude)
}
@ -392,7 +393,7 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> {
target_value: Amount,
anchor_height: BlockHeight,
exclude: &[Self::NoteRef],
) -> Result<Vec<SpendableNote<Self::NoteRef>>, Self::Error> {
) -> Result<Vec<ReceivedSaplingNote<Self::NoteRef>>, Self::Error> {
self.wallet_db
.select_spendable_sapling_notes(account, target_value, anchor_height, exclude)
}
@ -533,11 +534,11 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
// Mark notes as spent and remove them from the scanning cache
for spend in &tx.sapling_spends {
wallet::mark_sapling_note_spent(up, tx_row, spend.nf())?;
wallet::sapling::mark_sapling_note_spent(up, tx_row, spend.nf())?;
}
for output in &tx.sapling_outputs {
let received_note_id = wallet::put_received_note(up, output, tx_row)?;
let received_note_id = wallet::sapling::put_received_note(up, output, tx_row)?;
// Save witness for note.
new_witnesses.push((received_note_id, output.witness().clone()));
@ -548,7 +549,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
for (received_note_id, witness) in updated_witnesses.iter().chain(new_witnesses.iter())
{
if let NoteId::ReceivedNoteId(rnid) = *received_note_id {
wallet::insert_witness(up, rnid, witness, block.block_height)?;
wallet::sapling::insert_witness(up, rnid, witness, block.block_height)?;
} else {
return Err(SqliteClientError::InvalidNoteId);
}
@ -566,7 +567,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
fn store_decrypted_tx(
&mut self,
d_tx: &DecryptedTransaction,
d_tx: DecryptedTransaction,
) -> Result<Self::TxRef, Self::Error> {
self.transactionally(|up| {
let tx_ref = wallet::put_tx_data(up, d_tx.tx, None, None)?;
@ -593,7 +594,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
)?;
if matches!(recipient, Recipient::InternalAccount(_, _)) {
wallet::put_received_note(up, output, tx_ref)?;
wallet::sapling::put_received_note(up, output, tx_ref)?;
}
}
TransferType::Incoming => {
@ -607,7 +608,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
}
}
wallet::put_received_note(up, output, tx_ref)?;
wallet::sapling::put_received_note(up, output, tx_ref)?;
}
}
}
@ -620,7 +621,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
// If we have some transparent outputs:
if !d_tx.tx.transparent_bundle().iter().any(|b| b.vout.is_empty()) {
let nullifiers = self.wallet_db.get_all_nullifiers()?;
let nullifiers = self.wallet_db.get_sapling_nullifiers(data_api::NullifierQuery::All)?;
// If the transaction contains shielded spends from our wallet, we will store z->t
// transactions we observe in the same way they would be stored by
// create_spend_to_address.
@ -668,7 +669,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
// reasonable assumption for a light client such as a mobile phone.
if let Some(bundle) = sent_tx.tx.sapling_bundle() {
for spend in bundle.shielded_spends() {
wallet::mark_sapling_note_spent(up, tx_ref, spend.nullifier())?;
wallet::sapling::mark_sapling_note_spent(up, tx_ref, spend.nullifier())?;
}
}
@ -681,7 +682,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
wallet::insert_sent_output(up, tx_ref, sent_tx.account, output)?;
if let Some((account, note)) = output.sapling_change_to() {
wallet::put_received_note(
wallet::sapling::put_received_note(
up,
&DecryptedOutput {
index: output.output_index(),

View File

@ -64,7 +64,6 @@
//! wallet.
//! - `memo` the shielded memo associated with the output, if any.
use group::ff::PrimeField;
use rusqlite::{named_params, OptionalExtension, ToSql};
use std::collections::HashMap;
use std::convert::TryFrom;
@ -73,8 +72,7 @@ use zcash_primitives::{
block::BlockHash,
consensus::{self, BlockHeight, BranchId, NetworkUpgrade, Parameters},
memo::{Memo, MemoBytes},
merkle_tree::{read_commitment_tree, read_incremental_witness},
sapling::{self, Note, Nullifier},
sapling::CommitmentTree,
transaction::{components::Amount, Transaction, TxId},
zip32::{
sapling::{DiversifiableFullViewingKey, ExtendedFullViewingKey},
@ -86,13 +84,11 @@ use zcash_client_backend::{
address::{RecipientAddress, UnifiedAddress},
data_api::{PoolType, Recipient, SentTransactionOutput},
keys::UnifiedFullViewingKey,
wallet::{WalletSaplingOutput, WalletTx},
DecryptedOutput, TransferType,
wallet::WalletTx,
};
use crate::{
error::SqliteClientError, prepared::InsertAddress, DataConnStmtCache, NoteId, WalletDb,
PRUNING_HEIGHT,
error::SqliteClientError, prepared::InsertAddress, DataConnStmtCache, WalletDb, PRUNING_HEIGHT,
};
#[cfg(feature = "transparent-inputs")]
@ -110,7 +106,7 @@ use {
};
pub mod init;
pub mod transact;
pub(crate) mod sapling;
pub(crate) fn pool_code(pool_type: PoolType) -> i64 {
// These constants are *incidentally* shared with the typecodes
@ -122,59 +118,6 @@ pub(crate) fn pool_code(pool_type: PoolType) -> i64 {
}
}
/// This trait provides a generalization over shielded output representations.
pub(crate) trait ReceivedSaplingOutput {
fn index(&self) -> usize;
fn account(&self) -> AccountId;
fn note(&self) -> &Note;
fn memo(&self) -> Option<&MemoBytes>;
fn is_change(&self) -> bool;
fn nullifier(&self) -> Option<&Nullifier>;
}
impl ReceivedSaplingOutput for WalletSaplingOutput<Nullifier> {
fn index(&self) -> usize {
self.index()
}
fn account(&self) -> AccountId {
WalletSaplingOutput::account(self)
}
fn note(&self) -> &Note {
WalletSaplingOutput::note(self)
}
fn memo(&self) -> Option<&MemoBytes> {
None
}
fn is_change(&self) -> bool {
WalletSaplingOutput::is_change(self)
}
fn nullifier(&self) -> Option<&Nullifier> {
Some(self.nf())
}
}
impl ReceivedSaplingOutput for DecryptedOutput<Note> {
fn index(&self) -> usize {
self.index
}
fn account(&self) -> AccountId {
self.account
}
fn note(&self) -> &Note {
&self.note
}
fn memo(&self) -> Option<&MemoBytes> {
Some(&self.memo)
}
fn is_change(&self) -> bool {
self.transfer_type == TransferType::WalletInternal
}
fn nullifier(&self) -> Option<&Nullifier> {
None
}
}
pub(crate) fn get_max_account_id<P>(
wdb: &WalletDb<P>,
) -> Result<Option<AccountId>, SqliteClientError> {
@ -693,107 +636,6 @@ pub(crate) fn truncate_to_height<P: consensus::Parameters>(
Ok(())
}
/// Returns the commitment tree for the block at the specified height,
/// if any.
pub(crate) fn get_sapling_commitment_tree<P>(
wdb: &WalletDb<P>,
block_height: BlockHeight,
) -> Result<Option<sapling::CommitmentTree>, SqliteClientError> {
wdb.conn
.query_row_and_then(
"SELECT sapling_tree FROM blocks WHERE height = ?",
[u32::from(block_height)],
|row| {
let row_data: Vec<u8> = row.get(0)?;
read_commitment_tree(&row_data[..]).map_err(|e| {
rusqlite::Error::FromSqlConversionFailure(
row_data.len(),
rusqlite::types::Type::Blob,
Box::new(e),
)
})
},
)
.optional()
.map_err(SqliteClientError::from)
}
/// Returns the incremental witnesses for the block at the specified height,
/// if any.
pub(crate) fn get_sapling_witnesses<P>(
wdb: &WalletDb<P>,
block_height: BlockHeight,
) -> Result<Vec<(NoteId, sapling::IncrementalWitness)>, SqliteClientError> {
let mut stmt_fetch_witnesses = wdb
.conn
.prepare("SELECT note, witness FROM sapling_witnesses WHERE block = ?")?;
let witnesses = stmt_fetch_witnesses
.query_map([u32::from(block_height)], |row| {
let id_note = NoteId::ReceivedNoteId(row.get(0)?);
let wdb: Vec<u8> = row.get(1)?;
Ok(read_incremental_witness(&wdb[..]).map(|witness| (id_note, witness)))
})
.map_err(SqliteClientError::from)?;
// unwrap database error & IO error from IncrementalWitness::read
let res: Vec<_> = witnesses.collect::<Result<Result<_, _>, _>>()??;
Ok(res)
}
/// Retrieves the set of nullifiers for "potentially spendable" Sapling notes that the
/// wallet is tracking.
///
/// "Potentially spendable" means:
/// - The transaction in which the note was created has been observed as mined.
/// - No transaction in which the note's nullifier appears has been observed as mined.
pub(crate) fn get_sapling_nullifiers<P>(
wdb: &WalletDb<P>,
) -> Result<Vec<(AccountId, Nullifier)>, SqliteClientError> {
// Get the nullifiers for the notes we are tracking
let mut stmt_fetch_nullifiers = wdb.conn.prepare(
"SELECT rn.id_note, rn.account, rn.nf, tx.block as block
FROM sapling_received_notes rn
LEFT OUTER JOIN transactions tx
ON tx.id_tx = rn.spent
WHERE block IS NULL
AND nf IS NOT NULL",
)?;
let nullifiers = stmt_fetch_nullifiers.query_map([], |row| {
let account: u32 = row.get(1)?;
let nf_bytes: Vec<u8> = row.get(2)?;
Ok((
AccountId::from(account),
Nullifier::from_slice(&nf_bytes).unwrap(),
))
})?;
let res: Vec<_> = nullifiers.collect::<Result<_, _>>()?;
Ok(res)
}
/// Returns the nullifiers for the notes that this wallet is tracking.
pub(crate) fn get_all_sapling_nullifiers<P>(
wdb: &WalletDb<P>,
) -> Result<Vec<(AccountId, Nullifier)>, SqliteClientError> {
// Get the nullifiers for the notes we are tracking
let mut stmt_fetch_nullifiers = wdb.conn.prepare(
"SELECT rn.id_note, rn.account, rn.nf
FROM sapling_received_notes rn
WHERE nf IS NOT NULL",
)?;
let nullifiers = stmt_fetch_nullifiers.query_map([], |row| {
let account: u32 = row.get(1)?;
let nf_bytes: Vec<u8> = row.get(2)?;
Ok((
AccountId::from(account),
Nullifier::from_slice(&nf_bytes).unwrap(),
))
})?;
let res: Vec<_> = nullifiers.collect::<Result<_, _>>()?;
Ok(res)
}
/// Returns unspent transparent outputs that have been received by this wallet at the given
/// transparent address, such that the block that included the transaction was mined at a
/// height less than or equal to the provided `max_height`.
@ -895,7 +737,7 @@ pub(crate) fn insert_block<'a, P>(
block_height: BlockHeight,
block_hash: BlockHash,
block_time: u32,
commitment_tree: &sapling::CommitmentTree,
commitment_tree: &CommitmentTree,
) -> Result<(), SqliteClientError> {
stmts.stmt_insert_block(block_height, block_hash, block_time, commitment_tree)
}
@ -937,20 +779,6 @@ pub(crate) fn put_tx_data<'a, P>(
}
}
/// Marks a given nullifier as having been revealed in the construction
/// of the specified transaction.
///
/// Marking a note spent in this fashion does NOT imply that the
/// spending transaction has been mined.
pub(crate) fn mark_sapling_note_spent<'a, P>(
stmts: &mut DataConnStmtCache<'a, P>,
tx_ref: i64,
nf: &Nullifier,
) -> Result<(), SqliteClientError> {
stmts.stmt_mark_sapling_note_spent(tx_ref, nf)?;
Ok(())
}
/// Marks the given UTXO as having been spent.
#[cfg(feature = "transparent-inputs")]
pub(crate) fn mark_transparent_utxo_spent<'a, P>(
@ -1005,67 +833,6 @@ pub(crate) fn put_received_transparent_utxo<'a, P: consensus::Parameters>(
})
}
/// Records the specified shielded output as having been received.
///
/// This implementation relies on the facts that:
/// - A transaction will not contain more than 2^63 shielded outputs.
/// - A note value will never exceed 2^63 zatoshis.
pub(crate) fn put_received_note<'a, P, T: ReceivedSaplingOutput>(
stmts: &mut DataConnStmtCache<'a, P>,
output: &T,
tx_ref: i64,
) -> Result<NoteId, SqliteClientError> {
let rcm = output.note().rcm().to_repr();
let account = output.account();
let to = output.note().recipient();
let diversifier = to.diversifier();
let value = output.note().value();
let memo = output.memo();
let is_change = output.is_change();
let output_index = output.index();
let nf = output.nullifier();
// First try updating an existing received note into the database.
if !stmts.stmt_update_received_note(
account,
diversifier,
value.inner(),
rcm,
nf,
memo,
is_change,
tx_ref,
output_index,
)? {
// It isn't there, so insert our note into the database.
stmts.stmt_insert_received_note(
tx_ref,
output_index,
account,
diversifier,
value.inner(),
rcm,
nf,
memo,
is_change,
)
} else {
// It was there, so grab its row number.
stmts.stmt_select_received_note(tx_ref, output.index())
}
}
/// Records the incremental witness for the specified note,
/// as of the given block height.
pub(crate) fn insert_witness<'a, P>(
stmts: &mut DataConnStmtCache<'a, P>,
note_id: i64,
witness: &sapling::IncrementalWitness,
height: BlockHeight,
) -> Result<(), SqliteClientError> {
stmts.stmt_insert_witness(NoteId::ReceivedNoteId(note_id), height, witness)
}
/// Removes old incremental witnesses up to the given block height.
pub(crate) fn prune_witnesses<P>(
stmts: &mut DataConnStmtCache<'_, P>,

View File

@ -1,23 +1,78 @@
//! Functions for creating transactions.
//!
use rusqlite::{named_params, types::Value, Row};
use std::rc::Rc;
//! Functions for Sapling support in the wallet.
use group::ff::PrimeField;
use rusqlite::{named_params, types::Value, OptionalExtension, Row};
use std::rc::Rc;
use zcash_primitives::{
consensus::BlockHeight,
merkle_tree::read_incremental_witness,
sapling::{Diversifier, Rseed},
memo::MemoBytes,
merkle_tree::{read_commitment_tree, read_incremental_witness},
sapling::{self, Diversifier, Note, Nullifier, Rseed},
transaction::components::Amount,
zip32::AccountId,
};
use zcash_client_backend::wallet::SpendableNote;
use zcash_client_backend::{
wallet::{ReceivedSaplingNote, WalletSaplingOutput},
DecryptedOutput, TransferType,
};
use crate::{error::SqliteClientError, NoteId, WalletDb};
use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb};
fn to_spendable_note(row: &Row) -> Result<SpendableNote<NoteId>, SqliteClientError> {
/// This trait provides a generalization over shielded output representations.
pub(crate) trait ReceivedSaplingOutput {
fn index(&self) -> usize;
fn account(&self) -> AccountId;
fn note(&self) -> &Note;
fn memo(&self) -> Option<&MemoBytes>;
fn is_change(&self) -> bool;
fn nullifier(&self) -> Option<&Nullifier>;
}
impl ReceivedSaplingOutput for WalletSaplingOutput<Nullifier> {
fn index(&self) -> usize {
self.index()
}
fn account(&self) -> AccountId {
WalletSaplingOutput::account(self)
}
fn note(&self) -> &Note {
WalletSaplingOutput::note(self)
}
fn memo(&self) -> Option<&MemoBytes> {
None
}
fn is_change(&self) -> bool {
WalletSaplingOutput::is_change(self)
}
fn nullifier(&self) -> Option<&Nullifier> {
Some(self.nf())
}
}
impl ReceivedSaplingOutput for DecryptedOutput<Note> {
fn index(&self) -> usize {
self.index
}
fn account(&self) -> AccountId {
self.account
}
fn note(&self) -> &Note {
&self.note
}
fn memo(&self) -> Option<&MemoBytes> {
Some(&self.memo)
}
fn is_change(&self) -> bool {
self.transfer_type == TransferType::WalletInternal
}
fn nullifier(&self) -> Option<&Nullifier> {
None
}
}
fn to_spendable_note(row: &Row) -> Result<ReceivedSaplingNote<NoteId>, SqliteClientError> {
let note_id = NoteId::ReceivedNoteId(row.get(0)?);
let diversifier = {
let d: Vec<_> = row.get(1)?;
@ -53,7 +108,7 @@ fn to_spendable_note(row: &Row) -> Result<SpendableNote<NoteId>, SqliteClientErr
read_incremental_witness(&d[..])?
};
Ok(SpendableNote {
Ok(ReceivedSaplingNote {
note_id,
diversifier,
note_value,
@ -67,7 +122,7 @@ pub(crate) fn get_spendable_sapling_notes<P>(
account: AccountId,
anchor_height: BlockHeight,
exclude: &[NoteId],
) -> Result<Vec<SpendableNote<NoteId>>, SqliteClientError> {
) -> Result<Vec<ReceivedSaplingNote<NoteId>>, SqliteClientError> {
let mut stmt_select_notes = wdb.conn.prepare(
"SELECT id_note, diversifier, value, rcm, witness
FROM sapling_received_notes
@ -107,7 +162,7 @@ pub(crate) fn select_spendable_sapling_notes<P>(
target_value: Amount,
anchor_height: BlockHeight,
exclude: &[NoteId],
) -> Result<Vec<SpendableNote<NoteId>>, SqliteClientError> {
) -> Result<Vec<ReceivedSaplingNote<NoteId>>, SqliteClientError> {
// The goal of this SQL statement is to select the oldest notes until the required
// value has been reached, and then fetch the witnesses at the desired height for the
// selected notes. This is achieved in several steps:
@ -173,6 +228,182 @@ pub(crate) fn select_spendable_sapling_notes<P>(
notes.collect::<Result<_, _>>()
}
/// Returns the commitment tree for the block at the specified height,
/// if any.
pub(crate) fn get_sapling_commitment_tree<P>(
wdb: &WalletDb<P>,
block_height: BlockHeight,
) -> Result<Option<sapling::CommitmentTree>, SqliteClientError> {
wdb.conn
.query_row_and_then(
"SELECT sapling_tree FROM blocks WHERE height = ?",
[u32::from(block_height)],
|row| {
let row_data: Vec<u8> = row.get(0)?;
read_commitment_tree(&row_data[..]).map_err(|e| {
rusqlite::Error::FromSqlConversionFailure(
row_data.len(),
rusqlite::types::Type::Blob,
Box::new(e),
)
})
},
)
.optional()
.map_err(SqliteClientError::from)
}
/// Returns the incremental witnesses for the block at the specified height,
/// if any.
pub(crate) fn get_sapling_witnesses<P>(
wdb: &WalletDb<P>,
block_height: BlockHeight,
) -> Result<Vec<(NoteId, sapling::IncrementalWitness)>, SqliteClientError> {
let mut stmt_fetch_witnesses = wdb
.conn
.prepare("SELECT note, witness FROM sapling_witnesses WHERE block = ?")?;
let witnesses = stmt_fetch_witnesses
.query_map([u32::from(block_height)], |row| {
let id_note = NoteId::ReceivedNoteId(row.get(0)?);
let wdb: Vec<u8> = row.get(1)?;
Ok(read_incremental_witness(&wdb[..]).map(|witness| (id_note, witness)))
})
.map_err(SqliteClientError::from)?;
// unwrap database error & IO error from IncrementalWitness::read
let res: Vec<_> = witnesses.collect::<Result<Result<_, _>, _>>()??;
Ok(res)
}
/// Records the incremental witness for the specified note,
/// as of the given block height.
pub(crate) fn insert_witness<'a, P>(
stmts: &mut DataConnStmtCache<'a, P>,
note_id: i64,
witness: &sapling::IncrementalWitness,
height: BlockHeight,
) -> Result<(), SqliteClientError> {
stmts.stmt_insert_witness(NoteId::ReceivedNoteId(note_id), height, witness)
}
/// Retrieves the set of nullifiers for "potentially spendable" Sapling notes that the
/// wallet is tracking.
///
/// "Potentially spendable" means:
/// - The transaction in which the note was created has been observed as mined.
/// - No transaction in which the note's nullifier appears has been observed as mined.
pub(crate) fn get_sapling_nullifiers<P>(
wdb: &WalletDb<P>,
) -> Result<Vec<(AccountId, Nullifier)>, SqliteClientError> {
// Get the nullifiers for the notes we are tracking
let mut stmt_fetch_nullifiers = wdb.conn.prepare(
"SELECT rn.id_note, rn.account, rn.nf, tx.block as block
FROM sapling_received_notes rn
LEFT OUTER JOIN transactions tx
ON tx.id_tx = rn.spent
WHERE block IS NULL
AND nf IS NOT NULL",
)?;
let nullifiers = stmt_fetch_nullifiers.query_map([], |row| {
let account: u32 = row.get(1)?;
let nf_bytes: Vec<u8> = row.get(2)?;
Ok((
AccountId::from(account),
Nullifier::from_slice(&nf_bytes).unwrap(),
))
})?;
let res: Vec<_> = nullifiers.collect::<Result<_, _>>()?;
Ok(res)
}
/// Returns the nullifiers for the notes that this wallet is tracking.
pub(crate) fn get_all_sapling_nullifiers<P>(
wdb: &WalletDb<P>,
) -> Result<Vec<(AccountId, Nullifier)>, SqliteClientError> {
// Get the nullifiers for the notes we are tracking
let mut stmt_fetch_nullifiers = wdb.conn.prepare(
"SELECT rn.id_note, rn.account, rn.nf
FROM sapling_received_notes rn
WHERE nf IS NOT NULL",
)?;
let nullifiers = stmt_fetch_nullifiers.query_map([], |row| {
let account: u32 = row.get(1)?;
let nf_bytes: Vec<u8> = row.get(2)?;
Ok((
AccountId::from(account),
Nullifier::from_slice(&nf_bytes).unwrap(),
))
})?;
let res: Vec<_> = nullifiers.collect::<Result<_, _>>()?;
Ok(res)
}
/// Marks a given nullifier as having been revealed in the construction
/// of the specified transaction.
///
/// Marking a note spent in this fashion does NOT imply that the
/// spending transaction has been mined.
pub(crate) fn mark_sapling_note_spent<'a, P>(
stmts: &mut DataConnStmtCache<'a, P>,
tx_ref: i64,
nf: &Nullifier,
) -> Result<(), SqliteClientError> {
stmts.stmt_mark_sapling_note_spent(tx_ref, nf)?;
Ok(())
}
/// Records the specified shielded output as having been received.
///
/// This implementation relies on the facts that:
/// - A transaction will not contain more than 2^63 shielded outputs.
/// - A note value will never exceed 2^63 zatoshis.
pub(crate) fn put_received_note<'a, P, T: ReceivedSaplingOutput>(
stmts: &mut DataConnStmtCache<'a, P>,
output: &T,
tx_ref: i64,
) -> Result<NoteId, SqliteClientError> {
let rcm = output.note().rcm().to_repr();
let account = output.account();
let to = output.note().recipient();
let diversifier = to.diversifier();
let value = output.note().value();
let memo = output.memo();
let is_change = output.is_change();
let output_index = output.index();
let nf = output.nullifier();
// First try updating an existing received note into the database.
if !stmts.stmt_update_received_note(
account,
diversifier,
value.inner(),
rcm,
nf,
memo,
is_change,
tx_ref,
output_index,
)? {
// It isn't there, so insert our note into the database.
stmts.stmt_insert_received_note(
tx_ref,
output_index,
account,
diversifier,
value.inner(),
rcm,
nf,
memo,
is_change,
)
} else {
// It was there, so grab its row number.
stmts.stmt_select_received_note(tx_ref, output.index())
}
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {

View File

@ -90,7 +90,7 @@ impl Node {
}
}
impl incrementalmerkletree::Hashable for Node {
impl Hashable for Node {
fn empty_leaf() -> Self {
Node {
repr: UNCOMMITTED_SAPLING.to_repr(),