diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 61a76528b..2e2f3cbfc 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -1,969 +1,967 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "sendcoinsdialog.h" #include "ui_sendcoinsdialog.h" #include "addresstablemodel.h" #include "bitcoinunits.h" #include "clientmodel.h" #include "coincontroldialog.h" #include "guiutil.h" #include "optionsmodel.h" #include "platformstyle.h" #include "sendcoinsentry.h" #include "walletmodel.h" #include "base58.h" #include "chainparams.h" #include "txmempool.h" #include "ui_interface.h" #include "validation.h" // mempool and minRelayTxFee #include "wallet/coincontrol.h" #include "wallet/wallet.h" #include #include #include #include #include #define SEND_CONFIRM_DELAY 3 SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::SendCoinsDialog), clientModel(0), model(0), fNewRecipientAllowed(true), fFeeMinimized(true), platformStyle(_platformStyle) { ui->setupUi(this); if (!_platformStyle->getImagesOnButtons()) { ui->addButton->setIcon(QIcon()); ui->clearButton->setIcon(QIcon()); ui->sendButton->setIcon(QIcon()); } else { ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add")); ui->clearButton->setIcon( _platformStyle->SingleColorIcon(":/icons/remove")); ui->sendButton->setIcon( _platformStyle->SingleColorIcon(":/icons/send")); } GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this); addEntry(); connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry())); connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); // Coin Control connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked())); connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int))); connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &))); // Coin Control: clipboard actions QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this); QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity())); connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount())); connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee())); connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee())); connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes())); connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput())); connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange())); ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); ui->labelCoinControlAmount->addAction(clipboardAmountAction); ui->labelCoinControlFee->addAction(clipboardFeeAction); ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); ui->labelCoinControlBytes->addAction(clipboardBytesAction); ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); ui->labelCoinControlChange->addAction(clipboardChangeAction); // init transaction fee section QSettings settings; if (!settings.contains("fFeeSectionMinimized")) settings.setValue("fFeeSectionMinimized", true); // compatibility if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) { // custom settings.setValue("nFeeRadio", 1); } if (!settings.contains("nFeeRadio")) { // recommended settings.setValue("nFeeRadio", 0); } // compatibility if (!settings.contains("nCustomFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) { // total at least settings.setValue("nCustomFeeRadio", 1); } if (!settings.contains("nCustomFeeRadio")) { // per kilobyte settings.setValue("nCustomFeeRadio", 0); } 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); 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->customFee->setValue(settings.value("nTransactionFee").toLongLong()); ui->checkBoxMinimumFee->setChecked( settings.value("fPayOnlyMinFee").toBool()); minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool()); } void SendCoinsDialog::setClientModel(ClientModel *_clientModel) { this->clientModel = _clientModel; if (_clientModel) { connect(_clientModel, SIGNAL(numBlocksChanged(int, QDateTime, double, bool)), this, SLOT(updateSmartFeeLabel())); } } void SendCoinsDialog::setModel(WalletModel *_model) { this->model = _model; if (_model && _model->getOptionsModel()) { for (int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast( ui->entries->itemAt(i)->widget()); if (entry) { entry->setModel(_model); } } setBalance(_model->getBalance(), _model->getUnconfirmedBalance(), _model->getImmatureBalance(), _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))); ui->frameCoinControl->setVisible( _model->getOptionsModel()->getCoinControlFeatures()); coinControlUpdateLabels(); // fee section 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())); ui->customFee->setSingleStep(CWallet::GetRequiredFee(1000)); updateFeeSectionControls(); updateMinFeeLabel(); updateSmartFeeLabel(); updateGlobalFeeVariables(); // set the smartfee-sliders default value (wallets default conf.target // or last stored value) QSettings settings; if (settings.value("nSmartFeeSliderPosition").toInt() == 0) ui->sliderSmartFee->setValue(ui->sliderSmartFee->maximum() - model->getDefaultConfirmTarget() + 2); else ui->sliderSmartFee->setValue( settings.value("nSmartFeeSliderPosition").toInt()); } } 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()); delete ui; } void SendCoinsDialog::on_sendButton_clicked() { if (!model || !model->getOptionsModel()) return; QList recipients; bool valid = true; for (int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); if (entry) { if (entry->validate()) { recipients.append(entry->getValue()); } else { valid = false; } } } if (!valid || recipients.isEmpty()) { return; } fNewRecipientAllowed = false; WalletModel::UnlockContext ctx(model->requestUnlock()); if (!ctx.isValid()) { // Unlock wallet was cancelled fNewRecipientAllowed = true; return; } // prepare transaction for getting txFee earlier WalletModelTransaction currentTransaction(recipients); WalletModel::SendCoinsReturn prepareStatus; // Always use a CCoinControl instance, use the CoinControlDialog instance if // CoinControl has been enabled CCoinControl ctrl; if (model->getOptionsModel()->getCoinControlFeatures()) ctrl = *CoinControlDialog::coinControl; if (ui->radioSmartFee->isChecked()) ctrl.nConfirmTarget = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2; else ctrl.nConfirmTarget = 0; prepareStatus = model->prepareTransaction(currentTransaction, &ctrl); // process prepareStatus and on error generate message shown to user processSendCoinsReturn( prepareStatus, BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee())); if (prepareStatus.status != WalletModel::OK) { fNewRecipientAllowed = true; return; } CAmount txFee = currentTransaction.getTransactionFee(); // Format confirmation message QStringList formatted; for (const SendCoinsRecipient &rcp : currentTransaction.getRecipients()) { // generate bold amount string QString amount = "" + BitcoinUnits::formatHtmlWithUnit( model->getOptionsModel()->getDisplayUnit(), rcp.amount); amount.append(""); // generate monospace address string QString address = "" + rcp.address; address.append(""); QString recipientElement; // normal payment if (!rcp.paymentRequest.IsInitialized()) { if (rcp.label.length() > 0) { // label with address recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label)); recipientElement.append(QString(" (%1)").arg(address)); } else { // just address recipientElement = tr("%1 to %2").arg(amount, address); } } else if (!rcp.authenticatedMerchant.isEmpty()) { // authenticated payment request recipientElement = tr("%1 to %2") .arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant)); } else { // unauthenticated payment request recipientElement = tr("%1 to %2").arg(amount, address); } formatted.append(recipientElement); } QString questionString = tr("Are you sure you want to send?"); questionString.append("

%1"); if (txFee > 0) { // append fee string if a fee is required questionString.append("
"); 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 questionString.append("
"); CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee; QStringList alternativeUnits; for (BitcoinUnits::Unit u : BitcoinUnits::availableUnits()) { if (u != model->getOptionsModel()->getDisplayUnit()) alternativeUnits.append( BitcoinUnits::formatHtmlWithUnit(u, totalAmount)); } questionString.append( tr("Total Amount %1") .arg(BitcoinUnits::formatHtmlWithUnit( model->getOptionsModel()->getDisplayUnit(), totalAmount))); questionString.append( QString("
(=%2)
") .arg(alternativeUnits.join(" " + tr("or") + "
"))); SendConfirmationDialog confirmationDialog( tr("Confirm send coins"), questionString.arg(formatted.join("
")), SEND_CONFIRM_DELAY, this); confirmationDialog.exec(); QMessageBox::StandardButton retval = (QMessageBox::StandardButton)confirmationDialog.result(); if (retval != QMessageBox::Yes) { fNewRecipientAllowed = true; return; } // now send the prepared transaction WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction); // process sendStatus and on error generate message shown to user processSendCoinsReturn(sendStatus); if (sendStatus.status == WalletModel::OK) { accept(); CoinControlDialog::coinControl->UnSelectAll(); coinControlUpdateLabels(); } fNewRecipientAllowed = true; } void SendCoinsDialog::clear() { // Remove entries until only one left while (ui->entries->count()) { ui->entries->takeAt(0)->widget()->deleteLater(); } addEntry(); updateTabsAndLabels(); } void SendCoinsDialog::reject() { clear(); } void SendCoinsDialog::accept() { clear(); } SendCoinsEntry *SendCoinsDialog::addEntry() { SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this); entry->setModel(model); ui->entries->addWidget(entry); connect(entry, SIGNAL(removeEntry(SendCoinsEntry *)), this, SLOT(removeEntry(SendCoinsEntry *))); connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels())); connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels())); // Focus the field, so that entry can start immediately entry->clear(); entry->setFocus(); ui->scrollAreaWidgetContents->resize( ui->scrollAreaWidgetContents->sizeHint()); qApp->processEvents(); QScrollBar *bar = ui->scrollArea->verticalScrollBar(); if (bar) bar->setSliderPosition(bar->maximum()); updateTabsAndLabels(); return entry; } void SendCoinsDialog::updateTabsAndLabels() { setupTabChain(0); coinControlUpdateLabels(); } void SendCoinsDialog::removeEntry(SendCoinsEntry *entry) { entry->hide(); // If the last entry is about to be removed add an empty one if (ui->entries->count() == 1) addEntry(); entry->deleteLater(); updateTabsAndLabels(); } QWidget *SendCoinsDialog::setupTabChain(QWidget *prev) { for (int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); if (entry) { prev = entry->setupTabChain(prev); } } QWidget::setTabOrder(prev, ui->sendButton); QWidget::setTabOrder(ui->sendButton, ui->clearButton); QWidget::setTabOrder(ui->clearButton, ui->addButton); return ui->addButton; } void SendCoinsDialog::setAddress(const QString &address) { SendCoinsEntry *entry = 0; // Replace the first entry if it is still unused if (ui->entries->count() == 1) { SendCoinsEntry *first = qobject_cast(ui->entries->itemAt(0)->widget()); if (first->isClear()) { entry = first; } } if (!entry) { entry = addEntry(); } entry->setAddress(address); } void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) { if (!fNewRecipientAllowed) return; SendCoinsEntry *entry = 0; // Replace the first entry if it is still unused if (ui->entries->count() == 1) { SendCoinsEntry *first = qobject_cast(ui->entries->itemAt(0)->widget()); if (first->isClear()) { entry = first; } } if (!entry) { entry = addEntry(); } entry->setValue(rv); updateTabsAndLabels(); } bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv) { // Just paste the entry, all pre-checks are done in paymentserver.cpp. pasteEntry(rv); return true; } void SendCoinsDialog::setBalance(const CAmount &balance, const CAmount &unconfirmedBalance, const CAmount &immatureBalance, const CAmount &watchBalance, const CAmount &watchUnconfirmedBalance, const CAmount &watchImmatureBalance) { Q_UNUSED(unconfirmedBalance); Q_UNUSED(immatureBalance); Q_UNUSED(watchBalance); Q_UNUSED(watchUnconfirmedBalance); Q_UNUSED(watchImmatureBalance); if (model && model->getOptionsModel()) { ui->labelBalance->setText(BitcoinUnits::formatWithUnit( model->getOptionsModel()->getDisplayUnit(), balance)); } } 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) { QPair msgParams; // Default to a warning message, override if error message is needed msgParams.second = CClientUIInterface::MSG_WARNING; // This comment is specific to SendCoinsDialog usage of // WalletModel::SendCoinsReturn. // WalletModel::TransactionCommitFailed is used only in // WalletModel::sendCoins() all others are used only in // WalletModel::prepareTransaction() switch (sendCoinsReturn.status) { case WalletModel::InvalidAddress: msgParams.first = tr("The recipient address is not valid. Please recheck."); break; case WalletModel::InvalidAmount: msgParams.first = tr("The amount to pay must be larger than 0."); break; case WalletModel::AmountExceedsBalance: msgParams.first = tr("The amount exceeds your balance."); break; case WalletModel::AmountWithFeeExceedsBalance: msgParams.first = tr("The total exceeds your balance when the %1 " "transaction fee is included.") .arg(msgArg); break; case WalletModel::DuplicateAddress: msgParams.first = tr("Duplicate address found: addresses should " "only be used once each."); break; case WalletModel::TransactionCreationFailed: msgParams.first = tr("Transaction creation failed!"); msgParams.second = CClientUIInterface::MSG_ERROR; break; case WalletModel::TransactionCommitFailed: msgParams.first = tr("The transaction was rejected with the following reason: %1") .arg(sendCoinsReturn.reasonCommitFailed); msgParams.second = CClientUIInterface::MSG_ERROR; break; case WalletModel::AbsurdFee: msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.") .arg(BitcoinUnits::formatWithUnit( model->getOptionsModel()->getDisplayUnit(), maxTxFee)); break; case WalletModel::PaymentRequestExpired: msgParams.first = tr("Payment request expired."); msgParams.second = CClientUIInterface::MSG_ERROR; break; // included to prevent a compiler warning. case WalletModel::OK: default: return; } Q_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::GetRequiredFee(1000)); } 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->confirmationTargetLabel->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() && CoinControlDialog::coinControl->HasSelected()); ui->customFee->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); } void SendCoinsDialog::updateGlobalFeeVariables() { if (ui->radioSmartFee->isChecked()) { int nConfirmTarget = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2; payTxFee = CFeeRate(0); // set nMinimumTotalFee to 0 to not accidentally pay a custom fee CoinControlDialog::coinControl->nMinimumTotalFee = 0; // show the estimated required time for confirmation ui->confirmationTargetLabel->setText( GUIUtil::formatDurationStr( nConfirmTarget * Params().GetConsensus().nPowTargetSpacing) + " / " + tr("%n block(s)", "", nConfirmTarget)); } else { payTxFee = CFeeRate(ui->customFee->value()); // if user has selected to set a minimum absolute fee, pass the value to // coincontrol // set nMinimumTotalFee to 0 in case of user has selected that the fee // is per KB CoinControlDialog::coinControl->nMinimumTotalFee = ui->radioCustomAtLeast->isChecked() ? ui->customFee->value() : 0; } } 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 required fee of %1") .arg(BitcoinUnits::formatWithUnit( model->getOptionsModel()->getDisplayUnit(), CWallet::GetRequiredFee(1000)) + "/kB")); } void SendCoinsDialog::updateSmartFeeLabel() { if (!model || !model->getOptionsModel()) return; int nBlocksToConfirm = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2; int estimateFoundAtBlocks = nBlocksToConfirm; CFeeRate feeRate = mempool.estimateSmartFee(nBlocksToConfirm, &estimateFoundAtBlocks); // not enough data => minfee if (feeRate <= CFeeRate(0)) { ui->labelSmartFee->setText( BitcoinUnits::formatWithUnit( model->getOptionsModel()->getDisplayUnit(), std::max(CWallet::fallbackFee.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB"); // (Smart fee not initialized yet. This usually takes a few blocks...) ui->labelSmartFee2->show(); ui->labelFeeEstimation->setText(""); } else { ui->labelSmartFee->setText( BitcoinUnits::formatWithUnit( model->getOptionsModel()->getDisplayUnit(), std::max(feeRate.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB"); ui->labelSmartFee2->hide(); ui->labelFeeEstimation->setText( tr("Estimated to begin confirmation within %n block(s).", "", estimateFoundAtBlocks)); } updateFeeMinimizedLabel(); } // Coin Control: copy label "Quantity" to clipboard void SendCoinsDialog::coinControlClipboardQuantity() { GUIUtil::setClipboard(ui->labelCoinControlQuantity->text()); } // Coin Control: copy label "Amount" to clipboard void SendCoinsDialog::coinControlClipboardAmount() { GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left( ui->labelCoinControlAmount->text().indexOf(" "))); } // Coin Control: copy label "Fee" to clipboard void SendCoinsDialog::coinControlClipboardFee() { GUIUtil::setClipboard( ui->labelCoinControlFee->text() .left(ui->labelCoinControlFee->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // Coin Control: copy label "After fee" to clipboard void SendCoinsDialog::coinControlClipboardAfterFee() { GUIUtil::setClipboard( ui->labelCoinControlAfterFee->text() .left(ui->labelCoinControlAfterFee->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // Coin Control: copy label "Bytes" to clipboard void SendCoinsDialog::coinControlClipboardBytes() { GUIUtil::setClipboard( ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, "")); } // Coin Control: copy label "Dust" to clipboard void SendCoinsDialog::coinControlClipboardLowOutput() { GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text()); } // Coin Control: copy label "Change" to clipboard void SendCoinsDialog::coinControlClipboardChange() { GUIUtil::setClipboard( ui->labelCoinControlChange->text() .left(ui->labelCoinControlChange->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // Coin Control: settings menu - coin control enabled/disabled by user void SendCoinsDialog::coinControlFeatureChanged(bool checked) { ui->frameCoinControl->setVisible(checked); // coin control features disabled if (!checked && model) CoinControlDialog::coinControl->SetNull(); // make sure we set back the confirmation target updateGlobalFeeVariables(); coinControlUpdateLabels(); } // Coin Control: button inputs -> show actual coin control dialog void SendCoinsDialog::coinControlButtonClicked() { CoinControlDialog dlg(platformStyle); dlg.setModel(model); dlg.exec(); coinControlUpdateLabels(); } // Coin Control: checkbox custom change address void SendCoinsDialog::coinControlChangeChecked(int state) { if (state == Qt::Unchecked) { CoinControlDialog::coinControl->destChange = CNoDestination(); ui->labelCoinControlChangeLabel->clear(); } else { // use this to re-validate an already entered address coinControlChangeEdited(ui->lineEditCoinControlChange->text()); } ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked)); } // Coin Control: custom change address changed void SendCoinsDialog::coinControlChangeEdited(const QString &text) { if (model && model->getAddressTableModel()) { // Default to no change address until verified CoinControlDialog::coinControl->destChange = CNoDestination(); ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}"); CBitcoinAddress addr = CBitcoinAddress(text.toStdString()); if (text.isEmpty()) { // Nothing entered ui->labelCoinControlChangeLabel->setText(""); } else if (!addr.IsValid()) { // Invalid address ui->labelCoinControlChangeLabel->setText( tr("Warning: Invalid Bitcoin address")); } else { // Valid address - CKeyID keyid; - addr.GetKeyID(keyid); - // Unknown change address - if (!model->havePrivKey(keyid)) { + const CTxDestination dest = addr.Get(); + if (!model->IsSpendable(dest)) { ui->labelCoinControlChangeLabel->setText( tr("Warning: Unknown change address")); // confirmation dialog QMessageBox::StandardButton btnRetVal = QMessageBox::question( this, tr("Confirm custom change address"), tr("The address you selected for change is not part of " "this wallet. Any or all funds in your wallet may be " "sent to this address. Are you sure?"), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); if (btnRetVal == QMessageBox::Yes) - CoinControlDialog::coinControl->destChange = addr.Get(); + CoinControlDialog::coinControl->destChange = dest; else { ui->lineEditCoinControlChange->setText(""); ui->labelCoinControlChangeLabel->setStyleSheet( "QLabel{color:black;}"); ui->labelCoinControlChangeLabel->setText(""); } } else { // Known change address ui->labelCoinControlChangeLabel->setStyleSheet( "QLabel{color:black;}"); // Query label QString associatedLabel = model->getAddressTableModel()->labelForAddress(text); if (!associatedLabel.isEmpty()) ui->labelCoinControlChangeLabel->setText(associatedLabel); else ui->labelCoinControlChangeLabel->setText(tr("(no label)")); - CoinControlDialog::coinControl->destChange = addr.Get(); + CoinControlDialog::coinControl->destChange = dest; } } } } // Coin Control: update labels void SendCoinsDialog::coinControlUpdateLabels() { if (!model || !model->getOptionsModel()) return; if (model->getOptionsModel()->getCoinControlFeatures()) { // enable minimum absolute fee UI controls ui->radioCustomAtLeast->setVisible(true); // only enable the feature if inputs are selected ui->radioCustomAtLeast->setEnabled( ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked() && CoinControlDialog::coinControl->HasSelected()); } else { // in case coin control is disabled (=default), hide minimum absolute // fee UI controls ui->radioCustomAtLeast->setVisible(false); return; } // set pay amounts CoinControlDialog::payAmounts.clear(); CoinControlDialog::fSubtractFeeFromAmount = false; for (int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); if (entry && !entry->isHidden()) { SendCoinsRecipient rcp = entry->getValue(); CoinControlDialog::payAmounts.append(rcp.amount); if (rcp.fSubtractFeeFromAmount) CoinControlDialog::fSubtractFeeFromAmount = true; } } if (CoinControlDialog::coinControl->HasSelected()) { // actual coin control calculation CoinControlDialog::updateLabels(model, this); // show coin control stats ui->labelCoinControlAutomaticallySelected->hide(); ui->widgetCoinControl->show(); } else { // hide coin control stats ui->labelCoinControlAutomaticallySelected->show(); ui->widgetCoinControl->hide(); ui->labelCoinControlInsuffFunds->hide(); } } SendConfirmationDialog::SendConfirmationDialog(const QString &title, const QString &text, int _secDelay, QWidget *parent) : QMessageBox(QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::Cancel, parent), secDelay(_secDelay) { setDefaultButton(QMessageBox::Cancel); yesButton = button(QMessageBox::Yes); updateYesButton(); connect(&countDownTimer, SIGNAL(timeout()), this, SLOT(countDown())); } int SendConfirmationDialog::exec() { updateYesButton(); countDownTimer.start(1000); return QMessageBox::exec(); } void SendConfirmationDialog::countDown() { secDelay--; updateYesButton(); if (secDelay <= 0) { countDownTimer.stop(); } } void SendConfirmationDialog::updateYesButton() { if (secDelay > 0) { yesButton->setEnabled(false); yesButton->setText(tr("Yes") + " (" + QString::number(secDelay) + ")"); } else { yesButton->setEnabled(true); yesButton->setText(tr("Yes")); } } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 90eb3f91d..db6770366 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -1,682 +1,682 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "walletmodel.h" #include "addresstablemodel.h" #include "consensus/validation.h" #include "guiconstants.h" #include "guiutil.h" #include "paymentserver.h" #include "recentrequeststablemodel.h" #include "transactiontablemodel.h" #include "base58.h" #include "keystore.h" #include "net.h" // for g_connman #include "sync.h" #include "ui_interface.h" #include "util.h" // for GetBoolArg #include "validation.h" #include "wallet/wallet.h" #include "wallet/walletdb.h" // for BackupWallet #include #include #include #include WalletModel::WalletModel(const PlatformStyle *platformStyle, CWallet *_wallet, OptionsModel *_optionsModel, QObject *parent) : QObject(parent), wallet(_wallet), optionsModel(_optionsModel), addressTableModel(0), transactionTableModel(0), recentRequestsTableModel(0), cachedBalance(0), cachedUnconfirmedBalance(0), cachedImmatureBalance(0), cachedEncryptionStatus(Unencrypted), cachedNumBlocks(0) { fHaveWatchOnly = wallet->HaveWatchOnly(); fForceCheckBalanceChanged = false; addressTableModel = new AddressTableModel(wallet, this); transactionTableModel = new TransactionTableModel(platformStyle, wallet, this); recentRequestsTableModel = new RecentRequestsTableModel(wallet, this); // This timer will be fired repeatedly to update the balance pollTimer = new QTimer(this); connect(pollTimer, SIGNAL(timeout()), this, SLOT(pollBalanceChanged())); pollTimer->start(MODEL_UPDATE_DELAY); subscribeToCoreSignals(); } WalletModel::~WalletModel() { unsubscribeFromCoreSignals(); } CAmount WalletModel::getBalance(const CCoinControl *coinControl) const { if (coinControl) { CAmount nBalance = 0; std::vector vCoins; wallet->AvailableCoins(vCoins, true, coinControl); for (const COutput &out : vCoins) { if (out.fSpendable) nBalance += out.tx->tx->vout[out.i].nValue; } return nBalance; } return wallet->GetBalance(); } CAmount WalletModel::getUnconfirmedBalance() const { return wallet->GetUnconfirmedBalance(); } CAmount WalletModel::getImmatureBalance() const { return wallet->GetImmatureBalance(); } bool WalletModel::haveWatchOnly() const { return fHaveWatchOnly; } CAmount WalletModel::getWatchBalance() const { return wallet->GetWatchOnlyBalance(); } CAmount WalletModel::getWatchUnconfirmedBalance() const { return wallet->GetUnconfirmedWatchOnlyBalance(); } CAmount WalletModel::getWatchImmatureBalance() const { return wallet->GetImmatureWatchOnlyBalance(); } void WalletModel::updateStatus() { EncryptionStatus newEncryptionStatus = getEncryptionStatus(); if (cachedEncryptionStatus != newEncryptionStatus) Q_EMIT encryptionStatusChanged(newEncryptionStatus); } void WalletModel::pollBalanceChanged() { // Get required locks upfront. This avoids the GUI from getting stuck on // periodical polls if the core is holding the locks for a longer time - for // example, during a wallet rescan. TRY_LOCK(cs_main, lockMain); if (!lockMain) return; TRY_LOCK(wallet->cs_wallet, lockWallet); if (!lockWallet) return; if (fForceCheckBalanceChanged || chainActive.Height() != cachedNumBlocks) { fForceCheckBalanceChanged = false; // Balance and number of transactions might have changed cachedNumBlocks = chainActive.Height(); checkBalanceChanged(); if (transactionTableModel) transactionTableModel->updateConfirmations(); } } void WalletModel::checkBalanceChanged() { CAmount newBalance = getBalance(); CAmount newUnconfirmedBalance = getUnconfirmedBalance(); CAmount newImmatureBalance = getImmatureBalance(); CAmount newWatchOnlyBalance = 0; CAmount newWatchUnconfBalance = 0; CAmount newWatchImmatureBalance = 0; if (haveWatchOnly()) { newWatchOnlyBalance = getWatchBalance(); newWatchUnconfBalance = getWatchUnconfirmedBalance(); newWatchImmatureBalance = getWatchImmatureBalance(); } if (cachedBalance != newBalance || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance || cachedWatchOnlyBalance != newWatchOnlyBalance || cachedWatchUnconfBalance != newWatchUnconfBalance || cachedWatchImmatureBalance != newWatchImmatureBalance) { cachedBalance = newBalance; cachedUnconfirmedBalance = newUnconfirmedBalance; cachedImmatureBalance = newImmatureBalance; cachedWatchOnlyBalance = newWatchOnlyBalance; cachedWatchUnconfBalance = newWatchUnconfBalance; cachedWatchImmatureBalance = newWatchImmatureBalance; Q_EMIT balanceChanged(newBalance, newUnconfirmedBalance, newImmatureBalance, newWatchOnlyBalance, newWatchUnconfBalance, newWatchImmatureBalance); } } void WalletModel::updateTransaction() { // Balance and number of transactions might have changed fForceCheckBalanceChanged = true; } void WalletModel::updateAddressBook(const QString &address, const QString &label, bool isMine, const QString &purpose, int status) { if (addressTableModel) addressTableModel->updateEntry(address, label, isMine, purpose, status); } void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly) { fHaveWatchOnly = fHaveWatchonly; Q_EMIT notifyWatchonlyChanged(fHaveWatchonly); } bool WalletModel::validateAddress(const QString &address) { CBitcoinAddress addressParsed(address.toStdString()); return addressParsed.IsValid(); } WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl) { CAmount total = 0; bool fSubtractFeeFromAmount = false; QList recipients = transaction.getRecipients(); std::vector vecSend; if (recipients.empty()) { return OK; } // Used to detect duplicates QSet setAddress; int nAddresses = 0; // Pre-check input data for validity for (const SendCoinsRecipient &rcp : recipients) { if (rcp.fSubtractFeeFromAmount) fSubtractFeeFromAmount = true; // PaymentRequest... if (rcp.paymentRequest.IsInitialized()) { CAmount subtotal = 0; const payments::PaymentDetails &details = rcp.paymentRequest.getDetails(); for (int i = 0; i < details.outputs_size(); i++) { const payments::Output &out = details.outputs(i); if (out.amount() <= 0) continue; subtotal += out.amount(); const uint8_t *scriptStr = (const uint8_t *)out.script().data(); CScript scriptPubKey(scriptStr, scriptStr + out.script().size()); CAmount nAmount = out.amount(); CRecipient recipient = {scriptPubKey, nAmount, rcp.fSubtractFeeFromAmount}; vecSend.push_back(recipient); } if (subtotal <= 0) { return InvalidAmount; } total += subtotal; } else { // User-entered bitcoin address / amount: if (!validateAddress(rcp.address)) { return InvalidAddress; } if (rcp.amount <= 0) { return InvalidAmount; } setAddress.insert(rcp.address); ++nAddresses; CScript scriptPubKey = GetScriptForDestination( CBitcoinAddress(rcp.address.toStdString()).Get()); CRecipient recipient = {scriptPubKey, rcp.amount, rcp.fSubtractFeeFromAmount}; vecSend.push_back(recipient); total += rcp.amount; } } if (setAddress.size() != nAddresses) { return DuplicateAddress; } CAmount nBalance = getBalance(coinControl); if (total > nBalance) { return AmountExceedsBalance; } { LOCK2(cs_main, wallet->cs_wallet); transaction.newPossibleKeyChange(wallet); CAmount nFeeRequired = 0; int nChangePosRet = -1; std::string strFailReason; CWalletTx *newTx = transaction.getTransaction(); CReserveKey *keyChange = transaction.getPossibleKeyChange(); bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, nChangePosRet, strFailReason, coinControl); transaction.setTransactionFee(nFeeRequired); if (fSubtractFeeFromAmount && fCreated) transaction.reassignAmounts(nChangePosRet); if (!fCreated) { if (!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance) { return SendCoinsReturn(AmountWithFeeExceedsBalance); } Q_EMIT message(tr("Send Coins"), QString::fromStdString(strFailReason), CClientUIInterface::MSG_ERROR); return TransactionCreationFailed; } // reject absurdly high fee. (This can never happen because the wallet // caps the fee at maxTxFee. This merely serves as a belt-and-suspenders // check) if (nFeeRequired > maxTxFee) return AbsurdFee; } return SendCoinsReturn(OK); } WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &transaction) { /* store serialized transaction */ QByteArray transaction_array; { LOCK2(cs_main, wallet->cs_wallet); CWalletTx *newTx = transaction.getTransaction(); for (const SendCoinsRecipient &rcp : transaction.getRecipients()) { if (rcp.paymentRequest.IsInitialized()) { // Make sure any payment requests involved are still valid. if (PaymentServer::verifyExpired( rcp.paymentRequest.getDetails())) { return PaymentRequestExpired; } // Store PaymentRequests in wtx.vOrderForm in wallet. std::string key("PaymentRequest"); std::string value; rcp.paymentRequest.SerializeToString(&value); newTx->vOrderForm.push_back(make_pair(key, value)); } else if (!rcp.message.isEmpty()) { // Message from normal bitcoincash:URI // (bitcoincash:123...?message=example) newTx->vOrderForm.push_back( make_pair("Message", rcp.message.toStdString())); } } CReserveKey *keyChange = transaction.getPossibleKeyChange(); CValidationState state; if (!wallet->CommitTransaction(*newTx, *keyChange, g_connman.get(), state)) return SendCoinsReturn( TransactionCommitFailed, QString::fromStdString(state.GetRejectReason())); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << *newTx->tx; transaction_array.append(&(ssTx[0]), ssTx.size()); } // Add addresses / update labels that we've sent to to the address book, and // emit coinsSent signal for each recipient for (const SendCoinsRecipient &rcp : transaction.getRecipients()) { // Don't touch the address book when we have a payment request if (!rcp.paymentRequest.IsInitialized()) { std::string strAddress = rcp.address.toStdString(); CTxDestination dest = CBitcoinAddress(strAddress).Get(); std::string strLabel = rcp.label.toStdString(); { LOCK(wallet->cs_wallet); std::map::iterator mi = wallet->mapAddressBook.find(dest); // Check if we have a new address or an updated label if (mi == wallet->mapAddressBook.end()) { wallet->SetAddressBook(dest, strLabel, "send"); } else if (mi->second.name != strLabel) { // "" means don't change purpose wallet->SetAddressBook(dest, strLabel, ""); } } } Q_EMIT coinsSent(wallet, rcp, transaction_array); } // update balance immediately, otherwise there could be a short noticeable // delay until pollBalanceChanged hits checkBalanceChanged(); return SendCoinsReturn(OK); } OptionsModel *WalletModel::getOptionsModel() { return optionsModel; } AddressTableModel *WalletModel::getAddressTableModel() { return addressTableModel; } TransactionTableModel *WalletModel::getTransactionTableModel() { return transactionTableModel; } RecentRequestsTableModel *WalletModel::getRecentRequestsTableModel() { return recentRequestsTableModel; } WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const { if (!wallet->IsCrypted()) { return Unencrypted; } else if (wallet->IsLocked()) { return Locked; } else { return Unlocked; } } bool WalletModel::setWalletEncrypted(bool encrypted, const SecureString &passphrase) { if (encrypted) { // Encrypt return wallet->EncryptWallet(passphrase); } else { // Decrypt -- TODO; not supported yet return false; } } bool WalletModel::setWalletLocked(bool locked, const SecureString &passPhrase) { if (locked) { // Lock return wallet->Lock(); } else { // Unlock return wallet->Unlock(passPhrase); } } bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureString &newPass) { bool retval; { LOCK(wallet->cs_wallet); // Make sure wallet is locked before attempting pass change wallet->Lock(); retval = wallet->ChangeWalletPassphrase(oldPass, newPass); } return retval; } bool WalletModel::backupWallet(const QString &filename) { return wallet->BackupWallet(filename.toLocal8Bit().data()); } // Handlers for core signals static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel, CCryptoKeyStore *wallet) { qDebug() << "NotifyKeyStoreStatusChanged"; QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); } static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, const CTxDestination &address, const std::string &label, bool isMine, const std::string &purpose, ChangeType status) { QString strAddress = QString::fromStdString(CBitcoinAddress(address).ToString()); QString strLabel = QString::fromStdString(label); QString strPurpose = QString::fromStdString(purpose); qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status); QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, Q_ARG(QString, strAddress), Q_ARG(QString, strLabel), Q_ARG(bool, isMine), Q_ARG(QString, strPurpose), Q_ARG(int, status)); } static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet, const uint256 &hash, ChangeType status) { Q_UNUSED(wallet); Q_UNUSED(hash); Q_UNUSED(status); QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection); } static void ShowProgress(WalletModel *walletmodel, const std::string &title, int nProgress) { // emits signal "showProgress" QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(title)), Q_ARG(int, nProgress)); } static void NotifyWatchonlyChanged(WalletModel *walletmodel, bool fHaveWatchonly) { QMetaObject::invokeMethod(walletmodel, "updateWatchOnlyFlag", Qt::QueuedConnection, Q_ARG(bool, fHaveWatchonly)); } void WalletModel::subscribeToCoreSignals() { // Connect signals to wallet wallet->NotifyStatusChanged.connect( boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); wallet->NotifyAddressBookChanged.connect( boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5, _6)); wallet->NotifyTransactionChanged.connect( boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); wallet->ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); wallet->NotifyWatchonlyChanged.connect( boost::bind(NotifyWatchonlyChanged, this, _1)); } void WalletModel::unsubscribeFromCoreSignals() { // Disconnect signals from wallet wallet->NotifyStatusChanged.disconnect( boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); wallet->NotifyAddressBookChanged.disconnect( boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5, _6)); wallet->NotifyTransactionChanged.disconnect( boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); wallet->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); wallet->NotifyWatchonlyChanged.disconnect( boost::bind(NotifyWatchonlyChanged, this, _1)); } // WalletModel::UnlockContext implementation WalletModel::UnlockContext WalletModel::requestUnlock() { bool was_locked = getEncryptionStatus() == Locked; if (was_locked) { // Request UI to unlock wallet Q_EMIT requireUnlock(); } // If wallet is still locked, unlock was failed or cancelled, mark context // as invalid bool valid = getEncryptionStatus() != Locked; return UnlockContext(this, valid, was_locked); } WalletModel::UnlockContext::UnlockContext(WalletModel *_wallet, bool _valid, bool _relock) : wallet(_wallet), valid(_valid), relock(_relock) {} WalletModel::UnlockContext::~UnlockContext() { if (valid && relock) { wallet->setWalletLocked(true); } } void WalletModel::UnlockContext::CopyFrom(const UnlockContext &rhs) { // Transfer context; old object no longer relocks wallet *this = rhs; rhs.relock = false; } bool WalletModel::getPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const { return wallet->GetPubKey(address, vchPubKeyOut); } -bool WalletModel::havePrivKey(const CKeyID &address) const { - return wallet->HaveKey(address); +bool WalletModel::IsSpendable(const CTxDestination &dest) const { + return IsMine(*wallet, dest) & ISMINE_SPENDABLE; } bool WalletModel::getPrivKey(const CKeyID &address, CKey &vchPrivKeyOut) const { return wallet->GetKey(address, vchPrivKeyOut); } // returns a list of COutputs from COutPoints void WalletModel::getOutputs(const std::vector &vOutpoints, std::vector &vOutputs) { LOCK2(cs_main, wallet->cs_wallet); for (const COutPoint &outpoint : vOutpoints) { if (!wallet->mapWallet.count(outpoint.hash)) continue; int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain(); if (nDepth < 0) continue; COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true, true); vOutputs.push_back(out); } } bool WalletModel::isSpent(const COutPoint &outpoint) const { LOCK2(cs_main, wallet->cs_wallet); return wallet->IsSpent(outpoint.hash, outpoint.n); } // AvailableCoins + LockedCoins grouped by wallet address (put change in one // group with wallet address) void WalletModel::listCoins( std::map> &mapCoins) const { std::vector vCoins; wallet->AvailableCoins(vCoins); // ListLockedCoins, mapWallet LOCK2(cs_main, wallet->cs_wallet); std::vector vLockedCoins; wallet->ListLockedCoins(vLockedCoins); // add locked coins for (const COutPoint &outpoint : vLockedCoins) { if (!wallet->mapWallet.count(outpoint.hash)) continue; int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain(); if (nDepth < 0) continue; COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true, true); if (outpoint.n < out.tx->tx->vout.size() && wallet->IsMine(out.tx->tx->vout[outpoint.n]) == ISMINE_SPENDABLE) vCoins.push_back(out); } for (const COutput &out : vCoins) { COutput cout = out; while (wallet->IsChange(cout.tx->tx->vout[cout.i]) && cout.tx->tx->vin.size() > 0 && wallet->IsMine(cout.tx->tx->vin[0])) { if (!wallet->mapWallet.count(cout.tx->tx->vin[0].prevout.hash)) break; cout = COutput(&wallet->mapWallet[cout.tx->tx->vin[0].prevout.hash], cout.tx->tx->vin[0].prevout.n, 0, true, true); } CTxDestination address; if (!out.fSpendable || !ExtractDestination(cout.tx->tx->vout[cout.i].scriptPubKey, address)) continue; mapCoins[QString::fromStdString(CBitcoinAddress(address).ToString())] .push_back(out); } } bool WalletModel::isLockedCoin(uint256 hash, unsigned int n) const { LOCK2(cs_main, wallet->cs_wallet); return wallet->IsLockedCoin(hash, n); } void WalletModel::lockCoin(COutPoint &output) { LOCK2(cs_main, wallet->cs_wallet); wallet->LockCoin(output); } void WalletModel::unlockCoin(COutPoint &output) { LOCK2(cs_main, wallet->cs_wallet); wallet->UnlockCoin(output); } void WalletModel::listLockedCoins(std::vector &vOutpts) { LOCK2(cs_main, wallet->cs_wallet); wallet->ListLockedCoins(vOutpts); } void WalletModel::loadReceiveRequests( std::vector &vReceiveRequests) { LOCK(wallet->cs_wallet); for (const std::pair &item : wallet->mapAddressBook) { for (const std::pair &item2 : item.second.destdata) if (item2.first.size() > 2 && item2.first.substr(0, 2) == "rr") // receive request vReceiveRequests.push_back(item2.second); } } bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest) { CTxDestination dest = CBitcoinAddress(sAddress).Get(); std::stringstream ss; ss << nId; // "rr" prefix = "receive request" in destdata std::string key = "rr" + ss.str(); LOCK(wallet->cs_wallet); if (sRequest.empty()) return wallet->EraseDestData(dest, key); else return wallet->AddDestData(dest, key, sRequest); } bool WalletModel::transactionCanBeAbandoned(uint256 hash) const { LOCK2(cs_main, wallet->cs_wallet); const CWalletTx *wtx = wallet->GetWalletTx(hash); if (!wtx || wtx->isAbandoned() || wtx->GetDepthInMainChain() > 0 || wtx->InMempool()) return false; return true; } bool WalletModel::abandonTransaction(uint256 hash) const { LOCK2(cs_main, wallet->cs_wallet); return wallet->AbandonTransaction(hash); } bool WalletModel::isWalletEnabled() { return !GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET); } bool WalletModel::hdEnabled() const { return wallet->IsHDEnabled(); } int WalletModel::getDefaultConfirmTarget() const { return nTxConfirmTarget; } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 65ebc3bfb..fa8f5c19a 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -1,308 +1,308 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_WALLETMODEL_H #define BITCOIN_QT_WALLETMODEL_H #include "paymentrequestplus.h" #include "walletmodeltransaction.h" #include "support/allocators/secure.h" #include #include #include class AddressTableModel; class OptionsModel; class PlatformStyle; class RecentRequestsTableModel; class TransactionTableModel; class WalletModelTransaction; class CCoinControl; class CKeyID; class COutPoint; class COutput; class CPubKey; class CWallet; class uint256; QT_BEGIN_NAMESPACE class QTimer; QT_END_NAMESPACE class SendCoinsRecipient { public: explicit SendCoinsRecipient() : amount(0), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) {} explicit SendCoinsRecipient(const QString &addr, const QString &_label, const CAmount &_amount, const QString &_message) : address(addr), label(_label), amount(_amount), message(_message), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) {} // If from an unauthenticated payment request, this is used for storing the // addresses, e.g. address-A
address-B
address-C. // Info: As we don't need to process addresses in here when using payment // requests, we can abuse it for displaying an address list. // TOFO: This is a hack, should be replaced with a cleaner solution! QString address; QString label; CAmount amount; // If from a payment request, this is used for storing the memo QString message; // If from a payment request, paymentRequest.IsInitialized() will be true PaymentRequestPlus paymentRequest; // Empty if no authentication or invalid signature/cert/etc. QString authenticatedMerchant; // memory only bool fSubtractFeeFromAmount; static const int CURRENT_VERSION = 1; int nVersion; ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { std::string sAddress = address.toStdString(); std::string sLabel = label.toStdString(); std::string sMessage = message.toStdString(); std::string sPaymentRequest; if (!ser_action.ForRead() && paymentRequest.IsInitialized()) paymentRequest.SerializeToString(&sPaymentRequest); std::string sAuthenticatedMerchant = authenticatedMerchant.toStdString(); READWRITE(this->nVersion); READWRITE(sAddress); READWRITE(sLabel); READWRITE(amount); READWRITE(sMessage); READWRITE(sPaymentRequest); READWRITE(sAuthenticatedMerchant); if (ser_action.ForRead()) { address = QString::fromStdString(sAddress); label = QString::fromStdString(sLabel); message = QString::fromStdString(sMessage); if (!sPaymentRequest.empty()) paymentRequest.parse(QByteArray::fromRawData( sPaymentRequest.data(), sPaymentRequest.size())); authenticatedMerchant = QString::fromStdString(sAuthenticatedMerchant); } } }; /** Interface to Bitcoin wallet from Qt view code. */ class WalletModel : public QObject { Q_OBJECT public: explicit WalletModel(const PlatformStyle *platformStyle, CWallet *wallet, OptionsModel *optionsModel, QObject *parent = 0); ~WalletModel(); // Returned by sendCoins enum StatusCode { OK, InvalidAmount, InvalidAddress, AmountExceedsBalance, AmountWithFeeExceedsBalance, DuplicateAddress, // Error returned when wallet is still locked TransactionCreationFailed, TransactionCommitFailed, AbsurdFee, PaymentRequestExpired }; enum EncryptionStatus { // !wallet->IsCrypted() Unencrypted, // wallet->IsCrypted() && wallet->IsLocked() Locked, // wallet->IsCrypted() && !wallet->IsLocked() Unlocked }; OptionsModel *getOptionsModel(); AddressTableModel *getAddressTableModel(); TransactionTableModel *getTransactionTableModel(); RecentRequestsTableModel *getRecentRequestsTableModel(); CAmount getBalance(const CCoinControl *coinControl = nullptr) const; CAmount getUnconfirmedBalance() const; CAmount getImmatureBalance() const; bool haveWatchOnly() const; CAmount getWatchBalance() const; CAmount getWatchUnconfirmedBalance() const; CAmount getWatchImmatureBalance() const; EncryptionStatus getEncryptionStatus() const; // Check address for validity bool validateAddress(const QString &address); // Return status record for SendCoins, contains error id + information struct SendCoinsReturn { SendCoinsReturn(StatusCode _status = OK, QString _reasonCommitFailed = "") : status(_status), reasonCommitFailed(_reasonCommitFailed) {} StatusCode status; QString reasonCommitFailed; }; // prepare transaction for getting txfee before sending coins SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl = nullptr); // Send coins to a list of recipients SendCoinsReturn sendCoins(WalletModelTransaction &transaction); // Wallet encryption bool setWalletEncrypted(bool encrypted, const SecureString &passphrase); // Passphrase only needed when unlocking bool setWalletLocked(bool locked, const SecureString &passPhrase = SecureString()); bool changePassphrase(const SecureString &oldPass, const SecureString &newPass); // Wallet backup bool backupWallet(const QString &filename); // RAI object for unlocking wallet, returned by requestUnlock() class UnlockContext { public: UnlockContext(WalletModel *wallet, bool valid, bool relock); ~UnlockContext(); bool isValid() const { return valid; } // Copy operator and constructor transfer the context UnlockContext(const UnlockContext &obj) { CopyFrom(obj); } UnlockContext &operator=(const UnlockContext &rhs) { CopyFrom(rhs); return *this; } private: WalletModel *wallet; bool valid; // mutable, as it can be set to false by copying mutable bool relock; void CopyFrom(const UnlockContext &rhs); }; UnlockContext requestUnlock(); bool getPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const; - bool havePrivKey(const CKeyID &address) const; + bool IsSpendable(const CTxDestination &dest) const; bool getPrivKey(const CKeyID &address, CKey &vchPrivKeyOut) const; void getOutputs(const std::vector &vOutpoints, std::vector &vOutputs); bool isSpent(const COutPoint &outpoint) const; void listCoins(std::map> &mapCoins) const; bool isLockedCoin(uint256 hash, unsigned int n) const; void lockCoin(COutPoint &output); void unlockCoin(COutPoint &output); void listLockedCoins(std::vector &vOutpts); void loadReceiveRequests(std::vector &vReceiveRequests); bool saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest); bool transactionCanBeAbandoned(uint256 hash) const; bool abandonTransaction(uint256 hash) const; static bool isWalletEnabled(); bool hdEnabled() const; int getDefaultConfirmTarget() const; private: CWallet *wallet; bool fHaveWatchOnly; bool fForceCheckBalanceChanged; // Wallet has an options model for wallet-specific options (transaction fee, // for example) OptionsModel *optionsModel; AddressTableModel *addressTableModel; TransactionTableModel *transactionTableModel; RecentRequestsTableModel *recentRequestsTableModel; // Cache some values to be able to detect changes CAmount cachedBalance; CAmount cachedUnconfirmedBalance; CAmount cachedImmatureBalance; CAmount cachedWatchOnlyBalance; CAmount cachedWatchUnconfBalance; CAmount cachedWatchImmatureBalance; EncryptionStatus cachedEncryptionStatus; int cachedNumBlocks; QTimer *pollTimer; void subscribeToCoreSignals(); void unsubscribeFromCoreSignals(); void checkBalanceChanged(); Q_SIGNALS: // Signal that balance in wallet changed void balanceChanged(const CAmount &balance, const CAmount &unconfirmedBalance, const CAmount &immatureBalance, const CAmount &watchOnlyBalance, const CAmount &watchUnconfBalance, const CAmount &watchImmatureBalance); // Encryption status of wallet changed void encryptionStatusChanged(int status); // Signal emitted when wallet needs to be unlocked // It is valid behaviour for listeners to keep the wallet locked after this // signal; this means that the unlocking failed or was cancelled. void requireUnlock(); // Fired when a message should be reported to the user void message(const QString &title, const QString &message, unsigned int style); // Coins sent: from wallet, to recipient, in (serialized) transaction: void coinsSent(CWallet *wallet, SendCoinsRecipient recipient, QByteArray transaction); // Show progress dialog e.g. for rescan void showProgress(const QString &title, int nProgress); // Watch-only address added void notifyWatchonlyChanged(bool fHaveWatchonly); public Q_SLOTS: /* Wallet status might have changed */ void updateStatus(); /* New transaction, or transaction changed status */ void updateTransaction(); /* New, updated or removed address book entry */ void updateAddressBook(const QString &address, const QString &label, bool isMine, const QString &purpose, int status); /* Watch-only added */ void updateWatchOnlyFlag(bool fHaveWatchonly); /* Current, immature or unconfirmed balance might have changed - emit * 'balanceChanged' if so */ void pollBalanceChanged(); }; #endif // BITCOIN_QT_WALLETMODEL_H