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