walletmodel.cpp
1 // Copyright (c) 2011-present The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #include <qt/walletmodel.h> 6 7 #include <qt/addresstablemodel.h> 8 #include <qt/clientmodel.h> 9 #include <qt/guiconstants.h> 10 #include <qt/guiutil.h> 11 #include <qt/optionsmodel.h> 12 #include <qt/paymentserver.h> 13 #include <qt/recentrequeststablemodel.h> 14 #include <qt/sendcoinsdialog.h> 15 #include <qt/transactiontablemodel.h> 16 17 #include <common/args.h> 18 #include <interfaces/handler.h> 19 #include <interfaces/node.h> 20 #include <key_io.h> 21 #include <node/interface_ui.h> 22 #include <node/types.h> 23 #include <psbt.h> 24 #include <util/translation.h> 25 #include <wallet/coincontrol.h> 26 #include <wallet/wallet.h> 27 28 #include <cstdint> 29 #include <functional> 30 #include <memory> 31 #include <vector> 32 33 #include <QDebug> 34 #include <QMessageBox> 35 #include <QSet> 36 #include <QTimer> 37 38 using wallet::CCoinControl; 39 using wallet::CRecipient; 40 using wallet::DEFAULT_DISABLE_WALLET; 41 42 WalletModel::WalletModel(std::unique_ptr<interfaces::Wallet> wallet, ClientModel& client_model, const PlatformStyle *platformStyle, QObject *parent) : 43 QObject(parent), 44 m_wallet(std::move(wallet)), 45 m_client_model(&client_model), 46 m_node(client_model.node()), 47 optionsModel(client_model.getOptionsModel()), 48 timer(new QTimer(this)) 49 { 50 addressTableModel = new AddressTableModel(this); 51 transactionTableModel = new TransactionTableModel(platformStyle, this); 52 recentRequestsTableModel = new RecentRequestsTableModel(this); 53 54 subscribeToCoreSignals(); 55 } 56 57 WalletModel::~WalletModel() 58 { 59 unsubscribeFromCoreSignals(); 60 } 61 62 void WalletModel::startPollBalance() 63 { 64 // Update the cached balance right away, so every view can make use of it, 65 // so them don't need to waste resources recalculating it. 66 pollBalanceChanged(); 67 68 // This timer will be fired repeatedly to update the balance 69 // Since the QTimer::timeout is a private signal, it cannot be used 70 // in the GUIUtil::ExceptionSafeConnect directly. 71 connect(timer, &QTimer::timeout, this, &WalletModel::timerTimeout); 72 GUIUtil::ExceptionSafeConnect(this, &WalletModel::timerTimeout, this, &WalletModel::pollBalanceChanged); 73 timer->start(MODEL_UPDATE_DELAY); 74 } 75 76 void WalletModel::setClientModel(ClientModel* client_model) 77 { 78 m_client_model = client_model; 79 if (!m_client_model) timer->stop(); 80 } 81 82 void WalletModel::updateStatus() 83 { 84 EncryptionStatus newEncryptionStatus = getEncryptionStatus(); 85 86 if(cachedEncryptionStatus != newEncryptionStatus) { 87 Q_EMIT encryptionStatusChanged(); 88 } 89 } 90 91 void WalletModel::pollBalanceChanged() 92 { 93 // Avoid recomputing wallet balances unless a TransactionChanged or 94 // BlockTip notification was received. 95 if (!fForceCheckBalanceChanged && m_cached_last_update_tip == getLastBlockProcessed()) return; 96 97 // Try to get balances and return early if locks can't be acquired. This 98 // avoids the GUI from getting stuck on periodical polls if the core is 99 // holding the locks for a longer time - for example, during a wallet 100 // rescan. 101 interfaces::WalletBalances new_balances; 102 uint256 block_hash; 103 if (!m_wallet->tryGetBalances(new_balances, block_hash)) { 104 return; 105 } 106 107 if (fForceCheckBalanceChanged || block_hash != m_cached_last_update_tip) { 108 fForceCheckBalanceChanged = false; 109 110 // Balance and number of transactions might have changed 111 m_cached_last_update_tip = block_hash; 112 113 checkBalanceChanged(new_balances); 114 if(transactionTableModel) 115 transactionTableModel->updateConfirmations(); 116 } 117 } 118 119 void WalletModel::checkBalanceChanged(const interfaces::WalletBalances& new_balances) 120 { 121 if (new_balances.balanceChanged(m_cached_balances)) { 122 m_cached_balances = new_balances; 123 Q_EMIT balanceChanged(new_balances); 124 } 125 } 126 127 interfaces::WalletBalances WalletModel::getCachedBalance() const 128 { 129 return m_cached_balances; 130 } 131 132 void WalletModel::updateTransaction() 133 { 134 // Balance and number of transactions might have changed 135 fForceCheckBalanceChanged = true; 136 } 137 138 void WalletModel::updateAddressBook(const QString &address, const QString &label, 139 bool isMine, wallet::AddressPurpose purpose, int status) 140 { 141 if(addressTableModel) 142 addressTableModel->updateEntry(address, label, isMine, purpose, status); 143 } 144 145 bool WalletModel::validateAddress(const QString& address) const 146 { 147 return IsValidDestinationString(address.toStdString()); 148 } 149 150 WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl) 151 { 152 CAmount total = 0; 153 bool fSubtractFeeFromAmount = false; 154 QList<SendCoinsRecipient> recipients = transaction.getRecipients(); 155 std::vector<CRecipient> vecSend; 156 157 if(recipients.empty()) 158 { 159 return OK; 160 } 161 162 QSet<QString> setAddress; // Used to detect duplicates 163 int nAddresses = 0; 164 165 // Pre-check input data for validity 166 for (const SendCoinsRecipient &rcp : recipients) 167 { 168 if (rcp.fSubtractFeeFromAmount) 169 fSubtractFeeFromAmount = true; 170 { // User-entered bitcoin address / amount: 171 if(!validateAddress(rcp.address)) 172 { 173 return InvalidAddress; 174 } 175 if(rcp.amount <= 0) 176 { 177 return InvalidAmount; 178 } 179 setAddress.insert(rcp.address); 180 ++nAddresses; 181 182 vecSend.emplace_back(CRecipient{DecodeDestination(rcp.address.toStdString()), rcp.amount, rcp.fSubtractFeeFromAmount}); 183 184 total += rcp.amount; 185 } 186 } 187 if(setAddress.size() != nAddresses) 188 { 189 return DuplicateAddress; 190 } 191 192 // If no coin was manually selected, use the cached balance 193 // Future: can merge this call with 'createTransaction'. 194 CAmount nBalance = getAvailableBalance(&coinControl); 195 196 if(total > nBalance) 197 { 198 return AmountExceedsBalance; 199 } 200 201 try { 202 CAmount nFeeRequired = 0; 203 int nChangePosRet = -1; 204 205 auto& newTx = transaction.getWtx(); 206 const auto& res = m_wallet->createTransaction(vecSend, coinControl, /*sign=*/!wallet().privateKeysDisabled(), nChangePosRet, nFeeRequired); 207 newTx = res ? *res : nullptr; 208 transaction.setTransactionFee(nFeeRequired); 209 if (fSubtractFeeFromAmount && newTx) 210 transaction.reassignAmounts(nChangePosRet); 211 212 if(!newTx) 213 { 214 if(!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance) 215 { 216 return SendCoinsReturn(AmountWithFeeExceedsBalance); 217 } 218 Q_EMIT message(tr("Send Coins"), QString::fromStdString(util::ErrorString(res).translated), 219 CClientUIInterface::MSG_ERROR); 220 return TransactionCreationFailed; 221 } 222 223 // Reject absurdly high fee. (This can never happen because the 224 // wallet never creates transactions with fee greater than 225 // m_default_max_tx_fee. This merely a belt-and-suspenders check). 226 if (nFeeRequired > m_wallet->getDefaultMaxTxFee()) { 227 return AbsurdFee; 228 } 229 } catch (const std::runtime_error& err) { 230 // Something unexpected happened, instruct user to report this bug. 231 Q_EMIT message(tr("Send Coins"), QString::fromStdString(err.what()), 232 CClientUIInterface::MSG_ERROR); 233 return TransactionCreationFailed; 234 } 235 236 return SendCoinsReturn(OK); 237 } 238 239 void WalletModel::sendCoins(WalletModelTransaction& transaction) 240 { 241 QByteArray transaction_array; /* store serialized transaction */ 242 243 { 244 std::vector<std::pair<std::string, std::string>> vOrderForm; 245 for (const SendCoinsRecipient &rcp : transaction.getRecipients()) 246 { 247 if (!rcp.message.isEmpty()) // Message from normal bitcoin:URI (bitcoin:123...?message=example) 248 vOrderForm.emplace_back("Message", rcp.message.toStdString()); 249 } 250 251 auto& newTx = transaction.getWtx(); 252 wallet().commitTransaction(newTx, /*value_map=*/{}, std::move(vOrderForm)); 253 254 DataStream ssTx; 255 ssTx << TX_WITH_WITNESS(*newTx); 256 transaction_array.append((const char*)ssTx.data(), ssTx.size()); 257 } 258 259 // Add addresses / update labels that we've sent to the address book, 260 // and emit coinsSent signal for each recipient 261 for (const SendCoinsRecipient &rcp : transaction.getRecipients()) 262 { 263 { 264 std::string strAddress = rcp.address.toStdString(); 265 CTxDestination dest = DecodeDestination(strAddress); 266 std::string strLabel = rcp.label.toStdString(); 267 { 268 // Check if we have a new address or an updated label 269 std::string name; 270 if (!m_wallet->getAddress( 271 dest, &name, /*purpose=*/nullptr)) 272 { 273 m_wallet->setAddressBook(dest, strLabel, wallet::AddressPurpose::SEND); 274 } 275 else if (name != strLabel) 276 { 277 m_wallet->setAddressBook(dest, strLabel, {}); // {} means don't change purpose 278 } 279 } 280 } 281 Q_EMIT coinsSent(this, rcp, transaction_array); 282 } 283 284 checkBalanceChanged(m_wallet->getBalances()); // update balance immediately, otherwise there could be a short noticeable delay until pollBalanceChanged hits 285 } 286 287 OptionsModel* WalletModel::getOptionsModel() const 288 { 289 return optionsModel; 290 } 291 292 AddressTableModel* WalletModel::getAddressTableModel() const 293 { 294 return addressTableModel; 295 } 296 297 TransactionTableModel* WalletModel::getTransactionTableModel() const 298 { 299 return transactionTableModel; 300 } 301 302 RecentRequestsTableModel* WalletModel::getRecentRequestsTableModel() const 303 { 304 return recentRequestsTableModel; 305 } 306 307 WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const 308 { 309 if(!m_wallet->isCrypted()) 310 { 311 // A previous bug allowed for watchonly wallets to be encrypted (encryption keys set, but nothing is actually encrypted). 312 // To avoid misrepresenting the encryption status of such wallets, we only return NoKeys for watchonly wallets that are unencrypted. 313 if (m_wallet->privateKeysDisabled()) { 314 return NoKeys; 315 } 316 return Unencrypted; 317 } 318 else if(m_wallet->isLocked()) 319 { 320 return Locked; 321 } 322 else 323 { 324 return Unlocked; 325 } 326 } 327 328 bool WalletModel::setWalletEncrypted(const SecureString& passphrase) 329 { 330 return m_wallet->encryptWallet(passphrase); 331 } 332 333 bool WalletModel::setWalletLocked(bool locked, const SecureString &passPhrase) 334 { 335 if(locked) 336 { 337 // Lock 338 return m_wallet->lock(); 339 } 340 else 341 { 342 // Unlock 343 return m_wallet->unlock(passPhrase); 344 } 345 } 346 347 bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureString &newPass) 348 { 349 m_wallet->lock(); // Make sure wallet is locked before attempting pass change 350 return m_wallet->changeWalletPassphrase(oldPass, newPass); 351 } 352 353 // Handlers for core signals 354 static void NotifyUnload(WalletModel* walletModel) 355 { 356 qDebug() << "NotifyUnload"; 357 bool invoked = QMetaObject::invokeMethod(walletModel, "unload"); 358 assert(invoked); 359 } 360 361 static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel) 362 { 363 qDebug() << "NotifyKeyStoreStatusChanged"; 364 bool invoked = QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); 365 assert(invoked); 366 } 367 368 static void NotifyAddressBookChanged(WalletModel *walletmodel, 369 const CTxDestination &address, const std::string &label, bool isMine, 370 wallet::AddressPurpose purpose, ChangeType status) 371 { 372 QString strAddress = QString::fromStdString(EncodeDestination(address)); 373 QString strLabel = QString::fromStdString(label); 374 375 qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + QString::number(static_cast<uint8_t>(purpose)) + " status=" + QString::number(status); 376 bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook", 377 Q_ARG(QString, strAddress), 378 Q_ARG(QString, strLabel), 379 Q_ARG(bool, isMine), 380 Q_ARG(wallet::AddressPurpose, purpose), 381 Q_ARG(int, status)); 382 assert(invoked); 383 } 384 385 static void NotifyTransactionChanged(WalletModel *walletmodel, const Txid& hash, ChangeType status) 386 { 387 Q_UNUSED(hash); 388 Q_UNUSED(status); 389 bool invoked = QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection); 390 assert(invoked); 391 } 392 393 static void ShowProgress(WalletModel *walletmodel, const std::string &title, int nProgress) 394 { 395 // emits signal "showProgress" 396 bool invoked = QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection, 397 Q_ARG(QString, QString::fromStdString(title)), 398 Q_ARG(int, nProgress)); 399 assert(invoked); 400 } 401 402 static void NotifyCanGetAddressesChanged(WalletModel* walletmodel) 403 { 404 bool invoked = QMetaObject::invokeMethod(walletmodel, "canGetAddressesChanged"); 405 assert(invoked); 406 } 407 408 void WalletModel::subscribeToCoreSignals() 409 { 410 // Connect signals to wallet 411 m_handler_unload = m_wallet->handleUnload(std::bind(&NotifyUnload, this)); 412 m_handler_status_changed = m_wallet->handleStatusChanged(std::bind(&NotifyKeyStoreStatusChanged, this)); 413 m_handler_address_book_changed = m_wallet->handleAddressBookChanged(std::bind(NotifyAddressBookChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); 414 m_handler_transaction_changed = m_wallet->handleTransactionChanged(std::bind(NotifyTransactionChanged, this, std::placeholders::_1, std::placeholders::_2)); 415 m_handler_show_progress = m_wallet->handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2)); 416 m_handler_can_get_addrs_changed = m_wallet->handleCanGetAddressesChanged(std::bind(NotifyCanGetAddressesChanged, this)); 417 } 418 419 void WalletModel::unsubscribeFromCoreSignals() 420 { 421 // Disconnect signals from wallet 422 m_handler_unload->disconnect(); 423 m_handler_status_changed->disconnect(); 424 m_handler_address_book_changed->disconnect(); 425 m_handler_transaction_changed->disconnect(); 426 m_handler_show_progress->disconnect(); 427 m_handler_can_get_addrs_changed->disconnect(); 428 } 429 430 // WalletModel::UnlockContext implementation 431 WalletModel::UnlockContext WalletModel::requestUnlock() 432 { 433 // Bugs in earlier versions may have resulted in wallets with private keys disabled to become "encrypted" 434 // (encryption keys are present, but not actually doing anything). 435 // To avoid issues with such wallets, check if the wallet has private keys disabled, and if so, return a context 436 // that indicates the wallet is not encrypted. 437 if (m_wallet->privateKeysDisabled()) { 438 return UnlockContext(this, /*valid=*/true, /*relock=*/false); 439 } 440 bool was_locked = getEncryptionStatus() == Locked; 441 if(was_locked) 442 { 443 // Request UI to unlock wallet 444 Q_EMIT requireUnlock(); 445 } 446 // If wallet is still locked, unlock was failed or cancelled, mark context as invalid 447 bool valid = getEncryptionStatus() != Locked; 448 449 return UnlockContext(this, valid, was_locked); 450 } 451 452 WalletModel::UnlockContext::UnlockContext(WalletModel *_wallet, bool _valid, bool _relock): 453 wallet(_wallet), 454 valid(_valid), 455 relock(_relock) 456 { 457 } 458 459 WalletModel::UnlockContext::~UnlockContext() 460 { 461 if(valid && relock) 462 { 463 wallet->setWalletLocked(true); 464 } 465 } 466 467 bool WalletModel::bumpFee(Txid hash, Txid& new_hash) 468 { 469 CCoinControl coin_control; 470 coin_control.m_signal_bip125_rbf = true; 471 std::vector<bilingual_str> errors; 472 CAmount old_fee; 473 CAmount new_fee; 474 CMutableTransaction mtx; 475 if (!m_wallet->createBumpTransaction(hash, coin_control, errors, old_fee, new_fee, mtx)) { 476 QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Increasing transaction fee failed") + "<br />(" + 477 (errors.size() ? QString::fromStdString(errors[0].translated) : "") +")"); 478 return false; 479 } 480 481 // allow a user based fee verification 482 /*: Asks a user if they would like to manually increase the fee of a transaction that has already been created. */ 483 QString questionString = tr("Do you want to increase the fee?"); 484 questionString.append("<br />"); 485 questionString.append("<table style=\"text-align: left;\">"); 486 questionString.append("<tr><td>"); 487 questionString.append(tr("Current fee:")); 488 questionString.append("</td><td>"); 489 questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), old_fee)); 490 questionString.append("</td></tr><tr><td>"); 491 questionString.append(tr("Increase:")); 492 questionString.append("</td><td>"); 493 questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), new_fee - old_fee)); 494 questionString.append("</td></tr><tr><td>"); 495 questionString.append(tr("New fee:")); 496 questionString.append("</td><td>"); 497 questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), new_fee)); 498 questionString.append("</td></tr></table>"); 499 500 // Display warning in the "Confirm fee bump" window if the "Coin Control Features" option is enabled 501 if (getOptionsModel()->getCoinControlFeatures()) { 502 questionString.append("<br><br>"); 503 questionString.append(tr("Warning: This may pay the additional fee by reducing change outputs or adding inputs, when necessary. It may add a new change output if one does not already exist. These changes may potentially leak privacy.")); 504 } 505 506 const bool enable_send{!wallet().privateKeysDisabled() || wallet().hasExternalSigner()}; 507 const bool always_show_unsigned{getOptionsModel()->getEnablePSBTControls()}; 508 auto confirmationDialog = new SendConfirmationDialog(tr("Confirm fee bump"), questionString, "", "", SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, nullptr); 509 confirmationDialog->setAttribute(Qt::WA_DeleteOnClose); 510 // TODO: Replace QDialog::exec() with safer QDialog::show(). 511 const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec()); 512 513 // cancel sign&broadcast if user doesn't want to bump the fee 514 if (retval != QMessageBox::Yes && retval != QMessageBox::Save) { 515 return false; 516 } 517 518 // Short-circuit if we are returning a bumped transaction PSBT to clipboard 519 if (retval == QMessageBox::Save) { 520 // "Create Unsigned" clicked 521 PartiallySignedTransaction psbtx(mtx); 522 bool complete = false; 523 const auto err{wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true, nullptr, psbtx, complete)}; 524 if (err || complete) { 525 QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction.")); 526 return false; 527 } 528 // Serialize the PSBT 529 DataStream ssTx{}; 530 ssTx << psbtx; 531 GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); 532 Q_EMIT message(tr("PSBT copied"), tr("Fee-bump PSBT copied to clipboard"), CClientUIInterface::MSG_INFORMATION | CClientUIInterface::MODAL); 533 return true; 534 } 535 536 WalletModel::UnlockContext ctx(requestUnlock()); 537 if (!ctx.isValid()) { 538 return false; 539 } 540 541 assert(!m_wallet->privateKeysDisabled() || wallet().hasExternalSigner()); 542 543 // sign bumped transaction 544 if (!m_wallet->signBumpTransaction(mtx)) { 545 QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't sign transaction.")); 546 return false; 547 } 548 // commit the bumped transaction 549 if(!m_wallet->commitBumpTransaction(hash, std::move(mtx), errors, new_hash)) { 550 QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Could not commit transaction") + "<br />(" + 551 QString::fromStdString(errors[0].translated)+")"); 552 return false; 553 } 554 return true; 555 } 556 557 void WalletModel::displayAddress(std::string sAddress) const 558 { 559 CTxDestination dest = DecodeDestination(sAddress); 560 try { 561 util::Result<void> result = m_wallet->displayAddress(dest); 562 if (!result) { 563 QMessageBox::warning(nullptr, tr("Signer error"), QString::fromStdString(util::ErrorString(result).translated)); 564 } 565 } catch (const std::runtime_error& e) { 566 QMessageBox::critical(nullptr, tr("Can't display address"), e.what()); 567 } 568 } 569 570 bool WalletModel::isWalletEnabled() 571 { 572 return !gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET); 573 } 574 575 QString WalletModel::getWalletName() const 576 { 577 return QString::fromStdString(m_wallet->getWalletName()); 578 } 579 580 QString WalletModel::getDisplayName() const 581 { 582 return GUIUtil::WalletDisplayName(getWalletName()); 583 } 584 585 bool WalletModel::isMultiwallet() const 586 { 587 return m_node.walletLoader().getWallets().size() > 1; 588 } 589 590 void WalletModel::refresh(bool pk_hash_only) 591 { 592 addressTableModel = new AddressTableModel(this, pk_hash_only); 593 } 594 595 uint256 WalletModel::getLastBlockProcessed() const 596 { 597 return m_client_model ? m_client_model->getBestBlockHash() : uint256{}; 598 } 599 600 CAmount WalletModel::getAvailableBalance(const CCoinControl* control) 601 { 602 // No selected coins, return the cached balance 603 if (!control || !control->HasSelected()) { 604 const interfaces::WalletBalances& balances = getCachedBalance(); 605 return balances.balance; 606 } 607 // Fetch balance from the wallet, taking into account the selected coins 608 return wallet().getAvailableBalance(*control); 609 }