From 8b8757ce659f981eb6a3e0aa69c27d9115f3a783 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 15 Mar 2024 16:50:53 +0000 Subject: [PATCH] zcash_client_sqlite: Fix ambiguities in transaction views Co-authored-by: Kris Nuttycombe --- zcash_client_sqlite/src/testing.rs | 100 ++++++++++++++++++ zcash_client_sqlite/src/testing/pool.rs | 3 + zcash_client_sqlite/src/wallet/init.rs | 38 ++++--- .../init/migrations/orchard_received_notes.rs | 38 ++++--- 4 files changed, 147 insertions(+), 32 deletions(-) diff --git a/zcash_client_sqlite/src/testing.rs b/zcash_client_sqlite/src/testing.rs index e969b5152..472e094cf 100644 --- a/zcash_client_sqlite/src/testing.rs +++ b/zcash_client_sqlite/src/testing.rs @@ -63,6 +63,7 @@ use zcash_primitives::{ zip32::DiversifierIndex, }; use zcash_protocol::local_consensus::LocalNetwork; +use zcash_protocol::value::{ZatBalance, Zatoshis}; use crate::{ chain::init::init_cache_database, @@ -958,6 +959,105 @@ impl TestState { ) .unwrap() } + + /// Returns a vector of transaction summaries + pub(crate) fn get_tx_history( + &self, + ) -> Result>, SqliteClientError> { + let mut stmt = self.wallet().conn.prepare_cached( + "SELECT * + FROM v_transactions + ORDER BY mined_height DESC, tx_index DESC", + )?; + + let results = stmt + .query_and_then::, SqliteClientError, _, _>([], |row| { + Ok(TransactionSummary { + account_id: AccountId(row.get("account_id")?), + txid: TxId::from_bytes(row.get("txid")?), + expiry_height: row + .get::<_, Option>("expiry_height")? + .map(BlockHeight::from), + mined_height: row + .get::<_, Option>("mined_height")? + .map(BlockHeight::from), + account_value_delta: ZatBalance::from_i64(row.get("account_balance_delta")?)?, + fee_paid: row + .get::<_, Option>("fee_paid")? + .map(Zatoshis::from_nonnegative_i64) + .transpose()?, + has_change: row.get("has_change")?, + sent_note_count: row.get("sent_note_count")?, + received_note_count: row.get("received_note_count")?, + memo_count: row.get("memo_count")?, + expired_unmined: row.get("expired_unmined")?, + }) + })? + .collect::, _>>()?; + + Ok(results) + } +} + +pub(crate) struct TransactionSummary { + account_id: AccountId, + txid: TxId, + expiry_height: Option, + mined_height: Option, + account_value_delta: ZatBalance, + fee_paid: Option, + has_change: bool, + sent_note_count: usize, + received_note_count: usize, + memo_count: usize, + expired_unmined: bool, +} + +#[allow(dead_code)] +impl TransactionSummary { + pub(crate) fn account_id(&self) -> &AccountId { + &self.account_id + } + + pub(crate) fn txid(&self) -> TxId { + self.txid + } + + pub(crate) fn expiry_height(&self) -> Option { + self.expiry_height + } + + pub(crate) fn mined_height(&self) -> Option { + self.mined_height + } + + pub(crate) fn account_value_delta(&self) -> ZatBalance { + self.account_value_delta + } + + pub(crate) fn fee_paid(&self) -> Option { + self.fee_paid + } + + pub(crate) fn has_change(&self) -> bool { + self.has_change + } + + pub(crate) fn sent_note_count(&self) -> usize { + self.sent_note_count + } + + pub(crate) fn received_note_count(&self) -> usize { + self.received_note_count + } + + pub(crate) fn expired_unmined(&self) -> bool { + self.expired_unmined + } + + pub(crate) fn memo_count(&self) -> usize { + self.memo_count + } } /// Trait used by tests that require a full viewing key. diff --git a/zcash_client_sqlite/src/testing/pool.rs b/zcash_client_sqlite/src/testing/pool.rs index 235c640f0..24d8e955b 100644 --- a/zcash_client_sqlite/src/testing/pool.rs +++ b/zcash_client_sqlite/src/testing/pool.rs @@ -271,6 +271,9 @@ pub(crate) fn send_single_step_proposed_transfer() { .get_memo(NoteId::new(sent_tx_id, T::SHIELDED_PROTOCOL, 12345)), Ok(None) ); + + let tx_history = st.get_tx_history().unwrap(); + assert_eq!(tx_history.len(), 2); } #[cfg(feature = "transparent-inputs")] diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 00147527d..e58429470 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -552,14 +552,14 @@ mod tests { // v_received_notes "CREATE VIEW v_received_notes AS SELECT - id, - tx, + sapling_received_notes.id AS id_within_pool_table, + sapling_received_notes.tx, 2 AS pool, sapling_received_notes.output_index AS output_index, account_id, - value, + sapling_received_notes.value, is_change, - memo, + sapling_received_notes.memo, spent, sent_notes.id AS sent_note_id FROM sapling_received_notes @@ -568,14 +568,14 @@ mod tests { (sapling_received_notes.tx, 2, sapling_received_notes.output_index) UNION SELECT - id, - tx, + orchard_received_notes.id AS id_within_pool_table, + orchard_received_notes.tx, 3 AS pool, orchard_received_notes.action_index AS output_index, account_id, - value, + orchard_received_notes.value, is_change, - memo, + orchard_received_notes.memo, spent, sent_notes.id AS sent_note_id FROM orchard_received_notes @@ -650,11 +650,12 @@ mod tests { "CREATE VIEW v_transactions AS WITH notes AS ( - SELECT v_received_notes.id AS id, - v_received_notes.account_id AS account_id, + -- Shielded notes received in this transaction + SELECT v_received_notes.account_id AS account_id, transactions.block AS block, transactions.txid AS txid, v_received_notes.pool AS pool, + id_within_pool_table, v_received_notes.value AS value, CASE WHEN v_received_notes.is_change THEN 1 @@ -673,22 +674,24 @@ mod tests { JOIN transactions ON transactions.id_tx = v_received_notes.tx UNION - SELECT utxos.id AS id, - utxos.received_by_account_id AS account_id, + -- Transparent TXOs received in this transaction + SELECT utxos.received_by_account_id AS account_id, utxos.height AS block, utxos.prevout_txid AS txid, 0 AS pool, + utxos.id AS id_within_pool_table, utxos.value_zat AS value, 0 AS is_change, 1 AS received_count, 0 AS memo_present FROM utxos UNION - SELECT v_received_notes.id AS id, - v_received_notes.account_id AS account_id, + -- Shielded notes spent in this transaction + SELECT v_received_notes.account_id AS account_id, transactions.block AS block, transactions.txid AS txid, v_received_notes.pool AS pool, + id_within_pool_table, -v_received_notes.value AS value, 0 AS is_change, 0 AS received_count, @@ -697,11 +700,12 @@ mod tests { JOIN transactions ON transactions.id_tx = v_received_notes.spent UNION - SELECT utxos.id AS id, - utxos.received_by_account_id AS account_id, + -- Transparent TXOs spent in this transaction + SELECT utxos.received_by_account_id AS account_id, transactions.block AS block, transactions.txid AS txid, 0 AS pool, + utxos.id AS id_within_pool_table, -utxos.value_zat AS value, 0 AS is_change, 0 AS received_count, @@ -710,6 +714,8 @@ mod tests { JOIN transactions ON transactions.id_tx = utxos.spent_in_tx ), + -- Obtain a count of the notes that the wallet created in each transaction, + -- not counting change notes. sent_note_counts AS ( SELECT sent_notes.from_account_id AS account_id, transactions.txid AS txid, diff --git a/zcash_client_sqlite/src/wallet/init/migrations/orchard_received_notes.rs b/zcash_client_sqlite/src/wallet/init/migrations/orchard_received_notes.rs index 014f80dc4..7e174164b 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/orchard_received_notes.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/orchard_received_notes.rs @@ -70,14 +70,14 @@ impl RusqliteMigration for Migration { &format!( "CREATE VIEW v_received_notes AS SELECT - id, - tx, + sapling_received_notes.id AS id_within_pool_table, + sapling_received_notes.tx, {sapling_pool_code} AS pool, sapling_received_notes.output_index AS output_index, account_id, - value, + sapling_received_notes.value, is_change, - memo, + sapling_received_notes.memo, spent, sent_notes.id AS sent_note_id FROM sapling_received_notes @@ -86,14 +86,14 @@ impl RusqliteMigration for Migration { (sapling_received_notes.tx, {sapling_pool_code}, sapling_received_notes.output_index) UNION SELECT - id, - tx, + orchard_received_notes.id AS id_within_pool_table, + orchard_received_notes.tx, {orchard_pool_code} AS pool, orchard_received_notes.action_index AS output_index, account_id, - value, + orchard_received_notes.value, is_change, - memo, + orchard_received_notes.memo, spent, sent_notes.id AS sent_note_id FROM orchard_received_notes @@ -110,11 +110,12 @@ impl RusqliteMigration for Migration { CREATE VIEW v_transactions AS WITH notes AS ( - SELECT v_received_notes.id AS id, - v_received_notes.account_id AS account_id, + -- Shielded notes received in this transaction + SELECT v_received_notes.account_id AS account_id, transactions.block AS block, transactions.txid AS txid, v_received_notes.pool AS pool, + id_within_pool_table, v_received_notes.value AS value, CASE WHEN v_received_notes.is_change THEN 1 @@ -133,22 +134,24 @@ impl RusqliteMigration for Migration { JOIN transactions ON transactions.id_tx = v_received_notes.tx UNION - SELECT utxos.id AS id, - utxos.received_by_account_id AS account_id, + -- Transparent TXOs received in this transaction + SELECT utxos.received_by_account_id AS account_id, utxos.height AS block, utxos.prevout_txid AS txid, {transparent_pool_code} AS pool, + utxos.id AS id_within_pool_table, utxos.value_zat AS value, 0 AS is_change, 1 AS received_count, 0 AS memo_present FROM utxos UNION - SELECT v_received_notes.id AS id, - v_received_notes.account_id AS account_id, + -- Shielded notes spent in this transaction + SELECT v_received_notes.account_id AS account_id, transactions.block AS block, transactions.txid AS txid, v_received_notes.pool AS pool, + id_within_pool_table, -v_received_notes.value AS value, 0 AS is_change, 0 AS received_count, @@ -157,11 +160,12 @@ impl RusqliteMigration for Migration { JOIN transactions ON transactions.id_tx = v_received_notes.spent UNION - SELECT utxos.id AS id, - utxos.received_by_account_id AS account_id, + -- Transparent TXOs spent in this transaction + SELECT utxos.received_by_account_id AS account_id, transactions.block AS block, transactions.txid AS txid, {transparent_pool_code} AS pool, + utxos.id AS id_within_pool_table, -utxos.value_zat AS value, 0 AS is_change, 0 AS received_count, @@ -170,6 +174,8 @@ impl RusqliteMigration for Migration { JOIN transactions ON transactions.id_tx = utxos.spent_in_tx ), + -- Obtain a count of the notes that the wallet created in each transaction, + -- not counting change notes. sent_note_counts AS ( SELECT sent_notes.from_account_id AS account_id, transactions.txid AS txid,