diff --git a/base_layer/p2p/src/proto/message_type.proto b/base_layer/p2p/src/proto/message_type.proto index 91f66cfd3b..7363b2e23d 100644 --- a/base_layer/p2p/src/proto/message_type.proto +++ b/base_layer/p2p/src/proto/message_type.proto @@ -26,6 +26,7 @@ enum TariMessageType { TariMessageTypeMempoolResponse = 72; TariMessageTypeTransactionFinalized = 73; TariMessageTypeTransactionCancelled = 74; + // -- DAN Messages -- TariMessageTypeDanConsensusMessage = 101; // -- Extended -- diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 5202220dbc..4bac4520dd 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -884,7 +884,6 @@ where self.last_seen_tip_height, None, ); - let join_handle = tokio::spawn(protocol.execute()); join_handles.push(join_handle); @@ -1574,6 +1573,12 @@ where "A repeated Transaction (TxId: {}) has been received but has been previously cancelled or rejected", tx.tx_id ); + tokio::spawn(send_transaction_cancelled_message( + tx.tx_id, + source_pubkey, + self.resources.outbound_message_service.clone(), + )); + return Ok(()); } diff --git a/integration_tests/features/WalletTransactions.feature b/integration_tests/features/WalletTransactions.feature index 59b6f81fa9..a74efade21 100644 --- a/integration_tests/features/WalletTransactions.feature +++ b/integration_tests/features/WalletTransactions.feature @@ -338,3 +338,21 @@ Feature: Wallet Transactions And I wait 5 seconds Then I restart wallet WALLET_RECV Then I wait for wallet WALLET_RECV to have at least 1000000 uT + +@critical + Scenario: Wallet should cancel stale transactions + Given I have a seed node NODE + And I have 1 base nodes connected to all seed nodes + And I have non-default wallet WALLET_SENDER connected to all seed nodes using StoreAndForwardOnly + And I have wallet WALLET_RECV connected to all seed nodes + And I have mining node MINER connected to base node NODE and wallet WALLET_SENDER + And mining node MINER mines 5 blocks + Then all nodes are at height 5 + Then I wait for wallet WALLET_SENDER to have at least 10000000000 uT + And I stop wallet WALLET_RECV + When I wait 15 seconds + And I send 1000000 uT without waiting for broadcast from wallet WALLET_SENDER to wallet WALLET_RECV at fee 100 + Then I cancel last transaction in wallet WALLET_SENDER + Then I restart wallet WALLET_RECV + When I wait 15 seconds + When wallet WALLET_RECV detects last transaction is Cancelled diff --git a/integration_tests/features/support/wallet_steps.js b/integration_tests/features/support/wallet_steps.js index 5dd9f68a6e..abf2008858 100644 --- a/integration_tests/features/support/wallet_steps.js +++ b/integration_tests/features/support/wallet_steps.js @@ -1310,6 +1310,36 @@ Then( } ); +Then( + /wallet (.*) detects last transaction is Cancelled/, + { timeout: 120 * 1000 }, + async function (walletName) { + const wallet = this.getWallet(walletName); + const walletClient = await wallet.connectClient(); + + let lastTxId = this.lastResult.results[0].transaction_id; + console.log( + "Waiting for Transaction ", + lastTxId, + "to be cancelled in wallet", + walletName + ); + + await waitFor( + async () => walletClient.isTransactionCancelled(lastTxId), + true, + 115 * 1000, + 5 * 1000, + 5 + ); + const transactionPending = await walletClient.isTransactionPending( + lastTxId + ); + + expect(transactionPending).to.equal(true); + } +); + Then( /wallet (.*) detects all transactions are at least Completed/, { timeout: 1200 * 1000 }, // Must allow for many transactions; dynamic time out used below diff --git a/integration_tests/helpers/walletClient.js b/integration_tests/helpers/walletClient.js index b852ab4931..dd6317d5ce 100644 --- a/integration_tests/helpers/walletClient.js +++ b/integration_tests/helpers/walletClient.js @@ -252,6 +252,22 @@ class WalletClient { } } + async isTransactionCancelled(tx_id) { + try { + const txnDetails = await this.getTransactionInfo({ + transaction_ids: [tx_id.toString()], + }); + if (transactionStatus().indexOf(txnDetails.transactions[0].status) == 7) { + return true; + } else { + return false; + } + } catch (err) { + // Any error here must be treated as if the required status was not achieved + return false; + } + } + async isTransactionAtLeastCompleted(tx_id) { try { const txnDetails = await this.getTransactionInfo({