From 297c8285321aaf7d1f7be6314fcf0fdc520753ee Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Wed, 24 Apr 2024 09:03:58 +0100 Subject: [PATCH] Prevent wallet attempting to re-pay a cancelled invoice transaction (#707) --- controller/tests/invoice.rs | 42 +++++++++++++++++++++++++++++++++ libwallet/src/api_impl/owner.rs | 3 +++ libwallet/src/error.rs | 4 ++++ 3 files changed, 49 insertions(+) diff --git a/controller/tests/invoice.rs b/controller/tests/invoice.rs index e637b4cf..b1adaa55 100644 --- a/controller/tests/invoice.rs +++ b/controller/tests/invoice.rs @@ -246,6 +246,48 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { })?; assert_eq!(slate.state, SlateState::Invoice3); + // test that payee can only cancel once + let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false); + bh += 3; + + wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { + // Wallet 2 inititates an invoice transaction, requesting payment + let args = IssueInvoiceTxArgs { + amount: reward * 2, + ..Default::default() + }; + slate = api.issue_invoice_tx(m, args)?; + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Invoice1); + + let orig_slate = slate.clone(); + + wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { + // Wallet 1 receives the invoice transaction + let args = InitTxArgs { + src_acct_name: None, + amount: slate.amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + slate = api.process_invoice_tx(m, &slate, args.clone())?; + api.tx_lock_outputs(m, &slate)?; + + // Wallet 1 cancels the invoice transaction + api.cancel_tx(m, None, Some(slate.id))?; + + // Wallet 1 attempts to repay again + let res = api.process_invoice_tx(m, &orig_slate, args); + assert!(res.is_err()); + + Ok(()) + })?; + assert_eq!(slate.state, SlateState::Invoice2); + // let logging finish stopper.store(false, Ordering::Relaxed); thread::sleep(Duration::from_millis(200)); diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index 9563a5a8..6432656e 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -680,6 +680,9 @@ where if t.tx_type == TxLogEntryType::TxSent { return Err(Error::TransactionAlreadyReceived(ret_slate.id.to_string())); } + if t.tx_type == TxLogEntryType::TxSentCancelled { + return Err(Error::TransactionWasCancelled(ret_slate.id.to_string())); + } } let height = w.w2n_client().get_chain_tip()?.0; diff --git a/libwallet/src/error.rs b/libwallet/src/error.rs index 3ac0bc94..fe4bbd8c 100644 --- a/libwallet/src/error.rs +++ b/libwallet/src/error.rs @@ -153,6 +153,10 @@ pub enum Error { #[error("Transaction {0} has already been received")] TransactionAlreadyReceived(String), + /// Transaction has been cancelled + #[error("Transaction {0} has been cancelled")] + TransactionWasCancelled(String), + /// Attempt to repost a transaction that's not completed and stored #[error("Transaction building not completed: {0}")] TransactionBuildingNotCompleted(u32),