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`:
|
- `zcash_client_backend::address`:
|
||||||
- `RecipientAddress::Unified`
|
- `RecipientAddress::Unified`
|
||||||
- `zcash_client_backend::data_api`:
|
- `zcash_client_backend::data_api`:
|
||||||
|
- `PoolType`
|
||||||
|
- `Recipient`
|
||||||
|
- `SentTransactionOutput`
|
||||||
- `WalletRead::get_unified_full_viewing_keys`
|
- `WalletRead::get_unified_full_viewing_keys`
|
||||||
- `WalletRead::get_current_address`
|
- `WalletRead::get_current_address`
|
||||||
- `WalletRead::get_all_nullifiers`
|
- `WalletRead::get_all_nullifiers`
|
||||||
- `WalletWrite::create_account`
|
- `WalletWrite::create_account`
|
||||||
- `WalletWrite::remove_unmined_tx` (behind the `unstable` feature flag).
|
- `WalletWrite::remove_unmined_tx` (behind the `unstable` feature flag).
|
||||||
- `WalletWrite::get_next_available_address`
|
- `WalletWrite::get_next_available_address`
|
||||||
|
- `zcash_client_backend::decrypt`:
|
||||||
|
- `TransferType`
|
||||||
- `zcash_client_backend::proto`:
|
- `zcash_client_backend::proto`:
|
||||||
- `actions` field on `compact_formats::CompactTx`
|
- `actions` field on `compact_formats::CompactTx`
|
||||||
- `compact_formats::CompactOrchardAction`
|
- `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::new` for constructing a request from `Vec<Payment>`.
|
||||||
- `TransactionRequest::payments` for accessing the `Payments` that make up a
|
- `TransactionRequest::payments` for accessing the `Payments` that make up a
|
||||||
request.
|
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
|
- New experimental APIs that should be considered unstable, and are
|
||||||
likely to be modified and/or moved to a different module in a future
|
likely to be modified and/or moved to a different module in a future
|
||||||
release:
|
release:
|
||||||
|
|
|
@ -11,15 +11,16 @@ use secrecy::SecretVec;
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
consensus::BlockHeight,
|
consensus::BlockHeight,
|
||||||
|
legacy::TransparentAddress,
|
||||||
memo::{Memo, MemoBytes},
|
memo::{Memo, MemoBytes},
|
||||||
merkle_tree::{CommitmentTree, IncrementalWitness},
|
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||||
sapling::{Node, Nullifier},
|
sapling::{Node, Nullifier, PaymentAddress},
|
||||||
transaction::{components::Amount, Transaction, TxId},
|
transaction::{components::Amount, Transaction, TxId},
|
||||||
zip32::{AccountId, ExtendedFullViewingKey},
|
zip32::{AccountId, ExtendedFullViewingKey},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
address::{RecipientAddress, UnifiedAddress},
|
address::UnifiedAddress,
|
||||||
decrypt::DecryptedOutput,
|
decrypt::DecryptedOutput,
|
||||||
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
|
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
|
||||||
proto::compact_formats::CompactBlock,
|
proto::compact_formats::CompactBlock,
|
||||||
|
@ -27,10 +28,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use {
|
use {crate::wallet::WalletTransparentOutput, zcash_primitives::transaction::components::OutPoint};
|
||||||
crate::wallet::WalletTransparentOutput,
|
|
||||||
zcash_primitives::{legacy::TransparentAddress, transaction::components::OutPoint},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod chain;
|
pub mod chain;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
@ -248,13 +246,34 @@ pub struct SentTransaction<'a> {
|
||||||
pub tx: &'a Transaction,
|
pub tx: &'a Transaction,
|
||||||
pub created: time::OffsetDateTime,
|
pub created: time::OffsetDateTime,
|
||||||
pub account: AccountId,
|
pub account: AccountId,
|
||||||
pub outputs: Vec<SentTransactionOutput<'a>>,
|
pub outputs: Vec<SentTransactionOutput>,
|
||||||
pub fee_amount: Amount,
|
pub fee_amount: Amount,
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
pub utxos_spent: Vec<OutPoint>,
|
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.
|
/// The index within the transaction that contains the recipient output.
|
||||||
///
|
///
|
||||||
/// - If `recipient_address` is a Sapling address, this is an index into the Sapling
|
/// - 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
|
/// - If `recipient_address` is a transparent address, this is an index into the
|
||||||
/// transparent outputs of the transaction.
|
/// transparent outputs of the transaction.
|
||||||
pub output_index: usize,
|
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,
|
pub value: Amount,
|
||||||
|
/// The memo that was attached to the output, if any
|
||||||
pub memo: Option<MemoBytes>,
|
pub memo: Option<MemoBytes>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,8 @@ use {
|
||||||
use crate::{
|
use crate::{
|
||||||
address::RecipientAddress,
|
address::RecipientAddress,
|
||||||
data_api::{
|
data_api::{
|
||||||
error::Error, DecryptedTransaction, SentTransaction, SentTransactionOutput, WalletWrite,
|
error::Error, DecryptedTransaction, PoolType, Recipient, SentTransaction,
|
||||||
|
SentTransactionOutput, WalletWrite,
|
||||||
},
|
},
|
||||||
decrypt_transaction,
|
decrypt_transaction,
|
||||||
wallet::OvkPolicy,
|
wallet::OvkPolicy,
|
||||||
|
@ -373,14 +374,21 @@ where
|
||||||
let (tx, tx_metadata) = builder.build(&prover).map_err(Error::Builder)?;
|
let (tx, tx_metadata) = builder.build(&prover).map_err(Error::Builder)?;
|
||||||
|
|
||||||
let sent_outputs = request.payments().iter().enumerate().map(|(i, payment)| {
|
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.
|
// 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(addr) => {
|
||||||
RecipientAddress::Shielded(_) | RecipientAddress::Unified(_) =>
|
let idx = tx_metadata.output_index(i).expect("An output should exist in the transaction for each shielded payment.");
|
||||||
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) => {
|
RecipientAddress::Transparent(addr) => {
|
||||||
let script = addr.script();
|
let script = addr.script();
|
||||||
tx.transparent_bundle()
|
let idx = tx.transparent_bundle()
|
||||||
.and_then(|b| {
|
.and_then(|b| {
|
||||||
b.vout
|
b.vout
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -388,13 +396,15 @@ where
|
||||||
.find(|(_, tx_out)| tx_out.script_pubkey == script)
|
.find(|(_, tx_out)| tx_out.script_pubkey == script)
|
||||||
})
|
})
|
||||||
.map(|(index, _)| index)
|
.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 {
|
SentTransactionOutput {
|
||||||
output_index: idx,
|
output_index,
|
||||||
recipient_address: &payment.recipient_address,
|
recipient,
|
||||||
value: payment.amount,
|
value: payment.amount,
|
||||||
memo: payment.memo.clone()
|
memo: payment.memo.clone()
|
||||||
}
|
}
|
||||||
|
@ -512,12 +522,7 @@ where
|
||||||
|
|
||||||
// add the sapling output to shield the funds
|
// add the sapling output to shield the funds
|
||||||
builder
|
builder
|
||||||
.add_sapling_output(
|
.add_sapling_output(Some(ovk), shielding_address, amount_to_shield, memo.clone())
|
||||||
Some(ovk),
|
|
||||||
shielding_address.clone(),
|
|
||||||
amount_to_shield,
|
|
||||||
memo.clone(),
|
|
||||||
)
|
|
||||||
.map_err(Error::Builder)?;
|
.map_err(Error::Builder)?;
|
||||||
|
|
||||||
let (tx, tx_metadata) = builder.build(&prover).map_err(Error::Builder)?;
|
let (tx, tx_metadata) = builder.build(&prover).map_err(Error::Builder)?;
|
||||||
|
@ -531,8 +536,8 @@ where
|
||||||
account,
|
account,
|
||||||
outputs: vec![SentTransactionOutput {
|
outputs: vec![SentTransactionOutput {
|
||||||
output_index,
|
output_index,
|
||||||
recipient_address: &RecipientAddress::Shielded(shielding_address),
|
|
||||||
value: amount_to_shield,
|
value: amount_to_shield,
|
||||||
|
recipient: Recipient::InternalAccount(account, PoolType::Sapling),
|
||||||
memo: Some(memo.clone()),
|
memo: Some(memo.clone()),
|
||||||
}],
|
}],
|
||||||
fee_amount: fee,
|
fee_amount: fee,
|
||||||
|
|
|
@ -10,11 +10,25 @@ use zcash_primitives::{
|
||||||
Note, PaymentAddress,
|
Note, PaymentAddress,
|
||||||
},
|
},
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
zip32::AccountId,
|
zip32::{AccountId, Scope},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::keys::UnifiedFullViewingKey;
|
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.
|
/// A decrypted shielded output.
|
||||||
pub struct DecryptedOutput {
|
pub struct DecryptedOutput {
|
||||||
/// The index of the output within [`shielded_outputs`].
|
/// The index of the output within [`shielded_outputs`].
|
||||||
|
@ -33,7 +47,7 @@ pub struct DecryptedOutput {
|
||||||
/// this is a logical output of the transaction.
|
/// this is a logical output of the transaction.
|
||||||
///
|
///
|
||||||
/// [`OutgoingViewingKey`]: zcash_primitives::keys::OutgoingViewingKey
|
/// [`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
|
/// 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,
|
tx: &Transaction,
|
||||||
ufvks: &HashMap<AccountId, UnifiedFullViewingKey>,
|
ufvks: &HashMap<AccountId, UnifiedFullViewingKey>,
|
||||||
) -> Vec<DecryptedOutput> {
|
) -> Vec<DecryptedOutput> {
|
||||||
let mut decrypted = vec![];
|
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;
|
||||||
|
|
||||||
if let Some(bundle) = tx.sapling_bundle() {
|
bundle
|
||||||
for (account, ufvk) in ufvks.iter() {
|
.shielded_outputs
|
||||||
if let Some(dfvk) = ufvk.sapling() {
|
.iter()
|
||||||
let ivk = PreparedIncomingViewingKey::new(&dfvk.fvk().vk.ivk());
|
.enumerate()
|
||||||
let ovk = dfvk.fvk().ovk;
|
.flat_map(move |(index, output)| {
|
||||||
|
try_sapling_note_decryption(params, height, &ivk_external, output)
|
||||||
for (index, output) in bundle.shielded_outputs.iter().enumerate() {
|
.map(|ret| (ret, TransferType::Incoming))
|
||||||
let ((note, to, memo), outgoing) =
|
.or_else(|| {
|
||||||
match try_sapling_note_decryption(params, height, &ivk, output) {
|
try_sapling_note_decryption(
|
||||||
Some(ret) => (ret, false),
|
params,
|
||||||
None => match try_sapling_output_recovery(params, height, &ovk, output)
|
height,
|
||||||
{
|
&ivk_internal,
|
||||||
Some(ret) => (ret, true),
|
output,
|
||||||
None => continue,
|
)
|
||||||
},
|
.map(|ret| (ret, TransferType::WalletInternal))
|
||||||
};
|
})
|
||||||
|
.or_else(|| {
|
||||||
decrypted.push(DecryptedOutput {
|
try_sapling_output_recovery(params, height, &ovk, output)
|
||||||
index,
|
.map(|ret| (ret, TransferType::Outgoing))
|
||||||
note,
|
})
|
||||||
account: *account,
|
.into_iter()
|
||||||
to,
|
.map(move |((note, to, memo), transfer_type)| DecryptedOutput {
|
||||||
memo,
|
index,
|
||||||
outgoing,
|
note,
|
||||||
})
|
account,
|
||||||
}
|
to,
|
||||||
}
|
memo,
|
||||||
}
|
transfer_type,
|
||||||
}
|
})
|
||||||
|
})
|
||||||
decrypted
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,16 @@
|
||||||
//!
|
//!
|
||||||
//! [constants]: zcash_primitives::constants
|
//! [constants]: zcash_primitives::constants
|
||||||
|
|
||||||
|
use crate::address::UnifiedAddress;
|
||||||
use bech32::{self, Error, FromBase32, ToBase32, Variant};
|
use bech32::{self, Error, FromBase32, ToBase32, Variant};
|
||||||
use bs58::{self, decode::Error as Bs58Error};
|
use bs58::{self, decode::Error as Bs58Error};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
use zcash_address::unified::{self, Encoding};
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
consensus,
|
consensus,
|
||||||
legacy::TransparentAddress,
|
legacy::TransparentAddress,
|
||||||
sapling::PaymentAddress,
|
sapling,
|
||||||
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
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.
|
/// Writes an [`ExtendedSpendingKey`] as a Bech32-encoded string.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
@ -232,16 +269,18 @@ pub fn decode_extended_full_viewing_key(
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
/// [`PaymentAddress`]: zcash_primitives::sapling::PaymentAddress
|
/// [`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()))
|
bech32_encode(hrp, |w| w.write_all(&addr.to_bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes a [`PaymentAddress`] as a Bech32-encoded string
|
/// Writes a [`PaymentAddress`] as a Bech32-encoded string
|
||||||
/// using the human-readable prefix values defined in the specified
|
/// using the human-readable prefix values defined in the specified
|
||||||
/// network parameters.
|
/// network parameters.
|
||||||
|
///
|
||||||
|
/// [`PaymentAddress`]: zcash_primitives::sapling::PaymentAddress
|
||||||
pub fn encode_payment_address_p<P: consensus::Parameters>(
|
pub fn encode_payment_address_p<P: consensus::Parameters>(
|
||||||
params: &P,
|
params: &P,
|
||||||
addr: &PaymentAddress,
|
addr: &sapling::PaymentAddress,
|
||||||
) -> String {
|
) -> String {
|
||||||
encode_payment_address(params.hrp_sapling_payment_address(), addr)
|
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,
|
/// encoding::decode_payment_address,
|
||||||
/// };
|
/// };
|
||||||
/// use zcash_primitives::{
|
/// use zcash_primitives::{
|
||||||
/// constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS,
|
/// consensus::{TEST_NETWORK, Parameters},
|
||||||
/// sapling::{Diversifier, PaymentAddress},
|
/// sapling::{Diversifier, PaymentAddress},
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
|
@ -276,14 +315,17 @@ pub fn encode_payment_address_p<P: consensus::Parameters>(
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// decode_payment_address(
|
/// decode_payment_address(
|
||||||
/// HRP_SAPLING_PAYMENT_ADDRESS,
|
/// TEST_NETWORK.hrp_sapling_payment_address(),
|
||||||
/// "ztestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ss7jnk",
|
/// "ztestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ss7jnk",
|
||||||
/// ),
|
/// ),
|
||||||
/// Ok(pa),
|
/// Ok(pa),
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
/// [`PaymentAddress`]: zcash_primitives::sapling::PaymentAddress
|
/// [`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| {
|
bech32_decode(hrp, s, |data| {
|
||||||
if data.len() != 43 {
|
if data.len() != 43 {
|
||||||
return None;
|
return None;
|
||||||
|
@ -291,7 +333,7 @@ pub fn decode_payment_address(hrp: &str, s: &str) -> Result<PaymentAddress, Bech
|
||||||
|
|
||||||
let mut bytes = [0; 43];
|
let mut bytes = [0; 43];
|
||||||
bytes.copy_from_slice(&data);
|
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,
|
/// encoding::encode_transparent_address,
|
||||||
/// };
|
/// };
|
||||||
/// use zcash_primitives::{
|
/// use zcash_primitives::{
|
||||||
/// constants::testnet::{B58_PUBKEY_ADDRESS_PREFIX, B58_SCRIPT_ADDRESS_PREFIX},
|
/// consensus::{TEST_NETWORK, Parameters},
|
||||||
/// legacy::TransparentAddress,
|
/// legacy::TransparentAddress,
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// encode_transparent_address(
|
/// encode_transparent_address(
|
||||||
/// &B58_PUBKEY_ADDRESS_PREFIX,
|
/// &TEST_NETWORK.b58_pubkey_address_prefix(),
|
||||||
/// &B58_SCRIPT_ADDRESS_PREFIX,
|
/// &TEST_NETWORK.b58_script_address_prefix(),
|
||||||
/// &TransparentAddress::PublicKey([0; 20]),
|
/// &TransparentAddress::PublicKey([0; 20]),
|
||||||
/// ),
|
/// ),
|
||||||
/// "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma",
|
/// "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma",
|
||||||
|
@ -319,8 +361,8 @@ pub fn decode_payment_address(hrp: &str, s: &str) -> Result<PaymentAddress, Bech
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// encode_transparent_address(
|
/// encode_transparent_address(
|
||||||
/// &B58_PUBKEY_ADDRESS_PREFIX,
|
/// &TEST_NETWORK.b58_pubkey_address_prefix(),
|
||||||
/// &B58_SCRIPT_ADDRESS_PREFIX,
|
/// &TEST_NETWORK.b58_script_address_prefix(),
|
||||||
/// &TransparentAddress::Script([0; 20]),
|
/// &TransparentAddress::Script([0; 20]),
|
||||||
/// ),
|
/// ),
|
||||||
/// "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2",
|
/// "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2",
|
||||||
|
@ -369,7 +411,7 @@ pub fn encode_transparent_address_p<P: consensus::Parameters>(
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use zcash_primitives::{
|
/// use zcash_primitives::{
|
||||||
/// constants::testnet::{B58_PUBKEY_ADDRESS_PREFIX, B58_SCRIPT_ADDRESS_PREFIX},
|
/// consensus::{TEST_NETWORK, Parameters},
|
||||||
/// };
|
/// };
|
||||||
/// use zcash_client_backend::{
|
/// use zcash_client_backend::{
|
||||||
/// encoding::decode_transparent_address,
|
/// encoding::decode_transparent_address,
|
||||||
|
@ -378,8 +420,8 @@ pub fn encode_transparent_address_p<P: consensus::Parameters>(
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// decode_transparent_address(
|
/// decode_transparent_address(
|
||||||
/// &B58_PUBKEY_ADDRESS_PREFIX,
|
/// &TEST_NETWORK.b58_pubkey_address_prefix(),
|
||||||
/// &B58_SCRIPT_ADDRESS_PREFIX,
|
/// &TEST_NETWORK.b58_script_address_prefix(),
|
||||||
/// "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma",
|
/// "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma",
|
||||||
/// ),
|
/// ),
|
||||||
/// Ok(Some(TransparentAddress::PublicKey([0; 20]))),
|
/// Ok(Some(TransparentAddress::PublicKey([0; 20]))),
|
||||||
|
@ -387,8 +429,8 @@ pub fn encode_transparent_address_p<P: consensus::Parameters>(
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// decode_transparent_address(
|
/// decode_transparent_address(
|
||||||
/// &B58_PUBKEY_ADDRESS_PREFIX,
|
/// &TEST_NETWORK.b58_pubkey_address_prefix(),
|
||||||
/// &B58_SCRIPT_ADDRESS_PREFIX,
|
/// &TEST_NETWORK.b58_script_address_prefix(),
|
||||||
/// "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2",
|
/// "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2",
|
||||||
/// ),
|
/// ),
|
||||||
/// Ok(Some(TransparentAddress::Script([0; 20]))),
|
/// Ok(Some(TransparentAddress::Script([0; 20]))),
|
||||||
|
|
|
@ -19,4 +19,4 @@ pub mod wallet;
|
||||||
pub mod welding_rig;
|
pub mod welding_rig;
|
||||||
pub mod zip321;
|
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`:
|
- `zcash_client_sqlite::wallet`:
|
||||||
- `get_spendable_notes` to `get_spendable_sapling_notes`.
|
- `get_spendable_notes` to `get_spendable_sapling_notes`.
|
||||||
- `select_spendable_notes` to `select_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
|
- `zcash_client_sqlite::wallet::init_wallet_db` has been modified to
|
||||||
take the wallet seed as an argument so that it can correctly perform
|
take the wallet seed as an argument so that it can correctly perform
|
||||||
migrations that require re-deriving key material. In particular for
|
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.
|
migration process.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- `zcash_client_sqlite::wallet`:
|
- The following functions have been removed from the public interface of
|
||||||
- `get_extended_full_viewing_keys` (use
|
`zcash_client_sqlite::wallet`. Prefer methods defined on
|
||||||
`zcash_client_backend::data_api::WalletRead::get_unified_full_viewing_keys`
|
`zcash_client_backend::data_api::{WalletRead, WalletWrite}` instead.
|
||||||
instead).
|
- `get_extended_full_viewing_keys` (use `WalletRead::get_unified_full_viewing_keys` instead).
|
||||||
- `delete_utxos_above` (use
|
- `insert_sent_note` (use `WalletWrite::store_sent_tx` instead)
|
||||||
`zcash_client_backend::data_api::WalletWrite::rewind_to_height` 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_sqlite::with_blocks` (use
|
||||||
`zcash_client_backend::data_api::BlockSource::with_blocks` instead)
|
`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.
|
`zcash_client_backend::data_api` traits mentioned above instead.
|
||||||
- Deprecated in `zcash_client_sqlite::wallet`:
|
- Deprecated in `zcash_client_sqlite::wallet`:
|
||||||
- `get_address`
|
- `get_address`
|
||||||
- `get_extended_full_viewing_keys`
|
|
||||||
- `is_valid_account_extfvk`
|
- `is_valid_account_extfvk`
|
||||||
- `get_balance`
|
- `get_balance`
|
||||||
- `get_balance_at`
|
- `get_balance_at`
|
||||||
|
@ -131,10 +128,6 @@ and this library adheres to Rust's notion of
|
||||||
- `insert_witness`
|
- `insert_witness`
|
||||||
- `prune_witnesses`
|
- `prune_witnesses`
|
||||||
- `update_expired_notes`
|
- `update_expired_notes`
|
||||||
- `put_sent_note`
|
|
||||||
- `put_sent_utxo`
|
|
||||||
- `insert_sent_note`
|
|
||||||
- `insert_sent_utxo`
|
|
||||||
- `get_address`
|
- `get_address`
|
||||||
- Deprecated in `zcash_client_sqlite::wallet::transact`:
|
- Deprecated in `zcash_client_sqlite::wallet::transact`:
|
||||||
- `get_spendable_sapling_notes`
|
- `get_spendable_sapling_notes`
|
||||||
|
|
|
@ -53,13 +53,15 @@ use zcash_primitives::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
address::{RecipientAddress, UnifiedAddress},
|
address::UnifiedAddress,
|
||||||
data_api::{
|
data_api::{
|
||||||
BlockSource, DecryptedTransaction, PrunedBlock, SentTransaction, WalletRead, WalletWrite,
|
BlockSource, DecryptedTransaction, PoolType, PrunedBlock, Recipient, SentTransaction,
|
||||||
|
WalletRead, WalletWrite,
|
||||||
},
|
},
|
||||||
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
|
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
|
||||||
proto::compact_formats::CompactBlock,
|
proto::compact_formats::CompactBlock,
|
||||||
wallet::SpendableNote,
|
wallet::SpendableNote,
|
||||||
|
TransferType,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::error::SqliteClientError;
|
use crate::error::SqliteClientError;
|
||||||
|
@ -529,29 +531,38 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
|
||||||
|
|
||||||
let mut spending_account_id: Option<AccountId> = None;
|
let mut spending_account_id: Option<AccountId> = None;
|
||||||
for output in d_tx.sapling_outputs {
|
for output in d_tx.sapling_outputs {
|
||||||
if output.outgoing {
|
match output.transfer_type {
|
||||||
wallet::put_sent_note(
|
TransferType::Outgoing | TransferType::WalletInternal => {
|
||||||
up,
|
let recipient = if output.transfer_type == TransferType::Outgoing {
|
||||||
tx_ref,
|
Recipient::Sapling(output.to.clone())
|
||||||
output.index,
|
} else {
|
||||||
output.account,
|
Recipient::InternalAccount(output.account, PoolType::Sapling)
|
||||||
&output.to,
|
};
|
||||||
Amount::from_u64(output.note.value)
|
|
||||||
.map_err(|_| SqliteClientError::CorruptedData("Note value invalid.".to_string()))?,
|
|
||||||
Some(&output.memo),
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
match spending_account_id {
|
|
||||||
Some(id) =>
|
|
||||||
if id != output.account {
|
|
||||||
panic!("Unable to determine a unique account identifier for z->t spend.");
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
spending_account_id = Some(output.account);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wallet::put_received_note(up, output, tx_ref)?;
|
wallet::put_sent_output(
|
||||||
|
up,
|
||||||
|
output.account,
|
||||||
|
tx_ref,
|
||||||
|
output.index,
|
||||||
|
&recipient,
|
||||||
|
Amount::from_u64(output.note.value).map_err(|_|
|
||||||
|
SqliteClientError::CorruptedData("Note value is not a valid Zcash amount.".to_string()))?,
|
||||||
|
Some(&output.memo),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
TransferType::Incoming => {
|
||||||
|
match spending_account_id {
|
||||||
|
Some(id) =>
|
||||||
|
if id != output.account {
|
||||||
|
panic!("Unable to determine a unique account identifier for z->t spend.");
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
spending_account_id = Some(output.account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wallet::put_received_note(up, output, tx_ref)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,13 +577,15 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
|
||||||
.any(|input| *nf == input.nullifier)
|
.any(|input| *nf == input.nullifier)
|
||||||
) {
|
) {
|
||||||
for (output_index, txout) in d_tx.tx.transparent_bundle().iter().flat_map(|b| b.vout.iter()).enumerate() {
|
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,
|
up,
|
||||||
|
*account_id,
|
||||||
tx_ref,
|
tx_ref,
|
||||||
output_index,
|
output_index,
|
||||||
*account_id,
|
&recipient,
|
||||||
&txout.script_pubkey.address().unwrap(),
|
|
||||||
txout.value,
|
txout.value,
|
||||||
|
None
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -611,36 +624,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for output in &sent_tx.outputs {
|
for output in &sent_tx.outputs {
|
||||||
match output.recipient_address {
|
wallet::insert_sent_output(up, tx_ref, sent_tx.account, output)?;
|
||||||
// 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,
|
|
||||||
)?,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the row number of the transaction, so the caller can fetch it for sending.
|
// 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`].
|
//! - Build the statement in [`DataConnStmtCache::new`].
|
||||||
//! - Add a crate-private helper method to `DataConnStmtCache` for running the statement.
|
//! - 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::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
consensus::{self, BlockHeight},
|
consensus::{self, BlockHeight},
|
||||||
|
@ -18,15 +18,18 @@ use zcash_primitives::{
|
||||||
zip32::{AccountId, DiversifierIndex},
|
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")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use {
|
use {
|
||||||
crate::UtxoId,
|
crate::UtxoId, rusqlite::OptionalExtension,
|
||||||
rusqlite::{named_params, OptionalExtension},
|
zcash_client_backend::wallet::WalletTransparentOutput,
|
||||||
zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput},
|
|
||||||
zcash_primitives::transaction::components::transparent::OutPoint,
|
zcash_primitives::transaction::components::transparent::OutPoint,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -60,8 +63,8 @@ pub struct DataConnStmtCache<'a, P> {
|
||||||
stmt_update_received_note: Statement<'a>,
|
stmt_update_received_note: Statement<'a>,
|
||||||
stmt_select_received_note: Statement<'a>,
|
stmt_select_received_note: Statement<'a>,
|
||||||
|
|
||||||
stmt_insert_sent_note: Statement<'a>,
|
stmt_insert_sent_output: Statement<'a>,
|
||||||
stmt_update_sent_note: Statement<'a>,
|
stmt_update_sent_output: Statement<'a>,
|
||||||
|
|
||||||
stmt_insert_witness: Statement<'a>,
|
stmt_insert_witness: Statement<'a>,
|
||||||
stmt_prune_witnesses: 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(
|
stmt_select_received_note: wallet_db.conn.prepare(
|
||||||
"SELECT id_note FROM received_notes WHERE tx = ? AND output_index = ?"
|
"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
|
"UPDATE sent_notes
|
||||||
SET from_account = :account,
|
SET from_account = :from_account,
|
||||||
address = :address,
|
to_address = :to_address,
|
||||||
|
to_account = :to_account,
|
||||||
value = :value,
|
value = :value,
|
||||||
memo = IFNULL(:memo, memo)
|
memo = IFNULL(:memo, memo)
|
||||||
WHERE tx = :tx
|
WHERE tx = :tx
|
||||||
AND output_pool = :output_pool
|
AND output_pool = :output_pool
|
||||||
AND output_index = :output_index",
|
AND output_index = :output_index",
|
||||||
)?,
|
)?,
|
||||||
stmt_insert_sent_note: wallet_db.conn.prepare(
|
stmt_insert_sent_output: wallet_db.conn.prepare(
|
||||||
"INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value, memo)
|
"INSERT INTO sent_notes (
|
||||||
VALUES (:tx, :output_pool, :output_index, :from_account, :address, :value, :memo)"
|
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(
|
stmt_insert_witness: wallet_db.conn.prepare(
|
||||||
"INSERT INTO sapling_witnesses (note, block, witness)
|
"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> {
|
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.
|
/// Adds the given received UTXO to the datastore.
|
||||||
///
|
///
|
||||||
/// Returns the database row for the newly-inserted UTXO, or an error if the UTXO
|
/// 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)
|
.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
|
/// Records the incremental witness for the specified note, as of the given block
|
||||||
/// height.
|
/// height.
|
||||||
///
|
///
|
||||||
|
|
|
@ -27,8 +27,7 @@ use zcash_primitives::{
|
||||||
|
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
address::{RecipientAddress, UnifiedAddress},
|
address::{RecipientAddress, UnifiedAddress},
|
||||||
data_api::error::Error,
|
data_api::{error::Error, PoolType, Recipient, SentTransactionOutput},
|
||||||
encoding::{encode_payment_address_p, encode_transparent_address_p},
|
|
||||||
keys::UnifiedFullViewingKey,
|
keys::UnifiedFullViewingKey,
|
||||||
wallet::{WalletShieldedOutput, WalletTx},
|
wallet::{WalletShieldedOutput, WalletTx},
|
||||||
DecryptedOutput,
|
DecryptedOutput,
|
||||||
|
@ -36,8 +35,6 @@ use zcash_client_backend::{
|
||||||
|
|
||||||
use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb, PRUNING_HEIGHT};
|
use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDb, PRUNING_HEIGHT};
|
||||||
|
|
||||||
use zcash_primitives::legacy::TransparentAddress;
|
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use {
|
use {
|
||||||
crate::UtxoId,
|
crate::UtxoId,
|
||||||
|
@ -45,7 +42,7 @@ use {
|
||||||
std::collections::HashSet,
|
std::collections::HashSet,
|
||||||
zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput},
|
zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput},
|
||||||
zcash_primitives::{
|
zcash_primitives::{
|
||||||
legacy::{keys::IncomingViewingKey, Script},
|
legacy::{keys::IncomingViewingKey, Script, TransparentAddress},
|
||||||
transaction::components::{OutPoint, TxOut},
|
transaction::components::{OutPoint, TxOut},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -53,20 +50,13 @@ use {
|
||||||
pub mod init;
|
pub mod init;
|
||||||
pub mod transact;
|
pub mod transact;
|
||||||
|
|
||||||
pub(crate) enum PoolType {
|
pub(crate) fn pool_code(pool_type: PoolType) -> i64 {
|
||||||
Transparent,
|
// These constants are *incidentally* shared with the typecodes
|
||||||
Sapling,
|
// for unified addresses, but this is exclusively an internal
|
||||||
}
|
// implementation detail.
|
||||||
|
match pool_type {
|
||||||
impl PoolType {
|
PoolType::Transparent => 0i64,
|
||||||
pub(crate) fn typecode(&self) -> i64 {
|
PoolType::Sapling => 2i64,
|
||||||
// These constants are *incidentally* shared with the typecodes
|
|
||||||
// for unified addresses, but this is exclusively an internal
|
|
||||||
// implementation detail.
|
|
||||||
match self {
|
|
||||||
PoolType::Transparent => 0i64,
|
|
||||||
PoolType::Sapling => 2i64,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1153,128 +1143,50 @@ pub fn update_expired_notes<P>(
|
||||||
stmts.stmt_update_expired(height)
|
stmts.stmt_update_expired(height)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Records information about a note that your wallet created.
|
/// Records information about a transaction output 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."
|
/// This is a crate-internal convenience method.
|
||||||
)]
|
pub(crate) fn insert_sent_output<'a, P: consensus::Parameters>(
|
||||||
#[allow(deprecated)]
|
|
||||||
pub fn put_sent_note<'a, P: consensus::Parameters>(
|
|
||||||
stmts: &mut DataConnStmtCache<'a, P>,
|
stmts: &mut DataConnStmtCache<'a, P>,
|
||||||
tx_ref: i64,
|
tx_ref: i64,
|
||||||
output_index: usize,
|
from_account: AccountId,
|
||||||
account: AccountId,
|
output: &SentTransactionOutput,
|
||||||
to: &PaymentAddress,
|
|
||||||
value: Amount,
|
|
||||||
memo: Option<&MemoBytes>,
|
|
||||||
) -> Result<(), SqliteClientError> {
|
) -> Result<(), SqliteClientError> {
|
||||||
// Try updating an existing sent note.
|
stmts.stmt_insert_sent_output(
|
||||||
if !stmts.stmt_update_sent_note(
|
|
||||||
account,
|
|
||||||
&encode_payment_address_p(&stmts.wallet_db.params, to),
|
|
||||||
value,
|
|
||||||
memo,
|
|
||||||
tx_ref,
|
tx_ref,
|
||||||
PoolType::Sapling,
|
output.output_index,
|
||||||
output_index,
|
from_account,
|
||||||
)? {
|
&output.recipient,
|
||||||
// It isn't there, so insert.
|
output.value,
|
||||||
insert_sent_note(stmts, tx_ref, output_index, account, to, value, memo)?
|
output.memo.as_ref(),
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
/// This is a crate-internal convenience method.
|
||||||
#[deprecated(
|
#[allow(clippy::too_many_arguments)]
|
||||||
note = "This method will be removed in a future update. Use zcash_client_backend::data_api::WalletWrite::store_sent_tx instead."
|
pub(crate) fn put_sent_output<'a, P: consensus::Parameters>(
|
||||||
)]
|
|
||||||
pub fn insert_sent_utxo<'a, P: consensus::Parameters>(
|
|
||||||
stmts: &mut DataConnStmtCache<'a, P>,
|
stmts: &mut DataConnStmtCache<'a, P>,
|
||||||
|
from_account: AccountId,
|
||||||
tx_ref: i64,
|
tx_ref: i64,
|
||||||
output_index: usize,
|
output_index: usize,
|
||||||
account: AccountId,
|
recipient: &Recipient,
|
||||||
to: &TransparentAddress,
|
|
||||||
value: Amount,
|
value: Amount,
|
||||||
|
memo: Option<&MemoBytes>,
|
||||||
) -> Result<(), SqliteClientError> {
|
) -> Result<(), SqliteClientError> {
|
||||||
let to_str = encode_transparent_address_p(&stmts.wallet_db.params, to);
|
if !stmts.stmt_update_sent_output(from_account, recipient, value, memo, tx_ref, output_index)? {
|
||||||
|
stmts.stmt_insert_sent_output(
|
||||||
|
tx_ref,
|
||||||
|
output_index,
|
||||||
|
from_account,
|
||||||
|
recipient,
|
||||||
|
value,
|
||||||
|
memo,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
stmts.stmt_insert_sent_note(
|
Ok(())
|
||||||
tx_ref,
|
|
||||||
PoolType::Transparent,
|
|
||||||
output_index,
|
|
||||||
account,
|
|
||||||
&to_str,
|
|
||||||
value,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -122,8 +122,13 @@ fn init_wallet_db_internal<P: consensus::Parameters + 'static>(
|
||||||
seed: Option<SecretVec<u8>>,
|
seed: Option<SecretVec<u8>>,
|
||||||
target_migration: Option<Uuid>,
|
target_migration: Option<Uuid>,
|
||||||
) -> Result<(), MigratorError<WalletMigrationError>> {
|
) -> Result<(), MigratorError<WalletMigrationError>> {
|
||||||
|
// Turn off foreign keys, and ensure that table replacement/modification
|
||||||
|
// does not break views
|
||||||
wdb.conn
|
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)))?;
|
.map_err(|e| MigratorError::Adapter(WalletMigrationError::from(e)))?;
|
||||||
let adapter = RusqliteAdapter::new(&mut wdb.conn, Some("schemer_migrations".to_string()));
|
let adapter = RusqliteAdapter::new(&mut wdb.conn, Some("schemer_migrations".to_string()));
|
||||||
adapter.init().expect("Migrations table setup succeeds.");
|
adapter.init().expect("Migrations table setup succeeds.");
|
||||||
|
@ -311,7 +316,10 @@ mod tests {
|
||||||
use super::{init_accounts_table, init_blocks_table, init_wallet_db};
|
use super::{init_accounts_table, init_blocks_table, init_wallet_db};
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[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]
|
#[test]
|
||||||
fn verify_schema() {
|
fn verify_schema() {
|
||||||
|
@ -375,12 +383,17 @@ mod tests {
|
||||||
output_pool INTEGER NOT NULL ,
|
output_pool INTEGER NOT NULL ,
|
||||||
output_index INTEGER NOT NULL,
|
output_index INTEGER NOT NULL,
|
||||||
from_account INTEGER NOT NULL,
|
from_account INTEGER NOT NULL,
|
||||||
address TEXT NOT NULL,
|
to_address TEXT,
|
||||||
|
to_account INTEGER,
|
||||||
value INTEGER NOT NULL,
|
value INTEGER NOT NULL,
|
||||||
memo BLOB,
|
memo BLOB,
|
||||||
FOREIGN KEY (tx) REFERENCES transactions(id_tx),
|
FOREIGN KEY (tx) REFERENCES transactions(id_tx),
|
||||||
FOREIGN KEY (from_account) REFERENCES accounts(account),
|
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 (
|
"CREATE TABLE transactions (
|
||||||
id_tx INTEGER PRIMARY KEY,
|
id_tx INTEGER PRIMARY KEY,
|
||||||
|
@ -943,7 +956,7 @@ mod tests {
|
||||||
wdb.conn.execute(
|
wdb.conn.execute(
|
||||||
"INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value)
|
"INSERT INTO sent_notes (tx, output_pool, output_index, from_account, address, value)
|
||||||
VALUES (0, ?, 0, ?, ?, 0)",
|
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(())
|
Ok(())
|
||||||
|
|
|
@ -2,6 +2,7 @@ mod add_transaction_views;
|
||||||
mod add_utxo_account;
|
mod add_utxo_account;
|
||||||
mod addresses_table;
|
mod addresses_table;
|
||||||
mod initial_setup;
|
mod initial_setup;
|
||||||
|
mod sent_notes_to_internal;
|
||||||
mod ufvk_support;
|
mod ufvk_support;
|
||||||
mod utxos_table;
|
mod utxos_table;
|
||||||
|
|
||||||
|
@ -31,5 +32,6 @@ pub(super) fn all_migrations<P: consensus::Parameters + 'static>(
|
||||||
Box::new(add_utxo_account::Migration {
|
Box::new(add_utxo_account::Migration {
|
||||||
_params: params.clone(),
|
_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 secrecy::{ExposeSecret, SecretVec};
|
||||||
use uuid::Uuid;
|
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};
|
use zcash_primitives::{consensus, zip32::AccountId};
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
@ -18,7 +20,7 @@ use zcash_client_backend::encoding::AddressCodec;
|
||||||
|
|
||||||
use crate::wallet::{
|
use crate::wallet::{
|
||||||
init::{migrations::utxos_table, WalletMigrationError},
|
init::{migrations::utxos_table, WalletMigrationError},
|
||||||
PoolType,
|
pool_code,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) const MIGRATION_ID: Uuid = Uuid::from_fields(
|
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 {
|
let output_pool = match decoded_address {
|
||||||
RecipientAddress::Shielded(_) => Ok(PoolType::Sapling.typecode()),
|
RecipientAddress::Shielded(_) => Ok(pool_code(PoolType::Sapling)),
|
||||||
RecipientAddress::Transparent(_) => Ok(PoolType::Transparent.typecode()),
|
RecipientAddress::Transparent(_) => Ok(pool_code(PoolType::Transparent)),
|
||||||
RecipientAddress::Unified(_) => Err(WalletMigrationError::CorruptedData(
|
RecipientAddress::Unified(_) => Err(WalletMigrationError::CorruptedData(
|
||||||
"Unified addresses should not yet appear in the sent_notes table."
|
"Unified addresses should not yet appear in the sent_notes table."
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
|
Loading…
Reference in New Issue