diff --git a/src/init.cpp b/src/init.cpp index b290d5415..4a9982a1c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -290,6 +290,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += " -paytxfee= " + 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= " + 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 2c100337d..5b8ab23b2 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 4ab66001f..1bad8ce1b 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 ba0febe54..85b43b7b1 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -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 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 vCoinControl; vector 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::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 flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; int nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + double mempoolEstimatePriority = mempool.estimatePriority(nTxConfirmTarget); map > 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 cc388d626..bd4f5d7f1 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 payAmounts; static CCoinControl *coinControl; diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index 9d094c1a7..3446cf5c3 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -137,65 +137,6 @@ W&allet - - - - Optional transaction fee per kB that helps make sure your transactions are processed quickly. Most transactions are 1 kB. - - - Qt::PlainText - - - true - - - - - - - - - Pay transaction &fee - - - Qt::PlainText - - - transactionFee - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -225,6 +166,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -632,12 +586,6 @@ - - BitcoinAmountField - QLineEdit -
bitcoinamountfield.h
- 1 -
QValidatedLineEdit QLineEdit diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index dce7f4ce4..0bf04c75e 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -7,13 +7,13 @@ 0 0 850 - 400 + 526 Send Coins - + 8 @@ -617,7 +617,7 @@ 0 0 830 - 178 + 68 @@ -657,6 +657,590 @@ + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 10 + + + 0 + + + + + 0 + + + + + 0 + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 1 + 4 + + + + + + + + 10 + + + + + + 0 + 0 + + + + + 75 + true + + + + font-weight:bold; + + + Transaction Fee: + + + + + + + + + + + + + + Choose... + + + + + + + collapse fee-settings + + + Minimize + + + + + + + + + Qt::Vertical + + + + 1 + 1 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 10 + + + 4 + + + 10 + + + 4 + + + + + 6 + + + + + + + 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. + + + per kilobyte + + + true + + + groupCustomFee + + + + + + + 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. + + + total at least + + + groupCustomFee + + + + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + 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. + + + + + + + + + + true + + + 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. + + + (read the tooltip) + + + 5 + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + + + Recommended: + + + true + + + groupFee + + + + + + + Qt::Vertical + + + + 1 + 1 + + + + + + + + + + + + Custom: + + + groupFee + + + + + + + Qt::Vertical + + + + 1 + 1 + + + + + + + + + + 6 + + + 2 + + + + + + + + + + 2 + + + + + + + + + + + + + + (Smart fee not initialized yet. This usually takes a few blocks...) + + + 2 + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + + + + + Confirmation time: + + + 2 + + + + + + + Qt::Vertical + + + + 1 + 1 + + + + + + + + + + 30 + + + + + 0 + + + 24 + + + 1 + + + 0 + + + Qt::Horizontal + + + false + + + false + + + QSlider::NoTicks + + + + + + + + + normal + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + fast + + + + + + + + + + + + + Qt::Vertical + + + + 1 + 1 + + + + + + + + + + + + 8 + + + 4 + + + + + Send as zero-fee transaction if possible + + + + + + + (confirmation may take longer) + + + 5 + + + + + + + Qt::Horizontal + + + + 1 + 1 + + + + + + + + + + Qt::Vertical + + + + 1 + 1 + + + + + + + + + + + Qt::Vertical + + + + 800 + 1 + + + + + + + + + @@ -787,9 +1371,19 @@ QLineEdit
qvalidatedlineedit.h
+ + BitcoinAmountField + QLineEdit +
bitcoinamountfield.h
+ 1 +
+ + + + diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index f5a0759c9..069080219 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 794a39590..511719f53 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 c941ebd4c..7054509fe 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 e2dc067ed..84fd49a7b 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 ce94131cc..ff39829b9 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 #include +#include #include 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(" "); 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 eec661cbd..15d39782a 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -10,6 +10,7 @@ #include #include +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 f7b1552f3..b20465794 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 0c6077963..d7e391f8d 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 ddd2d09bb..e9e20c7d5 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 a880384ed..4272529ab 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -25,6 +25,7 @@ public: QList 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 3b8fdd7e5..9bab18010 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 0d3c8bba6..e68b21815 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 2d59506ba..5aea9881c 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -28,7 +28,7 @@ using namespace std; CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE); unsigned int nTxConfirmTarget = 1; bool bSpendZeroConfChange = true; -bool fSendFreeTransactions = true; +bool fSendFreeTransactions = false; bool fPayAtLeastCustomFee = true; /** @@ -1384,10 +1384,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, { LOCK2(cs_main, cs_wallet); { - if (fPayAtLeastCustomFee) - nFeeRet = payTxFee.GetFeePerK(); - else - nFeeRet = 0; + nFeeRet = 0; while (true) { txNew.vin.clear(); @@ -1636,6 +1633,9 @@ CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarge // 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);