diff options
author | Wladimir J. van der Laan <laanwj@gmail.com> | 2014-11-19 16:11:15 +0100 |
---|---|---|
committer | Wladimir J. van der Laan <laanwj@gmail.com> | 2014-11-19 16:27:04 +0100 |
commit | b7fe9cd04cd2756b1ce40b668d25a130441ba847 (patch) | |
tree | 8d56d76ed4b19a0a5689a5ec5c866c489ed62c78 | |
parent | 8c4185338001b8ba3ef247b09fb63afb65961c10 (diff) | |
parent | c1c9d5b415fda7d1310c23857e57d98ac14c3063 (diff) |
Merge pull request #5200
c1c9d5b [Qt] Add Smartfee to GUI (Cozz Lovan)
e7876b2 [Wallet] Prevent user from paying a non-sense fee (Cozz Lovan)
ed3e5e4 [Wallet] Add global boolean whether to pay at least the custom fee (default=true) (Cozz Lovan)
0ed9675 [Wallet] Add global boolean whether to send free transactions (default=true) (Cozz Lovan)
-rw-r--r-- | src/init.cpp | 2 | ||||
-rw-r--r-- | src/qt/bitcoinamountfield.cpp | 6 | ||||
-rw-r--r-- | src/qt/bitcoinamountfield.h | 3 | ||||
-rw-r--r-- | src/qt/coincontroldialog.cpp | 95 | ||||
-rw-r--r-- | src/qt/coincontroldialog.h | 2 | ||||
-rw-r--r-- | src/qt/forms/optionsdialog.ui | 78 | ||||
-rw-r--r-- | src/qt/forms/sendcoinsdialog.ui | 600 | ||||
-rw-r--r-- | src/qt/optionsdialog.cpp | 18 | ||||
-rw-r--r-- | src/qt/optionsdialog.h | 1 | ||||
-rw-r--r-- | src/qt/optionsmodel.cpp | 24 | ||||
-rw-r--r-- | src/qt/optionsmodel.h | 2 | ||||
-rw-r--r-- | src/qt/sendcoinsdialog.cpp | 196 | ||||
-rw-r--r-- | src/qt/sendcoinsdialog.h | 13 | ||||
-rw-r--r-- | src/qt/walletmodel.cpp | 4 | ||||
-rw-r--r-- | src/qt/walletmodel.h | 3 | ||||
-rw-r--r-- | src/qt/walletmodeltransaction.cpp | 5 | ||||
-rw-r--r-- | src/qt/walletmodeltransaction.h | 1 | ||||
-rw-r--r-- | src/qt/walletview.cpp | 1 | ||||
-rw-r--r-- | src/txmempool.h | 7 | ||||
-rw-r--r-- | src/wallet.cpp | 12 | ||||
-rw-r--r-- | src/wallet.h | 2 |
21 files changed, 911 insertions, 164 deletions
diff --git a/src/init.cpp b/src/init.cpp index b290d54158..4a9982a1cd 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -290,6 +290,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += " -paytxfee=<amt> " + strprintf(_("Fee (in BTC/kB) to add to transactions you send (default: %s)"), FormatMoney(payTxFee.GetFeePerK())) + "\n"; strUsage += " -rescan " + _("Rescan the block chain for missing wallet transactions") + " " + _("on startup") + "\n"; strUsage += " -salvagewallet " + _("Attempt to recover private keys from a corrupt wallet.dat") + " " + _("on startup") + "\n"; + strUsage += " -sendfreetransactions " + strprintf(_("Send transactions as zero-fee transactions if possible (default: %u)"), 0) + "\n"; strUsage += " -spendzeroconfchange " + strprintf(_("Spend unconfirmed change when sending transactions (default: %u)"), 1) + "\n"; strUsage += " -txconfirmtarget=<n> " + strprintf(_("If paytxfee is not set, include enough fee so transactions are confirmed on average within n blocks (default: %u)"), 1) + "\n"; strUsage += " -upgradewallet " + _("Upgrade wallet to latest format") + " " + _("on startup") + "\n"; @@ -704,6 +705,7 @@ bool AppInit2(boost::thread_group& threadGroup) } nTxConfirmTarget = GetArg("-txconfirmtarget", 1); bSpendZeroConfChange = GetArg("-spendzeroconfchange", true); + fSendFreeTransactions = GetArg("-sendfreetransactions", false); std::string strWalletFile = GetArg("-wallet", "wallet.dat"); #endif // ENABLE_WALLET diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index 2c100337d2..5b8ab23b26 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -221,6 +221,12 @@ void BitcoinAmountField::clear() unit->setCurrentIndex(0); } +void BitcoinAmountField::setEnabled(bool fEnabled) +{ + amount->setEnabled(fEnabled); + unit->setEnabled(fEnabled); +} + bool BitcoinAmountField::validate() { bool valid = false; diff --git a/src/qt/bitcoinamountfield.h b/src/qt/bitcoinamountfield.h index 4ab66001f0..1bad8ce1b8 100644 --- a/src/qt/bitcoinamountfield.h +++ b/src/qt/bitcoinamountfield.h @@ -48,6 +48,9 @@ public: /** Make field empty and ready for new input. */ void clear(); + /** Enable/Disable. */ + void setEnabled(bool fEnabled); + /** Qt messes up the tab chain by default in some cases (issue https://bugreports.qt-project.org/browse/QTBUG-10907), in these cases we have to set it up manually. */ diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index ba0febe546..85b43b7b1f 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -24,6 +24,7 @@ #include <QDialogButtonBox> #include <QFlags> #include <QIcon> +#include <QSettings> #include <QString> #include <QTreeWidget> #include <QTreeWidgetItem> @@ -130,10 +131,22 @@ CoinControlDialog::CoinControlDialog(QWidget *parent) : // default view is sorted by amount desc sortView(COLUMN_AMOUNT_INT64, Qt::DescendingOrder); + + // restore list mode and sortorder as a convenience feature + QSettings settings; + if (settings.contains("nCoinControlMode") && !settings.value("nCoinControlMode").toBool()) + ui->radioTreeMode->click(); + if (settings.contains("nCoinControlSortColumn") && settings.contains("nCoinControlSortOrder")) + sortView(settings.value("nCoinControlSortColumn").toInt(), ((Qt::SortOrder)settings.value("nCoinControlSortOrder").toInt())); } CoinControlDialog::~CoinControlDialog() { + QSettings settings; + settings.setValue("nCoinControlMode", ui->radioListMode->isChecked()); + settings.setValue("nCoinControlSortColumn", sortColumn); + settings.setValue("nCoinControlSortOrder", (int)sortOrder); + delete ui; } @@ -290,19 +303,19 @@ void CoinControlDialog::clipboardAmount() // copy label "Fee" to clipboard void CoinControlDialog::clipboardFee() { - GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" "))); + GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace("~", "")); } // copy label "After fee" to clipboard void CoinControlDialog::clipboardAfterFee() { - GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" "))); + GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace("~", "")); } // copy label "Bytes" to clipboard void CoinControlDialog::clipboardBytes() { - GUIUtil::setClipboard(ui->labelCoinControlBytes->text()); + GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace("~", "")); } // copy label "Priority" to clipboard @@ -320,7 +333,7 @@ void CoinControlDialog::clipboardLowOutput() // copy label "Change" to clipboard void CoinControlDialog::clipboardChange() { - GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" "))); + GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace("~", "")); } // treeview: sort @@ -402,26 +415,22 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) } // return human readable label for priority number -QString CoinControlDialog::getPriorityLabel(const CTxMemPool& pool, double dPriority) +QString CoinControlDialog::getPriorityLabel(double dPriority, double mempoolEstimatePriority) { - // confirmations -> textual description - typedef std::map<unsigned int, QString> PriorityDescription; - const static PriorityDescription priorityDescriptions = boost::assign::map_list_of - (1, tr("highest"))(2, tr("higher"))(3, tr("high")) - (5, tr("medium-high"))(6, tr("medium")) - (10, tr("low-medium"))(15, tr("low")) - (20, tr("lower")); - - BOOST_FOREACH(const PriorityDescription::value_type& i, priorityDescriptions) - { - double p = mempool.estimatePriority(i.first); - if (p > 0 && dPriority >= p) return i.second; - } - // Note: if mempool hasn't accumulated enough history (estimatePriority - // returns -1) we're conservative and classify as "lowest" - if (mempool.estimatePriority(nTxConfirmTarget) <= 0 && AllowFree(dPriority)) - return ">=" + tr("medium"); - return tr("lowest"); + double dPriorityMedium = mempoolEstimatePriority; + + if (dPriorityMedium <= 0) + dPriorityMedium = AllowFreeThreshold(); // not enough data, back to hard-coded + + if (dPriority / 1000000 > dPriorityMedium) return tr("highest"); + else if (dPriority / 100000 > dPriorityMedium) return tr("higher"); + else if (dPriority / 10000 > dPriorityMedium) return tr("high"); + else if (dPriority / 1000 > dPriorityMedium) return tr("medium-high"); + else if (dPriority > dPriorityMedium) return tr("medium"); + else if (dPriority * 10 > dPriorityMedium) return tr("low-medium"); + else if (dPriority * 100 > dPriorityMedium) return tr("low"); + else if (dPriority * 1000 > dPriorityMedium) return tr("lower"); + else return tr("lowest"); } // shows count of locked unspent outputs @@ -470,6 +479,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) double dPriorityInputs = 0; unsigned int nQuantity = 0; int nQuantityUncompressed = 0; + bool fAllowFree = false; vector<COutPoint> vCoinControl; vector<COutput> vOutputs; @@ -522,24 +532,22 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here // Priority + double mempoolEstimatePriority = mempool.estimatePriority(nTxConfirmTarget); dPriority = dPriorityInputs / (nBytes - nBytesInputs + (nQuantityUncompressed * 29)); // 29 = 180 - 151 (uncompressed public keys are over the limit. max 151 bytes of the input are ignored for priority) - sPriorityLabel = CoinControlDialog::getPriorityLabel(mempool, dPriority); + sPriorityLabel = CoinControlDialog::getPriorityLabel(dPriority, mempoolEstimatePriority); - // Voluntary Fee - nPayFee = payTxFee.GetFee(max((unsigned int)1000, nBytes)); + // Fee + nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool); - // Min Fee - if (nPayFee == 0) - { - nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool); - - double dPriorityNeeded = mempool.estimatePriority(nTxConfirmTarget); - if (dPriorityNeeded <= 0 && !AllowFree(dPriority)) // not enough mempool history: never send free - dPriorityNeeded = std::numeric_limits<double>::max(); + // Allow free? + double dPriorityNeeded = mempoolEstimatePriority; + if (dPriorityNeeded <= 0) + dPriorityNeeded = AllowFreeThreshold(); // not enough data, back to hard-coded + fAllowFree = (dPriority >= dPriorityNeeded); - if (nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE && dPriority >= dPriorityNeeded) + if (fSendFreeTransactions) + if (fAllowFree && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE) nPayFee = 0; - } if (nPayAmount > 0) { @@ -595,7 +603,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) l6->setText(sPriorityLabel); // Priority l7->setText(fDust ? tr("yes") : tr("no")); // Dust l8->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nChange)); // Change - if (nPayFee > 0) + if (nPayFee > 0 && !(payTxFee.GetFeePerK() > 0 && fPayAtLeastCustomFee && nBytes < 1000)) { l3->setText("~" + l3->text()); l4->setText("~" + l4->text()); @@ -605,7 +613,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) // turn labels "red" l5->setStyleSheet((nBytes >= MAX_FREE_TRANSACTION_CREATE_SIZE) ? "color:red;" : "");// Bytes >= 1000 - l6->setStyleSheet((dPriority > 0 && !AllowFree(dPriority)) ? "color:red;" : ""); // Priority < "medium" + l6->setStyleSheet((dPriority > 0 && !fAllowFree) ? "color:red;" : ""); // Priority < "medium" l7->setStyleSheet((fDust) ? "color:red;" : ""); // Dust = "yes" // tool tips @@ -620,7 +628,11 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) QString toolTip3 = tr("This label turns red, if any recipient receives an amount smaller than %1.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, ::minRelayTxFee.GetFee(546))); // how many satoshis the estimated fee can vary per byte we guess wrong - double dFeeVary = (double)std::max(CWallet::minTxFee.GetFeePerK(), std::max(payTxFee.GetFeePerK(), mempool.estimateFee(nTxConfirmTarget).GetFeePerK())) / 1000; + double dFeeVary; + if (payTxFee.GetFeePerK() > 0) + dFeeVary = (double)std::max(CWallet::minTxFee.GetFeePerK(), payTxFee.GetFeePerK()) / 1000; + else + dFeeVary = (double)std::max(CWallet::minTxFee.GetFeePerK(), mempool.estimateFee(nTxConfirmTarget).GetFeePerK()) / 1000; QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary); l3->setToolTip(toolTip4); @@ -656,6 +668,7 @@ void CoinControlDialog::updateView() QFlags<Qt::ItemFlag> flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; int nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + double mempoolEstimatePriority = mempool.estimatePriority(nTxConfirmTarget); map<QString, vector<COutput> > mapCoins; model->listCoins(mapCoins); @@ -745,7 +758,7 @@ void CoinControlDialog::updateView() // priority double dPriority = ((double)out.tx->vout[out.i].nValue / (nInputSize + 78)) * (out.nDepth+1); // 78 = 2 * 34 + 10 - itemOutput->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(mempool, dPriority)); + itemOutput->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(dPriority, mempoolEstimatePriority)); itemOutput->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64_t)dPriority), 20, " ")); dPrioritySum += (double)out.tx->vout[out.i].nValue * (out.nDepth+1); nInputSum += nInputSize; @@ -778,7 +791,7 @@ void CoinControlDialog::updateView() itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")"); itemWalletAddress->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, nSum)); itemWalletAddress->setText(COLUMN_AMOUNT_INT64, strPad(QString::number(nSum), 15, " ")); - itemWalletAddress->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(mempool, dPrioritySum)); + itemWalletAddress->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(dPrioritySum, mempoolEstimatePriority)); itemWalletAddress->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64_t)dPrioritySum), 20, " ")); } } diff --git a/src/qt/coincontroldialog.h b/src/qt/coincontroldialog.h index cc388d626e..bd4f5d7f1e 100644 --- a/src/qt/coincontroldialog.h +++ b/src/qt/coincontroldialog.h @@ -37,7 +37,7 @@ public: // static because also called from sendcoinsdialog static void updateLabels(WalletModel*, QDialog*); - static QString getPriorityLabel(const CTxMemPool& pool, double); + static QString getPriorityLabel(double dPriority, double mempoolEstimatePriority); static QList<CAmount> payAmounts; static CCoinControl *coinControl; diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index 9d094c1a73..3446cf5c33 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -138,65 +138,6 @@ </attribute> <layout class="QVBoxLayout" name="verticalLayout_Wallet"> <item> - <widget class="QLabel" name="transactionFeeInfoLabel"> - <property name="text"> - <string>Optional transaction fee per kB that helps make sure your transactions are processed quickly. Most transactions are 1 kB.</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_1_Wallet"> - <item> - <widget class="QLabel" name="transactionFeeLabel"> - <property name="text"> - <string>Pay transaction &fee</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="buddy"> - <cstring>transactionFee</cstring> - </property> - </widget> - </item> - <item> - <widget class="BitcoinAmountField" name="transactionFee"/> - </item> - <item> - <spacer name="horizontalSpacer_1_Wallet"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item> - <spacer name="verticalSpacer_Wallet"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> <widget class="QGroupBox" name="groupBox"> <property name="title"> <string>Expert</string> @@ -225,6 +166,19 @@ </layout> </widget> </item> + <item> + <spacer name="verticalSpacer_Wallet"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> </layout> </widget> <widget class="QWidget" name="tabNetwork"> @@ -633,12 +587,6 @@ </widget> <customwidgets> <customwidget> - <class>BitcoinAmountField</class> - <extends>QLineEdit</extends> - <header>bitcoinamountfield.h</header> - <container>1</container> - </customwidget> - <customwidget> <class>QValidatedLineEdit</class> <extends>QLineEdit</extends> <header>qvalidatedlineedit.h</header> diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index dce7f4ce4c..0bf04c75eb 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -7,13 +7,13 @@ <x>0</x> <y>0</y> <width>850</width> - <height>400</height> + <height>526</height> </rect> </property> <property name="windowTitle"> <string>Send Coins</string> </property> - <layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0"> + <layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,0"> <property name="bottomMargin"> <number>8</number> </property> @@ -617,7 +617,7 @@ <x>0</x> <y>0</y> <width>830</width> - <height>178</height> + <height>68</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1"> @@ -658,6 +658,590 @@ </widget> </item> <item> + <widget class="QFrame" name="frameFee"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Sunken</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayoutFee1"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QVBoxLayout" name="verticalLayoutFee2" stretch="0,0,0"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>10</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayoutFee1"> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QVBoxLayout" name="verticalLayoutFee7"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <spacer name="verticalSpacerSmartFee"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>1</width> + <height>4</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayoutSmartFee"> + <property name="spacing"> + <number>10</number> + </property> + <item> + <widget class="QLabel" name="labelFeeHeadline"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Transaction Fee:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="labelFeeMinimized"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="buttonChooseFee"> + <property name="text"> + <string>Choose...</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="buttonMinimizeFee"> + <property name="toolTip"> + <string>collapse fee-settings</string> + </property> + <property name="text"> + <string>Minimize</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer_5"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>1</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QFrame" name="frameFeeSelection"> + <layout class="QVBoxLayout" name="verticalLayoutFee12"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QGridLayout" name="gridLayoutFee"> + <property name="topMargin"> + <number>10</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <property name="horizontalSpacing"> + <number>10</number> + </property> + <property name="verticalSpacing"> + <number>4</number> + </property> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="verticalLayoutFee8"> + <property name="spacing"> + <number>6</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayoutFee13"> + <item> + <widget class="QRadioButton" name="radioCustomPerKilobyte"> + <property name="toolTip"> + <string>If the custom fee is set to 1000 satoshis and the transaction is only 250 bytes, then "per kilobyte" only pays 250 satoshis in fee, while "at least" pays 1000 satoshis. For transactions bigger than a kilobyte both pay by kilobyte.</string> + </property> + <property name="text"> + <string>per kilobyte</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <attribute name="buttonGroup"> + <string notr="true">groupCustomFee</string> + </attribute> + </widget> + </item> + <item> + <widget class="QRadioButton" name="radioCustomAtLeast"> + <property name="toolTip"> + <string>If the custom fee is set to 1000 satoshis and the transaction is only 250 bytes, then "per kilobyte" only pays 250 satoshis in fee, while "total at least" pays 1000 satoshis. For transactions bigger than a kilobyte both pay by kilobyte.</string> + </property> + <property name="text"> + <string>total at least</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">groupCustomFee</string> + </attribute> + </widget> + </item> + <item> + <widget class="BitcoinAmountField" name="customFee"/> + </item> + <item> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>1</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayoutFee8"> + <item> + <widget class="QCheckBox" name="checkBoxMinimumFee"> + <property name="toolTip"> + <string>Paying only the minimum fee is just fine as long as there is less transaction volume than space in the blocks. But be aware that this can end up in a never confirming transaction once there is more demand for bitcoin transactions than the network can process.</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="labelMinFeeWarning"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="toolTip"> + <string>Paying only the minimum fee is just fine as long as there is less transaction volume than space in the blocks. But be aware that this can end up in a never confirming transaction once there is more demand for bitcoin transactions than the network can process.</string> + </property> + <property name="text"> + <string>(read the tooltip)</string> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>1</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="verticalLayoutFee4" stretch="0,1"> + <item> + <widget class="QRadioButton" name="radioSmartFee"> + <property name="text"> + <string>Recommended:</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <attribute name="buttonGroup"> + <string notr="true">groupFee</string> + </attribute> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>1</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="0"> + <layout class="QVBoxLayout" name="verticalLayoutFee9" stretch="0,1"> + <item> + <widget class="QRadioButton" name="radioCustomFee"> + <property name="text"> + <string>Custom:</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">groupFee</string> + </attribute> + </widget> + </item> + <item> + <spacer name="verticalSpacer_6"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>1</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="verticalLayoutFee3" stretch="0,0,1"> + <property name="spacing"> + <number>6</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayoutFee12"> + <item> + <widget class="QLabel" name="labelSmartFee"> + <property name="text"> + <string/> + </property> + <property name="margin"> + <number>2</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="labelFeeEstimation"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="labelSmartFee2"> + <property name="text"> + <string>(Smart fee not initialized yet. This usually takes a few blocks...)</string> + </property> + <property name="margin"> + <number>2</number> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>1</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayoutFee9"> + <item> + <layout class="QVBoxLayout" name="verticalLayoutFee6"> + <item> + <widget class="QLabel" name="labelSmartFee3"> + <property name="text"> + <string>Confirmation time:</string> + </property> + <property name="margin"> + <number>2</number> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>1</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayoutFee5"> + <property name="rightMargin"> + <number>30</number> + </property> + <item> + <widget class="QSlider" name="sliderSmartFee"> + <property name="minimum"> + <number>0</number> + </property> + <property name="maximum"> + <number>24</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="value"> + <number>0</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="invertedAppearance"> + <bool>false</bool> + </property> + <property name="invertedControls"> + <bool>false</bool> + </property> + <property name="tickPosition"> + <enum>QSlider::NoTicks</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayoutFee10"> + <item> + <widget class="QLabel" name="labelSmartFeeNormal"> + <property name="text"> + <string>normal</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="labelSmartFeeFast"> + <property name="text"> + <string>fast</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer_4"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>1</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayoutFee5" stretch="0,0,0"> + <property name="spacing"> + <number>8</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QCheckBox" name="checkBoxFreeTx"> + <property name="text"> + <string>Send as zero-fee transaction if possible</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="labelFreeTx"> + <property name="text"> + <string>(confirmation may take longer)</string> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacerFee5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>1</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacerFee2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>1</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacerFee"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>800</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <widget class="QPushButton" name="sendButton"> @@ -787,9 +1371,19 @@ <extends>QLineEdit</extends> <header>qvalidatedlineedit.h</header> </customwidget> + <customwidget> + <class>BitcoinAmountField</class> + <extends>QLineEdit</extends> + <header>bitcoinamountfield.h</header> + <container>1</container> + </customwidget> </customwidgets> <resources> <include location="../bitcoin.qrc"/> </resources> <connections/> + <buttongroups> + <buttongroup name="groupFee"/> + <buttongroup name="groupCustomFee"/> + </buttongroups> </ui> diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index f5a0759c92..069080219e 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -105,9 +105,6 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : #endif ui->unit->setModel(new BitcoinUnits(this)); -#ifdef ENABLE_WALLET - ui->transactionFee->setSingleStep(CWallet::minTxFee.GetFeePerK()); -#endif /* Widget-to-option mapper */ mapper = new QDataWidgetMapper(this); @@ -139,16 +136,11 @@ void OptionsDialog::setModel(OptionsModel *model) strLabel = tr("none"); ui->overriddenByCommandLineLabel->setText(strLabel); - connect(model, SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); - mapper->setModel(model); setMapper(); mapper->toFirst(); } - /* update the display unit, to not use the default ("BTC") */ - updateDisplayUnit(); - /* warn when one of the following settings changes by user action (placed here so init via mapper doesn't trigger them) */ /* Main */ @@ -172,7 +164,6 @@ void OptionsDialog::setMapper() mapper->addMapping(ui->databaseCache, OptionsModel::DatabaseCache); /* Wallet */ - mapper->addMapping(ui->transactionFee, OptionsModel::Fee); mapper->addMapping(ui->spendZeroConfChange, OptionsModel::SpendZeroConfChange); mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures); @@ -264,15 +255,6 @@ void OptionsDialog::clearStatusLabel() ui->statusLabel->clear(); } -void OptionsDialog::updateDisplayUnit() -{ - if(model) - { - /* Update transactionFee with the current unit */ - ui->transactionFee->setDisplayUnit(model->getDisplayUnit()); - } -} - void OptionsDialog::doProxyIpChecks(QValidatedLineEdit *pUiProxyIp, int nProxyPort) { Q_UNUSED(nProxyPort); diff --git a/src/qt/optionsdialog.h b/src/qt/optionsdialog.h index 794a39590c..511719f53c 100644 --- a/src/qt/optionsdialog.h +++ b/src/qt/optionsdialog.h @@ -46,7 +46,6 @@ private slots: void showRestartWarning(bool fPersistent = false); void clearStatusLabel(); - void updateDisplayUnit(); void doProxyIpChecks(QValidatedLineEdit *pUiProxyIp, int nProxyPort); signals: diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index c941ebd4ca..7054509fe6 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -90,12 +90,6 @@ void OptionsModel::Init() // Wallet #ifdef ENABLE_WALLET - if (!settings.contains("nTransactionFee")) - settings.setValue("nTransactionFee", (qint64)DEFAULT_TRANSACTION_FEE); - payTxFee = CFeeRate(settings.value("nTransactionFee").toLongLong()); // if -paytxfee is set, this will be overridden later in init.cpp - if (mapArgs.count("-paytxfee")) - addOverriddenOption("-paytxfee"); - if (!settings.contains("bSpendZeroConfChange")) settings.setValue("bSpendZeroConfChange", true); if (!SoftSetBoolArg("-spendzeroconfchange", settings.value("bSpendZeroConfChange").toBool())) @@ -185,16 +179,6 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const } #ifdef ENABLE_WALLET - case Fee: { - // Attention: Init() is called before payTxFee is set in AppInit2()! - // To ensure we can change the fee on-the-fly update our QSetting when - // opening OptionsDialog, which queries Fee via the mapper. - if (!(payTxFee == CFeeRate(settings.value("nTransactionFee").toLongLong(), 1000))) - settings.setValue("nTransactionFee", (qint64)payTxFee.GetFeePerK()); - // Todo: Consider to revert back to use just payTxFee here, if we don't want - // -paytxfee to update our QSettings! - return settings.value("nTransactionFee"); - } case SpendZeroConfChange: return settings.value("bSpendZeroConfChange"); #endif @@ -276,14 +260,6 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in } break; #ifdef ENABLE_WALLET - case Fee: { // core option - can be changed on-the-fly - // Todo: Add is valid check and warn via message, if not - CAmount nTransactionFee(value.toLongLong()); - payTxFee = CFeeRate(nTransactionFee, 1000); - settings.setValue("nTransactionFee", qint64(nTransactionFee)); - emit transactionFeeChanged(nTransactionFee); - break; - } case SpendZeroConfChange: if (settings.value("bSpendZeroConfChange") != value) { settings.setValue("bSpendZeroConfChange", value); diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index e2dc067edb..84fd49a7b8 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -34,7 +34,6 @@ public: ProxyUse, // bool ProxyIP, // QString ProxyPort, // int - Fee, // qint64 DisplayUnit, // BitcoinUnits::Unit ThirdPartyTxUrls, // QString Language, // QString @@ -84,7 +83,6 @@ private: signals: void displayUnitChanged(int unit); - void transactionFeeChanged(const CAmount&); void coinControlFeaturesChanged(bool); }; diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index ce94131cce..ff39829b95 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -7,10 +7,12 @@ #include "addresstablemodel.h" #include "bitcoinunits.h" +#include "clientmodel.h" #include "coincontroldialog.h" #include "guiutil.h" #include "optionsmodel.h" #include "sendcoinsentry.h" +#include "wallet.h" #include "walletmodel.h" #include "base58.h" @@ -19,6 +21,7 @@ #include <QMessageBox> #include <QScrollBar> +#include <QSettings> #include <QTextDocument> SendCoinsDialog::SendCoinsDialog(QWidget *parent) : @@ -72,9 +75,46 @@ SendCoinsDialog::SendCoinsDialog(QWidget *parent) : ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); ui->labelCoinControlChange->addAction(clipboardChangeAction); + // init transaction fee section + QSettings settings; + if (!settings.contains("fFeeSectionMinimized")) + settings.setValue("fFeeSectionMinimized", true); + if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility + settings.setValue("nFeeRadio", 1); // custom + if (!settings.contains("nFeeRadio")) + settings.setValue("nFeeRadio", 0); // recommended + if (!settings.contains("nCustomFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility + settings.setValue("nCustomFeeRadio", 1); // total at least + if (!settings.contains("nCustomFeeRadio")) + settings.setValue("nCustomFeeRadio", 0); // per kilobyte + if (!settings.contains("nSmartFeeSliderPosition")) + settings.setValue("nSmartFeeSliderPosition", 0); + if (!settings.contains("nTransactionFee")) + settings.setValue("nTransactionFee", (qint64)DEFAULT_TRANSACTION_FEE); + if (!settings.contains("fPayOnlyMinFee")) + settings.setValue("fPayOnlyMinFee", false); + if (!settings.contains("fSendFreeTransactions")) + settings.setValue("fSendFreeTransactions", false); + ui->groupFee->setId(ui->radioSmartFee, 0); + ui->groupFee->setId(ui->radioCustomFee, 1); + ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true); + ui->groupCustomFee->setId(ui->radioCustomPerKilobyte, 0); + ui->groupCustomFee->setId(ui->radioCustomAtLeast, 1); + ui->groupCustomFee->button((int)std::max(0, std::min(1, settings.value("nCustomFeeRadio").toInt())))->setChecked(true); + ui->sliderSmartFee->setValue(settings.value("nSmartFeeSliderPosition").toInt()); + ui->customFee->setValue(settings.value("nTransactionFee").toLongLong()); + ui->checkBoxMinimumFee->setChecked(settings.value("fPayOnlyMinFee").toBool()); + ui->checkBoxFreeTx->setChecked(settings.value("fSendFreeTransactions").toBool()); + minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool()); + fNewRecipientAllowed = true; } +void SendCoinsDialog::setClientModel(ClientModel *clientModel) +{ + this->clientModel = clientModel; +} + void SendCoinsDialog::setModel(WalletModel *model) { this->model = model; @@ -94,18 +134,51 @@ void SendCoinsDialog::setModel(WalletModel *model) model->getWatchBalance(), model->getWatchUnconfirmedBalance(), model->getWatchImmatureBalance()); connect(model, SIGNAL(balanceChanged(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)), this, SLOT(setBalance(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount))); connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); + updateDisplayUnit(); // Coin Control connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels())); connect(model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool))); - connect(model->getOptionsModel(), SIGNAL(transactionFeeChanged(CAmount)), this, SLOT(coinControlUpdateLabels())); ui->frameCoinControl->setVisible(model->getOptionsModel()->getCoinControlFeatures()); coinControlUpdateLabels(); + + // fee section + connect(clientModel, SIGNAL(numBlocksChanged(int)), this, SLOT(updateSmartFeeLabel())); + connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(updateSmartFeeLabel())); + connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(updateGlobalFeeVariables())); + connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(coinControlUpdateLabels())); + connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls())); + connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables())); + connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels())); + connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables())); + connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels())); + connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(updateGlobalFeeVariables())); + connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels())); + connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee())); + connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls())); + connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables())); + connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels())); + connect(ui->checkBoxFreeTx, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables())); + connect(ui->checkBoxFreeTx, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels())); + ui->customFee->setSingleStep(CWallet::minTxFee.GetFeePerK()); + updateFeeSectionControls(); + updateMinFeeLabel(); + updateSmartFeeLabel(); + updateGlobalFeeVariables(); } } SendCoinsDialog::~SendCoinsDialog() { + QSettings settings; + settings.setValue("fFeeSectionMinimized", fFeeMinimized); + settings.setValue("nFeeRadio", ui->groupFee->checkedId()); + settings.setValue("nCustomFeeRadio", ui->groupCustomFee->checkedId()); + settings.setValue("nSmartFeeSliderPosition", ui->sliderSmartFee->value()); + settings.setValue("nTransactionFee", (qint64)ui->customFee->value()); + settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked()); + settings.setValue("fSendFreeTransactions", ui->checkBoxFreeTx->isChecked()); + delete ui; } @@ -214,6 +287,9 @@ void SendCoinsDialog::on_sendButton_clicked() questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee)); questionString.append("</span> "); questionString.append(tr("added as transaction fee")); + + // append transaction size + questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB)"); } // add total amount in all subdivision units @@ -402,6 +478,9 @@ void SendCoinsDialog::setBalance(const CAmount& balance, const CAmount& unconfir void SendCoinsDialog::updateDisplayUnit() { setBalance(model->getBalance(), 0, 0, 0, 0, 0); + ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); + updateMinFeeLabel(); + updateSmartFeeLabel(); } void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg) @@ -438,6 +517,9 @@ void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn msgParams.first = tr("The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); msgParams.second = CClientUIInterface::MSG_ERROR; break; + case WalletModel::InsaneFee: + msgParams.first = tr("A fee higher than %1 is considered an insanely high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), 10000000)); + break; // included to prevent a compiler warning. case WalletModel::OK: default: @@ -447,6 +529,110 @@ void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn emit message(tr("Send Coins"), msgParams.first, msgParams.second); } +void SendCoinsDialog::minimizeFeeSection(bool fMinimize) +{ + ui->labelFeeMinimized->setVisible(fMinimize); + ui->buttonChooseFee ->setVisible(fMinimize); + ui->buttonMinimizeFee->setVisible(!fMinimize); + ui->frameFeeSelection->setVisible(!fMinimize); + ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0); + fFeeMinimized = fMinimize; +} + +void SendCoinsDialog::on_buttonChooseFee_clicked() +{ + minimizeFeeSection(false); +} + +void SendCoinsDialog::on_buttonMinimizeFee_clicked() +{ + updateFeeMinimizedLabel(); + minimizeFeeSection(true); +} + +void SendCoinsDialog::setMinimumFee() +{ + ui->radioCustomPerKilobyte->setChecked(true); + ui->customFee->setValue(CWallet::minTxFee.GetFeePerK()); +} + +void SendCoinsDialog::updateFeeSectionControls() +{ + ui->sliderSmartFee ->setEnabled(ui->radioSmartFee->isChecked()); + ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked()); + ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked()); + ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked()); + ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked()); + ui->labelSmartFeeNormal ->setEnabled(ui->radioSmartFee->isChecked()); + ui->labelSmartFeeFast ->setEnabled(ui->radioSmartFee->isChecked()); + ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked()); + ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked()); + ui->radioCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); + ui->radioCustomAtLeast ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); + ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); +} + +void SendCoinsDialog::updateGlobalFeeVariables() +{ + if (ui->radioSmartFee->isChecked()) + { + nTxConfirmTarget = (int)25 - (int)std::max(0, std::min(24, ui->sliderSmartFee->value())); + payTxFee = CFeeRate(0); + } + else + { + nTxConfirmTarget = 25; + payTxFee = CFeeRate(ui->customFee->value()); + fPayAtLeastCustomFee = ui->radioCustomAtLeast->isChecked(); + } + + fSendFreeTransactions = ui->checkBoxFreeTx->isChecked(); +} + +void SendCoinsDialog::updateFeeMinimizedLabel() +{ + if(!model || !model->getOptionsModel()) + return; + + if (ui->radioSmartFee->isChecked()) + ui->labelFeeMinimized->setText(ui->labelSmartFee->text()); + else { + ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) + + ((ui->radioCustomPerKilobyte->isChecked()) ? "/kB" : "")); + } +} + +void SendCoinsDialog::updateMinFeeLabel() +{ + if (model && model->getOptionsModel()) + ui->checkBoxMinimumFee->setText(tr("Pay only the minimum fee of %1").arg( + BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), CWallet::minTxFee.GetFeePerK()) + "/kB") + ); +} + +void SendCoinsDialog::updateSmartFeeLabel() +{ + if(!model || !model->getOptionsModel()) + return; + + int nBlocksToConfirm = (int)25 - (int)std::max(0, std::min(24, ui->sliderSmartFee->value())); + CFeeRate feeRate = mempool.estimateFee(nBlocksToConfirm); + if (feeRate <= CFeeRate(0)) // not enough data => minfee + { + ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), CWallet::minTxFee.GetFeePerK()) + "/kB"); + ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...) + ui->labelFeeEstimation->setText(""); + } + else + { + ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB"); + ui->labelSmartFee2->hide(); + ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %1 block(s).").arg(nBlocksToConfirm)); + } + + updateFeeMinimizedLabel(); +} + // Coin Control: copy label "Quantity" to clipboard void SendCoinsDialog::coinControlClipboardQuantity() { @@ -462,19 +648,19 @@ void SendCoinsDialog::coinControlClipboardAmount() // Coin Control: copy label "Fee" to clipboard void SendCoinsDialog::coinControlClipboardFee() { - GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" "))); + GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace("~", "")); } // Coin Control: copy label "After fee" to clipboard void SendCoinsDialog::coinControlClipboardAfterFee() { - GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" "))); + GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace("~", "")); } // Coin Control: copy label "Bytes" to clipboard void SendCoinsDialog::coinControlClipboardBytes() { - GUIUtil::setClipboard(ui->labelCoinControlBytes->text()); + GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace("~", "")); } // Coin Control: copy label "Priority" to clipboard @@ -492,7 +678,7 @@ void SendCoinsDialog::coinControlClipboardLowOutput() // Coin Control: copy label "Change" to clipboard void SendCoinsDialog::coinControlClipboardChange() { - GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" "))); + GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace("~", "")); } // Coin Control: settings menu - coin control enabled/disabled by user diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index eec661cbd0..15d39782a1 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -10,6 +10,7 @@ #include <QDialog> #include <QString> +class ClientModel; class OptionsModel; class SendCoinsEntry; class SendCoinsRecipient; @@ -31,6 +32,7 @@ public: explicit SendCoinsDialog(QWidget *parent = 0); ~SendCoinsDialog(); + void setClientModel(ClientModel *clientModel); void setModel(WalletModel *model); /** Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://bugreports.qt-project.org/browse/QTBUG-10907). @@ -52,16 +54,22 @@ public slots: private: Ui::SendCoinsDialog *ui; + ClientModel *clientModel; WalletModel *model; bool fNewRecipientAllowed; + bool fFeeMinimized; // Process WalletModel::SendCoinsReturn and generate a pair consisting // of a message and message flags for use in emit message(). // Additional parameter msgArg can be used via .arg(msgArg). void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg = QString()); + void minimizeFeeSection(bool fMinimize); + void updateFeeMinimizedLabel(); private slots: void on_sendButton_clicked(); + void on_buttonChooseFee_clicked(); + void on_buttonMinimizeFee_clicked(); void removeEntry(SendCoinsEntry* entry); void updateDisplayUnit(); void coinControlFeatureChanged(bool); @@ -77,6 +85,11 @@ private slots: void coinControlClipboardPriority(); void coinControlClipboardLowOutput(); void coinControlClipboardChange(); + void setMinimumFee(); + void updateFeeSectionControls(); + void updateMinFeeLabel(); + void updateSmartFeeLabel(); + void updateGlobalFeeVariables(); signals: // Fired when a message should be reported to the user diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index f7b1552f3e..b20465794d 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -277,6 +277,10 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact CClientUIInterface::MSG_ERROR); return TransactionCreationFailed; } + + // reject insane fee > 0.1 bitcoin + if (nFeeRequired > 10000000) + return InsaneFee; } return SendCoinsReturn(OK); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 0c6077963d..d7e391f8d1 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -110,7 +110,8 @@ public: AmountWithFeeExceedsBalance, DuplicateAddress, TransactionCreationFailed, // Error returned when wallet is still locked - TransactionCommitFailed + TransactionCommitFailed, + InsaneFee }; enum EncryptionStatus diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp index ddd2d09bb5..e9e20c7d51 100644 --- a/src/qt/walletmodeltransaction.cpp +++ b/src/qt/walletmodeltransaction.cpp @@ -31,6 +31,11 @@ CWalletTx *WalletModelTransaction::getTransaction() return walletTransaction; } +unsigned int WalletModelTransaction::getTransactionSize() +{ + return (!walletTransaction ? 0 : (::GetSerializeSize(*(CTransaction*)walletTransaction, SER_NETWORK, PROTOCOL_VERSION))); +} + CAmount WalletModelTransaction::getTransactionFee() { return fee; diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h index a880384ed6..4272529abe 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -25,6 +25,7 @@ public: QList<SendCoinsRecipient> getRecipients(); CWalletTx *getTransaction(); + unsigned int getTransactionSize(); void setTransactionFee(const CAmount& newFee); CAmount getTransactionFee(); diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 3b8fdd7e5f..9bab180108 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -101,6 +101,7 @@ void WalletView::setClientModel(ClientModel *clientModel) this->clientModel = clientModel; overviewPage->setClientModel(clientModel); + sendCoinsPage->setClientModel(clientModel); } void WalletView::setWalletModel(WalletModel *walletModel) diff --git a/src/txmempool.h b/src/txmempool.h index 0d3c8bba6a..e68b218154 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -15,11 +15,16 @@ class CAutoFile; +inline double AllowFreeThreshold() +{ + return COIN * 144 / 250; +} + inline bool AllowFree(double dPriority) { // Large (in bytes) low-priority (new, small-coin) transactions // need a fee. - return dPriority > COIN * 144 / 250; + return dPriority > AllowFreeThreshold(); } /** Fake height value used in CCoins to signify they are only in the memory pool (since 0.8) */ diff --git a/src/wallet.cpp b/src/wallet.cpp index ec439c5aad..5aea9881cd 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -28,6 +28,8 @@ using namespace std; CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE); unsigned int nTxConfirmTarget = 1; bool bSpendZeroConfChange = true; +bool fSendFreeTransactions = false; +bool fPayAtLeastCustomFee = true; /** * Fees smaller than this (in satoshi) are considered zero fee (for transaction creation) @@ -1382,7 +1384,7 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend, { LOCK2(cs_main, cs_wallet); { - nFeeRet = payTxFee.GetFeePerK(); + nFeeRet = 0; while (true) { txNew.vin.clear(); @@ -1502,7 +1504,7 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend, break; // Done, enough fee included. // Too big to send for free? Include more fee and try again: - if (nBytes > MAX_FREE_TRANSACTION_CREATE_SIZE) + if (!fSendFreeTransactions || nBytes > MAX_FREE_TRANSACTION_CREATE_SIZE) { nFeeRet = nFeeNeeded; continue; @@ -1628,6 +1630,12 @@ CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarge { // payTxFee is user-set "I want to pay this much" CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes); + // prevent user from paying a non-sense fee (like 1 satoshi): 0 < fee < minRelayFee + if (nFeeNeeded > 0 && nFeeNeeded < ::minRelayTxFee.GetFee(nTxBytes)) + nFeeNeeded = ::minRelayTxFee.GetFee(nTxBytes); + // user selected total at least (default=true) + if (fPayAtLeastCustomFee && nFeeNeeded > 0 && nFeeNeeded < payTxFee.GetFeePerK()) + nFeeNeeded = payTxFee.GetFeePerK(); // User didn't set: use -txconfirmtarget to estimate... if (nFeeNeeded == 0) nFeeNeeded = pool.estimateFee(nConfirmTarget).GetFee(nTxBytes); diff --git a/src/wallet.h b/src/wallet.h index b692ad056b..0244ce2368 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -32,6 +32,8 @@ extern CFeeRate payTxFee; extern unsigned int nTxConfirmTarget; extern bool bSpendZeroConfChange; +extern bool fSendFreeTransactions; +extern bool fPayAtLeastCustomFee; //! -paytxfee default static const CAmount DEFAULT_TRANSACTION_FEE = 0; |