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:
commit
de771b7f22
|
@ -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" }
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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]))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)> {
|
||||
|
|
|
@ -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,
|
||||
)?
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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 {
|
|
@ -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(),
|
||||
|
|
Loading…
Reference in New Issue