Track inputs sent to wallet-internal recipients.
Ensure that we're attempting trial-decryption with the internal IVK and correctly track internal vs. external recipients in the wallet database.
This commit is contained in:
parent
1dc3cfe724
commit
06e43a572a
|
@ -248,13 +248,19 @@ 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> {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Recipient {
|
||||
Address(RecipientAddress),
|
||||
InternalAccount(AccountId),
|
||||
}
|
||||
|
||||
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,7 +268,7 @@ 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,
|
||||
pub recipient: Recipient,
|
||||
pub value: Amount,
|
||||
pub memo: Option<MemoBytes>,
|
||||
}
|
||||
|
|
|
@ -23,7 +23,8 @@ use {
|
|||
use crate::{
|
||||
address::RecipientAddress,
|
||||
data_api::{
|
||||
error::Error, DecryptedTransaction, SentTransaction, SentTransactionOutput, WalletWrite,
|
||||
error::Error, DecryptedTransaction, Recipient, SentTransaction, SentTransactionOutput,
|
||||
WalletWrite,
|
||||
},
|
||||
decrypt_transaction,
|
||||
wallet::OvkPolicy,
|
||||
|
@ -394,7 +395,7 @@ where
|
|||
|
||||
SentTransactionOutput {
|
||||
output_index: idx,
|
||||
recipient_address: &payment.recipient_address,
|
||||
recipient: Recipient::Address(payment.recipient_address.clone()),
|
||||
value: payment.amount,
|
||||
memo: payment.memo.clone()
|
||||
}
|
||||
|
@ -512,12 +513,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 +527,8 @@ where
|
|||
account,
|
||||
outputs: vec![SentTransactionOutput {
|
||||
output_index,
|
||||
recipient_address: &RecipientAddress::Shielded(shielding_address),
|
||||
value: amount_to_shield,
|
||||
recipient: Recipient::InternalAccount(account),
|
||||
memo: Some(memo.clone()),
|
||||
}],
|
||||
fee_amount: fee,
|
||||
|
|
|
@ -10,11 +10,22 @@ 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 transfer was received on one of the wallet's external addresses.
|
||||
Incoming,
|
||||
/// The transfer was received on one of the wallet's internal-only addresses.
|
||||
WalletInternal,
|
||||
/// The transfer was decrypted using one of the wallet's outgoing viewing keys.
|
||||
Outgoing,
|
||||
}
|
||||
|
||||
/// A decrypted shielded output.
|
||||
pub struct DecryptedOutput {
|
||||
/// The index of the output within [`shielded_outputs`].
|
||||
|
@ -33,7 +44,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
|
||||
|
@ -49,19 +60,29 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
|
|||
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());
|
||||
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,
|
||||
},
|
||||
};
|
||||
let decryption_result =
|
||||
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))
|
||||
});
|
||||
|
||||
let ((note, to, memo), transfer_type) = match decryption_result {
|
||||
Some(result) => result,
|
||||
None => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
decrypted.push(DecryptedOutput {
|
||||
index,
|
||||
|
@ -69,7 +90,7 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
|
|||
account: *account,
|
||||
to,
|
||||
memo,
|
||||
outgoing,
|
||||
transfer_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -55,11 +55,13 @@ use zcash_primitives::{
|
|||
use zcash_client_backend::{
|
||||
address::{RecipientAddress, UnifiedAddress},
|
||||
data_api::{
|
||||
BlockSource, DecryptedTransaction, PrunedBlock, SentTransaction, WalletRead, WalletWrite,
|
||||
BlockSource, DecryptedTransaction, PrunedBlock, Recipient, SentTransaction, WalletRead,
|
||||
WalletWrite,
|
||||
},
|
||||
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
|
||||
proto::compact_formats::CompactBlock,
|
||||
wallet::SpendableNote,
|
||||
TransferType,
|
||||
};
|
||||
|
||||
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;
|
||||
for output in d_tx.sapling_outputs {
|
||||
if output.outgoing {
|
||||
wallet::put_sent_note(
|
||||
up,
|
||||
tx_ref,
|
||||
output.index,
|
||||
output.account,
|
||||
&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);
|
||||
}
|
||||
}
|
||||
match output.transfer_type {
|
||||
TransferType::Outgoing | TransferType::WalletInternal => {
|
||||
let recipient = if output.transfer_type == TransferType::Outgoing {
|
||||
Recipient::Address(RecipientAddress::Shielded(output.to.clone()))
|
||||
} else {
|
||||
Recipient::InternalAccount(output.account)
|
||||
};
|
||||
|
||||
wallet::put_received_note(up, output, tx_ref)?;
|
||||
wallet::put_sent_note(
|
||||
up,
|
||||
tx_ref,
|
||||
output.index,
|
||||
output.account,
|
||||
&recipient,
|
||||
Amount::from_u64(output.note.value)
|
||||
.map_err(|_| SqliteClientError::CorruptedData("Note value invalid.".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)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -611,35 +622,26 @@ 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(
|
||||
match &output.recipient {
|
||||
Recipient::Address(RecipientAddress::Transparent(addr)) => {
|
||||
wallet::insert_sent_utxo(
|
||||
up,
|
||||
tx_ref,
|
||||
output.output_index,
|
||||
sent_tx.account,
|
||||
addr,
|
||||
output.value,
|
||||
)?
|
||||
}
|
||||
shielded_recipient => wallet::insert_sent_note(
|
||||
up,
|
||||
tx_ref,
|
||||
output.output_index,
|
||||
sent_tx.account,
|
||||
ua.sapling().expect("TODO: Add Orchard support"),
|
||||
shielded_recipient,
|
||||
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,
|
||||
)?,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,14 +18,14 @@ use zcash_primitives::{
|
|||
zip32::{AccountId, DiversifierIndex},
|
||||
};
|
||||
|
||||
use zcash_client_backend::address::UnifiedAddress;
|
||||
use zcash_client_backend::{address::UnifiedAddress, data_api::Recipient};
|
||||
|
||||
use crate::{error::SqliteClientError, wallet::PoolType, NoteId, WalletDb};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use {
|
||||
crate::UtxoId,
|
||||
rusqlite::{named_params, OptionalExtension},
|
||||
rusqlite::OptionalExtension,
|
||||
zcash_client_backend::{encoding::AddressCodec, wallet::WalletTransparentOutput},
|
||||
zcash_primitives::transaction::components::transparent::OutPoint,
|
||||
};
|
||||
|
@ -155,7 +155,8 @@ impl<'a, P> DataConnStmtCache<'a, P> {
|
|||
stmt_update_sent_note: wallet_db.conn.prepare(
|
||||
"UPDATE sent_notes
|
||||
SET from_account = :account,
|
||||
address = :address,
|
||||
to_address = :to_address,
|
||||
to_account = :to_account,
|
||||
value = :value,
|
||||
memo = IFNULL(:memo, memo)
|
||||
WHERE tx = :tx
|
||||
|
@ -163,8 +164,12 @@ impl<'a, P> DataConnStmtCache<'a, P> {
|
|||
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)"
|
||||
"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 +351,80 @@ 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 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_note(
|
||||
&mut self,
|
||||
tx_ref: i64,
|
||||
pool_type: PoolType,
|
||||
output_index: usize,
|
||||
from_account: AccountId,
|
||||
to: &Recipient,
|
||||
value: Amount,
|
||||
memo: Option<&MemoBytes>,
|
||||
) -> Result<(), SqliteClientError> {
|
||||
let (to_address, to_account) = match to {
|
||||
Recipient::Address(addr) => (Some(addr.encode(&self.wallet_db.params)), None),
|
||||
Recipient::InternalAccount(id) => (None, Some(u32::from(*id))),
|
||||
};
|
||||
|
||||
self.stmt_insert_sent_note.execute(named_params![
|
||||
":tx": &tx_ref,
|
||||
":output_pool": &pool_type.typecode(),
|
||||
":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_note(
|
||||
&mut self,
|
||||
account: AccountId,
|
||||
to: &Recipient,
|
||||
value: Amount,
|
||||
memo: Option<&MemoBytes>,
|
||||
tx_ref: i64,
|
||||
pool_type: PoolType,
|
||||
output_index: usize,
|
||||
) -> Result<bool, SqliteClientError> {
|
||||
let (to_address, to_account) = match to {
|
||||
Recipient::Address(addr) => (Some(addr.encode(&self.wallet_db.params)), None),
|
||||
Recipient::InternalAccount(id) => (None, Some(u32::from(*id))),
|
||||
};
|
||||
match self.stmt_update_sent_note.execute(named_params![
|
||||
":account": &u32::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_type.typecode(),
|
||||
":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 +607,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, Recipient},
|
||||
keys::UnifiedFullViewingKey,
|
||||
wallet::{WalletShieldedOutput, WalletTx},
|
||||
DecryptedOutput,
|
||||
|
@ -1163,14 +1162,14 @@ pub fn put_sent_note<'a, P: consensus::Parameters>(
|
|||
tx_ref: i64,
|
||||
output_index: usize,
|
||||
account: AccountId,
|
||||
to: &PaymentAddress,
|
||||
to: &Recipient,
|
||||
value: Amount,
|
||||
memo: Option<&MemoBytes>,
|
||||
) -> Result<(), SqliteClientError> {
|
||||
// Try updating an existing sent note.
|
||||
if !stmts.stmt_update_sent_note(
|
||||
account,
|
||||
&encode_payment_address_p(&stmts.wallet_db.params, to),
|
||||
to,
|
||||
value,
|
||||
memo,
|
||||
tx_ref,
|
||||
|
@ -1203,7 +1202,7 @@ pub fn put_sent_utxo<'a, P: consensus::Parameters>(
|
|||
// Try updating an existing sent UTXO.
|
||||
if !stmts.stmt_update_sent_note(
|
||||
account,
|
||||
&encode_transparent_address_p(&stmts.wallet_db.params, to),
|
||||
&Recipient::Address(RecipientAddress::Transparent(*to)),
|
||||
value,
|
||||
None,
|
||||
tx_ref,
|
||||
|
@ -1225,26 +1224,21 @@ pub fn put_sent_utxo<'a, P: consensus::Parameters>(
|
|||
/// 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>(
|
||||
pub(crate) fn insert_sent_note<'a, P: consensus::Parameters>(
|
||||
stmts: &mut DataConnStmtCache<'a, P>,
|
||||
tx_ref: i64,
|
||||
output_index: usize,
|
||||
account: AccountId,
|
||||
to: &PaymentAddress,
|
||||
from_account: AccountId,
|
||||
to: &Recipient,
|
||||
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,
|
||||
from_account,
|
||||
to,
|
||||
value,
|
||||
memo,
|
||||
)
|
||||
|
@ -1264,14 +1258,12 @@ pub fn insert_sent_utxo<'a, P: consensus::Parameters>(
|
|||
to: &TransparentAddress,
|
||||
value: Amount,
|
||||
) -> Result<(), SqliteClientError> {
|
||||
let to_str = encode_transparent_address_p(&stmts.wallet_db.params, to);
|
||||
|
||||
stmts.stmt_insert_sent_note(
|
||||
tx_ref,
|
||||
PoolType::Transparent,
|
||||
output_index,
|
||||
account,
|
||||
&to_str,
|
||||
&Recipient::Address(RecipientAddress::Transparent(*to)),
|
||||
value,
|
||||
None,
|
||||
)
|
||||
|
|
|
@ -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.");
|
||||
|
@ -375,11 +380,13 @@ 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),
|
||||
FOREIGN KEY (to_account) REFERENCES accounts(account),
|
||||
CONSTRAINT tx_output UNIQUE (tx, output_pool, output_index)
|
||||
)",
|
||||
"CREATE TABLE transactions (
|
||||
|
|
|
@ -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,88 @@
|
|||
//! 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> {
|
||||
transaction.execute_batch("ALTER TABLE sent_notes ADD COLUMN to_account INTEGER;")?;
|
||||
|
||||
// `to_account` should be null for all migrated rows, since internal addresses
|
||||
// have not been used for change or shielding prior to this migration.
|
||||
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)
|
||||
);
|
||||
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.");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue