Merge pull request #659 from nuttycom/wallet/decrypt_with_internal_key
Track inputs sent to wallet-internal recipients
This commit is contained in:
commit
f689018fcd
|
@ -31,12 +31,17 @@ and this library adheres to Rust's notion of
|
|||
- `zcash_client_backend::address`:
|
||||
- `RecipientAddress::Unified`
|
||||
- `zcash_client_backend::data_api`:
|
||||
- `PoolType`
|
||||
- `Recipient`
|
||||
- `SentTransactionOutput`
|
||||
- `WalletRead::get_unified_full_viewing_keys`
|
||||
- `WalletRead::get_current_address`
|
||||
- `WalletRead::get_all_nullifiers`
|
||||
- `WalletWrite::create_account`
|
||||
- `WalletWrite::remove_unmined_tx` (behind the `unstable` feature flag).
|
||||
- `WalletWrite::get_next_available_address`
|
||||
- `zcash_client_backend::decrypt`:
|
||||
- `TransferType`
|
||||
- `zcash_client_backend::proto`:
|
||||
- `actions` field on `compact_formats::CompactTx`
|
||||
- `compact_formats::CompactOrchardAction`
|
||||
|
@ -44,7 +49,10 @@ and this library adheres to Rust's notion of
|
|||
- `TransactionRequest::new` for constructing a request from `Vec<Payment>`.
|
||||
- `TransactionRequest::payments` for accessing the `Payments` that make up a
|
||||
request.
|
||||
- `zcash_client_backend::encoding::KeyError`
|
||||
- `zcash_client_backend::encoding`
|
||||
- `KeyError`
|
||||
- `AddressCodec` implementations for `sapling::PaymentAddress` and
|
||||
`UnifiedAddress`
|
||||
- New experimental APIs that should be considered unstable, and are
|
||||
likely to be modified and/or moved to a different module in a future
|
||||
release:
|
||||
|
|
|
@ -11,15 +11,16 @@ use secrecy::SecretVec;
|
|||
use zcash_primitives::{
|
||||
block::BlockHash,
|
||||
consensus::BlockHeight,
|
||||
legacy::TransparentAddress,
|
||||
memo::{Memo, MemoBytes},
|
||||
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||
sapling::{Node, Nullifier},
|
||||
sapling::{Node, Nullifier, PaymentAddress},
|
||||
transaction::{components::Amount, Transaction, TxId},
|
||||
zip32::{AccountId, ExtendedFullViewingKey},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
address::{RecipientAddress, UnifiedAddress},
|
||||
address::UnifiedAddress,
|
||||
decrypt::DecryptedOutput,
|
||||
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
|
||||
proto::compact_formats::CompactBlock,
|
||||
|
@ -27,10 +28,7 @@ use crate::{
|
|||
};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use {
|
||||
crate::wallet::WalletTransparentOutput,
|
||||
zcash_primitives::{legacy::TransparentAddress, transaction::components::OutPoint},
|
||||
};
|
||||
use {crate::wallet::WalletTransparentOutput, zcash_primitives::transaction::components::OutPoint};
|
||||
|
||||
pub mod chain;
|
||||
pub mod error;
|
||||
|
@ -248,13 +246,34 @@ pub struct SentTransaction<'a> {
|
|||
pub tx: &'a Transaction,
|
||||
pub created: time::OffsetDateTime,
|
||||
pub account: AccountId,
|
||||
pub outputs: Vec<SentTransactionOutput<'a>>,
|
||||
pub outputs: Vec<SentTransactionOutput>,
|
||||
pub fee_amount: Amount,
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
pub utxos_spent: Vec<OutPoint>,
|
||||
}
|
||||
|
||||
pub struct SentTransactionOutput<'a> {
|
||||
/// A value pool to which the wallet supports sending transaction outputs.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum PoolType {
|
||||
/// The transparent value pool
|
||||
Transparent,
|
||||
/// The Sapling value pool
|
||||
Sapling,
|
||||
}
|
||||
|
||||
/// A type that represents the recipient of a transaction output; a recipient address (and, for
|
||||
/// unified addresses, the pool to which the payment is sent) in the case of outgoing output, or an
|
||||
/// internal account ID and the pool to which funds were sent in the case of a wallet-internal
|
||||
/// output.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Recipient {
|
||||
Transparent(TransparentAddress),
|
||||
Sapling(PaymentAddress),
|
||||
Unified(UnifiedAddress, PoolType),
|
||||
InternalAccount(AccountId, PoolType),
|
||||
}
|
||||
|
||||
pub struct SentTransactionOutput {
|
||||
/// The index within the transaction that contains the recipient output.
|
||||
///
|
||||
/// - If `recipient_address` is a Sapling address, this is an index into the Sapling
|
||||
|
@ -262,8 +281,12 @@ pub struct SentTransactionOutput<'a> {
|
|||
/// - If `recipient_address` is a transparent address, this is an index into the
|
||||
/// transparent outputs of the transaction.
|
||||
pub output_index: usize,
|
||||
pub recipient_address: &'a RecipientAddress,
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@ use {
|
|||
use crate::{
|
||||
address::RecipientAddress,
|
||||
data_api::{
|
||||
error::Error, DecryptedTransaction, SentTransaction, SentTransactionOutput, WalletWrite,
|
||||
error::Error, DecryptedTransaction, PoolType, Recipient, SentTransaction,
|
||||
SentTransactionOutput, WalletWrite,
|
||||
},
|
||||
decrypt_transaction,
|
||||
wallet::OvkPolicy,
|
||||
|
@ -373,14 +374,21 @@ where
|
|||
let (tx, tx_metadata) = builder.build(&prover).map_err(Error::Builder)?;
|
||||
|
||||
let sent_outputs = request.payments().iter().enumerate().map(|(i, payment)| {
|
||||
let idx = match &payment.recipient_address {
|
||||
let (output_index, recipient) = match &payment.recipient_address {
|
||||
// Sapling outputs are shuffled, so we need to look up where the output ended up.
|
||||
// TODO: When we add Orchard support, we will need to trial-decrypt to find them.
|
||||
RecipientAddress::Shielded(_) | RecipientAddress::Unified(_) =>
|
||||
tx_metadata.output_index(i).expect("An output should exist in the transaction for each shielded payment."),
|
||||
RecipientAddress::Shielded(addr) => {
|
||||
let idx = tx_metadata.output_index(i).expect("An output should exist in the transaction for each shielded payment.");
|
||||
(idx, Recipient::Sapling(addr.clone()))
|
||||
}
|
||||
RecipientAddress::Unified(addr) => {
|
||||
// TODO: When we add Orchard support, we will need to trial-decrypt to find them,
|
||||
// and return the appropriate pool type.
|
||||
let idx = tx_metadata.output_index(i).expect("An output should exist in the transaction for each shielded payment.");
|
||||
(idx, Recipient::Unified(addr.clone(), PoolType::Sapling))
|
||||
}
|
||||
RecipientAddress::Transparent(addr) => {
|
||||
let script = addr.script();
|
||||
tx.transparent_bundle()
|
||||
let idx = tx.transparent_bundle()
|
||||
.and_then(|b| {
|
||||
b.vout
|
||||
.iter()
|
||||
|
@ -388,13 +396,15 @@ where
|
|||
.find(|(_, tx_out)| tx_out.script_pubkey == script)
|
||||
})
|
||||
.map(|(index, _)| index)
|
||||
.expect("An output should exist in the transaction for each transparent payment.")
|
||||
.expect("An output should exist in the transaction for each transparent payment.");
|
||||
|
||||
(idx, Recipient::Transparent(*addr))
|
||||
}
|
||||
};
|
||||
|
||||
SentTransactionOutput {
|
||||
output_index: idx,
|
||||
recipient_address: &payment.recipient_address,
|
||||
output_index,
|
||||
recipient,
|
||||
value: payment.amount,
|
||||
memo: payment.memo.clone()
|
||||
}
|
||||
|
@ -512,12 +522,7 @@ where
|
|||
|
||||
// add the sapling output to shield the funds
|
||||
builder
|
||||
.add_sapling_output(
|
||||
Some(ovk),
|
||||
shielding_address.clone(),
|
||||
amount_to_shield,
|
||||
memo.clone(),
|
||||
)
|
||||
.add_sapling_output(Some(ovk), shielding_address, amount_to_shield, memo.clone())
|
||||
.map_err(Error::Builder)?;
|
||||
|
||||
let (tx, tx_metadata) = builder.build(&prover).map_err(Error::Builder)?;
|
||||
|
@ -531,8 +536,8 @@ where
|
|||
account,
|
||||
outputs: vec![SentTransactionOutput {
|
||||
output_index,
|
||||
recipient_address: &RecipientAddress::Shielded(shielding_address),
|
||||
value: amount_to_shield,
|
||||
recipient: Recipient::InternalAccount(account, PoolType::Sapling),
|
||||
memo: Some(memo.clone()),
|
||||
}],
|
||||
fee_amount: fee,
|
||||
|
|
|
@ -10,11 +10,25 @@ use zcash_primitives::{
|
|||
Note, PaymentAddress,
|
||||
},
|
||||
transaction::Transaction,
|
||||
zip32::AccountId,
|
||||
zip32::{AccountId, Scope},
|
||||
};
|
||||
|
||||
use crate::keys::UnifiedFullViewingKey;
|
||||
|
||||
/// An enumeration of the possible relationships a TXO can have to the wallet.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum TransferType {
|
||||
/// The output was received on one of the wallet's external addresses via decryption using the
|
||||
/// associated incoming viewing key, or at one of the wallet's transparent addresses.
|
||||
Incoming,
|
||||
/// The output was received on one of the wallet's internal-only shielded addresses via trial
|
||||
/// decryption using one of the wallet's internal incoming viewing keys.
|
||||
WalletInternal,
|
||||
/// The output was decrypted using one of the wallet's outgoing viewing keys, or was created
|
||||
/// in a transaction constructed by this wallet.
|
||||
Outgoing,
|
||||
}
|
||||
|
||||
/// A decrypted shielded output.
|
||||
pub struct DecryptedOutput {
|
||||
/// The index of the output within [`shielded_outputs`].
|
||||
|
@ -33,7 +47,7 @@ pub struct DecryptedOutput {
|
|||
/// this is a logical output of the transaction.
|
||||
///
|
||||
/// [`OutgoingViewingKey`]: zcash_primitives::keys::OutgoingViewingKey
|
||||
pub outgoing: bool,
|
||||
pub transfer_type: TransferType,
|
||||
}
|
||||
|
||||
/// Scans a [`Transaction`] for any information that can be decrypted by the set of
|
||||
|
@ -44,37 +58,52 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
|
|||
tx: &Transaction,
|
||||
ufvks: &HashMap<AccountId, UnifiedFullViewingKey>,
|
||||
) -> Vec<DecryptedOutput> {
|
||||
let mut decrypted = vec![];
|
||||
|
||||
if let Some(bundle) = tx.sapling_bundle() {
|
||||
for (account, ufvk) in ufvks.iter() {
|
||||
if let Some(dfvk) = ufvk.sapling() {
|
||||
let ivk = PreparedIncomingViewingKey::new(&dfvk.fvk().vk.ivk());
|
||||
tx.sapling_bundle()
|
||||
.iter()
|
||||
.flat_map(|bundle| {
|
||||
ufvks
|
||||
.iter()
|
||||
.flat_map(move |(account, ufvk)| {
|
||||
ufvk.sapling().into_iter().map(|dfvk| (*account, dfvk))
|
||||
})
|
||||
.flat_map(move |(account, dfvk)| {
|
||||
let ivk_external =
|
||||
PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::External));
|
||||
let ivk_internal =
|
||||
PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::Internal));
|
||||
let ovk = dfvk.fvk().ovk;
|
||||
|
||||
for (index, output) in bundle.shielded_outputs.iter().enumerate() {
|
||||
let ((note, to, memo), outgoing) =
|
||||
match try_sapling_note_decryption(params, height, &ivk, output) {
|
||||
Some(ret) => (ret, false),
|
||||
None => match try_sapling_output_recovery(params, height, &ovk, output)
|
||||
{
|
||||
Some(ret) => (ret, true),
|
||||
None => continue,
|
||||
},
|
||||
};
|
||||
|
||||
decrypted.push(DecryptedOutput {
|
||||
bundle
|
||||
.shielded_outputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(move |(index, output)| {
|
||||
try_sapling_note_decryption(params, height, &ivk_external, output)
|
||||
.map(|ret| (ret, TransferType::Incoming))
|
||||
.or_else(|| {
|
||||
try_sapling_note_decryption(
|
||||
params,
|
||||
height,
|
||||
&ivk_internal,
|
||||
output,
|
||||
)
|
||||
.map(|ret| (ret, TransferType::WalletInternal))
|
||||
})
|
||||
.or_else(|| {
|
||||
try_sapling_output_recovery(params, height, &ovk, output)
|
||||
.map(|ret| (ret, TransferType::Outgoing))
|
||||
})
|
||||
.into_iter()
|
||||
.map(move |((note, to, memo), transfer_type)| DecryptedOutput {
|
||||
index,
|
||||
note,
|
||||
account: *account,
|
||||
account,
|
||||
to,
|
||||
memo,
|
||||
outgoing,
|
||||
transfer_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decrypted
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
|
@ -5,14 +5,16 @@
|
|||
//!
|
||||
//! [constants]: zcash_primitives::constants
|
||||
|
||||
use crate::address::UnifiedAddress;
|
||||
use bech32::{self, Error, FromBase32, ToBase32, Variant};
|
||||
use bs58::{self, decode::Error as Bs58Error};
|
||||
use std::fmt;
|
||||
use std::io::{self, Write};
|
||||
use zcash_address::unified::{self, Encoding};
|
||||
use zcash_primitives::{
|
||||
consensus,
|
||||
legacy::TransparentAddress,
|
||||
sapling::PaymentAddress,
|
||||
sapling,
|
||||
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||
};
|
||||
|
||||
|
@ -132,6 +134,41 @@ impl<P: consensus::Parameters> AddressCodec<P> for TransparentAddress {
|
|||
}
|
||||
}
|
||||
|
||||
impl<P: consensus::Parameters> AddressCodec<P> for sapling::PaymentAddress {
|
||||
type Error = Bech32DecodeError;
|
||||
|
||||
fn encode(&self, params: &P) -> String {
|
||||
encode_payment_address(params.hrp_sapling_payment_address(), self)
|
||||
}
|
||||
|
||||
fn decode(params: &P, address: &str) -> Result<Self, Bech32DecodeError> {
|
||||
decode_payment_address(params.hrp_sapling_payment_address(), address)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: consensus::Parameters> AddressCodec<P> for UnifiedAddress {
|
||||
type Error = String;
|
||||
|
||||
fn encode(&self, params: &P) -> String {
|
||||
self.encode(params)
|
||||
}
|
||||
|
||||
fn decode(params: &P, address: &str) -> Result<Self, String> {
|
||||
unified::Address::decode(address)
|
||||
.map_err(|e| format!("{}", e))
|
||||
.and_then(|(network, addr)| {
|
||||
if params.address_network() == Some(network) {
|
||||
UnifiedAddress::try_from(addr).map_err(|e| e.to_owned())
|
||||
} else {
|
||||
Err(format!(
|
||||
"Address {} is for a different network: {:?}",
|
||||
address, network
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes an [`ExtendedSpendingKey`] as a Bech32-encoded string.
|
||||
///
|
||||
/// # Examples
|
||||
|
@ -232,16 +269,18 @@ pub fn decode_extended_full_viewing_key(
|
|||
/// );
|
||||
/// ```
|
||||
/// [`PaymentAddress`]: zcash_primitives::sapling::PaymentAddress
|
||||
pub fn encode_payment_address(hrp: &str, addr: &PaymentAddress) -> String {
|
||||
pub fn encode_payment_address(hrp: &str, addr: &sapling::PaymentAddress) -> String {
|
||||
bech32_encode(hrp, |w| w.write_all(&addr.to_bytes()))
|
||||
}
|
||||
|
||||
/// Writes a [`PaymentAddress`] as a Bech32-encoded string
|
||||
/// using the human-readable prefix values defined in the specified
|
||||
/// network parameters.
|
||||
///
|
||||
/// [`PaymentAddress`]: zcash_primitives::sapling::PaymentAddress
|
||||
pub fn encode_payment_address_p<P: consensus::Parameters>(
|
||||
params: &P,
|
||||
addr: &PaymentAddress,
|
||||
addr: &sapling::PaymentAddress,
|
||||
) -> String {
|
||||
encode_payment_address(params.hrp_sapling_payment_address(), addr)
|
||||
}
|
||||
|
@ -259,7 +298,7 @@ pub fn encode_payment_address_p<P: consensus::Parameters>(
|
|||
/// encoding::decode_payment_address,
|
||||
/// };
|
||||
/// use zcash_primitives::{
|
||||
/// constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS,
|
||||
/// consensus::{TEST_NETWORK, Parameters},
|
||||
/// sapling::{Diversifier, PaymentAddress},
|
||||
/// };
|
||||
///
|
||||
|
@ -276,14 +315,17 @@ pub fn encode_payment_address_p<P: consensus::Parameters>(
|
|||
///
|
||||
/// assert_eq!(
|
||||
/// decode_payment_address(
|
||||
/// HRP_SAPLING_PAYMENT_ADDRESS,
|
||||
/// TEST_NETWORK.hrp_sapling_payment_address(),
|
||||
/// "ztestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ss7jnk",
|
||||
/// ),
|
||||
/// Ok(pa),
|
||||
/// );
|
||||
/// ```
|
||||
/// [`PaymentAddress`]: zcash_primitives::sapling::PaymentAddress
|
||||
pub fn decode_payment_address(hrp: &str, s: &str) -> Result<PaymentAddress, Bech32DecodeError> {
|
||||
pub fn decode_payment_address(
|
||||
hrp: &str,
|
||||
s: &str,
|
||||
) -> Result<sapling::PaymentAddress, Bech32DecodeError> {
|
||||
bech32_decode(hrp, s, |data| {
|
||||
if data.len() != 43 {
|
||||
return None;
|
||||
|
@ -291,7 +333,7 @@ pub fn decode_payment_address(hrp: &str, s: &str) -> Result<PaymentAddress, Bech
|
|||
|
||||
let mut bytes = [0; 43];
|
||||
bytes.copy_from_slice(&data);
|
||||
PaymentAddress::from_bytes(&bytes)
|
||||
sapling::PaymentAddress::from_bytes(&bytes)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -304,14 +346,14 @@ pub fn decode_payment_address(hrp: &str, s: &str) -> Result<PaymentAddress, Bech
|
|||
/// encoding::encode_transparent_address,
|
||||
/// };
|
||||
/// use zcash_primitives::{
|
||||
/// constants::testnet::{B58_PUBKEY_ADDRESS_PREFIX, B58_SCRIPT_ADDRESS_PREFIX},
|
||||
/// consensus::{TEST_NETWORK, Parameters},
|
||||
/// legacy::TransparentAddress,
|
||||
/// };
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// encode_transparent_address(
|
||||
/// &B58_PUBKEY_ADDRESS_PREFIX,
|
||||
/// &B58_SCRIPT_ADDRESS_PREFIX,
|
||||
/// &TEST_NETWORK.b58_pubkey_address_prefix(),
|
||||
/// &TEST_NETWORK.b58_script_address_prefix(),
|
||||
/// &TransparentAddress::PublicKey([0; 20]),
|
||||
/// ),
|
||||
/// "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma",
|
||||
|
@ -319,8 +361,8 @@ pub fn decode_payment_address(hrp: &str, s: &str) -> Result<PaymentAddress, Bech
|
|||
///
|
||||
/// assert_eq!(
|
||||
/// encode_transparent_address(
|
||||
/// &B58_PUBKEY_ADDRESS_PREFIX,
|
||||
/// &B58_SCRIPT_ADDRESS_PREFIX,
|
||||
/// &TEST_NETWORK.b58_pubkey_address_prefix(),
|
||||
/// &TEST_NETWORK.b58_script_address_prefix(),
|
||||
/// &TransparentAddress::Script([0; 20]),
|
||||
/// ),
|
||||
/// "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2",
|
||||
|
@ -369,7 +411,7 @@ pub fn encode_transparent_address_p<P: consensus::Parameters>(
|
|||
///
|
||||
/// ```
|
||||
/// use zcash_primitives::{
|
||||
/// constants::testnet::{B58_PUBKEY_ADDRESS_PREFIX, B58_SCRIPT_ADDRESS_PREFIX},
|
||||
/// consensus::{TEST_NETWORK, Parameters},
|
||||
/// };
|
||||
/// use zcash_client_backend::{
|
||||
/// encoding::decode_transparent_address,
|
||||
|
@ -378,8 +420,8 @@ pub fn encode_transparent_address_p<P: consensus::Parameters>(
|
|||
///
|
||||
/// assert_eq!(
|
||||
/// decode_transparent_address(
|
||||
/// &B58_PUBKEY_ADDRESS_PREFIX,
|
||||
/// &B58_SCRIPT_ADDRESS_PREFIX,
|
||||
/// &TEST_NETWORK.b58_pubkey_address_prefix(),
|
||||
/// &TEST_NETWORK.b58_script_address_prefix(),
|
||||
/// "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma",
|
||||
/// ),
|
||||
/// Ok(Some(TransparentAddress::PublicKey([0; 20]))),
|
||||
|
@ -387,8 +429,8 @@ pub fn encode_transparent_address_p<P: consensus::Parameters>(
|
|||
///
|
||||
/// assert_eq!(
|
||||
/// decode_transparent_address(
|
||||
/// &B58_PUBKEY_ADDRESS_PREFIX,
|
||||
/// &B58_SCRIPT_ADDRESS_PREFIX,
|
||||
/// &TEST_NETWORK.b58_pubkey_address_prefix(),
|
||||
/// &TEST_NETWORK.b58_script_address_prefix(),
|
||||
/// "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2",
|
||||
/// ),
|
||||
/// Ok(Some(TransparentAddress::Script([0; 20]))),
|
||||
|
|
|
@ -19,4 +19,4 @@ pub mod wallet;
|
|||
pub mod welding_rig;
|
||||
pub mod zip321;
|
||||
|
||||
pub use decrypt::{decrypt_transaction, DecryptedOutput};
|
||||
pub use decrypt::{decrypt_transaction, DecryptedOutput, TransferType};
|
||||
|
|
|
@ -76,11 +76,6 @@ and this library adheres to Rust's notion of
|
|||
- `zcash_client_sqlite::wallet`:
|
||||
- `get_spendable_notes` to `get_spendable_sapling_notes`.
|
||||
- `select_spendable_notes` to `select_spendable_sapling_notes`.
|
||||
- Altered the arguments to `zcash_client_sqlite::wallet::put_sent_note`
|
||||
to take the components of a `DecryptedOutput` value to allow this
|
||||
method to be used in contexts where a transaction has just been
|
||||
constructed, rather than only in the case that a transaction has
|
||||
been decrypted after being retrieved from the network.
|
||||
- `zcash_client_sqlite::wallet::init_wallet_db` has been modified to
|
||||
take the wallet seed as an argument so that it can correctly perform
|
||||
migrations that require re-deriving key material. In particular for
|
||||
|
@ -89,12 +84,15 @@ and this library adheres to Rust's notion of
|
|||
migration process.
|
||||
|
||||
### Removed
|
||||
- `zcash_client_sqlite::wallet`:
|
||||
- `get_extended_full_viewing_keys` (use
|
||||
`zcash_client_backend::data_api::WalletRead::get_unified_full_viewing_keys`
|
||||
instead).
|
||||
- `delete_utxos_above` (use
|
||||
`zcash_client_backend::data_api::WalletWrite::rewind_to_height` instead)
|
||||
- The following functions have been removed from the public interface of
|
||||
`zcash_client_sqlite::wallet`. Prefer methods defined on
|
||||
`zcash_client_backend::data_api::{WalletRead, WalletWrite}` instead.
|
||||
- `get_extended_full_viewing_keys` (use `WalletRead::get_unified_full_viewing_keys` instead).
|
||||
- `insert_sent_note` (use `WalletWrite::store_sent_tx` instead)
|
||||
- `insert_sent_utxo` (use `WalletWrite::store_sent_tx` instead)
|
||||
- `put_sent_note` (use `WalletWrite::store_decrypted_tx` instead)
|
||||
- `put_sent_utxo` (use `WalletWrite::store_decrypted_tx` instead)
|
||||
- `delete_utxos_above` (use `WalletWrite::rewind_to_height` instead)
|
||||
- `zcash_client_sqlite::with_blocks` (use
|
||||
`zcash_client_backend::data_api::BlockSource::with_blocks` instead)
|
||||
|
||||
|
@ -111,7 +109,6 @@ and this library adheres to Rust's notion of
|
|||
`zcash_client_backend::data_api` traits mentioned above instead.
|
||||
- Deprecated in `zcash_client_sqlite::wallet`:
|
||||
- `get_address`
|
||||
- `get_extended_full_viewing_keys`
|
||||
- `is_valid_account_extfvk`
|
||||
- `get_balance`
|
||||
- `get_balance_at`
|
||||
|
@ -131,10 +128,6 @@ and this library adheres to Rust's notion of
|
|||
- `insert_witness`
|
||||
- `prune_witnesses`
|
||||
- `update_expired_notes`
|
||||
- `put_sent_note`
|
||||
- `put_sent_utxo`
|
||||
- `insert_sent_note`
|
||||
- `insert_sent_utxo`
|
||||
- `get_address`
|
||||
- Deprecated in `zcash_client_sqlite::wallet::transact`:
|
||||
- `get_spendable_sapling_notes`
|
||||
|
|
|
@ -53,13 +53,15 @@ use zcash_primitives::{
|
|||
};
|
||||
|
||||
use zcash_client_backend::{
|
||||
address::{RecipientAddress, UnifiedAddress},
|
||||
address::UnifiedAddress,
|
||||
data_api::{
|
||||
BlockSource, DecryptedTransaction, PrunedBlock, SentTransaction, WalletRead, WalletWrite,
|
||||
BlockSource, DecryptedTransaction, PoolType, PrunedBlock, Recipient, SentTransaction,
|
||||
WalletRead, WalletWrite,
|
||||
},
|
||||
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
|
||||
proto::compact_formats::CompactBlock,
|
||||
wallet::SpendableNote,
|
||||
TransferType,
|
||||
};
|
||||
|
||||
use crate::error::SqliteClientError;
|
||||
|
@ -529,18 +531,26 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
|
|||
|
||||
let mut spending_account_id: Option<AccountId> = None;
|
||||
for output in d_tx.sapling_outputs {
|
||||
if output.outgoing {
|
||||
wallet::put_sent_note(
|
||||
match output.transfer_type {
|
||||
TransferType::Outgoing | TransferType::WalletInternal => {
|
||||
let recipient = if output.transfer_type == TransferType::Outgoing {
|
||||
Recipient::Sapling(output.to.clone())
|
||||
} else {
|
||||
Recipient::InternalAccount(output.account, PoolType::Sapling)
|
||||
};
|
||||
|
||||
wallet::put_sent_output(
|
||||
up,
|
||||
output.account,
|
||||
tx_ref,
|
||||
output.index,
|
||||
output.account,
|
||||
&output.to,
|
||||
Amount::from_u64(output.note.value)
|
||||
.map_err(|_| SqliteClientError::CorruptedData("Note value invalid.".to_string()))?,
|
||||
&recipient,
|
||||
Amount::from_u64(output.note.value).map_err(|_|
|
||||
SqliteClientError::CorruptedData("Note value is not a valid Zcash amount.".to_string()))?,
|
||||
Some(&output.memo),
|
||||
)?;
|
||||
} else {
|
||||
}
|
||||
TransferType::Incoming => {
|
||||
match spending_account_id {
|
||||
Some(id) =>
|
||||
if id != output.account {
|
||||
|
@ -554,6 +564,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
|
|||
wallet::put_received_note(up, output, tx_ref)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have some transparent outputs:
|
||||
if !d_tx.tx.transparent_bundle().iter().any(|b| b.vout.is_empty()) {
|
||||
|
@ -566,13 +577,15 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
|
|||
.any(|input| *nf == input.nullifier)
|
||||
) {
|
||||
for (output_index, txout) in d_tx.tx.transparent_bundle().iter().flat_map(|b| b.vout.iter()).enumerate() {
|
||||
wallet::put_sent_utxo(
|
||||
let recipient = Recipient::Transparent(txout.script_pubkey.address().unwrap());
|
||||
wallet::put_sent_output(
|
||||
up,
|
||||
*account_id,
|
||||
tx_ref,
|
||||
output_index,
|
||||
*account_id,
|
||||
&txout.script_pubkey.address().unwrap(),
|
||||
&recipient,
|
||||
txout.value,
|
||||
None
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
@ -611,36 +624,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
|
|||
}
|
||||
|
||||
for output in &sent_tx.outputs {
|
||||
match output.recipient_address {
|
||||
// TODO: Store the entire UA, not just the Sapling component.
|
||||
// This will require more info about the output index.
|
||||
RecipientAddress::Unified(ua) => wallet::insert_sent_note(
|
||||
up,
|
||||
tx_ref,
|
||||
output.output_index,
|
||||
sent_tx.account,
|
||||
ua.sapling().expect("TODO: Add Orchard support"),
|
||||
output.value,
|
||||
output.memo.as_ref(),
|
||||
)?,
|
||||
RecipientAddress::Shielded(addr) => wallet::insert_sent_note(
|
||||
up,
|
||||
tx_ref,
|
||||
output.output_index,
|
||||
sent_tx.account,
|
||||
addr,
|
||||
output.value,
|
||||
output.memo.as_ref(),
|
||||
)?,
|
||||
RecipientAddress::Transparent(addr) => wallet::insert_sent_utxo(
|
||||
up,
|
||||
tx_ref,
|
||||
output.output_index,
|
||||
sent_tx.account,
|
||||
addr,
|
||||
output.value,
|
||||
)?,
|
||||
}
|
||||
wallet::insert_sent_output(up, tx_ref, sent_tx.account, output)?;
|
||||
}
|
||||
|
||||
// Return the row number of the transaction, so the caller can fetch it for sending.
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//! - Build the statement in [`DataConnStmtCache::new`].
|
||||
//! - Add a crate-private helper method to `DataConnStmtCache` for running the statement.
|
||||
|
||||
use rusqlite::{params, Statement, ToSql};
|
||||
use rusqlite::{named_params, params, Statement, ToSql};
|
||||
use zcash_primitives::{
|
||||
block::BlockHash,
|
||||
consensus::{self, BlockHeight},
|
||||
|
@ -18,15 +18,18 @@ use zcash_primitives::{
|
|||
zip32::{AccountId, DiversifierIndex},
|
||||
};
|
||||
|
||||
use zcash_client_backend::address::UnifiedAddress;
|
||||
use zcash_client_backend::{
|
||||
address::UnifiedAddress,
|
||||
data_api::{PoolType, Recipient},
|
||||
encoding::AddressCodec,
|
||||
};
|
||||
|
||||
use crate::{error::SqliteClientError, wallet::PoolType, NoteId, WalletDb};
|
||||
use crate::{error::SqliteClientError, wallet::pool_code, NoteId, WalletDb};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use {
|
||||
crate::UtxoId,
|
||||
rusqlite::{named_params, OptionalExtension},
|
||||
zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput},
|
||||
crate::UtxoId, rusqlite::OptionalExtension,
|
||||
zcash_client_backend::wallet::WalletTransparentOutput,
|
||||
zcash_primitives::transaction::components::transparent::OutPoint,
|
||||
};
|
||||
|
||||
|
@ -60,8 +63,8 @@ pub struct DataConnStmtCache<'a, P> {
|
|||
stmt_update_received_note: Statement<'a>,
|
||||
stmt_select_received_note: Statement<'a>,
|
||||
|
||||
stmt_insert_sent_note: Statement<'a>,
|
||||
stmt_update_sent_note: Statement<'a>,
|
||||
stmt_insert_sent_output: Statement<'a>,
|
||||
stmt_update_sent_output: Statement<'a>,
|
||||
|
||||
stmt_insert_witness: Statement<'a>,
|
||||
stmt_prune_witnesses: Statement<'a>,
|
||||
|
@ -152,19 +155,24 @@ impl<'a, P> DataConnStmtCache<'a, P> {
|
|||
stmt_select_received_note: wallet_db.conn.prepare(
|
||||
"SELECT id_note FROM received_notes WHERE tx = ? AND output_index = ?"
|
||||
)?,
|
||||
stmt_update_sent_note: wallet_db.conn.prepare(
|
||||
stmt_update_sent_output: wallet_db.conn.prepare(
|
||||
"UPDATE sent_notes
|
||||
SET from_account = :account,
|
||||
address = :address,
|
||||
SET from_account = :from_account,
|
||||
to_address = :to_address,
|
||||
to_account = :to_account,
|
||||
value = :value,
|
||||
memo = IFNULL(:memo, memo)
|
||||
WHERE tx = :tx
|
||||
AND output_pool = :output_pool
|
||||
AND output_index = :output_index",
|
||||
)?,
|
||||
stmt_insert_sent_note: wallet_db.conn.prepare(
|
||||
"INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value, memo)
|
||||
VALUES (:tx, :output_pool, :output_index, :from_account, :address, :value, :memo)"
|
||||
stmt_insert_sent_output: wallet_db.conn.prepare(
|
||||
"INSERT INTO sent_notes (
|
||||
tx, output_pool, output_index, from_account,
|
||||
to_address, to_account, value, memo)
|
||||
VALUES (
|
||||
:tx, :output_pool, :output_index, :from_account,
|
||||
:to_address, :to_account, :value, :memo)"
|
||||
)?,
|
||||
stmt_insert_witness: wallet_db.conn.prepare(
|
||||
"INSERT INTO sapling_witnesses (note, block, witness)
|
||||
|
@ -346,6 +354,105 @@ impl<'a, P> DataConnStmtCache<'a, P> {
|
|||
}
|
||||
|
||||
impl<'a, P: consensus::Parameters> DataConnStmtCache<'a, P> {
|
||||
/// Inserts a sent note into the wallet database.
|
||||
///
|
||||
/// `output_index` is the index within the transaction that contains the recipient output:
|
||||
///
|
||||
/// - If `to` is a Unified address, this is an index into the outputs of the transaction
|
||||
/// within the bundle associated with the recipient's output pool.
|
||||
/// - If `to` is a Sapling address, this is an index into the Sapling outputs of the
|
||||
/// transaction.
|
||||
/// - If `to` is a transparent address, this is an index into the transparent outputs of
|
||||
/// the transaction.
|
||||
/// - If `to` is an internal account, this is an index into the Sapling outputs of the
|
||||
/// transaction.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn stmt_insert_sent_output(
|
||||
&mut self,
|
||||
tx_ref: i64,
|
||||
output_index: usize,
|
||||
from_account: AccountId,
|
||||
to: &Recipient,
|
||||
value: Amount,
|
||||
memo: Option<&MemoBytes>,
|
||||
) -> Result<(), SqliteClientError> {
|
||||
let (to_address, to_account, pool_type) = match to {
|
||||
Recipient::Transparent(addr) => (
|
||||
Some(addr.encode(&self.wallet_db.params)),
|
||||
None,
|
||||
PoolType::Transparent,
|
||||
),
|
||||
Recipient::Sapling(addr) => (
|
||||
Some(addr.encode(&self.wallet_db.params)),
|
||||
None,
|
||||
PoolType::Sapling,
|
||||
),
|
||||
Recipient::Unified(addr, pool) => {
|
||||
(Some(addr.encode(&self.wallet_db.params)), None, *pool)
|
||||
}
|
||||
Recipient::InternalAccount(id, pool) => (None, Some(u32::from(*id)), *pool),
|
||||
};
|
||||
|
||||
self.stmt_insert_sent_output.execute(named_params![
|
||||
":tx": &tx_ref,
|
||||
":output_pool": &pool_code(pool_type),
|
||||
":output_index": &i64::try_from(output_index).unwrap(),
|
||||
":from_account": &u32::from(from_account),
|
||||
":to_address": &to_address,
|
||||
":to_account": &to_account,
|
||||
":value": &i64::from(value),
|
||||
":memo": &memo.filter(|m| *m != &MemoBytes::empty()).map(|m| m.as_slice()),
|
||||
])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the data for the given sent note.
|
||||
///
|
||||
/// Returns `false` if the transaction doesn't exist in the wallet.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn stmt_update_sent_output(
|
||||
&mut self,
|
||||
from_account: AccountId,
|
||||
to: &Recipient,
|
||||
value: Amount,
|
||||
memo: Option<&MemoBytes>,
|
||||
tx_ref: i64,
|
||||
output_index: usize,
|
||||
) -> Result<bool, SqliteClientError> {
|
||||
let (to_address, to_account, pool_type) = match to {
|
||||
Recipient::Transparent(addr) => (
|
||||
Some(addr.encode(&self.wallet_db.params)),
|
||||
None,
|
||||
PoolType::Transparent,
|
||||
),
|
||||
Recipient::Sapling(addr) => (
|
||||
Some(addr.encode(&self.wallet_db.params)),
|
||||
None,
|
||||
PoolType::Sapling,
|
||||
),
|
||||
Recipient::Unified(addr, pool) => {
|
||||
(Some(addr.encode(&self.wallet_db.params)), None, *pool)
|
||||
}
|
||||
Recipient::InternalAccount(id, pool) => (None, Some(u32::from(*id)), *pool),
|
||||
};
|
||||
|
||||
match self.stmt_update_sent_output.execute(named_params![
|
||||
":from_account": &u32::from(from_account),
|
||||
":to_address": &to_address,
|
||||
":to_account": &to_account,
|
||||
":value": &i64::from(value),
|
||||
":memo": &memo.filter(|m| *m != &MemoBytes::empty()).map(|m| m.as_slice()),
|
||||
":tx": &tx_ref,
|
||||
":output_pool": &pool_code(pool_type),
|
||||
":output_index": &i64::try_from(output_index).unwrap(),
|
||||
])? {
|
||||
0 => Ok(false),
|
||||
1 => Ok(true),
|
||||
_ => unreachable!("tx_output constraint is marked as UNIQUE"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the given received UTXO to the datastore.
|
||||
///
|
||||
/// Returns the database row for the newly-inserted UTXO, or an error if the UTXO
|
||||
|
@ -528,78 +635,6 @@ impl<'a, P> DataConnStmtCache<'a, P> {
|
|||
.map_err(SqliteClientError::from)
|
||||
}
|
||||
|
||||
/// Inserts a sent note into the wallet database.
|
||||
///
|
||||
/// `output_index` is the index within the transaction that contains the recipient output:
|
||||
///
|
||||
/// - If `to` is a Sapling address, this is an index into the Sapling outputs of the
|
||||
/// transaction.
|
||||
/// - If `to` is a transparent address, this is an index into the transparent outputs of
|
||||
/// the transaction.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn stmt_insert_sent_note(
|
||||
&mut self,
|
||||
tx_ref: i64,
|
||||
pool_type: PoolType,
|
||||
output_index: usize,
|
||||
account: AccountId,
|
||||
to_str: &str,
|
||||
value: Amount,
|
||||
memo: Option<&MemoBytes>,
|
||||
) -> Result<(), SqliteClientError> {
|
||||
let sql_args: &[(&str, &dyn ToSql)] = &[
|
||||
(":tx", &tx_ref),
|
||||
(":output_pool", &pool_type.typecode()),
|
||||
(":output_index", &i64::try_from(output_index).unwrap()),
|
||||
(":from_account", &u32::from(account)),
|
||||
(":address", &to_str),
|
||||
(":value", &i64::from(value)),
|
||||
(
|
||||
":memo",
|
||||
&memo
|
||||
.filter(|m| *m != &MemoBytes::empty())
|
||||
.map(|m| m.as_slice()),
|
||||
),
|
||||
];
|
||||
self.stmt_insert_sent_note.execute(sql_args)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the data for the given sent note.
|
||||
///
|
||||
/// Returns `false` if the transaction doesn't exist in the wallet.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn stmt_update_sent_note(
|
||||
&mut self,
|
||||
account: AccountId,
|
||||
to_str: &str,
|
||||
value: Amount,
|
||||
memo: Option<&MemoBytes>,
|
||||
tx_ref: i64,
|
||||
pool_type: PoolType,
|
||||
output_index: usize,
|
||||
) -> Result<bool, SqliteClientError> {
|
||||
let sql_args: &[(&str, &dyn ToSql)] = &[
|
||||
(":account", &u32::from(account)),
|
||||
(":address", &to_str),
|
||||
(":value", &i64::from(value)),
|
||||
(
|
||||
":memo",
|
||||
&memo
|
||||
.filter(|m| *m != &MemoBytes::empty())
|
||||
.map(|m| m.as_slice()),
|
||||
),
|
||||
(":tx", &tx_ref),
|
||||
(":output_pool", &pool_type.typecode()),
|
||||
(":output_index", &i64::try_from(output_index).unwrap()),
|
||||
];
|
||||
match self.stmt_update_sent_note.execute(sql_args)? {
|
||||
0 => Ok(false),
|
||||
1 => Ok(true),
|
||||
_ => unreachable!("tx_output constraint is marked as UNIQUE"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Records the incremental witness for the specified note, as of the given block
|
||||
/// height.
|
||||
///
|
||||
|
|
|
@ -27,8 +27,7 @@ use zcash_primitives::{
|
|||
|
||||
use zcash_client_backend::{
|
||||
address::{RecipientAddress, UnifiedAddress},
|
||||
data_api::error::Error,
|
||||
encoding::{encode_payment_address_p, encode_transparent_address_p},
|
||||
data_api::{error::Error, PoolType, Recipient, SentTransactionOutput},
|
||||
keys::UnifiedFullViewingKey,
|
||||
wallet::{WalletShieldedOutput, WalletTx},
|
||||
DecryptedOutput,
|
||||
|
@ -36,8 +35,6 @@ use zcash_client_backend::{
|
|||
|
||||
use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb, PRUNING_HEIGHT};
|
||||
|
||||
use zcash_primitives::legacy::TransparentAddress;
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use {
|
||||
crate::UtxoId,
|
||||
|
@ -45,7 +42,7 @@ use {
|
|||
std::collections::HashSet,
|
||||
zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput},
|
||||
zcash_primitives::{
|
||||
legacy::{keys::IncomingViewingKey, Script},
|
||||
legacy::{keys::IncomingViewingKey, Script, TransparentAddress},
|
||||
transaction::components::{OutPoint, TxOut},
|
||||
},
|
||||
};
|
||||
|
@ -53,22 +50,15 @@ use {
|
|||
pub mod init;
|
||||
pub mod transact;
|
||||
|
||||
pub(crate) enum PoolType {
|
||||
Transparent,
|
||||
Sapling,
|
||||
}
|
||||
|
||||
impl PoolType {
|
||||
pub(crate) fn typecode(&self) -> i64 {
|
||||
pub(crate) fn pool_code(pool_type: PoolType) -> i64 {
|
||||
// These constants are *incidentally* shared with the typecodes
|
||||
// for unified addresses, but this is exclusively an internal
|
||||
// implementation detail.
|
||||
match self {
|
||||
match pool_type {
|
||||
PoolType::Transparent => 0i64,
|
||||
PoolType::Sapling => 2i64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait provides a generalization over shielded output representations.
|
||||
#[deprecated(note = "This trait will be removed in a future release.")]
|
||||
|
@ -1153,128 +1143,50 @@ pub fn update_expired_notes<P>(
|
|||
stmts.stmt_update_expired(height)
|
||||
}
|
||||
|
||||
/// Records information about a note that your wallet created.
|
||||
#[deprecated(
|
||||
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletWrite::store_decrypted_tx instead."
|
||||
)]
|
||||
#[allow(deprecated)]
|
||||
pub fn put_sent_note<'a, P: consensus::Parameters>(
|
||||
/// Records information about a transaction output that your wallet created.
|
||||
///
|
||||
/// This is a crate-internal convenience method.
|
||||
pub(crate) fn insert_sent_output<'a, P: consensus::Parameters>(
|
||||
stmts: &mut DataConnStmtCache<'a, P>,
|
||||
tx_ref: i64,
|
||||
output_index: usize,
|
||||
account: AccountId,
|
||||
to: &PaymentAddress,
|
||||
value: Amount,
|
||||
memo: Option<&MemoBytes>,
|
||||
from_account: AccountId,
|
||||
output: &SentTransactionOutput,
|
||||
) -> Result<(), SqliteClientError> {
|
||||
// Try updating an existing sent note.
|
||||
if !stmts.stmt_update_sent_note(
|
||||
account,
|
||||
&encode_payment_address_p(&stmts.wallet_db.params, to),
|
||||
value,
|
||||
memo,
|
||||
stmts.stmt_insert_sent_output(
|
||||
tx_ref,
|
||||
PoolType::Sapling,
|
||||
output_index,
|
||||
)? {
|
||||
// It isn't there, so insert.
|
||||
insert_sent_note(stmts, tx_ref, output_index, account, to, value, memo)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds information about a sent transparent UTXO to the database if it does not already
|
||||
/// exist, or updates it if a record for the UTXO already exists.
|
||||
///
|
||||
/// `output_index` is the index within transparent UTXOs of the transaction that contains the recipient output.
|
||||
#[deprecated(
|
||||
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletWrite::store_decrypted_tx instead."
|
||||
)]
|
||||
#[allow(deprecated)]
|
||||
pub fn put_sent_utxo<'a, P: consensus::Parameters>(
|
||||
stmts: &mut DataConnStmtCache<'a, P>,
|
||||
tx_ref: i64,
|
||||
output_index: usize,
|
||||
account: AccountId,
|
||||
to: &TransparentAddress,
|
||||
value: Amount,
|
||||
) -> Result<(), SqliteClientError> {
|
||||
// Try updating an existing sent UTXO.
|
||||
if !stmts.stmt_update_sent_note(
|
||||
account,
|
||||
&encode_transparent_address_p(&stmts.wallet_db.params, to),
|
||||
value,
|
||||
None,
|
||||
tx_ref,
|
||||
PoolType::Transparent,
|
||||
output_index,
|
||||
)? {
|
||||
// It isn't there, so insert.
|
||||
insert_sent_utxo(stmts, tx_ref, output_index, account, to, value)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Inserts a sent note into the wallet database.
|
||||
///
|
||||
/// `output_index` is the index within the transaction that contains the recipient output:
|
||||
///
|
||||
/// - If `to` is a Sapling address, this is an index into the Sapling outputs of the
|
||||
/// transaction.
|
||||
/// - If `to` is a transparent address, this is an index into the transparent outputs of
|
||||
/// the transaction.
|
||||
#[deprecated(
|
||||
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletWrite::store_sent_tx instead."
|
||||
)]
|
||||
pub fn insert_sent_note<'a, P: consensus::Parameters>(
|
||||
stmts: &mut DataConnStmtCache<'a, P>,
|
||||
tx_ref: i64,
|
||||
output_index: usize,
|
||||
account: AccountId,
|
||||
to: &PaymentAddress,
|
||||
value: Amount,
|
||||
memo: Option<&MemoBytes>,
|
||||
) -> Result<(), SqliteClientError> {
|
||||
let to_str = encode_payment_address_p(&stmts.wallet_db.params, to);
|
||||
|
||||
stmts.stmt_insert_sent_note(
|
||||
tx_ref,
|
||||
PoolType::Sapling,
|
||||
output_index,
|
||||
account,
|
||||
&to_str,
|
||||
value,
|
||||
memo,
|
||||
output.output_index,
|
||||
from_account,
|
||||
&output.recipient,
|
||||
output.value,
|
||||
output.memo.as_ref(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Inserts information about a sent transparent UTXO into the wallet database.
|
||||
/// Records information about a transaction output that your wallet created.
|
||||
///
|
||||
/// `output_index` is the index within transparent UTXOs of the transaction that contains the recipient output.
|
||||
#[deprecated(
|
||||
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletWrite::store_sent_tx instead."
|
||||
)]
|
||||
pub fn insert_sent_utxo<'a, P: consensus::Parameters>(
|
||||
/// This is a crate-internal convenience method.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn put_sent_output<'a, P: consensus::Parameters>(
|
||||
stmts: &mut DataConnStmtCache<'a, P>,
|
||||
from_account: AccountId,
|
||||
tx_ref: i64,
|
||||
output_index: usize,
|
||||
account: AccountId,
|
||||
to: &TransparentAddress,
|
||||
recipient: &Recipient,
|
||||
value: Amount,
|
||||
memo: Option<&MemoBytes>,
|
||||
) -> Result<(), SqliteClientError> {
|
||||
let to_str = encode_transparent_address_p(&stmts.wallet_db.params, to);
|
||||
|
||||
stmts.stmt_insert_sent_note(
|
||||
if !stmts.stmt_update_sent_output(from_account, recipient, value, memo, tx_ref, output_index)? {
|
||||
stmts.stmt_insert_sent_output(
|
||||
tx_ref,
|
||||
PoolType::Transparent,
|
||||
output_index,
|
||||
account,
|
||||
&to_str,
|
||||
from_account,
|
||||
recipient,
|
||||
value,
|
||||
None,
|
||||
)
|
||||
memo,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -122,8 +122,13 @@ fn init_wallet_db_internal<P: consensus::Parameters + 'static>(
|
|||
seed: Option<SecretVec<u8>>,
|
||||
target_migration: Option<Uuid>,
|
||||
) -> Result<(), MigratorError<WalletMigrationError>> {
|
||||
// Turn off foreign keys, and ensure that table replacement/modification
|
||||
// does not break views
|
||||
wdb.conn
|
||||
.execute("PRAGMA foreign_keys = OFF", [])
|
||||
.execute_batch(
|
||||
"PRAGMA foreign_keys = OFF;
|
||||
PRAGMA legacy_alter_table = TRUE;",
|
||||
)
|
||||
.map_err(|e| MigratorError::Adapter(WalletMigrationError::from(e)))?;
|
||||
let adapter = RusqliteAdapter::new(&mut wdb.conn, Some("schemer_migrations".to_string()));
|
||||
adapter.init().expect("Migrations table setup succeeds.");
|
||||
|
@ -311,7 +316,10 @@ mod tests {
|
|||
use super::{init_accounts_table, init_blocks_table, init_wallet_db};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use {crate::wallet::PoolType, zcash_primitives::legacy::keys as transparent};
|
||||
use {
|
||||
crate::wallet::{pool_code, PoolType},
|
||||
zcash_primitives::legacy::keys as transparent,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn verify_schema() {
|
||||
|
@ -375,12 +383,17 @@ mod tests {
|
|||
output_pool INTEGER NOT NULL ,
|
||||
output_index INTEGER NOT NULL,
|
||||
from_account INTEGER NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
to_address TEXT,
|
||||
to_account INTEGER,
|
||||
value INTEGER NOT NULL,
|
||||
memo BLOB,
|
||||
FOREIGN KEY (tx) REFERENCES transactions(id_tx),
|
||||
FOREIGN KEY (from_account) REFERENCES accounts(account),
|
||||
CONSTRAINT tx_output UNIQUE (tx, output_pool, output_index)
|
||||
FOREIGN KEY (to_account) REFERENCES accounts(account),
|
||||
CONSTRAINT tx_output UNIQUE (tx, output_pool, output_index),
|
||||
CONSTRAINT note_recipient CHECK (
|
||||
(to_address IS NOT NULL) != (to_account IS NOT NULL)
|
||||
)
|
||||
)",
|
||||
"CREATE TABLE transactions (
|
||||
id_tx INTEGER PRIMARY KEY,
|
||||
|
@ -943,7 +956,7 @@ mod tests {
|
|||
wdb.conn.execute(
|
||||
"INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value)
|
||||
VALUES (0, ?, 0, ?, ?, 0)",
|
||||
[PoolType::Transparent.typecode().to_sql()?, u32::from(account).to_sql()?, taddr.to_sql()?])?;
|
||||
[pool_code(PoolType::Transparent).to_sql()?, u32::from(account).to_sql()?, taddr.to_sql()?])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -2,6 +2,7 @@ mod add_transaction_views;
|
|||
mod add_utxo_account;
|
||||
mod addresses_table;
|
||||
mod initial_setup;
|
||||
mod sent_notes_to_internal;
|
||||
mod ufvk_support;
|
||||
mod utxos_table;
|
||||
|
||||
|
@ -31,5 +32,6 @@ pub(super) fn all_migrations<P: consensus::Parameters + 'static>(
|
|||
Box::new(add_utxo_account::Migration {
|
||||
_params: params.clone(),
|
||||
}),
|
||||
Box::new(sent_notes_to_internal::Migration {}),
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
//! A migration that adds an identifier for the account that received a sent note
|
||||
//! on an internal address to the sent_notes table.
|
||||
use std::collections::HashSet;
|
||||
|
||||
use rusqlite;
|
||||
use schemer;
|
||||
use schemer_rusqlite::RusqliteMigration;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::{addresses_table, utxos_table};
|
||||
use crate::wallet::init::WalletMigrationError;
|
||||
|
||||
/// This migration adds the `to_account` field to the `sent_notes` table.
|
||||
///
|
||||
/// 0ddbe561-8259-4212-9ab7-66fdc4a74e1d
|
||||
pub(super) const MIGRATION_ID: Uuid = Uuid::from_fields(
|
||||
0x0ddbe561,
|
||||
0x8259,
|
||||
0x4212,
|
||||
b"\x9a\xb7\x66\xfd\xc4\xa7\x4e\x1d",
|
||||
);
|
||||
|
||||
pub(super) struct Migration;
|
||||
|
||||
impl schemer::Migration for Migration {
|
||||
fn id(&self) -> Uuid {
|
||||
MIGRATION_ID
|
||||
}
|
||||
|
||||
fn dependencies(&self) -> HashSet<Uuid> {
|
||||
[utxos_table::MIGRATION_ID, addresses_table::MIGRATION_ID]
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Adds an identifier for the account that received an internal note to the sent_notes table"
|
||||
}
|
||||
}
|
||||
|
||||
impl RusqliteMigration for Migration {
|
||||
type Error = WalletMigrationError;
|
||||
|
||||
fn up(&self, transaction: &rusqlite::Transaction) -> Result<(), WalletMigrationError> {
|
||||
// Adds the `to_account` column to the `sent_notes` table and establishes the
|
||||
// foreign key relationship with the `account` table.
|
||||
transaction.execute_batch(
|
||||
"CREATE TABLE sent_notes_new (
|
||||
id_note INTEGER PRIMARY KEY,
|
||||
tx INTEGER NOT NULL,
|
||||
output_pool INTEGER NOT NULL ,
|
||||
output_index INTEGER NOT NULL,
|
||||
from_account INTEGER NOT NULL,
|
||||
to_address TEXT,
|
||||
to_account INTEGER,
|
||||
value INTEGER NOT NULL,
|
||||
memo BLOB,
|
||||
FOREIGN KEY (tx) REFERENCES transactions(id_tx),
|
||||
FOREIGN KEY (from_account) REFERENCES accounts(account),
|
||||
FOREIGN KEY (to_account) REFERENCES accounts(account),
|
||||
CONSTRAINT tx_output UNIQUE (tx, output_pool, output_index),
|
||||
CONSTRAINT note_recipient CHECK (
|
||||
(to_address IS NOT NULL) != (to_account IS NOT NULL)
|
||||
)
|
||||
);
|
||||
INSERT INTO sent_notes_new (
|
||||
id_note, tx, output_pool, output_index,
|
||||
from_account, to_address,
|
||||
value, memo)
|
||||
SELECT
|
||||
id_note, tx, output_pool, output_index,
|
||||
from_account, address,
|
||||
value, memo
|
||||
FROM sent_notes;",
|
||||
)?;
|
||||
|
||||
transaction.execute_batch(
|
||||
"DROP TABLE sent_notes;
|
||||
ALTER TABLE sent_notes_new RENAME TO sent_notes;",
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn down(&self, _transaction: &rusqlite::Transaction) -> Result<(), WalletMigrationError> {
|
||||
// TODO: something better than just panic?
|
||||
panic!("Cannot revert this migration.");
|
||||
}
|
||||
}
|
|
@ -7,7 +7,9 @@ use schemer_rusqlite::RusqliteMigration;
|
|||
use secrecy::{ExposeSecret, SecretVec};
|
||||
use uuid::Uuid;
|
||||
|
||||
use zcash_client_backend::{address::RecipientAddress, keys::UnifiedSpendingKey};
|
||||
use zcash_client_backend::{
|
||||
address::RecipientAddress, data_api::PoolType, keys::UnifiedSpendingKey,
|
||||
};
|
||||
use zcash_primitives::{consensus, zip32::AccountId};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
|
@ -18,7 +20,7 @@ use zcash_client_backend::encoding::AddressCodec;
|
|||
|
||||
use crate::wallet::{
|
||||
init::{migrations::utxos_table, WalletMigrationError},
|
||||
PoolType,
|
||||
pool_code,
|
||||
};
|
||||
|
||||
pub(super) const MIGRATION_ID: Uuid = Uuid::from_fields(
|
||||
|
@ -227,8 +229,8 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
|||
))
|
||||
})?;
|
||||
let output_pool = match decoded_address {
|
||||
RecipientAddress::Shielded(_) => Ok(PoolType::Sapling.typecode()),
|
||||
RecipientAddress::Transparent(_) => Ok(PoolType::Transparent.typecode()),
|
||||
RecipientAddress::Shielded(_) => Ok(pool_code(PoolType::Sapling)),
|
||||
RecipientAddress::Transparent(_) => Ok(pool_code(PoolType::Transparent)),
|
||||
RecipientAddress::Unified(_) => Err(WalletMigrationError::CorruptedData(
|
||||
"Unified addresses should not yet appear in the sent_notes table."
|
||||
.to_string(),
|
||||
|
|
Loading…
Reference in New Issue