From 917312bb681cea60cb35f4e85016caea948221ac Mon Sep 17 00:00:00 2001 From: David Burkett Date: Thu, 28 Mar 2024 10:08:08 -0400 Subject: [PATCH] Add context menu option for rebroadcasting unconfirmed transactions --- src/interfaces/wallet.cpp | 6 ++++++ src/interfaces/wallet.h | 6 ++++++ src/qt/transactionview.cpp | 19 +++++++++++++++++ src/qt/transactionview.h | 2 ++ src/wallet/wallet.cpp | 43 ++++++++++++++++++++++++++++++++++++++ src/wallet/wallet.h | 6 ++++++ 6 files changed, 82 insertions(+) diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 2b8d7f6ed5..4d1c4c01d9 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -368,6 +368,12 @@ public: return feebumper::CommitTransaction(*m_wallet.get(), txid, std::move(mtx), errors, bumped_txid) == feebumper::Result::OK; } + bool transactionCanBeRebroadcast(const uint256& txid) override { return m_wallet->TransactionCanBeRebroadcast(txid); } + bool rebroadcastTransaction(const uint256& txid) override + { + LOCK(m_wallet->cs_wallet); + return m_wallet->RebroadcastTransaction(txid); + } CTransactionRef getTx(const uint256& txid) override { LOCK(m_wallet->cs_wallet); diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 71a8d52ee7..f56ff4cc94 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -185,6 +185,12 @@ public: std::vector& errors, uint256& bumped_txid) = 0; + //! Return whether transaction can be rebroadcast. + virtual bool transactionCanBeRebroadcast(const uint256& txid) = 0; + + //! Rebroadcast transaction. + virtual bool rebroadcastTransaction(const uint256& txid) = 0; + //! Get a transaction. virtual CTransactionRef getTx(const uint256& txid) = 0; diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index a79e14f0aa..71277bd0dd 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -151,6 +151,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa abandonAction = new QAction(tr("Abandon transaction"), this); bumpFeeAction = new QAction(tr("Increase transaction fee"), this); bumpFeeAction->setObjectName("bumpFeeAction"); + rebroadcastAction = new QAction(tr("Rebroadcast transaction"), this); copyAddressAction = new QAction(tr("Copy address"), this); copyLabelAction = new QAction(tr("Copy label"), this); QAction *copyAmountAction = new QAction(tr("Copy amount"), this); @@ -172,6 +173,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa contextMenu->addSeparator(); contextMenu->addAction(bumpFeeAction); contextMenu->addAction(abandonAction); + contextMenu->addAction(rebroadcastAction); contextMenu->addAction(editLabelAction); connect(dateWidget, static_cast(&QComboBox::activated), this, &TransactionView::chooseDate); @@ -187,6 +189,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa connect(bumpFeeAction, &QAction::triggered, this, &TransactionView::bumpFee); connect(abandonAction, &QAction::triggered, this, &TransactionView::abandonTx); + connect(rebroadcastAction, &QAction::triggered, this, &TransactionView::rebroadcastTx); connect(copyAddressAction, &QAction::triggered, this, &TransactionView::copyAddress); connect(copyLabelAction, &QAction::triggered, this, &TransactionView::copyLabel); connect(copyAmountAction, &QAction::triggered, this, &TransactionView::copyAmount); @@ -394,6 +397,7 @@ void TransactionView::contextualMenu(const QPoint &point) hash.SetHex(selection.at(0).data(TransactionTableModel::TxHashRole).toString().toStdString()); abandonAction->setEnabled(model->wallet().transactionCanBeAbandoned(hash)); bumpFeeAction->setEnabled(model->wallet().transactionCanBeBumped(hash)); + rebroadcastAction->setEnabled(model->wallet().transactionCanBeRebroadcast(hash)); copyAddressAction->setEnabled(GUIUtil::hasEntryData(transactionView, 0, TransactionTableModel::AddressRole)); copyLabelAction->setEnabled(GUIUtil::hasEntryData(transactionView, 0, TransactionTableModel::LabelRole)); @@ -443,6 +447,21 @@ void TransactionView::bumpFee() } } +void TransactionView::rebroadcastTx() +{ + if (!transactionView || !transactionView->selectionModel()) + return; + QModelIndexList selection = transactionView->selectionModel()->selectedRows(0); + + // get the hash from the TxHashRole (QVariant / QString) + uint256 hash; + QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString(); + hash.SetHex(hashQStr.toStdString()); + + // Rebroadcast the wallet transaction over the walletModel + model->wallet().rebroadcastTransaction(hash); +} + void TransactionView::copyAddress() { GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole); diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h index 9ce7f4ad97..2820315631 100644 --- a/src/qt/transactionview.h +++ b/src/qt/transactionview.h @@ -77,6 +77,7 @@ private: QDateTimeEdit *dateTo; QAction *abandonAction{nullptr}; QAction *bumpFeeAction{nullptr}; + QAction *rebroadcastAction{nullptr}; QAction *copyAddressAction{nullptr}; QAction *copyLabelAction{nullptr}; @@ -103,6 +104,7 @@ private Q_SLOTS: void updateWatchOnlyColumn(bool fHaveWatchOnly); void abandonTx(); void bumpFee(); + void rebroadcastTx(); Q_SIGNALS: void doubleClicked(const QModelIndex&); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 9dd65ce8bd..6f46bb6357 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1175,6 +1175,49 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) return true; } +bool CWallet::TransactionCanBeRebroadcast(const uint256& hashTx) const +{ + LOCK(cs_wallet); + + // Can't relay if wallet is not broadcasting + if (!GetBroadcastTransactions()) return false; + + const CWalletTx* wtx = GetWalletTx(hashTx); + return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0; +} + +bool CWallet::RebroadcastTransaction(const uint256& hashTx) +{ + LOCK(cs_wallet); + + // Can't relay if wallet is not broadcasting + if (!GetBroadcastTransactions()) return false; + + // Can't mark abandoned if confirmed or in mempool + auto it = mapWallet.find(hashTx); + assert(it != mapWallet.end()); + const CWalletTx& wtx = it->second; + + // Don't relay abandoned transactions + if (wtx.isAbandoned()) return false; + // Don't try to submit coinbase or HogEx transactions. These would fail anyway but would + // cause log spam. + if (wtx.IsCoinBase() || wtx.IsHogEx()) return false; + // Don't try to submit conflicted or confirmed transactions. + if (wtx.GetDepthInMainChain() != 0) return false; + + // Submit transaction to mempool for relay + WalletLogPrintf("Submitting wtx %s to mempool for relay\n", wtx.GetHash().ToString()); + + std::string err_string; + const bool ret = chain().broadcastTransaction(wtx.tx, m_default_max_tx_fee, true, err_string); + if (!ret) { + WalletLogPrintf("RebroadcastTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string); + } + + return ret; +} + void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, const uint256& hashTx) { LOCK(cs_wallet); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 616a4f1832..e611083594 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1293,6 +1293,12 @@ public: /* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */ bool AbandonTransaction(const uint256& hashTx); + /** Return whether transaction can be rebroadcast */ + bool TransactionCanBeRebroadcast(const uint256& hashTx) const; + + /* Rebroadcast a transaction. */ + bool RebroadcastTransaction(const uint256& hashTx); + /** Mark a transaction as replaced by another transaction (e.g., BIP 125). */ bool MarkReplaced(const uint256& originalHash, const uint256& newHash);