@@ -130,7 +130,7 @@ use crate::{
130130 encoding:: LEGACY_ADDRESS_INDEX_NULL ,
131131 } ,
132132 AccountRef , AccountUuid , AddressRef , SqlTransaction , TransferType , TxRef ,
133- WalletCommitmentTrees , WalletDb , PRUNING_DEPTH ,
133+ WalletCommitmentTrees , WalletDb , PRUNING_DEPTH , VERIFY_LOOKAHEAD ,
134134} ;
135135
136136#[ cfg( feature = "transparent-inputs" ) ]
@@ -3010,12 +3010,21 @@ pub(crate) fn set_transaction_status(
30103010 match status {
30113011 TransactionStatus :: TxidNotRecognized | TransactionStatus :: NotInMainChain => {
30123012 // Remove the txid from the retrieval queue unless an unexpired transaction having that
3013- // txid exists in the transactions table.
3013+ // txid exists in the transactions table, with a `VERIFY_LOOKAHEAD` buffer .
30143014 if let Some ( chain_tip) = chain_tip_height ( conn) ? {
30153015 conn. execute (
30163016 "DELETE FROM tx_retrieval_queue
30173017 WHERE txid = :txid
3018- AND request_expiry <= :chain_tip" ,
3018+ AND request_expiry <= :chain_tip
3019+ AND txid NOT IN (
3020+ SELECT tx.txid
3021+ FROM transactions tx
3022+ WHERE tx.mined_height IS NULL
3023+ AND (
3024+ tx.expiry_height == 0 -- tx will never expire
3025+ OR tx.expiry_height > :chain_tip -- tx is unexpired
3026+ )
3027+ )" ,
30193028 named_params ! [
30203029 ":txid" : txid. as_ref( ) ,
30213030 ":chain_tip" : u32 :: from( chain_tip)
@@ -3054,6 +3063,67 @@ pub(crate) fn set_transaction_status(
30543063 Ok ( ( ) )
30553064}
30563065
3066+ pub ( crate ) fn refresh_status_requests (
3067+ conn : & rusqlite:: Transaction ,
3068+ ) -> Result < ( ) , SqliteClientError > {
3069+ if let Some ( chain_tip) = chain_tip_height ( conn) ? {
3070+ let mut unmined_query = conn. prepare (
3071+ "SELECT
3072+ t.txid,
3073+ t.expiry_height,
3074+ r.query_type IS NOT NULL AS status_request_exists
3075+ FROM transactions t
3076+ LEFT OUTER JOIN tx_retrieval_queue r ON r.txid = t.txid
3077+ WHERE t.mined_height IS NULL
3078+ AND (
3079+ t.expiry_height IS NULL -- expiry is unknown
3080+ OR t.expiry_height = 0 -- tx will note expire
3081+ OR t.expiry_height > :chain_tip -- tx is unexpired
3082+ )
3083+ AND (
3084+ r.query_type IS NULL
3085+ OR r.query_type = :status_type
3086+ )" ,
3087+ ) ?;
3088+
3089+ let mut rows = unmined_query. query ( named_params ! {
3090+ ":chain_tip" : u32 :: from( chain_tip) ,
3091+ ":status_type" : TxQueryType :: Status . code( ) ,
3092+ } ) ?;
3093+ while let Some ( row) = rows. next ( ) ? {
3094+ let txid = row. get :: < _ , Vec < u8 > > ( "txid" ) ?;
3095+ let tx_expiry = row. get :: < _ , Option < u32 > > ( "expiry_height" ) ?;
3096+
3097+ if row. get :: < _ , bool > ( "status_request_exists" ) ? {
3098+ conn. execute (
3099+ "UPDATE tx_retrieval_queue
3100+ SET request_expiry = :new_expiry
3101+ WHERE txid = :txid
3102+ AND query_type = :status_type" ,
3103+ named_params ! {
3104+ ":new_expiry" : tx_retrieval_expiry( chain_tip, tx_expiry) ,
3105+ ":txid" : & txid[ ..] ,
3106+ ":status_type" : TxQueryType :: Status . code( ) ,
3107+ } ,
3108+ ) ?;
3109+ } else {
3110+ conn. execute (
3111+ "INSERT INTO tx_retrieval_queue (txid, query_type, request_expiry)
3112+ VALUES (:txid, :status_type, :new_expiry)
3113+ ON CONFLICT (txid) DO NOTHING" ,
3114+ named_params ! {
3115+ ":new_expiry" : tx_retrieval_expiry( chain_tip, tx_expiry) ,
3116+ ":txid" : & txid[ ..] ,
3117+ ":status_type" : TxQueryType :: Status . code( ) ,
3118+ } ,
3119+ ) ?;
3120+ }
3121+ }
3122+ }
3123+
3124+ Ok ( ( ) )
3125+ }
3126+
30573127/// Truncates the database to at most the given height.
30583128///
30593129/// If the requested height is greater than or equal to the height of the last scanned
@@ -4118,7 +4188,12 @@ pub(crate) fn queue_tx_retrieval(
41184188 txids : impl Iterator < Item = TxId > ,
41194189 dependent_tx_ref : Option < TxRef > ,
41204190) -> Result < ( ) , SqliteClientError > {
4121- let chain_tip = chain_tip_height ( conn) ?;
4191+ let chain_tip = match chain_tip_height ( conn) ? {
4192+ Some ( h) => h,
4193+ None => {
4194+ return Ok ( ( ) ) ;
4195+ }
4196+ } ;
41224197
41234198 let mut q_tx_expiry = conn. prepare_cached (
41244199 "SELECT expiry_height
@@ -4150,7 +4225,7 @@ pub(crate) fn queue_tx_retrieval(
41504225 ) ?;
41514226
41524227 for txid in txids {
4153- let request_expiry = q_tx_expiry
4228+ let tx_expiry = q_tx_expiry
41544229 . query_row (
41554230 named_params ! {
41564231 ":txid" : txid. as_ref( ) ,
@@ -4165,16 +4240,34 @@ pub(crate) fn queue_tx_retrieval(
41654240 ":status_type" : TxQueryType :: Status . code( ) ,
41664241 ":enhancement_type" : TxQueryType :: Enhancement . code( ) ,
41674242 ":dependent_transaction_id" : dependent_tx_ref. map( |r| r. 0 ) ,
4168- ":request_expiry" : request_expiry. map_or_else(
4169- || chain_tip. map( |h| u32 :: from( h) + DEFAULT_TX_EXPIRY_DELTA ) ,
4170- Some
4171- )
4243+ ":request_expiry" : tx_retrieval_expiry( chain_tip, tx_expiry)
41724244 } ) ?;
41734245 }
41744246
41754247 Ok ( ( ) )
41764248}
41774249
4250+ // Computes a block height at which transaction status requests should be considered
4251+ // expired. Note that even expired requests will be serviced; expiry is only used
4252+ // to determine when a request can be deleted and not retried.
4253+ //
4254+ // TODO: the underlying `request_expiry` mechanism that this method is being used to configure is
4255+ // not sufficiently precise and should probably be scrapped in favor of something better; however,
4256+ // this will do for now.
4257+ fn tx_retrieval_expiry ( chain_tip_height : BlockHeight , tx_expiry : Option < u32 > ) -> u32 {
4258+ match tx_expiry {
4259+ // If the transaction never expires or we don't know its expiry, keep checking for the next
4260+ // expiry delta from the chain tip.
4261+ Some ( 0 ) | None => u32:: from ( chain_tip_height) + DEFAULT_TX_EXPIRY_DELTA ,
4262+ // If the transaction expires in the future, the request can expire with the transaction.
4263+ Some ( h) if h > u32:: from ( chain_tip_height) => h + VERIFY_LOOKAHEAD ,
4264+ // The transaction has already expired; we can keep checking for a few blocks, but if we
4265+ // don't get a positive response for the transaction being mined soon, we can assume it
4266+ // will never be mined.
4267+ Some ( _) => u32:: from ( chain_tip_height) + VERIFY_LOOKAHEAD ,
4268+ }
4269+ }
4270+
41784271/// Returns the vector of [`TransactionDataRequest`]s that represents the information needed by the
41794272/// wallet backend in order to be able to present a complete view of wallet history and memo data.
41804273pub ( crate ) fn transaction_data_requests (
0 commit comments