Merge branch 'main' into 823-secp256k1-0.26

This commit is contained in:
str4d 2023-05-08 18:53:33 +01:00 committed by GitHub
commit 579ab92b93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 916 additions and 592 deletions

View File

@ -121,7 +121,7 @@ jobs:
- name: Generate coverage report
run: cargo tarpaulin --engine llvm --all-features --release --timeout 600 --out Xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3.1.2
uses: codecov/codecov-action@v3.1.3
doc-links:
name: Intra-doc links

View File

@ -9,6 +9,39 @@ and this library adheres to Rust's notion of
### Changed
- Bumped dependencies to `hdwallet 0.4`.
## [0.9.0] - 2023-04-28
### Added
- `data_api::SentTransactionOutput::from_parts`
- `data_api::WalletRead::get_min_unspent_height`
### Changed
- `decrypt::DecryptedOutput` is now parameterized by a `Note` type parameter,
to allow reuse of the data structure for non-Sapling contexts.
- `data_api::SentTransactionOutput` must now be constructed using
`SentTransactionOutput::from_parts`. The internal state of `SentTransactionOutput`
is now private, and accessible via methods that have the same names as the
previously exposed fields.
### Renamed
- The following types and fields have been renamed in preparation for supporting
`orchard` in wallet APIs:
- `WalletTx::shielded_spends` -> `WalletTx::sapling_spends`
- `WalletTx::shielded_outputs` -> `WalletTx::sapling_outputs`
- `WalletShieldedSpend` -> `WalletSaplingSpend`. Also, the internals of this
data structure have been made private.
- `WalletShieldedOutput` -> `WalletSaplingOutput`. Also, the internals of this
data structure have been made private.
- The `data_api::WalletWrite::rewind_to_height` method has been renamed to
`truncate_to_height` to better reflect its semantics.
### Removed
- `wallet::WalletTx::num_spends`
- `wallet::WalletTx::num_outputs`
- `wallet::WalletSaplingOutput::to` is redundant and has been removed; the
recipient address can be obtained from the note.
- `decrypt::DecryptedOutput::to` is redundant and has been removed; the
recipient address can be obtained from the note.
## [0.8.0] - 2023-04-15
### Changed
- Bumped dependencies to `bls12_381 0.8`, `group 0.13`, `orchard 0.4`,

View File

@ -1,7 +1,7 @@
[package]
name = "zcash_client_backend"
description = "APIs for creating shielded Zcash light clients"
version = "0.8.0"
version = "0.9.0"
authors = [
"Jack Grigg <jack@z.cash>",
"Kris Nuttycombe <kris@electriccoin.co>"

View File

@ -11,7 +11,7 @@ use zcash_primitives::{
legacy::TransparentAddress,
memo::{Memo, MemoBytes},
merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::{Node, Nullifier, PaymentAddress},
sapling::{self, Node, Nullifier, PaymentAddress},
transaction::{
components::{amount::Amount, OutPoint},
Transaction, TxId,
@ -83,6 +83,9 @@ pub trait WalletRead {
})
}
/// Returns the minimum block height corresponding to an unspent note in the wallet.
fn get_min_unspent_height(&self) -> Result<Option<BlockHeight>, Self::Error>;
/// Returns the block hash for the block at the given height, if the
/// associated block data is available. Returns `Ok(None)` if the hash
/// is not found in the database.
@ -247,7 +250,7 @@ pub struct PrunedBlock<'a> {
/// wallet database when transactions are successfully decrypted.
pub struct DecryptedTransaction<'a> {
pub tx: &'a Transaction,
pub sapling_outputs: &'a Vec<DecryptedOutput>,
pub sapling_outputs: &'a Vec<DecryptedOutput<sapling::Note>>,
}
/// A transaction that was constructed and sent by the wallet.
@ -287,21 +290,58 @@ pub enum Recipient {
InternalAccount(AccountId, PoolType),
}
/// A type that represents an output (either Sapling or transparent) that was sent by the wallet.
pub struct SentTransactionOutput {
/// The index within the transaction that contains the recipient output.
output_index: usize,
recipient: Recipient,
value: Amount,
memo: Option<MemoBytes>,
sapling_change_to: Option<(AccountId, sapling::Note)>,
}
impl SentTransactionOutput {
pub fn from_parts(
output_index: usize,
recipient: Recipient,
value: Amount,
memo: Option<MemoBytes>,
sapling_change_to: Option<(AccountId, sapling::Note)>,
) -> Self {
Self {
output_index,
recipient,
value,
memo,
sapling_change_to,
}
}
/// Returns the index within the transaction that contains the recipient output.
///
/// - If `recipient_address` is a Sapling address, this is an index into the Sapling
/// outputs of the transaction.
/// - If `recipient_address` is a transparent address, this is an index into the
/// transparent outputs of the transaction.
pub output_index: usize,
/// The recipient address of the transaction, or the account
/// id for wallet-internal transactions.
pub recipient: Recipient,
/// The value of the newly created output
pub value: Amount,
/// The memo that was attached to the output, if any
pub memo: Option<MemoBytes>,
pub fn output_index(&self) -> usize {
self.output_index
}
/// Returns the recipient address of the transaction, or the account id for wallet-internal
/// transactions.
pub fn recipient(&self) -> &Recipient {
&self.recipient
}
/// Returns the value of the newly created output.
pub fn value(&self) -> Amount {
self.value
}
/// Returns the memo that was attached to the output, if any.
pub fn memo(&self) -> Option<&MemoBytes> {
self.memo.as_ref()
}
/// Returns t decrypted note, if the sent output belongs to this wallet
pub fn sapling_change_to(&self) -> Option<&(AccountId, sapling::Note)> {
self.sapling_change_to.as_ref()
}
}
/// This trait encapsulates the write capabilities required to update stored
@ -361,7 +401,7 @@ pub trait WalletWrite: WalletRead {
/// persistent wallet store.
fn store_sent_tx(&mut self, sent_tx: &SentTransaction) -> Result<Self::TxRef, Self::Error>;
/// Rewinds the wallet database to the specified height.
/// Truncates the wallet database to the specified height.
///
/// This method assumes that the state of the underlying data store is
/// consistent up to a particular block height. Since it is possible that
@ -373,8 +413,8 @@ pub trait WalletWrite: WalletRead {
/// most recent block and all other operations will treat this block
/// as the chain tip for balance determination purposes.
///
/// There may be restrictions on how far it is possible to rewind.
fn rewind_to_height(&mut self, block_height: BlockHeight) -> Result<(), Self::Error>;
/// There may be restrictions on heights to which it is possible to truncate.
fn truncate_to_height(&mut self, block_height: BlockHeight) -> Result<(), Self::Error>;
/// Adds a transparent UTXO received by the wallet to the data store.
fn put_received_transparent_utxo(
@ -423,6 +463,10 @@ pub mod testing {
Ok(None)
}
fn get_min_unspent_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
Ok(None)
}
fn get_block_hash(
&self,
_block_height: BlockHeight,
@ -588,7 +632,7 @@ pub mod testing {
Ok(TxId::from_bytes([0u8; 32]))
}
fn rewind_to_height(&mut self, _block_height: BlockHeight) -> Result<(), Self::Error> {
fn truncate_to_height(&mut self, _block_height: BlockHeight) -> Result<(), Self::Error> {
Ok(())
}

View File

@ -55,7 +55,7 @@
//! let rewind_height = e.at_height() - 10;
//!
//! // b) Rewind scanned block information.
//! db_data.rewind_to_height(rewind_height);
//! db_data.truncate_to_height(rewind_height);
//!
//! // c) Delete cached blocks from rewind_height onwards.
//! //
@ -323,13 +323,13 @@ where
}
}
for tx in &txs {
for output in tx.shielded_outputs.iter() {
if output.witness.root() != cur_root {
for output in tx.sapling_outputs.iter() {
if output.witness().root() != cur_root {
return Err(ChainError::invalid_new_witness_anchor(
current_height,
tx.txid,
output.index,
output.witness.root(),
output.index(),
output.witness().root(),
)
.into());
}
@ -350,15 +350,16 @@ where
)
.map_err(Error::Wallet)?;
let spent_nf: Vec<Nullifier> = txs
let spent_nf: Vec<&Nullifier> = txs
.iter()
.flat_map(|tx| tx.shielded_spends.iter().map(|spend| spend.nf))
.flat_map(|tx| tx.sapling_spends.iter().map(|spend| spend.nf()))
.collect();
nullifiers.retain(|(_, nf)| !spent_nf.contains(nf));
nullifiers.extend(
txs.iter()
.flat_map(|tx| tx.shielded_outputs.iter().map(|out| (out.account, out.nf))),
);
nullifiers.retain(|(_, nf)| !spent_nf.contains(&nf));
nullifiers.extend(txs.iter().flat_map(|tx| {
tx.sapling_outputs
.iter()
.map(|out| (out.account(), *out.nf()))
}));
witnesses.extend(new_witnesses);

View File

@ -5,7 +5,12 @@ use zcash_primitives::{
consensus::{self, NetworkUpgrade},
memo::MemoBytes,
merkle_tree::MerklePath,
sapling::{self, prover::TxProver as SaplingProver, Node},
sapling::{
self,
note_encryption::{try_sapling_note_decryption, PreparedIncomingViewingKey},
prover::TxProver as SaplingProver,
Node,
},
transaction::{
builder::Builder,
components::amount::{Amount, BalanceError},
@ -554,6 +559,7 @@ where
// Build the transaction with the specified fee rule
let (tx, sapling_build_meta) = builder.build(&prover, proposal.fee_rule())?;
let internal_ivk = PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::Internal));
let sapling_outputs =
sapling_output_meta
.into_iter()
@ -563,12 +569,22 @@ where
.output_index(i)
.expect("An output should exist in the transaction for each shielded payment.");
SentTransactionOutput {
output_index,
recipient,
value,
memo,
}
let received_as =
if let Recipient::InternalAccount(account, PoolType::Sapling) = recipient {
tx.sapling_bundle().and_then(|bundle| {
try_sapling_note_decryption(
params,
proposal.target_height(),
&internal_ivk,
&bundle.shielded_outputs()[output_index],
)
.map(|(note, _, _)| (account, note))
})
} else {
None
};
SentTransactionOutput::from_parts(output_index, recipient, value, memo, received_as)
});
let transparent_outputs = transparent_output_meta.into_iter().map(|(addr, value)| {
@ -584,12 +600,13 @@ where
.map(|(index, _)| index)
.expect("An output should exist in the transaction for each transparent payment.");
SentTransactionOutput {
SentTransactionOutput::from_parts(
output_index,
recipient: Recipient::Transparent(addr),
Recipient::Transparent(addr),
value,
memo: None,
}
None,
None,
)
});
wallet_db

View File

@ -4,10 +4,10 @@ use zcash_primitives::{
consensus::{self, BlockHeight},
memo::MemoBytes,
sapling::{
self,
note_encryption::{
try_sapling_note_decryption, try_sapling_output_recovery, PreparedIncomingViewingKey,
},
Note, PaymentAddress,
},
transaction::Transaction,
zip32::{AccountId, Scope},
@ -30,7 +30,7 @@ pub enum TransferType {
}
/// A decrypted shielded output.
pub struct DecryptedOutput {
pub struct DecryptedOutput<Note> {
/// The index of the output within [`shielded_outputs`].
///
/// [`shielded_outputs`]: zcash_primitives::transaction::TransactionData
@ -39,8 +39,6 @@ pub struct DecryptedOutput {
pub note: Note,
/// The account that decrypted the note.
pub account: AccountId,
/// The address the note was sent to.
pub to: PaymentAddress,
/// The memo bytes included with the note.
pub memo: MemoBytes,
/// True if this output was recovered using an [`OutgoingViewingKey`], meaning that
@ -57,7 +55,7 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
height: BlockHeight,
tx: &Transaction,
ufvks: &HashMap<AccountId, UnifiedFullViewingKey>,
) -> Vec<DecryptedOutput> {
) -> Vec<DecryptedOutput<sapling::Note>> {
tx.sapling_bundle()
.iter()
.flat_map(|bundle| {
@ -94,11 +92,10 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
.map(|ret| (ret, TransferType::Outgoing))
})
.into_iter()
.map(move |((note, to, memo), transfer_type)| DecryptedOutput {
.map(move |((note, _, memo), transfer_type)| DecryptedOutput {
index,
note,
account,
to,
memo,
transfer_type,
})

View File

@ -185,7 +185,7 @@ mod tests {
&Vec::<TxOut>::new(),
&[TestSaplingInput {
note_id: 0,
value: Amount::from_u64(45000).unwrap(),
value: Amount::from_u64(60000).unwrap(),
}],
&[SaplingPayment::new(Amount::from_u64(40000).unwrap())],
&DustOutputPolicy::default(),
@ -193,8 +193,8 @@ mod tests {
assert_matches!(
result,
Ok(balance) if balance.proposed_change() == [ChangeValue::Sapling(Amount::from_u64(4000).unwrap())]
&& balance.fee_required() == Amount::from_u64(1000).unwrap()
Ok(balance) if balance.proposed_change() == [ChangeValue::Sapling(Amount::from_u64(10000).unwrap())]
&& balance.fee_required() == Amount::from_u64(10000).unwrap()
);
}
@ -218,7 +218,7 @@ mod tests {
// enough to pay a fee, plus dust
TestSaplingInput {
note_id: 0,
value: Amount::from_u64(1100).unwrap(),
value: Amount::from_u64(10100).unwrap(),
},
],
&[SaplingPayment::new(Amount::from_u64(40000).unwrap())],
@ -228,7 +228,7 @@ mod tests {
assert_matches!(
result,
Err(ChangeError::InsufficientFunds { available, required })
if available == Amount::from_u64(41100).unwrap() && required == Amount::from_u64(42000).unwrap()
if available == Amount::from_u64(50100).unwrap() && required == Amount::from_u64(60000).unwrap()
);
}
}

View File

@ -7,12 +7,10 @@ use zcash_primitives::{
keys::OutgoingViewingKey,
legacy::TransparentAddress,
merkle_tree::IncrementalWitness,
sapling::{
note::ExtractedNoteCommitment, Diversifier, Node, Note, Nullifier, PaymentAddress, Rseed,
},
sapling,
transaction::{
components::{
sapling,
sapling::fees as sapling_fees,
transparent::{self, OutPoint, TxOut},
Amount,
},
@ -27,10 +25,8 @@ use zcash_primitives::{
pub struct WalletTx<N> {
pub txid: TxId,
pub index: usize,
pub num_spends: usize,
pub num_outputs: usize,
pub shielded_spends: Vec<WalletShieldedSpend>,
pub shielded_outputs: Vec<WalletShieldedOutput<N>>,
pub sapling_spends: Vec<WalletSaplingSpend>,
pub sapling_outputs: Vec<WalletSaplingOutput<N>>,
}
#[derive(Debug, Clone)]
@ -90,38 +86,107 @@ impl transparent::fees::InputView for WalletTransparentOutput {
/// A subset of a [`SpendDescription`] relevant to wallets and light clients.
///
/// [`SpendDescription`]: zcash_primitives::transaction::components::SpendDescription
pub struct WalletShieldedSpend {
pub index: usize,
pub nf: Nullifier,
pub account: AccountId,
pub struct WalletSaplingSpend {
index: usize,
nf: sapling::Nullifier,
account: AccountId,
}
impl WalletSaplingSpend {
pub fn from_parts(index: usize, nf: sapling::Nullifier, account: AccountId) -> Self {
Self { index, nf, account }
}
pub fn index(&self) -> usize {
self.index
}
pub fn nf(&self) -> &sapling::Nullifier {
&self.nf
}
pub fn account(&self) -> AccountId {
self.account
}
}
/// A subset of an [`OutputDescription`] relevant to wallets and light clients.
///
/// [`OutputDescription`]: zcash_primitives::transaction::components::OutputDescription
pub struct WalletShieldedOutput<N> {
pub index: usize,
pub cmu: ExtractedNoteCommitment,
pub ephemeral_key: EphemeralKeyBytes,
pub account: AccountId,
pub note: Note,
pub to: PaymentAddress,
pub is_change: bool,
pub witness: IncrementalWitness<Node>,
pub nf: N,
pub struct WalletSaplingOutput<N> {
index: usize,
cmu: sapling::note::ExtractedNoteCommitment,
ephemeral_key: EphemeralKeyBytes,
account: AccountId,
note: sapling::Note,
is_change: bool,
witness: IncrementalWitness<sapling::Node>,
nf: N,
}
impl<N> WalletSaplingOutput<N> {
/// Constructs a new `WalletSaplingOutput` value from its constituent parts.
#[allow(clippy::too_many_arguments)]
pub fn from_parts(
index: usize,
cmu: sapling::note::ExtractedNoteCommitment,
ephemeral_key: EphemeralKeyBytes,
account: AccountId,
note: sapling::Note,
is_change: bool,
witness: IncrementalWitness<sapling::Node>,
nf: N,
) -> Self {
Self {
index,
cmu,
ephemeral_key,
account,
note,
is_change,
witness,
nf,
}
}
pub fn index(&self) -> usize {
self.index
}
pub fn cmu(&self) -> &sapling::note::ExtractedNoteCommitment {
&self.cmu
}
pub fn ephemeral_key(&self) -> &EphemeralKeyBytes {
&self.ephemeral_key
}
pub fn account(&self) -> AccountId {
self.account
}
pub fn note(&self) -> &sapling::Note {
&self.note
}
pub fn is_change(&self) -> bool {
self.is_change
}
pub fn witness(&self) -> &IncrementalWitness<sapling::Node> {
&self.witness
}
pub fn witness_mut(&mut self) -> &mut IncrementalWitness<sapling::Node> {
&mut self.witness
}
pub fn nf(&self) -> &N {
&self.nf
}
}
/// 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 note_id: NoteRef,
pub diversifier: Diversifier,
pub diversifier: sapling::Diversifier,
pub note_value: Amount,
pub rseed: Rseed,
pub witness: IncrementalWitness<Node>,
pub rseed: sapling::Rseed,
pub witness: IncrementalWitness<sapling::Node>,
}
impl<NoteRef> sapling::fees::InputView<NoteRef> for SpendableNote<NoteRef> {
impl<NoteRef> sapling_fees::InputView<NoteRef> for SpendableNote<NoteRef> {
fn note_id(&self) -> &NoteRef {
&self.note_id
}

View File

@ -20,7 +20,7 @@ use zcash_primitives::{
use crate::{
proto::compact_formats::CompactBlock,
scan::{Batch, BatchRunner, Tasks},
wallet::{WalletShieldedOutput, WalletShieldedSpend, WalletTx},
wallet::{WalletSaplingOutput, WalletSaplingSpend, WalletTx},
};
/// A key that can be used to perform trial decryption and nullifier
@ -115,7 +115,7 @@ impl ScanningKey for SaplingIvk {
///
/// Returns a vector of [`WalletTx`]s belonging to any of the given
/// [`ScanningKey`]s. If scanning with a full viewing key, the nullifiers
/// of the resulting [`WalletShieldedOutput`]s will also be computed.
/// of the resulting [`WalletSaplingOutput`]s will also be computed.
///
/// The given [`CommitmentTree`] and existing [`IncrementalWitness`]es are
/// incremented appropriately.
@ -123,7 +123,7 @@ impl ScanningKey for SaplingIvk {
/// The implementation of [`ScanningKey`] may either support or omit the computation of
/// the nullifiers for received notes; the implementation for [`ExtendedFullViewingKey`]
/// will derive the nullifiers for received notes and return them as part of the resulting
/// [`WalletShieldedOutput`]s, whereas the implementation for [`SaplingIvk`] cannot
/// [`WalletSaplingOutput`]s, whereas the implementation for [`SaplingIvk`] cannot
/// do so and will return the unit value in those outputs instead.
///
/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
@ -132,7 +132,7 @@ impl ScanningKey for SaplingIvk {
/// [`ScanningKey`]: crate::welding_rig::ScanningKey
/// [`CommitmentTree`]: zcash_primitives::merkle_tree::CommitmentTree
/// [`IncrementalWitness`]: zcash_primitives::merkle_tree::IncrementalWitness
/// [`WalletShieldedOutput`]: crate::wallet::WalletShieldedOutput
/// [`WalletSaplingOutput`]: crate::wallet::WalletSaplingOutput
/// [`WalletTx`]: crate::wallet::WalletTx
pub fn scan_block<P: consensus::Parameters + Send + 'static, K: ScanningKey>(
params: &P,
@ -211,8 +211,6 @@ pub(crate) fn scan_block_with_runner<
for tx in block.vtx.into_iter() {
let txid = tx.txid();
let index = tx.index as usize;
let num_spends = tx.spends.len();
let num_outputs = tx.outputs.len();
// Check for spent notes
// The only step that is not constant-time is the filter() at the end.
@ -233,22 +231,20 @@ pub(crate) fn scan_block_with_runner<
CtOption::new(AccountId::from(0), 0.into()),
|first, next| CtOption::conditional_select(&next, &first, first.is_some()),
)
.map(|account| WalletShieldedSpend {
index,
nf: spend_nf,
account,
})
.map(|account| WalletSaplingSpend::from_parts(index, spend_nf, account))
})
.filter(|spend| spend.is_some().into())
.map(|spend| spend.unwrap())
.collect();
// Collect the set of accounts that were spent from in this transaction
let spent_from_accounts: HashSet<_> =
shielded_spends.iter().map(|spend| spend.account).collect();
let spent_from_accounts: HashSet<_> = shielded_spends
.iter()
.map(|spend| spend.account())
.collect();
// Check for incoming notes while incrementing tree and witnesses
let mut shielded_outputs: Vec<WalletShieldedOutput<K::Nf>> = vec![];
let mut shielded_outputs: Vec<WalletSaplingOutput<K::Nf>> = vec![];
{
// Grab mutable references to new witnesses from previous transactions
// in this block so that we can update them. Scoped so we don't hold
@ -256,9 +252,9 @@ pub(crate) fn scan_block_with_runner<
let mut block_witnesses: Vec<_> = wtxs
.iter_mut()
.flat_map(|tx| {
tx.shielded_outputs
tx.sapling_outputs
.iter_mut()
.map(|output| &mut output.witness)
.map(|output| output.witness_mut())
})
.collect();
@ -330,7 +326,7 @@ pub(crate) fn scan_block_with_runner<
// don't hold mutable references to shielded_outputs for too long.
let new_witnesses: Vec<_> = shielded_outputs
.iter_mut()
.map(|out| &mut out.witness)
.map(|out| out.witness_mut())
.collect();
// Increment tree and witnesses
@ -346,7 +342,7 @@ pub(crate) fn scan_block_with_runner<
}
tree.append(node).unwrap();
if let Some(((note, to), account, nk)) = dec_output {
if let Some(((note, _), account, nk)) = dec_output {
// A note is marked as "change" if the account that received it
// also spent notes in the same transaction. This will catch,
// for instance:
@ -357,17 +353,16 @@ pub(crate) fn scan_block_with_runner<
let witness = IncrementalWitness::from_tree(tree);
let nf = K::sapling_nf(&nk, &note, &witness);
shielded_outputs.push(WalletShieldedOutput {
shielded_outputs.push(WalletSaplingOutput::from_parts(
index,
cmu: output.cmu,
ephemeral_key: output.ephemeral_key.clone(),
output.cmu,
output.ephemeral_key.clone(),
account,
note,
to,
is_change,
witness,
nf,
})
))
}
}
}
@ -376,10 +371,8 @@ pub(crate) fn scan_block_with_runner<
wtxs.push(WalletTx {
txid,
index,
num_spends,
num_outputs,
shielded_spends,
shielded_outputs,
sapling_spends: shielded_spends,
sapling_outputs: shielded_outputs,
});
}
}
@ -569,16 +562,14 @@ mod tests {
let tx = &txs[0];
assert_eq!(tx.index, 1);
assert_eq!(tx.num_spends, 1);
assert_eq!(tx.num_outputs, 1);
assert_eq!(tx.shielded_spends.len(), 0);
assert_eq!(tx.shielded_outputs.len(), 1);
assert_eq!(tx.shielded_outputs[0].index, 0);
assert_eq!(tx.shielded_outputs[0].account, account);
assert_eq!(tx.shielded_outputs[0].note.value().inner(), 5);
assert_eq!(tx.sapling_spends.len(), 0);
assert_eq!(tx.sapling_outputs.len(), 1);
assert_eq!(tx.sapling_outputs[0].index(), 0);
assert_eq!(tx.sapling_outputs[0].account(), account);
assert_eq!(tx.sapling_outputs[0].note().value().inner(), 5);
// Check that the witness root matches
assert_eq!(tx.shielded_outputs[0].witness.root(), tree.root());
assert_eq!(tx.sapling_outputs[0].witness().root(), tree.root());
}
go(false);
@ -632,16 +623,14 @@ mod tests {
let tx = &txs[0];
assert_eq!(tx.index, 1);
assert_eq!(tx.num_spends, 1);
assert_eq!(tx.num_outputs, 1);
assert_eq!(tx.shielded_spends.len(), 0);
assert_eq!(tx.shielded_outputs.len(), 1);
assert_eq!(tx.shielded_outputs[0].index, 0);
assert_eq!(tx.shielded_outputs[0].account, AccountId::from(0));
assert_eq!(tx.shielded_outputs[0].note.value().inner(), 5);
assert_eq!(tx.sapling_spends.len(), 0);
assert_eq!(tx.sapling_outputs.len(), 1);
assert_eq!(tx.sapling_outputs[0].index(), 0);
assert_eq!(tx.sapling_outputs[0].account(), AccountId::from(0));
assert_eq!(tx.sapling_outputs[0].note().value().inner(), 5);
// Check that the witness root matches
assert_eq!(tx.shielded_outputs[0].witness.root(), tree.root());
assert_eq!(tx.sapling_outputs[0].witness().root(), tree.root());
}
go(false);
@ -672,12 +661,10 @@ mod tests {
let tx = &txs[0];
assert_eq!(tx.index, 1);
assert_eq!(tx.num_spends, 1);
assert_eq!(tx.num_outputs, 1);
assert_eq!(tx.shielded_spends.len(), 1);
assert_eq!(tx.shielded_outputs.len(), 0);
assert_eq!(tx.shielded_spends[0].index, 0);
assert_eq!(tx.shielded_spends[0].nf, nf);
assert_eq!(tx.shielded_spends[0].account, account);
assert_eq!(tx.sapling_spends.len(), 1);
assert_eq!(tx.sapling_outputs.len(), 0);
assert_eq!(tx.sapling_spends[0].index(), 0);
assert_eq!(tx.sapling_spends[0].nf(), &nf);
assert_eq!(tx.sapling_spends[0].account(), account);
}
}

View File

@ -9,6 +9,41 @@ and this library adheres to Rust's notion of
### Changed
- Bumped dependencies to `hdwallet 0.4`.
## [0.7.0] - 2023-04-28
### Changed
- Bumped dependencies to `zcash_client_backend 0.9`.
### Removed
- The following deprecated types and methods have been removed from the public API:
- `wallet::ShieldedOutput`
- `wallet::block_height_extrema`
- `wallet::get_address`
- `wallet::get_all_nullifiers`
- `wallet::get_balance`
- `wallet::get_balance_at`
- `wallet::get_block_hash`
- `wallet::get_commitment_tree`
- `wallet::get_nullifiers`
- `wallet::get_received_memo`
- `wallet::get_rewind_height`
- `wallet::get_sent_memo`
- `wallet::get_spendable_sapling_notes`
- `wallet::get_transaction`
- `wallet::get_tx_height`
- `wallet::get_unified_full_viewing_keys`
- `wallet::get_witnesses`
- `wallet::insert_block`
- `wallet::insert_witnesses`
- `wallet::is_valid_account_extfvk`
- `wallet::mark_sapling_note_spent`
- `wallet::put_tx_data`
- `wallet::put_tx_meta`
- `wallet::prune_witnesses`
- `wallet::select_spendable_sapling_notes`
- `wallet::update_expired_notes`
- `wallet::transact::get_spendable_sapling_notes`
- `wallet::transact::select_spendable_sapling_notes`
## [0.6.0] - 2023-04-15
### Added
- SQLite view `v_tx_outputs`, exposing the history of transaction outputs sent

View File

@ -1,7 +1,7 @@
[package]
name = "zcash_client_sqlite"
description = "An SQLite-based Zcash light client"
version = "0.6.0"
version = "0.7.0"
authors = [
"Jack Grigg <jack@z.cash>",
"Kris Nuttycombe <kris@electriccoin.co>"
@ -14,7 +14,7 @@ edition = "2021"
rust-version = "1.60"
[dependencies]
zcash_client_backend = { version = "0.8", path = "../zcash_client_backend" }
zcash_client_backend = { version = "0.9", path = "../zcash_client_backend" }
zcash_primitives = { version = "0.11", path = "../zcash_primitives", default-features = false }
# Dependencies exposed in a public API:

View File

@ -141,7 +141,7 @@ pub(crate) fn blockmetadb_insert(
}
#[cfg(feature = "unstable")]
pub(crate) fn blockmetadb_rewind_to_height(
pub(crate) fn blockmetadb_truncate_to_height(
conn: &Connection,
block_height: BlockHeight,
) -> Result<(), rusqlite::Error> {
@ -284,7 +284,7 @@ mod tests {
self, fake_compact_block, fake_compact_block_spending, init_test_accounts_table,
insert_into_cache, sapling_activation_height, AddressType,
},
wallet::{get_balance, init::init_wallet_db, rewind_to_height},
wallet::{get_balance, init::init_wallet_db, truncate_to_height},
AccountId, BlockDb, WalletDb,
};
@ -479,7 +479,7 @@ mod tests {
}
#[test]
fn data_db_rewinding() {
fn data_db_truncation() {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
init_cache_database(&db_cache).unwrap();
@ -529,7 +529,7 @@ mod tests {
);
// "Rewind" to height of last scanned block
rewind_to_height(&db_data, sapling_activation_height() + 1).unwrap();
truncate_to_height(&db_data, sapling_activation_height() + 1).unwrap();
// Account balance should be unaltered
assert_eq!(
@ -538,7 +538,7 @@ mod tests {
);
// Rewind so that one block is dropped
rewind_to_height(&db_data, sapling_activation_height()).unwrap();
truncate_to_height(&db_data, sapling_activation_height()).unwrap();
// Account balance should only contain the first received note
assert_eq!(get_balance(&db_data, AccountId::from(0)).unwrap(), value);

View File

@ -42,7 +42,7 @@ use zcash_primitives::{
block::BlockHash,
consensus::{self, BlockHeight},
legacy::TransparentAddress,
memo::Memo,
memo::{Memo, MemoBytes},
merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::{Node, Nullifier},
transaction::{
@ -61,7 +61,7 @@ use zcash_client_backend::{
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
proto::compact_formats::CompactBlock,
wallet::{SpendableNote, WalletTransparentOutput},
TransferType,
DecryptedOutput, TransferType,
};
use crate::error::SqliteClientError;
@ -136,24 +136,24 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
type TxRef = i64;
fn block_height_extrema(&self) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
#[allow(deprecated)]
wallet::block_height_extrema(self).map_err(SqliteClientError::from)
}
fn get_min_unspent_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
wallet::get_min_unspent_height(self).map_err(SqliteClientError::from)
}
fn get_block_hash(&self, block_height: BlockHeight) -> Result<Option<BlockHash>, Self::Error> {
#[allow(deprecated)]
wallet::get_block_hash(self, block_height).map_err(SqliteClientError::from)
}
fn get_tx_height(&self, txid: TxId) -> Result<Option<BlockHeight>, Self::Error> {
#[allow(deprecated)]
wallet::get_tx_height(self, txid).map_err(SqliteClientError::from)
}
fn get_unified_full_viewing_keys(
&self,
) -> Result<HashMap<AccountId, UnifiedFullViewingKey>, Self::Error> {
#[allow(deprecated)]
wallet::get_unified_full_viewing_keys(self)
}
@ -176,7 +176,6 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
account: AccountId,
extfvk: &ExtendedFullViewingKey,
) -> Result<bool, Self::Error> {
#[allow(deprecated)]
wallet::is_valid_account_extfvk(self, account, extfvk)
}
@ -185,17 +184,14 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
account: AccountId,
anchor_height: BlockHeight,
) -> Result<Amount, Self::Error> {
#[allow(deprecated)]
wallet::get_balance_at(self, account, anchor_height)
}
fn get_transaction(&self, id_tx: i64) -> Result<Transaction, Self::Error> {
#[allow(deprecated)]
wallet::get_transaction(self, id_tx)
}
fn get_memo(&self, id_note: Self::NoteRef) -> Result<Memo, Self::Error> {
#[allow(deprecated)]
match id_note {
NoteId::SentNoteId(id_note) => wallet::get_sent_memo(self, id_note),
NoteId::ReceivedNoteId(id_note) => wallet::get_received_memo(self, id_note),
@ -206,8 +202,7 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
&self,
block_height: BlockHeight,
) -> Result<Option<CommitmentTree<Node>>, Self::Error> {
#[allow(deprecated)]
wallet::get_commitment_tree(self, block_height)
wallet::get_sapling_commitment_tree(self, block_height)
}
#[allow(clippy::type_complexity)]
@ -215,18 +210,15 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
&self,
block_height: BlockHeight,
) -> Result<Vec<(Self::NoteRef, IncrementalWitness<Node>)>, Self::Error> {
#[allow(deprecated)]
wallet::get_witnesses(self, block_height)
wallet::get_sapling_witnesses(self, block_height)
}
fn get_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error> {
#[allow(deprecated)]
wallet::get_nullifiers(self)
wallet::get_sapling_nullifiers(self)
}
fn get_all_nullifiers(&self) -> Result<Vec<(AccountId, Nullifier)>, Self::Error> {
#[allow(deprecated)]
wallet::get_all_nullifiers(self)
wallet::get_all_sapling_nullifiers(self)
}
fn get_spendable_sapling_notes(
@ -235,7 +227,6 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
anchor_height: BlockHeight,
exclude: &[Self::NoteRef],
) -> Result<Vec<SpendableNote<Self::NoteRef>>, Self::Error> {
#[allow(deprecated)]
wallet::transact::get_spendable_sapling_notes(self, account, anchor_height, exclude)
}
@ -246,7 +237,6 @@ impl<P: consensus::Parameters> WalletRead for WalletDb<P> {
anchor_height: BlockHeight,
exclude: &[Self::NoteRef],
) -> Result<Vec<SpendableNote<Self::NoteRef>>, Self::Error> {
#[allow(deprecated)]
wallet::transact::select_spendable_sapling_notes(
self,
account,
@ -308,6 +298,10 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> {
self.wallet_db.block_height_extrema()
}
fn get_min_unspent_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
self.wallet_db.get_min_unspent_height()
}
fn get_block_hash(&self, block_height: BlockHeight) -> Result<Option<BlockHash>, Self::Error> {
self.wallet_db.get_block_hash(block_height)
}
@ -458,7 +452,6 @@ impl<'a, P: consensus::Parameters> DataConnStmtCache<'a, P> {
}
}
#[allow(deprecated)]
impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
type UtxoRef = UtxoId;
@ -540,15 +533,15 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
let tx_row = wallet::put_tx_meta(up, tx, block.block_height)?;
// Mark notes as spent and remove them from the scanning cache
for spend in &tx.shielded_spends {
wallet::mark_sapling_note_spent(up, tx_row, &spend.nf)?;
for spend in &tx.sapling_spends {
wallet::mark_sapling_note_spent(up, tx_row, spend.nf())?;
}
for output in &tx.shielded_outputs {
for output in &tx.sapling_outputs {
let received_note_id = wallet::put_received_note(up, output, tx_row)?;
// Save witness for note.
new_witnesses.push((received_note_id, output.witness.clone()));
new_witnesses.push((received_note_id, output.witness().clone()));
}
}
@ -584,7 +577,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
match output.transfer_type {
TransferType::Outgoing | TransferType::WalletInternal => {
let recipient = if output.transfer_type == TransferType::Outgoing {
Recipient::Sapling(output.to)
Recipient::Sapling(output.note.recipient())
} else {
Recipient::InternalAccount(output.account, PoolType::Sapling)
};
@ -599,6 +592,10 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
SqliteClientError::CorruptedData("Note value is not a valid Zcash amount.".to_string()))?,
Some(&output.memo),
)?;
if matches!(recipient, Recipient::InternalAccount(_, _)) {
wallet::put_received_note(up, output, tx_ref)?;
}
}
TransferType::Incoming => {
match spending_account_id {
@ -683,6 +680,22 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
for output in &sent_tx.outputs {
wallet::insert_sent_output(up, tx_ref, sent_tx.account, output)?;
if let Some((account, note)) = output.sapling_change_to() {
wallet::put_received_note(
up,
&DecryptedOutput {
index: output.output_index(),
note: note.clone(),
account: *account,
memo: output
.memo()
.map_or_else(MemoBytes::empty, |memo| memo.clone()),
transfer_type: TransferType::WalletInternal,
},
tx_ref,
)?;
}
}
// Return the row number of the transaction, so the caller can fetch it for sending.
@ -690,8 +703,8 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
})
}
fn rewind_to_height(&mut self, block_height: BlockHeight) -> Result<(), Self::Error> {
wallet::rewind_to_height(self.wallet_db, block_height)
fn truncate_to_height(&mut self, block_height: BlockHeight) -> Result<(), Self::Error> {
wallet::truncate_to_height(self.wallet_db, block_height)
}
fn put_received_transparent_utxo(
@ -890,8 +903,8 @@ impl FsBlockDb {
/// If the requested height is greater than or equal to the height
/// of the last scanned block, or if the DB is empty, this function
/// does nothing.
pub fn rewind_to_height(&self, block_height: BlockHeight) -> Result<(), FsBlockDbError> {
Ok(chain::blockmetadb_rewind_to_height(
pub fn truncate_to_height(&self, block_height: BlockHeight) -> Result<(), FsBlockDbError> {
Ok(chain::blockmetadb_truncate_to_height(
&self.conn,
block_height,
)?)
@ -968,7 +981,6 @@ impl std::fmt::Display for FsBlockDbError {
extern crate assert_matches;
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use prost::Message;
use rand_core::{OsRng, RngCore};
@ -1379,7 +1391,9 @@ mod tests {
assert_eq!(db_meta.find_block(BlockHeight::from_u32(3)).unwrap(), None);
// Rewinding to height 1 should cause the metadata for height 2 to be deleted.
db_meta.rewind_to_height(BlockHeight::from_u32(1)).unwrap();
db_meta
.truncate_to_height(BlockHeight::from_u32(1))
.unwrap();
assert_eq!(
db_meta.get_max_cached_height().unwrap(),
Some(BlockHeight::from_u32(1)),

View File

@ -156,7 +156,7 @@ impl<'a, P> DataConnStmtCache<'a, P> {
"SELECT id_tx FROM transactions WHERE txid = ?",
)?,
stmt_mark_sapling_note_spent: wallet_db.conn.prepare(
"UPDATE received_notes SET spent = ? WHERE nf = ?"
"UPDATE sapling_received_notes SET spent = ? WHERE nf = ?"
)?,
#[cfg(feature = "transparent-inputs")]
stmt_mark_transparent_utxo_spent: wallet_db.conn.prepare(
@ -217,11 +217,11 @@ impl<'a, P> DataConnStmtCache<'a, P> {
RETURNING id_utxo"
)?,
stmt_insert_received_note: wallet_db.conn.prepare(
"INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, memo, nf, is_change)
"INSERT INTO sapling_received_notes (tx, output_index, account, diversifier, value, rcm, memo, nf, is_change)
VALUES (:tx, :output_index, :account, :diversifier, :value, :rcm, :memo, :nf, :is_change)",
)?,
stmt_update_received_note: wallet_db.conn.prepare(
"UPDATE received_notes
"UPDATE sapling_received_notes
SET account = :account,
diversifier = :diversifier,
value = :value,
@ -232,7 +232,7 @@ impl<'a, P> DataConnStmtCache<'a, P> {
WHERE tx = :tx AND output_index = :output_index",
)?,
stmt_select_received_note: wallet_db.conn.prepare(
"SELECT id_note FROM received_notes WHERE tx = ? AND output_index = ?"
"SELECT id_note FROM sapling_received_notes WHERE tx = ? AND output_index = ?"
)?,
stmt_update_sent_output: wallet_db.conn.prepare(
"UPDATE sent_notes
@ -261,9 +261,9 @@ impl<'a, P> DataConnStmtCache<'a, P> {
"DELETE FROM sapling_witnesses WHERE block < ?"
)?,
stmt_update_expired: wallet_db.conn.prepare(
"UPDATE received_notes SET spent = NULL WHERE EXISTS (
"UPDATE sapling_received_notes SET spent = NULL WHERE EXISTS (
SELECT id_tx FROM transactions
WHERE id_tx = received_notes.spent AND block IS NULL AND expiry_height < ?
WHERE id_tx = sapling_received_notes.spent AND block IS NULL AND expiry_height < ?
)",
)?,
stmt_insert_address: InsertAddress::new(&wallet_db.conn)?
@ -684,9 +684,9 @@ impl<'a, P> DataConnStmtCache<'a, P> {
diversifier: &Diversifier,
value: u64,
rcm: [u8; 32],
nf: &Option<Nullifier>,
nf: Option<&Nullifier>,
memo: Option<&MemoBytes>,
is_change: Option<bool>,
is_change: bool,
) -> Result<NoteId, SqliteClientError> {
let sql_args: &[(&str, &dyn ToSql)] = &[
(":tx", &tx_ref),
@ -695,7 +695,7 @@ impl<'a, P> DataConnStmtCache<'a, P> {
(":diversifier", &diversifier.0.as_ref()),
(":value", &(value as i64)),
(":rcm", &rcm.as_ref()),
(":nf", &nf.as_ref().map(|nf| nf.0.as_ref())),
(":nf", &nf.map(|nf| nf.0.as_ref())),
(
":memo",
&memo
@ -726,9 +726,9 @@ impl<'a, P> DataConnStmtCache<'a, P> {
diversifier: &Diversifier,
value: u64,
rcm: [u8; 32],
nf: &Option<Nullifier>,
nf: Option<&Nullifier>,
memo: Option<&MemoBytes>,
is_change: Option<bool>,
is_change: bool,
tx_ref: i64,
output_index: usize,
) -> Result<bool, SqliteClientError> {
@ -737,7 +737,7 @@ impl<'a, P> DataConnStmtCache<'a, P> {
(":diversifier", &diversifier.0.as_ref()),
(":value", &(value as i64)),
(":rcm", &rcm.as_ref()),
(":nf", &nf.as_ref().map(|nf| nf.0.as_ref())),
(":nf", &nf.map(|nf| nf.0.as_ref())),
(
":memo",
&memo

View File

@ -74,7 +74,7 @@ use zcash_primitives::{
consensus::{self, BlockHeight, BranchId, NetworkUpgrade, Parameters},
memo::{Memo, MemoBytes},
merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::{Node, Note, Nullifier, PaymentAddress},
sapling::{Node, Note, Nullifier},
transaction::{components::Amount, Transaction, TxId},
zip32::{
sapling::{DiversifiableFullViewingKey, ExtendedFullViewingKey},
@ -86,8 +86,8 @@ use zcash_client_backend::{
address::{RecipientAddress, UnifiedAddress},
data_api::{PoolType, Recipient, SentTransactionOutput},
keys::UnifiedFullViewingKey,
wallet::{WalletShieldedOutput, WalletTx},
DecryptedOutput,
wallet::{WalletSaplingOutput, WalletTx},
DecryptedOutput, TransferType,
};
use crate::{
@ -123,113 +123,56 @@ pub(crate) fn pool_code(pool_type: PoolType) -> i64 {
}
/// This trait provides a generalization over shielded output representations.
#[deprecated(note = "This trait will be removed in a future release.")]
pub trait ShieldedOutput {
pub(crate) trait ReceivedSaplingOutput {
fn index(&self) -> usize;
fn account(&self) -> AccountId;
fn to(&self) -> &PaymentAddress;
fn note(&self) -> &Note;
fn memo(&self) -> Option<&MemoBytes>;
fn is_change(&self) -> Option<bool>;
fn nullifier(&self) -> Option<Nullifier>;
fn is_change(&self) -> bool;
fn nullifier(&self) -> Option<&Nullifier>;
}
#[allow(deprecated)]
impl ShieldedOutput for WalletShieldedOutput<Nullifier> {
impl ReceivedSaplingOutput for WalletSaplingOutput<Nullifier> {
fn index(&self) -> usize {
self.index
self.index()
}
fn account(&self) -> AccountId {
self.account
}
fn to(&self) -> &PaymentAddress {
&self.to
WalletSaplingOutput::account(self)
}
fn note(&self) -> &Note {
&self.note
WalletSaplingOutput::note(self)
}
fn memo(&self) -> Option<&MemoBytes> {
None
}
fn is_change(&self) -> Option<bool> {
Some(self.is_change)
fn is_change(&self) -> bool {
WalletSaplingOutput::is_change(self)
}
fn nullifier(&self) -> Option<Nullifier> {
Some(self.nf)
fn nullifier(&self) -> Option<&Nullifier> {
Some(self.nf())
}
}
#[allow(deprecated)]
impl ShieldedOutput for DecryptedOutput {
impl ReceivedSaplingOutput for DecryptedOutput<Note> {
fn index(&self) -> usize {
self.index
}
fn account(&self) -> AccountId {
self.account
}
fn to(&self) -> &PaymentAddress {
&self.to
}
fn note(&self) -> &Note {
&self.note
}
fn memo(&self) -> Option<&MemoBytes> {
Some(&self.memo)
}
fn is_change(&self) -> Option<bool> {
fn is_change(&self) -> bool {
self.transfer_type == TransferType::WalletInternal
}
fn nullifier(&self) -> Option<&Nullifier> {
None
}
fn nullifier(&self) -> Option<Nullifier> {
None
}
}
/// Returns the address for the account.
///
/// # Examples
///
/// ```
/// use tempfile::NamedTempFile;
/// use zcash_primitives::{
/// consensus::{self, Network},
/// zip32::AccountId,
/// };
/// use zcash_client_sqlite::{
/// WalletDb,
/// wallet::get_address,
/// };
///
/// let data_file = NamedTempFile::new().unwrap();
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let addr = get_address(&db, AccountId::from(0));
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_address instead."
)]
pub fn get_address<P: consensus::Parameters>(
wdb: &WalletDb<P>,
account: AccountId,
) -> Result<Option<PaymentAddress>, SqliteClientError> {
// This returns the most recently generated address.
let addr: String = wdb.conn.query_row(
"SELECT address
FROM addresses WHERE account = ?
ORDER BY diversifier_index_be DESC
LIMIT 1",
[u32::from(account)],
|row| row.get(0),
)?;
RecipientAddress::decode(&wdb.params, &addr)
.ok_or_else(|| {
SqliteClientError::CorruptedData("Not a valid Zcash recipient address".to_owned())
})
.map(|addr| match addr {
// TODO: Return the UA, not its Sapling component.
RecipientAddress::Unified(ua) => ua.sapling().cloned(),
_ => None,
})
}
pub(crate) fn get_max_account_id<P>(
@ -451,10 +394,7 @@ pub(crate) fn get_account_for_ufvk<P: consensus::Parameters>(
/// specified account.
///
/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::is_valid_account_extfvk instead."
)]
pub fn is_valid_account_extfvk<P: consensus::Parameters>(
pub(crate) fn is_valid_account_extfvk<P: consensus::Parameters>(
wdb: &WalletDb<P>,
account: AccountId,
extfvk: &ExtendedFullViewingKey,
@ -488,31 +428,14 @@ pub fn is_valid_account_extfvk<P: consensus::Parameters>(
/// to chain reorgs. You should generally not show this balance to users without some
/// caveat. Use [`get_balance_at`] where you need a more reliable indication of the
/// wallet balance.
///
/// # Examples
///
/// ```
/// use tempfile::NamedTempFile;
/// use zcash_primitives::{
/// consensus::Network,
/// zip32::AccountId,
/// };
/// use zcash_client_sqlite::{
/// WalletDb,
/// wallet::get_balance,
/// };
///
/// let data_file = NamedTempFile::new().unwrap();
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let addr = get_balance(&db, AccountId::from(0));
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_balance_at instead."
)]
pub fn get_balance<P>(wdb: &WalletDb<P>, account: AccountId) -> Result<Amount, SqliteClientError> {
#[cfg(test)]
pub(crate) fn get_balance<P>(
wdb: &WalletDb<P>,
account: AccountId,
) -> Result<Amount, SqliteClientError> {
let balance = wdb.conn.query_row(
"SELECT SUM(value) FROM received_notes
INNER JOIN transactions ON transactions.id_tx = received_notes.tx
"SELECT SUM(value) FROM sapling_received_notes
INNER JOIN transactions ON transactions.id_tx = sapling_received_notes.tx
WHERE account = ? AND spent IS NULL AND transactions.block IS NOT NULL",
[u32::from(account)],
|row| row.get(0).or(Ok(0)),
@ -521,7 +444,7 @@ pub fn get_balance<P>(wdb: &WalletDb<P>, account: AccountId) -> Result<Amount, S
match Amount::from_i64(balance) {
Ok(amount) if !amount.is_negative() => Ok(amount),
_ => Err(SqliteClientError::CorruptedData(
"Sum of values in received_notes is out of range".to_string(),
"Sum of values in sapling_received_notes is out of range".to_string(),
)),
}
}
@ -529,35 +452,14 @@ pub fn get_balance<P>(wdb: &WalletDb<P>, account: AccountId) -> Result<Amount, S
/// Returns the verified balance for the account at the specified 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.
///
/// # Examples
///
/// ```
/// use tempfile::NamedTempFile;
/// use zcash_primitives::{
/// consensus::{BlockHeight, Network},
/// zip32::AccountId,
/// };
/// use zcash_client_sqlite::{
/// WalletDb,
/// wallet::get_balance_at,
/// };
///
/// let data_file = NamedTempFile::new().unwrap();
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let addr = get_balance_at(&db, AccountId::from(0), BlockHeight::from_u32(0));
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_balance_at instead."
)]
pub fn get_balance_at<P>(
pub(crate) fn get_balance_at<P>(
wdb: &WalletDb<P>,
account: AccountId,
anchor_height: BlockHeight,
) -> Result<Amount, SqliteClientError> {
let balance = wdb.conn.query_row(
"SELECT SUM(value) FROM received_notes
INNER JOIN transactions ON transactions.id_tx = received_notes.tx
"SELECT SUM(value) FROM sapling_received_notes
INNER JOIN transactions ON transactions.id_tx = sapling_received_notes.tx
WHERE account = ? AND spent IS NULL AND transactions.block <= ?",
[u32::from(account), u32::from(anchor_height)],
|row| row.get(0).or(Ok(0)),
@ -566,37 +468,21 @@ pub fn get_balance_at<P>(
match Amount::from_i64(balance) {
Ok(amount) if !amount.is_negative() => Ok(amount),
_ => Err(SqliteClientError::CorruptedData(
"Sum of values in received_notes is out of range".to_string(),
"Sum of values in sapling_received_notes is out of range".to_string(),
)),
}
}
/// Returns the memo for a received note.
///
/// The note is identified by its row index in the `received_notes` table within the wdb
/// The note is identified by its row index in the `sapling_received_notes` table within the wdb
/// database.
///
/// # Examples
///
/// ```
/// use tempfile::NamedTempFile;
/// use zcash_primitives::consensus::Network;
/// use zcash_client_sqlite::{
/// NoteId,
/// WalletDb,
/// wallet::get_received_memo,
/// };
///
/// let data_file = NamedTempFile::new().unwrap();
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let memo = get_received_memo(&db, 27);
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_memo instead."
)]
pub fn get_received_memo<P>(wdb: &WalletDb<P>, id_note: i64) -> Result<Memo, SqliteClientError> {
pub(crate) fn get_received_memo<P>(
wdb: &WalletDb<P>,
id_note: i64,
) -> Result<Memo, SqliteClientError> {
let memo_bytes: Vec<_> = wdb.conn.query_row(
"SELECT memo FROM received_notes
"SELECT memo FROM sapling_received_notes
WHERE id_note = ?",
[id_note],
|row| row.get(0),
@ -633,26 +519,7 @@ pub(crate) fn get_transaction<P: Parameters>(
///
/// The note is identified by its row index in the `sent_notes` table within the wdb
/// database.
///
/// # Examples
///
/// ```
/// use tempfile::NamedTempFile;
/// use zcash_primitives::consensus::Network;
/// use zcash_client_sqlite::{
/// NoteId,
/// WalletDb,
/// wallet::get_sent_memo,
/// };
///
/// let data_file = NamedTempFile::new().unwrap();
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let memo = get_sent_memo(&db, 12);
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_memo instead."
)]
pub fn get_sent_memo<P>(wdb: &WalletDb<P>, id_note: i64) -> Result<Memo, SqliteClientError> {
pub(crate) fn get_sent_memo<P>(wdb: &WalletDb<P>, id_note: i64) -> Result<Memo, SqliteClientError> {
let memo_bytes: Vec<_> = wdb.conn.query_row(
"SELECT memo FROM sent_notes
WHERE id_note = ?",
@ -666,25 +533,7 @@ pub fn get_sent_memo<P>(wdb: &WalletDb<P>, id_note: i64) -> Result<Memo, SqliteC
}
/// Returns the minimum and maximum heights for blocks stored in the wallet database.
///
/// # Examples
///
/// ```
/// use tempfile::NamedTempFile;
/// use zcash_primitives::consensus::Network;
/// use zcash_client_sqlite::{
/// WalletDb,
/// wallet::block_height_extrema,
/// };
///
/// let data_file = NamedTempFile::new().unwrap();
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let bounds = block_height_extrema(&db);
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::block_height_extrema instead."
)]
pub fn block_height_extrema<P>(
pub(crate) fn block_height_extrema<P>(
wdb: &WalletDb<P>,
) -> Result<Option<(BlockHeight, BlockHeight)>, rusqlite::Error> {
wdb.conn
@ -703,26 +552,7 @@ pub fn block_height_extrema<P>(
/// Returns the block height at which the specified transaction was mined,
/// if any.
///
/// # Examples
///
/// ```
/// use tempfile::NamedTempFile;
/// use zcash_primitives::consensus::Network;
/// use zcash_primitives::transaction::TxId;
/// use zcash_client_sqlite::{
/// WalletDb,
/// wallet::get_tx_height,
/// };
///
/// let data_file = NamedTempFile::new().unwrap();
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let height = get_tx_height(&db, TxId::from_bytes([0u8; 32]));
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_tx_height instead."
)]
pub fn get_tx_height<P>(
pub(crate) fn get_tx_height<P>(
wdb: &WalletDb<P>,
txid: TxId,
) -> Result<Option<BlockHeight>, rusqlite::Error> {
@ -737,25 +567,7 @@ pub fn get_tx_height<P>(
/// Returns the block hash for the block at the specified height,
/// if any.
///
/// # Examples
///
/// ```
/// use tempfile::NamedTempFile;
/// use zcash_primitives::consensus::{H0, Network};
/// use zcash_client_sqlite::{
/// WalletDb,
/// wallet::get_block_hash,
/// };
///
/// let data_file = NamedTempFile::new().unwrap();
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let hash = get_block_hash(&db, H0);
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_block_hash instead."
)]
pub fn get_block_hash<P>(
pub(crate) fn get_block_hash<P>(
wdb: &WalletDb<P>,
block_height: BlockHeight,
) -> Result<Option<BlockHash>, rusqlite::Error> {
@ -771,14 +583,15 @@ pub fn get_block_hash<P>(
.optional()
}
/// Gets the height to which the database must be rewound if any rewind greater than the pruning
/// height is attempted.
#[deprecated(note = "This function will be removed in a future release.")]
pub fn get_rewind_height<P>(wdb: &WalletDb<P>) -> Result<Option<BlockHeight>, SqliteClientError> {
/// Gets the height to which the database must be truncated if any truncation that would remove a
/// number of blocks greater than the pruning height is attempted.
pub(crate) fn get_min_unspent_height<P>(
wdb: &WalletDb<P>,
) -> Result<Option<BlockHeight>, SqliteClientError> {
wdb.conn
.query_row(
"SELECT MIN(tx.block)
FROM received_notes n
FROM sapling_received_notes n
JOIN transactions tx ON tx.id_tx = n.tx
WHERE n.spent IS NULL",
[],
@ -790,13 +603,13 @@ pub fn get_rewind_height<P>(wdb: &WalletDb<P>) -> Result<Option<BlockHeight>, Sq
.map_err(SqliteClientError::from)
}
/// Rewinds the database to the given height.
/// Truncates the database to the given height.
///
/// If the requested height is greater than or equal to the height of the last scanned
/// block, this function does nothing.
///
/// This should only be executed inside a transactional context.
pub(crate) fn rewind_to_height<P: consensus::Parameters>(
pub(crate) fn truncate_to_height<P: consensus::Parameters>(
wdb: &WalletDb<P>,
block_height: BlockHeight,
) -> Result<(), SqliteClientError> {
@ -815,8 +628,7 @@ pub(crate) fn rewind_to_height<P: consensus::Parameters>(
})?;
if block_height < last_scanned_height - PRUNING_HEIGHT {
#[allow(deprecated)]
if let Some(h) = get_rewind_height(wdb)? {
if let Some(h) = get_min_unspent_height(wdb)? {
if block_height > h {
return Err(SqliteClientError::RequestedRewindInvalid(h, block_height));
}
@ -833,10 +645,10 @@ pub(crate) fn rewind_to_height<P: consensus::Parameters>(
// Rewind received notes
wdb.conn.execute(
"DELETE FROM received_notes
"DELETE FROM sapling_received_notes
WHERE id_note IN (
SELECT rn.id_note
FROM received_notes rn
FROM sapling_received_notes rn
LEFT OUTER JOIN transactions tx
ON tx.id_tx = rn.tx
WHERE tx.block IS NOT NULL AND tx.block > ?
@ -872,25 +684,7 @@ pub(crate) fn rewind_to_height<P: consensus::Parameters>(
/// Returns the commitment tree for the block at the specified height,
/// if any.
///
/// # Examples
///
/// ```
/// use tempfile::NamedTempFile;
/// use zcash_primitives::consensus::{Network, H0};
/// use zcash_client_sqlite::{
/// WalletDb,
/// wallet::get_commitment_tree,
/// };
///
/// let data_file = NamedTempFile::new().unwrap();
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let tree = get_commitment_tree(&db, H0);
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_commitment_tree instead."
)]
pub fn get_commitment_tree<P>(
pub(crate) fn get_sapling_commitment_tree<P>(
wdb: &WalletDb<P>,
block_height: BlockHeight,
) -> Result<Option<CommitmentTree<Node>>, SqliteClientError> {
@ -915,25 +709,7 @@ pub fn get_commitment_tree<P>(
/// Returns the incremental witnesses for the block at the specified height,
/// if any.
///
/// # Examples
///
/// ```
/// use tempfile::NamedTempFile;
/// use zcash_primitives::consensus::{Network, H0};
/// use zcash_client_sqlite::{
/// WalletDb,
/// wallet::get_witnesses,
/// };
///
/// let data_file = NamedTempFile::new().unwrap();
/// let db = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
/// let witnesses = get_witnesses(&db, H0);
/// ```
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_witnesses instead."
)]
pub fn get_witnesses<P>(
pub(crate) fn get_sapling_witnesses<P>(
wdb: &WalletDb<P>,
block_height: BlockHeight,
) -> Result<Vec<(NoteId, IncrementalWitness<Node>)>, SqliteClientError> {
@ -953,22 +729,23 @@ pub fn get_witnesses<P>(
Ok(res)
}
/// Retrieve the nullifiers for notes that the wallet is tracking
/// that have not yet been confirmed as a consequence of the spending
/// transaction being included in a block.
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletRead::get_nullifiers instead."
)]
pub fn get_nullifiers<P>(
/// 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 received_notes rn
LEFT OUTER JOIN transactions tx
ON tx.id_tx = rn.spent
WHERE block IS NULL",
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)?;
@ -984,13 +761,14 @@ pub fn get_nullifiers<P>(
}
/// Returns the nullifiers for the notes that this wallet is tracking.
pub(crate) fn get_all_nullifiers<P>(
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 received_notes rn",
FROM sapling_received_notes rn
WHERE nf IS NOT NULL",
)?;
let nullifiers = stmt_fetch_nullifiers.query_map([], |row| {
let account: u32 = row.get(1)?;
@ -1101,10 +879,7 @@ pub(crate) fn get_transparent_balances<P: consensus::Parameters>(
}
/// Inserts information about a scanned block into the database.
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletWrite::advance_by_block instead."
)]
pub fn insert_block<'a, P>(
pub(crate) fn insert_block<'a, P>(
stmts: &mut DataConnStmtCache<'a, P>,
block_height: BlockHeight,
block_hash: BlockHash,
@ -1116,10 +891,7 @@ pub fn insert_block<'a, P>(
/// Inserts information about a mined transaction that was observed to
/// contain a note related to this wallet into the database.
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletWrite::advance_by_block instead."
)]
pub fn put_tx_meta<'a, P, N>(
pub(crate) fn put_tx_meta<'a, P, N>(
stmts: &mut DataConnStmtCache<'a, P>,
tx: &WalletTx<N>,
height: BlockHeight,
@ -1134,10 +906,7 @@ pub fn put_tx_meta<'a, P, N>(
}
/// Inserts full transaction data into the database.
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletWrite::store_decrypted_tx instead."
)]
pub fn put_tx_data<'a, P>(
pub(crate) fn put_tx_data<'a, P>(
stmts: &mut DataConnStmtCache<'a, P>,
tx: &Transaction,
fee: Option<Amount>,
@ -1162,10 +931,7 @@ pub fn put_tx_data<'a, P>(
///
/// Marking a note spent in this fashion does NOT imply that the
/// spending transaction has been mined.
#[deprecated(
note = "This function will be removed in a future release. Use zcash_client_backend::data_api::WalletWrite::store_sent_tx instead."
)]
pub fn mark_sapling_note_spent<'a, P>(
pub(crate) fn mark_sapling_note_spent<'a, P>(
stmts: &mut DataConnStmtCache<'a, P>,
tx_ref: i64,
nf: &Nullifier,
@ -1233,18 +999,15 @@ pub(crate) fn put_received_transparent_utxo<'a, P: consensus::Parameters>(
/// 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.
#[deprecated(
note = "This method will be removed in a future release. Use zcash_client_backend::data_api::WalletWrite::store_decrypted_tx instead."
)]
#[allow(deprecated)]
pub fn put_received_note<'a, P, T: ShieldedOutput>(
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 diversifier = output.to().diversifier();
let to = output.note().recipient();
let diversifier = to.diversifier();
let value = output.note().value();
let memo = output.memo();
let is_change = output.is_change();
@ -1257,7 +1020,7 @@ pub fn put_received_note<'a, P, T: ShieldedOutput>(
diversifier,
value.inner(),
rcm,
&nf,
nf,
memo,
is_change,
tx_ref,
@ -1271,7 +1034,7 @@ pub fn put_received_note<'a, P, T: ShieldedOutput>(
diversifier,
value.inner(),
rcm,
&nf,
nf,
memo,
is_change,
)
@ -1283,10 +1046,7 @@ pub fn put_received_note<'a, P, T: ShieldedOutput>(
/// Records the incremental witness for the specified note,
/// as of the given block height.
#[deprecated(
note = "This method will be removed in a future release. Use zcash_client_backend::data_api::WalletWrite::store_decrypted_tx instead."
)]
pub fn insert_witness<'a, P>(
pub(crate) fn insert_witness<'a, P>(
stmts: &mut DataConnStmtCache<'a, P>,
note_id: i64,
witness: &IncrementalWitness<Node>,
@ -1296,10 +1056,7 @@ pub fn insert_witness<'a, P>(
}
/// Removes old incremental witnesses up to the given block height.
#[deprecated(
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletWrite::advance_by_block instead."
)]
pub fn prune_witnesses<P>(
pub(crate) fn prune_witnesses<P>(
stmts: &mut DataConnStmtCache<'_, P>,
below_height: BlockHeight,
) -> Result<(), SqliteClientError> {
@ -1308,10 +1065,7 @@ pub fn prune_witnesses<P>(
/// Marks notes that have not been mined in transactions
/// as expired, up to the given block height.
#[deprecated(
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletWrite::advance_by_block instead."
)]
pub fn update_expired_notes<P>(
pub(crate) fn update_expired_notes<P>(
stmts: &mut DataConnStmtCache<'_, P>,
height: BlockHeight,
) -> Result<(), SqliteClientError> {
@ -1329,11 +1083,11 @@ pub(crate) fn insert_sent_output<'a, P: consensus::Parameters>(
) -> Result<(), SqliteClientError> {
stmts.stmt_insert_sent_output(
tx_ref,
output.output_index,
output.output_index(),
from_account,
&output.recipient,
output.value,
output.memo.as_ref(),
output.recipient(),
output.value(),
output.memo(),
)
}
@ -1365,7 +1119,6 @@ pub(crate) fn put_sent_output<'a, P: consensus::Parameters>(
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use secrecy::Secret;
use tempfile::NamedTempFile;
@ -1374,9 +1127,13 @@ mod tests {
use zcash_client_backend::data_api::WalletRead;
use crate::{tests, wallet::init::init_wallet_db, AccountId, WalletDb};
use crate::{
tests,
wallet::{get_current_address, init::init_wallet_db},
AccountId, WalletDb,
};
use super::{get_address, get_balance};
use super::get_balance;
#[cfg(feature = "transparent-inputs")]
use {
@ -1408,7 +1165,7 @@ mod tests {
assert_eq!(db_data.get_target_and_anchor_heights(10).unwrap(), None);
// An invalid account has zero balance
assert!(get_address(&db_data, AccountId::from(1)).is_err());
assert_matches!(get_current_address(&db_data, AccountId::from(1)), Ok(None));
assert_eq!(
get_balance(&db_data, AccountId::from(0)).unwrap(),
Amount::zero()

View File

@ -158,11 +158,11 @@ fn init_wallet_db_internal<P: consensus::Parameters + 'static>(
/// [`WalletWrite::create_account`]: zcash_client_backend::data_api::WalletWrite::create_account
///
/// The [`UnifiedFullViewingKey`]s are stored internally and used by other APIs such as
/// [`get_address`], [`scan_cached_blocks`], and [`create_spend_to_address`]. Account identifiers
/// in `keys` **MUST** form a consecutive sequence beginning at account 0, and the
/// [`UnifiedFullViewingKey`] corresponding to a given account identifier **MUST** be derived from
/// the wallet's mnemonic seed at the BIP-44 `account` path level as described by
/// [ZIP 316](https://zips.z.cash/zip-0316)
/// [`scan_cached_blocks`], and [`create_spend_to_address`]. Account identifiers in `keys` **MUST**
/// form a consecutive sequence beginning at account 0, and the [`UnifiedFullViewingKey`]
/// corresponding to a given account identifier **MUST** be derived from the wallet's mnemonic seed
/// at the BIP-44 `account` path level as described by [ZIP
/// 316](https://zips.z.cash/zip-0316)
///
/// # Examples
///
@ -301,6 +301,7 @@ mod tests {
use zcash_client_backend::{
address::RecipientAddress,
data_api::WalletRead,
encoding::{encode_extended_full_viewing_key, encode_payment_address},
keys::{sapling, UnifiedFullViewingKey, UnifiedSpendingKey},
};
@ -315,7 +316,6 @@ mod tests {
use crate::{
error::SqliteClientError,
tests::{self, network},
wallet::get_address,
AccountId, WalletDb,
};
@ -361,7 +361,7 @@ mod tests {
time INTEGER NOT NULL,
sapling_tree BLOB NOT NULL
)",
"CREATE TABLE received_notes (
"CREATE TABLE sapling_received_notes (
id_note INTEGER PRIMARY KEY,
tx INTEGER NOT NULL,
output_index INTEGER NOT NULL,
@ -369,7 +369,7 @@ mod tests {
diversifier BLOB NOT NULL,
value INTEGER NOT NULL,
rcm BLOB NOT NULL,
nf BLOB NOT NULL UNIQUE,
nf BLOB UNIQUE,
is_change INTEGER NOT NULL,
memo BLOB,
spent INTEGER,
@ -383,7 +383,7 @@ mod tests {
note INTEGER NOT NULL,
block INTEGER NOT NULL,
witness BLOB NOT NULL,
FOREIGN KEY (note) REFERENCES received_notes(id_note),
FOREIGN KEY (note) REFERENCES sapling_received_notes(id_note),
FOREIGN KEY (block) REFERENCES blocks(height),
CONSTRAINT witness_height UNIQUE (note, block)
)",
@ -455,23 +455,23 @@ mod tests {
"CREATE VIEW v_transactions AS
WITH
notes AS (
SELECT received_notes.account AS account_id,
received_notes.tx AS id_tx,
SELECT sapling_received_notes.account AS account_id,
sapling_received_notes.tx AS id_tx,
2 AS pool,
received_notes.value AS value,
sapling_received_notes.value AS value,
CASE
WHEN received_notes.is_change THEN 1
WHEN sapling_received_notes.is_change THEN 1
ELSE 0
END AS is_change,
CASE
WHEN received_notes.is_change THEN 0
WHEN sapling_received_notes.is_change THEN 0
ELSE 1
END AS received_count,
CASE
WHEN received_notes.memo IS NULL THEN 0
WHEN sapling_received_notes.memo IS NULL THEN 0
ELSE 1
END AS memo_present
FROM received_notes
FROM sapling_received_notes
UNION
SELECT utxos.received_by_account AS account_id,
transactions.id_tx AS id_tx,
@ -484,15 +484,15 @@ mod tests {
JOIN transactions
ON transactions.txid = utxos.prevout_txid
UNION
SELECT received_notes.account AS account_id,
received_notes.spent AS id_tx,
SELECT sapling_received_notes.account AS account_id,
sapling_received_notes.spent AS id_tx,
2 AS pool,
-received_notes.value AS value,
-sapling_received_notes.value AS value,
0 AS is_change,
0 AS received_count,
0 AS memo_present
FROM received_notes
WHERE received_notes.spent IS NOT NULL
FROM sapling_received_notes
WHERE sapling_received_notes.spent IS NOT NULL
),
sent_note_counts AS (
SELECT sent_notes.from_account AS account_id,
@ -505,11 +505,11 @@ mod tests {
END
) AS memo_count
FROM sent_notes
LEFT JOIN received_notes
LEFT JOIN sapling_received_notes
ON (sent_notes.tx, sent_notes.output_pool, sent_notes.output_index) =
(received_notes.tx, 2, received_notes.output_index)
WHERE received_notes.is_change IS NULL
OR received_notes.is_change = 0
(sapling_received_notes.tx, 2, sapling_received_notes.output_index)
WHERE sapling_received_notes.is_change IS NULL
OR sapling_received_notes.is_change = 0
GROUP BY account_id, id_tx
),
blocks_max_height AS (
@ -543,19 +543,19 @@ mod tests {
GROUP BY notes.account_id, transactions.id_tx",
// v_tx_outputs
"CREATE VIEW v_tx_outputs AS
SELECT received_notes.tx AS id_tx,
2 AS output_pool,
received_notes.output_index AS output_index,
sent_notes.from_account AS from_account,
received_notes.account AS to_account,
NULL AS to_address,
received_notes.value AS value,
received_notes.is_change AS is_change,
received_notes.memo AS memo
FROM received_notes
SELECT sapling_received_notes.tx AS id_tx,
2 AS output_pool,
sapling_received_notes.output_index AS output_index,
sent_notes.from_account AS from_account,
sapling_received_notes.account AS to_account,
NULL AS to_address,
sapling_received_notes.value AS value,
sapling_received_notes.is_change AS is_change,
sapling_received_notes.memo AS memo
FROM sapling_received_notes
LEFT JOIN sent_notes
ON (sent_notes.tx, sent_notes.output_pool, sent_notes.output_index) =
(received_notes.tx, 2, sent_notes.output_index)
(sapling_received_notes.tx, 2, sent_notes.output_index)
UNION
SELECT transactions.id_tx AS id_tx,
0 AS output_pool,
@ -570,21 +570,21 @@ mod tests {
JOIN transactions
ON transactions.txid = utxos.prevout_txid
UNION
SELECT sent_notes.tx AS id_tx,
sent_notes.output_pool AS output_pool,
sent_notes.output_index AS output_index,
sent_notes.from_account AS from_account,
received_notes.account AS to_account,
sent_notes.to_address AS to_address,
sent_notes.value AS value,
false AS is_change,
sent_notes.memo AS memo
SELECT sent_notes.tx AS id_tx,
sent_notes.output_pool AS output_pool,
sent_notes.output_index AS output_index,
sent_notes.from_account AS from_account,
sapling_received_notes.account AS to_account,
sent_notes.to_address AS to_address,
sent_notes.value AS value,
false AS is_change,
sent_notes.memo AS memo
FROM sent_notes
LEFT JOIN received_notes
LEFT JOIN sapling_received_notes
ON (sent_notes.tx, sent_notes.output_pool, sent_notes.output_index) =
(received_notes.tx, 2, received_notes.output_index)
WHERE received_notes.is_change IS NULL
OR received_notes.is_change = 0",
(sapling_received_notes.tx, 2, sapling_received_notes.output_index)
WHERE sapling_received_notes.is_change IS NULL
OR sapling_received_notes.is_change = 0"
];
let mut views_query = db_data
@ -1142,8 +1142,8 @@ mod tests {
init_accounts_table(&db_data, &ufvks).unwrap();
// The account's address should be in the data DB
let pa = get_address(&db_data, AccountId::from(0)).unwrap();
assert_eq!(pa.unwrap(), expected_address);
let ua = db_data.get_current_address(AccountId::from(0)).unwrap();
assert_eq!(ua.unwrap().sapling().unwrap(), &expected_address);
}
#[test]

View File

@ -2,6 +2,7 @@ mod add_transaction_views;
mod add_utxo_account;
mod addresses_table;
mod initial_setup;
mod received_notes_nullable_nf;
mod sent_notes_to_internal;
mod ufvk_support;
mod utxos_table;
@ -44,5 +45,6 @@ pub(super) fn all_migrations<P: consensus::Parameters + 'static>(
Box::new(sent_notes_to_internal::Migration {}),
Box::new(add_transaction_views::Migration),
Box::new(v_transactions_net::Migration),
Box::new(received_notes_nullable_nf::Migration),
]
}

View File

@ -0,0 +1,375 @@
//! A migration that renames the `received_notes` table to `sapling_received_notes`
//! and makes the `nf` column nullable. This allows change notes to be added to the
//! table prior to being mined.
use std::collections::HashSet;
use rusqlite;
use schemer;
use schemer_rusqlite::RusqliteMigration;
use uuid::Uuid;
use super::v_transactions_net;
use crate::wallet::init::WalletMigrationError;
pub(super) const MIGRATION_ID: Uuid = Uuid::from_fields(
0xbdcdcedc,
0x7b29,
0x4f1c,
b"\x83\x07\x35\xf9\x37\xf0\xd3\x2a",
);
pub(crate) struct Migration;
impl schemer::Migration for Migration {
fn id(&self) -> Uuid {
MIGRATION_ID
}
fn dependencies(&self) -> HashSet<Uuid> {
[v_transactions_net::MIGRATION_ID].into_iter().collect()
}
fn description(&self) -> &'static str {
"Rename `received_notes` to `sapling_received_notes` and make the `nf` column nullable."
}
}
impl RusqliteMigration for Migration {
type Error = WalletMigrationError;
fn up(&self, transaction: &rusqlite::Transaction) -> Result<(), WalletMigrationError> {
// As of this migration, the `received_notes` table only stores Sapling notes, so
// we can hard-code the `output_pool` value.
transaction.execute_batch(
"CREATE TABLE sapling_received_notes (
id_note INTEGER PRIMARY KEY,
tx INTEGER NOT NULL,
output_index INTEGER NOT NULL,
account INTEGER NOT NULL,
diversifier BLOB NOT NULL,
value INTEGER NOT NULL,
rcm BLOB NOT NULL,
nf BLOB UNIQUE,
is_change INTEGER NOT NULL,
memo BLOB,
spent INTEGER,
FOREIGN KEY (tx) REFERENCES transactions(id_tx),
FOREIGN KEY (account) REFERENCES accounts(account),
FOREIGN KEY (spent) REFERENCES transactions(id_tx),
CONSTRAINT tx_output UNIQUE (tx, output_index)
);
INSERT INTO sapling_received_notes SELECT * FROM received_notes;",
)?;
transaction.execute_batch(
"ALTER TABLE sapling_witnesses RENAME TO sapling_witnesses_old;
CREATE TABLE sapling_witnesses (
id_witness INTEGER PRIMARY KEY,
note INTEGER NOT NULL,
block INTEGER NOT NULL,
witness BLOB NOT NULL,
FOREIGN KEY (note) REFERENCES sapling_received_notes(id_note),
FOREIGN KEY (block) REFERENCES blocks(height),
CONSTRAINT witness_height UNIQUE (note, block)
);
INSERT INTO sapling_witnesses SELECT * FROM sapling_witnesses_old;
DROP TABLE sapling_witnesses_old;",
)?;
transaction.execute_batch(
"DROP VIEW v_tx_outputs;
CREATE VIEW v_tx_outputs AS
SELECT sapling_received_notes.tx AS id_tx,
2 AS output_pool,
sapling_received_notes.output_index AS output_index,
sent_notes.from_account AS from_account,
sapling_received_notes.account AS to_account,
NULL AS to_address,
sapling_received_notes.value AS value,
sapling_received_notes.is_change AS is_change,
sapling_received_notes.memo AS memo
FROM sapling_received_notes
LEFT JOIN sent_notes
ON (sent_notes.tx, sent_notes.output_pool, sent_notes.output_index) =
(sapling_received_notes.tx, 2, sent_notes.output_index)
UNION
SELECT transactions.id_tx AS id_tx,
0 AS output_pool,
utxos.prevout_idx AS output_index,
NULL AS from_account,
utxos.received_by_account AS to_account,
utxos.address AS to_address,
utxos.value_zat AS value,
false AS is_change,
NULL AS memo
FROM utxos
JOIN transactions
ON transactions.txid = utxos.prevout_txid
UNION
SELECT sent_notes.tx AS id_tx,
sent_notes.output_pool AS output_pool,
sent_notes.output_index AS output_index,
sent_notes.from_account AS from_account,
sapling_received_notes.account AS to_account,
sent_notes.to_address AS to_address,
sent_notes.value AS value,
false AS is_change,
sent_notes.memo AS memo
FROM sent_notes
LEFT JOIN sapling_received_notes
ON (sent_notes.tx, sent_notes.output_pool, sent_notes.output_index) =
(sapling_received_notes.tx, 2, sapling_received_notes.output_index)
WHERE sapling_received_notes.is_change IS NULL
OR sapling_received_notes.is_change = 0",
)?;
transaction.execute_batch(
"DROP VIEW v_transactions;
CREATE VIEW v_transactions AS
WITH
notes AS (
SELECT sapling_received_notes.account AS account_id,
sapling_received_notes.tx AS id_tx,
2 AS pool,
sapling_received_notes.value AS value,
CASE
WHEN sapling_received_notes.is_change THEN 1
ELSE 0
END AS is_change,
CASE
WHEN sapling_received_notes.is_change THEN 0
ELSE 1
END AS received_count,
CASE
WHEN sapling_received_notes.memo IS NULL THEN 0
ELSE 1
END AS memo_present
FROM sapling_received_notes
UNION
SELECT utxos.received_by_account AS account_id,
transactions.id_tx AS id_tx,
0 AS pool,
utxos.value_zat AS value,
0 AS is_change,
1 AS received_count,
0 AS memo_present
FROM utxos
JOIN transactions
ON transactions.txid = utxos.prevout_txid
UNION
SELECT sapling_received_notes.account AS account_id,
sapling_received_notes.spent AS id_tx,
2 AS pool,
-sapling_received_notes.value AS value,
0 AS is_change,
0 AS received_count,
0 AS memo_present
FROM sapling_received_notes
WHERE sapling_received_notes.spent IS NOT NULL
),
sent_note_counts AS (
SELECT sent_notes.from_account AS account_id,
sent_notes.tx AS id_tx,
COUNT(DISTINCT sent_notes.id_note) as sent_notes,
SUM(
CASE
WHEN sent_notes.memo IS NULL THEN 0
ELSE 1
END
) AS memo_count
FROM sent_notes
LEFT JOIN sapling_received_notes
ON (sent_notes.tx, sent_notes.output_pool, sent_notes.output_index) =
(sapling_received_notes.tx, 2, sapling_received_notes.output_index)
WHERE sapling_received_notes.is_change IS NULL
OR sapling_received_notes.is_change = 0
GROUP BY account_id, id_tx
),
blocks_max_height AS (
SELECT MAX(blocks.height) as max_height FROM blocks
)
SELECT notes.account_id AS account_id,
transactions.id_tx AS id_tx,
transactions.block AS mined_height,
transactions.tx_index AS tx_index,
transactions.txid AS txid,
transactions.expiry_height AS expiry_height,
transactions.raw AS raw,
SUM(notes.value) AS account_balance_delta,
transactions.fee AS fee_paid,
SUM(notes.is_change) > 0 AS has_change,
MAX(COALESCE(sent_note_counts.sent_notes, 0)) AS sent_note_count,
SUM(notes.received_count) AS received_note_count,
SUM(notes.memo_present) + MAX(COALESCE(sent_note_counts.memo_count, 0)) AS memo_count,
blocks.time AS block_time,
(
blocks.height IS NULL
AND transactions.expiry_height <= blocks_max_height.max_height
) AS expired_unmined
FROM transactions
JOIN notes ON notes.id_tx = transactions.id_tx
JOIN blocks_max_height
LEFT JOIN blocks ON blocks.height = transactions.block
LEFT JOIN sent_note_counts
ON sent_note_counts.account_id = notes.account_id
AND sent_note_counts.id_tx = notes.id_tx
GROUP BY notes.account_id, transactions.id_tx",
)?;
transaction.execute_batch("DROP TABLE received_notes;")?;
Ok(())
}
fn down(&self, _transaction: &rusqlite::Transaction) -> Result<(), WalletMigrationError> {
// TODO: something better than just panic?
panic!("Cannot revert this migration.");
}
}
#[cfg(test)]
mod tests {
use rusqlite::{self, params};
use tempfile::NamedTempFile;
use zcash_client_backend::keys::UnifiedSpendingKey;
use zcash_primitives::zip32::AccountId;
use crate::{
tests,
wallet::init::{init_wallet_db_internal, migrations::v_transactions_net},
WalletDb,
};
#[test]
fn received_notes_nullable_migration() {
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
init_wallet_db_internal(&mut db_data, None, &[v_transactions_net::MIGRATION_ID]).unwrap();
// Create an account in the wallet
let usk0 =
UnifiedSpendingKey::from_seed(&tests::network(), &[0u8; 32][..], AccountId::from(0))
.unwrap();
let ufvk0 = usk0.to_unified_full_viewing_key();
db_data
.conn
.execute(
"INSERT INTO accounts (account, ufvk) VALUES (0, ?)",
params![ufvk0.encode(&tests::network())],
)
.unwrap();
// Tx 0 contains two received notes of 2 and 5 zatoshis that are controlled by account 0.
db_data.conn.execute_batch(
"INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, '');
INSERT INTO transactions (block, id_tx, txid) VALUES (0, 0, 'tx0');
INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, nf, is_change)
VALUES (0, 0, 0, '', 2, '', 'nf_a', false);
INSERT INTO received_notes (tx, output_index, account, diversifier, value, rcm, nf, is_change)
VALUES (0, 3, 0, '', 5, '', 'nf_b', false);").unwrap();
// Apply the current migration
init_wallet_db_internal(&mut db_data, None, &[super::MIGRATION_ID]).unwrap();
{
let mut q = db_data
.conn
.prepare(
"SELECT account_id, id_tx, account_balance_delta, has_change, memo_count, sent_note_count, received_note_count
FROM v_transactions",
)
.unwrap();
let mut rows = q.query([]).unwrap();
let mut row_count = 0;
while let Some(row) = rows.next().unwrap() {
row_count += 1;
let account: i64 = row.get(0).unwrap();
let tx: i64 = row.get(1).unwrap();
let account_balance_delta: i64 = row.get(2).unwrap();
let has_change: bool = row.get(3).unwrap();
let memo_count: i64 = row.get(4).unwrap();
let sent_note_count: i64 = row.get(5).unwrap();
let received_note_count: i64 = row.get(6).unwrap();
match (account, tx) {
(0, 0) => {
assert_eq!(account_balance_delta, 7);
assert!(!has_change);
assert_eq!(memo_count, 0);
assert_eq!(sent_note_count, 0);
assert_eq!(received_note_count, 2);
}
other => {
panic!("(Account, Transaction) pair {:?} is not expected to exist in the wallet.", other);
}
}
}
assert_eq!(row_count, 1);
}
// Now create an unmined transaction that spends both of our notes
db_data.conn.execute_batch(
"INSERT INTO transactions (id_tx, txid) VALUES (1, 'tx1');
-- Mark our existing notes as spent
UPDATE sapling_received_notes SET spent = 1 WHERE tx = 0;
-- The note sent to the external recipient.
INSERT INTO sent_notes (tx, output_pool, output_index, from_account, to_account, to_address, value)
VALUES (1, 2, 0, 0, NULL, 'zfake', 4);
-- The change notes. We send two notes to ensure that having multiple rows with NULL nullifiers
-- does not violate the uniqueness constraint
INSERT INTO sent_notes (tx, output_pool, output_index, from_account, to_account, to_address, value)
VALUES (1, 2, 1, 0, 0, NULL, 1);
INSERT INTO sent_notes (tx, output_pool, output_index, from_account, to_account, to_address, value)
VALUES (1, 2, 2, 0, 0, NULL, 1);
INSERT INTO sapling_received_notes (tx, output_index, account, diversifier, value, rcm, nf, is_change)
VALUES (1, 1, 0, '', 1, '', NULL, true);
INSERT INTO sapling_received_notes (tx, output_index, account, diversifier, value, rcm, nf, is_change)
VALUES (1, 2, 0, '', 1, '', NULL, true);
").unwrap();
{
let mut q = db_data
.conn
.prepare(
"SELECT account_id, id_tx, account_balance_delta, has_change, memo_count, sent_note_count, received_note_count
FROM v_transactions",
)
.unwrap();
let mut rows = q.query([]).unwrap();
let mut row_count = 0;
while let Some(row) = rows.next().unwrap() {
row_count += 1;
let account: i64 = row.get(0).unwrap();
let tx: i64 = row.get(1).unwrap();
let account_balance_delta: i64 = row.get(2).unwrap();
let has_change: bool = row.get(3).unwrap();
let memo_count: i64 = row.get(4).unwrap();
let sent_note_count: i64 = row.get(5).unwrap();
let received_note_count: i64 = row.get(6).unwrap();
match (account, tx) {
(0, 0) => {
assert_eq!(account_balance_delta, 7);
assert!(!has_change);
assert_eq!(memo_count, 0);
assert_eq!(sent_note_count, 0);
assert_eq!(received_note_count, 2);
}
(0, 1) => {
assert_eq!(account_balance_delta, -6);
assert!(has_change);
assert_eq!(memo_count, 0);
assert_eq!(sent_note_count, 1);
assert_eq!(received_note_count, 0);
}
other => {
panic!("(Account, Transaction) pair {:?} is not expected to exist in the wallet.", other);
}
}
}
assert_eq!(row_count, 2);
}
}
}

View File

@ -62,10 +62,7 @@ fn to_spendable_note(row: &Row) -> Result<SpendableNote<NoteId>, SqliteClientErr
})
}
#[deprecated(
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletRead::get_spendable_sapling_notes instead."
)]
pub fn get_spendable_sapling_notes<P>(
pub(crate) fn get_spendable_sapling_notes<P>(
wdb: &WalletDb<P>,
account: AccountId,
anchor_height: BlockHeight,
@ -73,9 +70,9 @@ pub fn get_spendable_sapling_notes<P>(
) -> Result<Vec<SpendableNote<NoteId>>, SqliteClientError> {
let mut stmt_select_notes = wdb.conn.prepare(
"SELECT id_note, diversifier, value, rcm, witness
FROM received_notes
INNER JOIN transactions ON transactions.id_tx = received_notes.tx
INNER JOIN sapling_witnesses ON sapling_witnesses.note = received_notes.id_note
FROM sapling_received_notes
INNER JOIN transactions ON transactions.id_tx = sapling_received_notes.tx
INNER JOIN sapling_witnesses ON sapling_witnesses.note = sapling_received_notes.id_note
WHERE account = :account
AND spent IS NULL
AND transactions.block <= :anchor_height
@ -104,10 +101,7 @@ pub fn get_spendable_sapling_notes<P>(
notes.collect::<Result<_, _>>()
}
#[deprecated(
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletRead::select_spendable_sapling_notes instead."
)]
pub fn select_spendable_sapling_notes<P>(
pub(crate) fn select_spendable_sapling_notes<P>(
wdb: &WalletDb<P>,
account: AccountId,
target_value: Amount,
@ -138,8 +132,8 @@ pub fn select_spendable_sapling_notes<P>(
SELECT id_note, diversifier, value, rcm,
SUM(value) OVER
(PARTITION BY account, spent ORDER BY id_note) AS so_far
FROM received_notes
INNER JOIN transactions ON transactions.id_tx = received_notes.tx
FROM sapling_received_notes
INNER JOIN transactions ON transactions.id_tx = sapling_received_notes.tx
WHERE account = :account
AND spent IS NULL
AND transactions.block <= :anchor_height
@ -360,7 +354,7 @@ mod tests {
available,
required
})
if available == Amount::zero() && required == Amount::from_u64(1001).unwrap()
if available == Amount::zero() && required == Amount::from_u64(10001).unwrap()
);
}
@ -443,7 +437,7 @@ mod tests {
required
})
if available == Amount::from_u64(50000).unwrap()
&& required == Amount::from_u64(71000).unwrap()
&& required == Amount::from_u64(80000).unwrap()
);
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 2 to 9 until just before the second
@ -478,7 +472,7 @@ mod tests {
required
})
if available == Amount::from_u64(50000).unwrap()
&& required == Amount::from_u64(71000).unwrap()
&& required == Amount::from_u64(80000).unwrap()
);
// Mine block 11 so that the second note becomes verified
@ -574,7 +568,7 @@ mod tests {
available,
required
})
if available == Amount::zero() && required == Amount::from_u64(3000).unwrap()
if available == Amount::zero() && required == Amount::from_u64(12000).unwrap()
);
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 1 to 21 (that don't send us funds)
@ -608,7 +602,7 @@ mod tests {
available,
required
})
if available == Amount::zero() && required == Amount::from_u64(3000).unwrap()
if available == Amount::zero() && required == Amount::from_u64(12000).unwrap()
);
// Mine block SAPLING_ACTIVATION_HEIGHT + 22 so that the first transaction expires
@ -758,7 +752,7 @@ mod tests {
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
// Add funds to the wallet in a single note
let value = Amount::from_u64(51000).unwrap();
let value = Amount::from_u64(60000).unwrap();
let (cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
@ -812,7 +806,7 @@ mod tests {
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
// Add funds to the wallet in a single note
let value = Amount::from_u64(51000).unwrap();
let value = Amount::from_u64(60000).unwrap();
let (cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),

View File

@ -814,7 +814,7 @@ mod tests {
// create some inputs to spend
let extsk = ExtendedSpendingKey::master(&[]);
let to = extsk.default_address().1;
let note1 = to.create_note(101000, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)));
let note1 = to.create_note(110000, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)));
let cm1 = Node::from_cmu(&note1.cmu());
let mut tree = CommitmentTree::empty();
// fake that the note appears in some previous

View File

@ -8,6 +8,7 @@ and this library adheres to Rust's notion of
## [Unreleased]
### Changed
- MSRV is now 1.60.0.
- Bumped dependencies to `primitive-types 0.12`.
## [0.3.0] - 2022-05-11
### Added

View File

@ -14,7 +14,7 @@ assert_matches = "1.3.0"
proptest = "1.0.0"
[dependencies]
primitive-types = "0.11"
primitive-types = { version = "0.12", default-features = false }
byteorder = "1"
blake2 = { package = "blake2b_simd", version = "1" }
proptest = { version = "1.0.0", optional = true }

View File

@ -8,6 +8,8 @@ and this library adheres to Rust's notion of
## [Unreleased]
### Changed
- Bumped dependencies to `secp256k1 0.26`, `hdwallet 0.4`.
- `zcash_primitives::transactions::component::amount::DEFAULT_FEE` increased zip317
minimum possible fee.
## [0.11.0] - 2023-04-15
### Added

View File

@ -653,7 +653,7 @@ mod tests {
builder
.add_transparent_output(
&TransparentAddress::PublicKey([0; 20]),
Amount::from_u64(49000).unwrap(),
Amount::from_u64(40000).unwrap(),
)
.unwrap();
@ -689,7 +689,7 @@ mod tests {
builder
.add_transparent_output(
&TransparentAddress::PublicKey([0; 20]),
Amount::from_u64(49000).unwrap(),
Amount::from_u64(40000).unwrap(),
)
.unwrap();
@ -778,7 +778,7 @@ mod tests {
);
}
let note1 = to.create_note(50999, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)));
let note1 = to.create_note(59999, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)));
let cmu1 = Node::from_cmu(&note1.cmu());
let mut tree = CommitmentTree::empty();
tree.append(cmu1).unwrap();

View File

@ -8,7 +8,7 @@ use orchard::value as orchard;
pub const COIN: i64 = 1_0000_0000;
pub const MAX_MONEY: i64 = 21_000_000 * COIN;
pub const DEFAULT_FEE: Amount = Amount(1000);
pub const DEFAULT_FEE: Amount = Amount(10_000);
/// A type-safe representation of some quantity of Zcash.
///