Skip to content

Commit 962cd3f

Browse files
committed
Merge #9697: [Qt] simple fee bumper with user verification
a387837 Make sure we re-check the conditions of a feebump during commit (Jonas Schnelli) 9b9ca53 Only update the transactionrecord if the fee bump has been commited (Jonas Schnelli) 6ed4368 Make sure we use nTxConfirmTarget during Qt fee bumps (Jonas Schnelli) be08fc3 Make sure we always update the table row after a bumpfee call (Jonas Schnelli) 2678d3d Show old-fee, increase a new-fee in Qt fee bumper confirmation dialog (Jonas Schnelli) 2ec911f Add cs_wallet lock assertion to SignTransaction() (Jonas Schnelli) fbf385c [Qt] simple fee bumper with user verification (Jonas Schnelli) Tree-SHA512: a3ce626201abf64cee496dd1d83870de51ba633de40c48eb0219c3eba5085c038af34c284512130d2544de20c1bff9fea1b78f92e3574c21dd4e96c11b8e7d76
2 parents 2acface + a387837 commit 962cd3f

File tree

9 files changed

+152
-23
lines changed

9 files changed

+152
-23
lines changed

src/qt/sendcoinsdialog.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131
#include <QTextDocument>
3232
#include <QTimer>
3333

34-
#define SEND_CONFIRM_DELAY 3
35-
3634
SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
3735
QDialog(parent),
3836
ui(new Ui::SendCoinsDialog),

src/qt/sendcoinsdialog.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,14 @@ private Q_SLOTS:
100100
};
101101

102102

103+
#define SEND_CONFIRM_DELAY 3
103104

104105
class SendConfirmationDialog : public QMessageBox
105106
{
106107
Q_OBJECT
107108

108109
public:
109-
SendConfirmationDialog(const QString &title, const QString &text, int secDelay = 0, QWidget *parent = 0);
110+
SendConfirmationDialog(const QString &title, const QString &text, int secDelay = SEND_CONFIRM_DELAY, QWidget *parent = 0);
110111
int exec();
111112

112113
private Q_SLOTS:

src/qt/transactionview.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "guiutil.h"
1212
#include "optionsmodel.h"
1313
#include "platformstyle.h"
14+
#include "sendcoinsdialog.h"
1415
#include "transactiondescdialog.h"
1516
#include "transactionfilterproxy.h"
1617
#include "transactionrecord.h"
@@ -37,7 +38,7 @@
3738

3839
TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *parent) :
3940
QWidget(parent), model(0), transactionProxyModel(0),
40-
transactionView(0), abandonAction(0), columnResizingFixer(0)
41+
transactionView(0), abandonAction(0), bumpFeeAction(0), columnResizingFixer(0)
4142
{
4243
// Build filter row
4344
setContentsMargins(0,0,0,0);
@@ -138,6 +139,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
138139

139140
// Actions
140141
abandonAction = new QAction(tr("Abandon transaction"), this);
142+
bumpFeeAction = new QAction(tr("Increase transaction fee"), this);
141143
QAction *copyAddressAction = new QAction(tr("Copy address"), this);
142144
QAction *copyLabelAction = new QAction(tr("Copy label"), this);
143145
QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
@@ -156,6 +158,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
156158
contextMenu->addAction(copyTxPlainText);
157159
contextMenu->addAction(showDetailsAction);
158160
contextMenu->addSeparator();
161+
contextMenu->addAction(bumpFeeAction);
159162
contextMenu->addAction(abandonAction);
160163
contextMenu->addAction(editLabelAction);
161164

@@ -173,6 +176,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
173176
connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex)));
174177
connect(view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint)));
175178

179+
connect(bumpFeeAction, SIGNAL(triggered()), this, SLOT(bumpFee()));
176180
connect(abandonAction, SIGNAL(triggered()), this, SLOT(abandonTx()));
177181
connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress()));
178182
connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel()));
@@ -372,6 +376,7 @@ void TransactionView::contextualMenu(const QPoint &point)
372376
uint256 hash;
373377
hash.SetHex(selection.at(0).data(TransactionTableModel::TxHashRole).toString().toStdString());
374378
abandonAction->setEnabled(model->transactionCanBeAbandoned(hash));
379+
bumpFeeAction->setEnabled(model->transactionSignalsRBF(hash));
375380

376381
if(index.isValid())
377382
{
@@ -397,6 +402,24 @@ void TransactionView::abandonTx()
397402
model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, false);
398403
}
399404

405+
void TransactionView::bumpFee()
406+
{
407+
if(!transactionView || !transactionView->selectionModel())
408+
return;
409+
QModelIndexList selection = transactionView->selectionModel()->selectedRows(0);
410+
411+
// get the hash from the TxHashRole (QVariant / QString)
412+
uint256 hash;
413+
QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString();
414+
hash.SetHex(hashQStr.toStdString());
415+
416+
// Bump tx fee over the walletModel
417+
if (model->bumpFee(hash)) {
418+
// Update the table
419+
model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, false);
420+
}
421+
}
422+
400423
void TransactionView::copyAddress()
401424
{
402425
GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole);

src/qt/transactionview.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class TransactionView : public QWidget
7676
QDateTimeEdit *dateFrom;
7777
QDateTimeEdit *dateTo;
7878
QAction *abandonAction;
79+
QAction *bumpFeeAction;
7980

8081
QWidget *createDateRangeWidget();
8182

@@ -99,6 +100,7 @@ private Q_SLOTS:
99100
void openThirdPartyTxUrl(QString url);
100101
void updateWatchOnlyColumn(bool fHaveWatchOnly);
101102
void abandonTx();
103+
void bumpFee();
102104

103105
Q_SIGNALS:
104106
void doubleClicked(const QModelIndex&);

src/qt/walletmodel.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,29 @@
88
#include "consensus/validation.h"
99
#include "guiconstants.h"
1010
#include "guiutil.h"
11+
#include "optionsmodel.h"
1112
#include "paymentserver.h"
1213
#include "recentrequeststablemodel.h"
14+
#include "sendcoinsdialog.h"
1315
#include "transactiontablemodel.h"
1416

1517
#include "base58.h"
1618
#include "chain.h"
1719
#include "keystore.h"
1820
#include "validation.h"
1921
#include "net.h" // for g_connman
22+
#include "policy/rbf.h"
2023
#include "sync.h"
2124
#include "ui_interface.h"
2225
#include "util.h" // for GetBoolArg
26+
#include "wallet/feebumper.h"
2327
#include "wallet/wallet.h"
2428
#include "wallet/walletdb.h" // for BackupWallet
2529

2630
#include <stdint.h>
2731

2832
#include <QDebug>
33+
#include <QMessageBox>
2934
#include <QSet>
3035
#include <QTimer>
3136

@@ -693,6 +698,86 @@ bool WalletModel::abandonTransaction(uint256 hash) const
693698
return wallet->AbandonTransaction(hash);
694699
}
695700

701+
bool WalletModel::transactionSignalsRBF(uint256 hash) const
702+
{
703+
LOCK2(cs_main, wallet->cs_wallet);
704+
const CWalletTx *wtx = wallet->GetWalletTx(hash);
705+
if (wtx && SignalsOptInRBF(*wtx))
706+
return true;
707+
return false;
708+
}
709+
710+
bool WalletModel::bumpFee(uint256 hash)
711+
{
712+
std::unique_ptr<CFeeBumper> feeBump;
713+
{
714+
LOCK2(cs_main, wallet->cs_wallet);
715+
feeBump.reset(new CFeeBumper(wallet, hash, nTxConfirmTarget, false, 0, true));
716+
}
717+
if (feeBump->getResult() != BumpFeeResult::OK)
718+
{
719+
QMessageBox::critical(0, tr("Fee bump error"), tr("Increasing transaction fee failed") + "<br />(" +
720+
(feeBump->getErrors().size() ? QString::fromStdString(feeBump->getErrors()[0]) : "") +")");
721+
return false;
722+
}
723+
724+
// allow a user based fee verification
725+
QString questionString = tr("Do you want to increase the fee?");
726+
questionString.append("<br />");
727+
CAmount oldFee = feeBump->getOldFee();
728+
CAmount newFee = feeBump->getNewFee();
729+
questionString.append("<table style=\"text-align: left;\">");
730+
questionString.append("<tr><td>");
731+
questionString.append(tr("Current fee:"));
732+
questionString.append("</td><td>");
733+
questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), oldFee));
734+
questionString.append("</td></tr><tr><td>");
735+
questionString.append(tr("Increase:"));
736+
questionString.append("</td><td>");
737+
questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), newFee - oldFee));
738+
questionString.append("</td></tr><tr><td>");
739+
questionString.append(tr("New fee:"));
740+
questionString.append("</td><td>");
741+
questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), newFee));
742+
questionString.append("</td></tr></table>");
743+
SendConfirmationDialog confirmationDialog(tr("Confirm fee bump"), questionString);
744+
confirmationDialog.exec();
745+
QMessageBox::StandardButton retval = (QMessageBox::StandardButton)confirmationDialog.result();
746+
747+
// cancel sign&broadcast if users doesn't want to bump the fee
748+
if (retval != QMessageBox::Yes) {
749+
return false;
750+
}
751+
752+
WalletModel::UnlockContext ctx(requestUnlock());
753+
if(!ctx.isValid())
754+
{
755+
return false;
756+
}
757+
758+
// sign bumped transaction
759+
bool res = false;
760+
{
761+
LOCK2(cs_main, wallet->cs_wallet);
762+
res = feeBump->signTransaction(wallet);
763+
}
764+
if (!res) {
765+
QMessageBox::critical(0, tr("Fee bump error"), tr("Can't sign transaction."));
766+
return false;
767+
}
768+
// commit the bumped transaction
769+
{
770+
LOCK2(cs_main, wallet->cs_wallet);
771+
res = feeBump->commit(wallet);
772+
}
773+
if(!res) {
774+
QMessageBox::critical(0, tr("Fee bump error"), tr("Could not commit transaction") + "<br />(" +
775+
QString::fromStdString(feeBump->getErrors()[0])+")");
776+
return false;
777+
}
778+
return true;
779+
}
780+
696781
bool WalletModel::isWalletEnabled()
697782
{
698783
return !GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET);

src/qt/walletmodel.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ class WalletModel : public QObject
207207
bool transactionCanBeAbandoned(uint256 hash) const;
208208
bool abandonTransaction(uint256 hash) const;
209209

210+
bool transactionSignalsRBF(uint256 hash) const;
211+
bool bumpFee(uint256 hash);
212+
210213
static bool isWalletEnabled();
211214

212215
bool hdEnabled() const;

src/wallet/feebumper.cpp

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,31 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *pWal
4141
return GetVirtualTransactionSize(txNew);
4242
}
4343

44+
bool CFeeBumper::preconditionChecks(const CWallet *pWallet, const CWalletTx& wtx) {
45+
if (pWallet->HasWalletSpend(wtx.GetHash())) {
46+
vErrors.push_back("Transaction has descendants in the wallet");
47+
currentResult = BumpFeeResult::INVALID_PARAMETER;
48+
return false;
49+
}
50+
51+
{
52+
LOCK(mempool.cs);
53+
auto it_mp = mempool.mapTx.find(wtx.GetHash());
54+
if (it_mp != mempool.mapTx.end() && it_mp->GetCountWithDescendants() > 1) {
55+
vErrors.push_back("Transaction has descendants in the mempool");
56+
currentResult = BumpFeeResult::INVALID_PARAMETER;
57+
return false;
58+
}
59+
}
60+
61+
if (wtx.GetDepthInMainChain() != 0) {
62+
vErrors.push_back("Transaction has been mined, or is conflicted with a mined transaction");
63+
currentResult = BumpFeeResult::WALLET_ERROR;
64+
return false;
65+
}
66+
return true;
67+
}
68+
4469
CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConfirmTarget, bool specifiedConfirmTarget, CAmount totalFee, bool newTxReplaceable)
4570
:
4671
txid(std::move(txidIn)),
@@ -58,25 +83,7 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConf
5883
auto it = pWallet->mapWallet.find(txid);
5984
const CWalletTx& wtx = it->second;
6085

61-
if (pWallet->HasWalletSpend(txid)) {
62-
vErrors.push_back("Transaction has descendants in the wallet");
63-
currentResult = BumpFeeResult::INVALID_PARAMETER;
64-
return;
65-
}
66-
67-
{
68-
LOCK(mempool.cs);
69-
auto it_mp = mempool.mapTx.find(txid);
70-
if (it_mp != mempool.mapTx.end() && it_mp->GetCountWithDescendants() > 1) {
71-
vErrors.push_back("Transaction has descendants in the mempool");
72-
currentResult = BumpFeeResult::INVALID_PARAMETER;
73-
return;
74-
}
75-
}
76-
77-
if (wtx.GetDepthInMainChain() != 0) {
78-
vErrors.push_back("Transaction has been mined, or is conflicted with a mined transaction");
79-
currentResult = BumpFeeResult::WALLET_ERROR;
86+
if (!preconditionChecks(pWallet, wtx)) {
8087
return;
8188
}
8289

@@ -248,6 +255,11 @@ bool CFeeBumper::commit(CWallet *pWallet)
248255
}
249256
CWalletTx& oldWtx = pWallet->mapWallet[txid];
250257

258+
// make sure the transaction still has no descendants and hasen't been mined in the meantime
259+
if (!preconditionChecks(pWallet, oldWtx)) {
260+
return false;
261+
}
262+
251263
CWalletTx wtxBumped(pWallet, MakeTransactionRef(std::move(mtx)));
252264
// commit/broadcast the tx
253265
CReserveKey reservekey(pWallet);

src/wallet/feebumper.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <primitives/transaction.h>
99

1010
class CWallet;
11+
class CWalletTx;
1112
class uint256;
1213

1314
enum class BumpFeeResult
@@ -44,6 +45,8 @@ class CFeeBumper
4445
bool commit(CWallet *pWalletNonConst);
4546

4647
private:
48+
bool preconditionChecks(const CWallet *pWallet, const CWalletTx& wtx);
49+
4750
const uint256 txid;
4851
uint256 bumpedTxid;
4952
CMutableTransaction mtx;

src/wallet/wallet.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2311,6 +2311,8 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
23112311

23122312
bool CWallet::SignTransaction(CMutableTransaction &tx)
23132313
{
2314+
AssertLockHeld(cs_wallet); // mapWallet
2315+
23142316
// sign the new tx
23152317
CTransaction txNewConst(tx);
23162318
int nIn = 0;

0 commit comments

Comments
 (0)