diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index f8ca00bcbe..159cbfa3c4 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -1187,7 +1187,7 @@ impl WalletWrite for WalletDb d_tx: DecryptedTransaction, ) -> Result<(), Self::Error> { self.transactionally(|wdb| { - let tx_ref = wallet::put_tx_data(wdb.conn.0, d_tx.tx(), None, None)?; + let tx_ref = wallet::put_tx_data(wdb.conn.0, d_tx.tx(), None, None, None)?; let funding_accounts = wallet::get_funding_accounts(wdb.conn.0, d_tx.tx())?; // TODO(#1305): Correctly track accounts that fund each transaction output. diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 4afeb83f17..f248383f90 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -1857,6 +1857,7 @@ pub(crate) fn store_transaction_to_be_sent( sent_tx.tx(), Some(sent_tx.fee_amount()), Some(sent_tx.created()), + chain_tip_height(wdb.conn.0)?.map(|h| h + 1), )?; // Mark notes as spent. @@ -2357,10 +2358,11 @@ pub(crate) fn put_tx_data( tx: &Transaction, fee: Option, created_at: Option, + target_height: Option, ) -> Result { let mut stmt_upsert_tx_data = conn.prepare_cached( - "INSERT INTO transactions (txid, created, expiry_height, raw, fee) - VALUES (:txid, :created_at, :expiry_height, :raw, :fee) + "INSERT INTO transactions (txid, created, expiry_height, raw, fee, target_height) + VALUES (:txid, :created_at, :expiry_height, :raw, :fee, :target_height) ON CONFLICT (txid) DO UPDATE SET expiry_height = :expiry_height, raw = :raw, @@ -2378,6 +2380,7 @@ pub(crate) fn put_tx_data( ":expiry_height": u32::from(tx.expiry_height()), ":raw": raw_tx, ":fee": fee.map(u64::from), + ":target_height": target_height.map(u32::from), ]; stmt_upsert_tx_data diff --git a/zcash_client_sqlite/src/wallet/db.rs b/zcash_client_sqlite/src/wallet/db.rs index 633e44b0b8..3c5a34e9d0 100644 --- a/zcash_client_sqlite/src/wallet/db.rs +++ b/zcash_client_sqlite/src/wallet/db.rs @@ -186,6 +186,7 @@ CREATE TABLE "transactions" ( raw BLOB, fee INTEGER, confirmed_unmined INTEGER, + target_height INTEGER, FOREIGN KEY (block) REFERENCES blocks(height), CONSTRAINT height_consistency CHECK (block IS NULL OR mined_height = block) )"#; diff --git a/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs b/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs index 3389af518a..d270153838 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs @@ -307,6 +307,7 @@ mod tests { builder::{BuildConfig, BuildResult, Builder}, components::{amount::NonNegativeAmount, transparent}, fees::fixed, + Transaction, }, zip32::{self, Scope}, }; @@ -322,7 +323,7 @@ mod tests { memo_repr, parse_scope, sapling::ReceivedSaplingOutput, }, - AccountId, WalletDb, + AccountId, TxRef, WalletDb, }; // These must be different. @@ -471,6 +472,41 @@ mod tests { Ok(()) } + /// This reproduces [`crate::wallet::put_tx_data`] as it was at the time + /// of the creation of this migration. + fn put_tx_data( + conn: &rusqlite::Connection, + tx: &Transaction, + fee: Option, + created_at: Option, + ) -> Result { + let mut stmt_upsert_tx_data = conn.prepare_cached( + "INSERT INTO transactions (txid, created, expiry_height, raw, fee) + VALUES (:txid, :created_at, :expiry_height, :raw, :fee) + ON CONFLICT (txid) DO UPDATE + SET expiry_height = :expiry_height, + raw = :raw, + fee = IFNULL(:fee, fee) + RETURNING id_tx", + )?; + + let txid = tx.txid(); + let mut raw_tx = vec![]; + tx.write(&mut raw_tx)?; + + let tx_params = named_params![ + ":txid": &txid.as_ref()[..], + ":created_at": created_at, + ":expiry_height": u32::from(tx.expiry_height()), + ":raw": raw_tx, + ":fee": fee.map(u64::from), + ]; + + stmt_upsert_tx_data + .query_row(tx_params, |row| row.get::<_, i64>(0).map(TxRef)) + .map_err(SqliteClientError::from) + } + #[test] fn receiving_key_scopes_migration_enhanced() { let params = Network::TestNetwork; @@ -504,7 +540,7 @@ mod tests { db_data .transactionally::<_, _, rusqlite::Error>(|wdb| { - let tx_ref = crate::wallet::put_tx_data(wdb.conn.0, d_tx.tx(), None, None).unwrap(); + let tx_ref = put_tx_data(wdb.conn.0, d_tx.tx(), None, None).unwrap(); let mut spending_account_id: Option = None; diff --git a/zcash_client_sqlite/src/wallet/init/migrations/tx_retrieval_queue.rs b/zcash_client_sqlite/src/wallet/init/migrations/tx_retrieval_queue.rs index a5a10b7eb2..06315303c1 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/tx_retrieval_queue.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/tx_retrieval_queue.rs @@ -1,9 +1,10 @@ //! A migration to add the `tx_retrieval_queue` table to the database. -use rusqlite::Transaction; +use rusqlite::{named_params, Transaction}; use schemer_rusqlite::RusqliteMigration; use std::collections::HashSet; use uuid::Uuid; +use zcash_primitives::transaction::builder::DEFAULT_TX_EXPIRY_DELTA; use crate::wallet::init::WalletMigrationError; @@ -38,7 +39,16 @@ impl RusqliteMigration for Migration { dependent_transaction_id INTEGER, FOREIGN KEY (dependent_transaction_id) REFERENCES transactions(id_tx) ); - ALTER TABLE transactions ADD COLUMN confirmed_unmined INTEGER;", + + ALTER TABLE transactions ADD COLUMN confirmed_unmined INTEGER; + ALTER TABLE transactions ADD COLUMN target_height INTEGER;", + )?; + + transaction.execute( + "UPDATE transactions + SET target_height = expiry_height - :default_expiry_delta + WHERE expiry_height IS NOT NULL AND created IS NOT NULL", + named_params![":default_expiry_delta": DEFAULT_TX_EXPIRY_DELTA], )?; Ok(()) @@ -46,7 +56,8 @@ impl RusqliteMigration for Migration { fn down(&self, transaction: &Transaction) -> Result<(), WalletMigrationError> { transaction.execute_batch( - "ALTER TABLE transactions DROP COLUMN confirmed_unmined; + "ALTER TABLE transactions DROP COLUMN target_height; + ALTER TABLE transactions DROP COLUMN confirmed_unmined; DROP TABLE tx_retrieval_queue;", )?; diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 47c0995028..b40655dc55 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -55,7 +55,7 @@ use super::components::sapling::zip212_enforcement; /// Since Blossom activation, the default transaction expiry delta should be 40 blocks. /// -const DEFAULT_TX_EXPIRY_DELTA: u32 = 40; +pub const DEFAULT_TX_EXPIRY_DELTA: u32 = 40; /// Errors that can occur during fee calculation. #[derive(Debug)]