diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 6bcde7e75af5..e5451734cebf 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -421,6 +421,10 @@ class Wallet using CanGetAddressesChangedFn = std::function; virtual std::unique_ptr handleCanGetAddressesChanged(CanGetAddressesChangedFn fn) = 0; + //! Register handler for locked coins changed messages. + using LockedCoinsChangedFn = std::function; + virtual std::unique_ptr handleLockedCoinsChanged(LockedCoinsChangedFn fn) = 0; + //! Get governance objects stored in the wallet. virtual std::vector getGovernanceObjects() = 0; @@ -498,20 +502,26 @@ struct WalletBalances CAmount balance = 0; CAmount unconfirmed_balance = 0; CAmount immature_balance = 0; + CAmount locked_balance = 0; //!< Subset of balance locked via LockCoin CAmount anonymized_balance = 0; bool have_watch_only = false; CAmount watch_only_balance = 0; CAmount unconfirmed_watch_only_balance = 0; CAmount immature_watch_only_balance = 0; + CAmount locked_watch_only_balance = 0; //!< Subset of watch_only_balance locked via LockCoin CAmount denominated_untrusted_pending = 0; CAmount denominated_trusted = 0; bool balanceChanged(const WalletBalances& prev) const { return balance != prev.balance || unconfirmed_balance != prev.unconfirmed_balance || anonymized_balance != prev.anonymized_balance || - immature_balance != prev.immature_balance || watch_only_balance != prev.watch_only_balance || + immature_balance != prev.immature_balance || locked_balance != prev.locked_balance || + watch_only_balance != prev.watch_only_balance || unconfirmed_watch_only_balance != prev.unconfirmed_watch_only_balance || - immature_watch_only_balance != prev.immature_watch_only_balance; + immature_watch_only_balance != prev.immature_watch_only_balance || + locked_watch_only_balance != prev.locked_watch_only_balance || + denominated_untrusted_pending != prev.denominated_untrusted_pending || + denominated_trusted != prev.denominated_trusted; } }; diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 9ce945530a5b..e827820cbc25 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -163,8 +163,6 @@ OverviewPage::OverviewPage(QWidget* parent) : GUIUtil::updateFonts(); - m_balances.balance = -1; - // Recent transactions ui->listTransactions->setItemDelegate(txdelegate); // Note: minimum height of listTransactions will be set later in updateAdvancedCJUI() to reflect actual settings @@ -202,8 +200,9 @@ void OverviewPage::setPrivacy(bool privacy) { m_privacy = privacy; clientModel->getOptionsModel()->setOption(OptionsModel::OptionID::MaskValues, privacy); - if (m_balances.balance != -1) { - setBalance(m_balances); + const auto& balances = walletModel->getCachedBalance(); + if (balances.balance != -1) { + setBalance(balances); coinJoinStatus(true); } @@ -226,7 +225,6 @@ OverviewPage::~OverviewPage() void OverviewPage::setBalance(const interfaces::WalletBalances& balances) { BitcoinUnit unit = walletModel->getOptionsModel()->getDisplayUnit(); - m_balances = balances; if (walletModel->wallet().isLegacy()) { if (walletModel->wallet().privateKeysDisabled()) { ui->labelBalance->setText(BitcoinUnits::floorHtmlWithPrivacy(unit, balances.watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); @@ -312,12 +310,11 @@ void OverviewPage::setWalletModel(WalletModel *model) // update the display unit, to not use the default ("DASH") updateDisplayUnit(); // Keep up to date with wallet - interfaces::Wallet& wallet = model->wallet(); - interfaces::WalletBalances balances = wallet.getBalances(); - setBalance(balances); + setBalance(model->getCachedBalance()); connect(model, &WalletModel::balanceChanged, this, &OverviewPage::setBalance); - updateWatchOnlyLabels((wallet.haveWatchOnly() && !model->wallet().privateKeysDisabled()) || gArgs.GetBoolArg("-debug-ui", false)); + interfaces::Wallet& wallet = model->wallet(); + updateWatchOnlyLabels((wallet.haveWatchOnly() && !wallet.privateKeysDisabled()) || gArgs.GetBoolArg("-debug-ui", false)); connect(model, &WalletModel::notifyWatchonlyChanged, [this](bool showWatchOnly) { updateWatchOnlyLabels(showWatchOnly && !walletModel->wallet().privateKeysDisabled()); }); @@ -348,11 +345,11 @@ void OverviewPage::setWalletModel(WalletModel *model) void OverviewPage::updateDisplayUnit() { - if(walletModel && walletModel->getOptionsModel()) - { + if (walletModel && walletModel->getOptionsModel()) { m_display_bitcoin_unit = walletModel->getOptionsModel()->getDisplayUnit(); - if (m_balances.balance != -1) { - setBalance(m_balances); + const auto& balances = walletModel->getCachedBalance(); + if (balances.balance != -1) { + setBalance(balances); } // Update txdelegate->unit with the current unit @@ -404,7 +401,8 @@ void OverviewPage::updateCoinJoinProgress() QString strAmountAndRounds; QString strCoinJoinAmount = BitcoinUnits::formatHtmlWithUnit(m_display_bitcoin_unit, clientModel->coinJoinOptions().getAmount() * COIN, false, BitcoinUnits::SeparatorStyle::ALWAYS); - if(m_balances.balance == 0) + const auto& balances = walletModel->getCachedBalance(); + if(balances.balance == 0) { ui->coinJoinProgress->setValue(0); ui->coinJoinProgress->setToolTip(tr("No inputs detected")); @@ -420,7 +418,7 @@ void OverviewPage::updateCoinJoinProgress() CAmount nAnonymizableBalance = walletModel->wallet().getAnonymizableBalance(false, false); - CAmount nMaxToAnonymize = nAnonymizableBalance + m_balances.anonymized_balance; + CAmount nMaxToAnonymize = nAnonymizableBalance + balances.anonymized_balance; // If it's more than the anon threshold, limit to that. if (nMaxToAnonymize > clientModel->coinJoinOptions().getAmount() * COIN) nMaxToAnonymize = clientModel->coinJoinOptions().getAmount() * COIN; @@ -451,7 +449,6 @@ void OverviewPage::updateCoinJoinProgress() if (!fShowAdvancedCJUI) return; - const interfaces::WalletBalances balances = walletModel->wallet().getBalances(); CAmount nDenominatedConfirmedBalance = balances.denominated_trusted; CAmount nDenominatedUnconfirmedBalance = balances.denominated_untrusted_pending; CAmount nNormalizedAnonymizedBalance; @@ -477,7 +474,7 @@ void OverviewPage::updateCoinJoinProgress() anonNormPart = anonNormPart > 1 ? 1 : anonNormPart; anonNormPart *= 100; - anonFullPart = (float)m_balances.anonymized_balance / nMaxToAnonymize; + anonFullPart = (float)balances.anonymized_balance / nMaxToAnonymize; anonFullPart = anonFullPart > 1 ? 1 : anonFullPart; anonFullPart *= 100; @@ -692,7 +689,7 @@ void OverviewPage::coinJoinStatus(bool fForce) setWidgetsVisible(true); } -void OverviewPage::toggleCoinJoin(){ +void OverviewPage::toggleCoinJoin() { QSettings settings; // Popup some information on first mixing QString hasMixed = settings.value("hasMixed").toString(); @@ -707,9 +704,10 @@ void OverviewPage::toggleCoinJoin(){ bool mixing{false}; walletModel->withCoinJoin([&](auto& client) { mixing = client.isMixing(); }); if (!mixing) { + const auto& balances = walletModel->getCachedBalance(); auto& options = walletModel->node().coinJoinOptions(); const CAmount nMinAmount = options.getSmallestDenomination() + options.getMaxCollateralAmount(); - if(m_balances.balance < nMinAmount) { + if(balances.balance < nMinAmount) { QString strMinAmount(BitcoinUnits::formatWithUnit(m_display_bitcoin_unit, nMinAmount)); QMessageBox::warning(this, strCoinJoinName, tr("%1 requires at least %2 to use.").arg(strCoinJoinName).arg(strMinAmount), diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index b1a5c2f047ea..a12d74e5705b 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -54,7 +54,6 @@ public Q_SLOTS: Ui::OverviewPage *ui; ClientModel* clientModel{nullptr}; WalletModel* walletModel{nullptr}; - interfaces::WalletBalances m_balances; bool m_privacy{false}; BitcoinUnit m_display_bitcoin_unit; bool fShowAdvancedCJUI; diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 1b25c171abb5..c8073ebdc378 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -176,11 +176,9 @@ void SendCoinsDialog::setModel(WalletModel *_model) } } - interfaces::WalletBalances balances = _model->wallet().getBalances(); - setBalance(balances); connect(_model, &WalletModel::balanceChanged, this, &SendCoinsDialog::setBalance); - connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::updateDisplayUnit); - updateDisplayUnit(); + connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::refreshBalance); + refreshBalance(); // Coin Control connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::coinControlUpdateLabels); @@ -822,9 +820,9 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances) } } -void SendCoinsDialog::updateDisplayUnit() +void SendCoinsDialog::refreshBalance() { - setBalance(model->wallet().getBalances()); + setBalance(model->getCachedBalance()); coinControlUpdateLabels(); ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); updateSmartFeeLabel(); @@ -896,7 +894,7 @@ void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry) m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner(); // Calculate available amount to send. - CAmount amount = model->wallet().getAvailableBalance(*m_coin_control); + CAmount amount = model->getAvailableBalance(m_coin_control.get()); for (int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry* e = qobject_cast(ui->entries->itemAt(i)->widget()); if (e && !e->isHidden() && e != entry) { diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index d691f1555195..65a65999a579 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -99,7 +99,7 @@ private Q_SLOTS: void on_buttonMinimizeFee_clicked(); void removeEntry(SendCoinsEntry* entry); void useAvailableBalance(SendCoinsEntry* entry); - void updateDisplayUnit(); + void refreshBalance(); void coinControlFeatureChanged(bool); void coinControlButtonClicked(); void coinControlChangeChecked(int); diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 1394ac1fdea1..3be37a434f34 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -100,6 +100,15 @@ QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid) return {}; } +void CompareBalance(WalletModel& walletModel, CAmount expected_balance, QLabel* balance_label_to_check, bool use_privacy_formatting) +{ + BitcoinUnit unit = walletModel.getOptionsModel()->getDisplayUnit(); + QString balanceComparison = use_privacy_formatting + ? BitcoinUnits::floorHtmlWithPrivacy(unit, expected_balance, BitcoinUnits::SeparatorStyle::ALWAYS, false) + : BitcoinUnits::formatWithUnit(unit, expected_balance, false/*, BitcoinUnits::SeparatorStyle::ALWAYS*/); + QCOMPARE(balance_label_to_check->text().trimmed(), balanceComparison); +} + //! Simple qt wallet tests. // // Test widgets can be debugged interactively calling show() on them and @@ -162,15 +171,10 @@ void TestGUI(interfaces::Node& node) sendCoinsDialog.setModel(&walletModel); transactionView.setModel(&walletModel); - { - // Check balance in send dialog - QLabel* balanceLabel = sendCoinsDialog.findChild("labelBalance"); - QString balanceText = balanceLabel->text(); - BitcoinUnit unit = walletModel.getOptionsModel()->getDisplayUnit(); - CAmount balance = walletModel.wallet().getBalance(); - QString balanceComparison = BitcoinUnits::formatWithUnit(unit, balance, false /*, BitcoinUnits::SeparatorStyle::ALWAYS*/); - QCOMPARE(balanceText, balanceComparison); - } + // Update walletModel cached balance which will trigger an update for the 'labelBalance' QLabel. + walletModel.pollBalanceChanged(); + // Check balance in send dialog + CompareBalance(walletModel, walletModel.wallet().getBalance(), sendCoinsDialog.findChild("labelBalance"), false); // Send two transactions, and verify they are added to transaction list. TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel(); @@ -187,12 +191,8 @@ void TestGUI(interfaces::Node& node) OverviewPage overviewPage; overviewPage.setClientModel(&clientModel); overviewPage.setWalletModel(&walletModel); - QLabel* balanceLabel = overviewPage.findChild("labelBalance"); - QString balanceText = balanceLabel->text().trimmed(); - BitcoinUnit unit = walletModel.getOptionsModel()->getDisplayUnit(); - CAmount balance = walletModel.wallet().getBalance(); - QString balanceComparison = BitcoinUnits::floorHtmlWithPrivacy(unit, balance, BitcoinUnits::SeparatorStyle::ALWAYS, false); - QCOMPARE(balanceText, balanceComparison); + walletModel.pollBalanceChanged(); // Manual balance polling update + CompareBalance(walletModel, walletModel.wallet().getBalance(), overviewPage.findChild("labelBalance"), true); // Check that each autobackup failure state selects its specific tooltip on the CoinJoin status label { @@ -238,6 +238,7 @@ void TestGUI(interfaces::Node& node) QPushButton* requestPaymentButton = receiveCoinsDialog.findChild("receiveButton"); requestPaymentButton->click(); QString address; + BitcoinUnit unit = walletModel.getOptionsModel()->getDisplayUnit(); for (QWidget* widget : QApplication::topLevelWidgets()) { if (widget->inherits("ReceiveRequestDialog")) { ReceiveRequestDialog* receiveRequestDialog = qobject_cast(widget); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index d4f25cee6410..d72dbb993d46 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -63,6 +63,9 @@ WalletModel::WalletModel(std::unique_ptr wallet, ClientModel connect(optionsModel, &OptionsModel::dustProtectionChanged, this, &WalletModel::lockExistingDustOutputs); // Lock existing dust on startup if dust protection is enabled lockExistingDustOutputs(); + // CoinJoin balances are calculated only while CoinJoin is enabled, + // so the cached balance must be recalculated when it is toggled + connect(optionsModel, &OptionsModel::showCoinJoinChanged, this, [this] { fForceCheckBalanceChanged = true; }); } } @@ -73,6 +76,10 @@ WalletModel::~WalletModel() void WalletModel::startPollBalance() { + // Update the cached balance right away, so every view can make use of it, + // so them don't need to waste resources recalculating it. + pollBalanceChanged(); + // This timer will be fired repeatedly to update the balance // Since the QTimer::timeout is a private signal, it cannot be used // in the GUIUtil::ExceptionSafeConnect directly. @@ -137,18 +144,29 @@ void WalletModel::pollBalanceChanged() void WalletModel::checkBalanceChanged(const interfaces::WalletBalances& new_balances) { - if(new_balances.balanceChanged(m_cached_balances)) { + if (new_balances.balanceChanged(m_cached_balances)) { m_cached_balances = new_balances; Q_EMIT balanceChanged(new_balances); } } +interfaces::WalletBalances WalletModel::getCachedBalance() const +{ + return m_cached_balances; +} + void WalletModel::updateTransaction() { // Balance and number of transactions might have changed fForceCheckBalanceChanged = true; } +void WalletModel::updateLockedCoins() +{ + // Locked share of the balance changed + fForceCheckBalanceChanged = true; +} + void WalletModel::lockExistingDustOutputs() { if (!optionsModel) return; @@ -258,7 +276,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact } } - CAmount nBalance = m_wallet->getAvailableBalance(coinControl); + // If no coin was manually selected, use the cached balance + // Future: can merge this call with 'createTransaction'. + CAmount nBalance = getAvailableBalance(&coinControl); if(total > nBalance) { @@ -513,6 +533,12 @@ static void NotifyCanGetAddressesChanged(WalletModel* walletmodel) assert(invoked); } +static void NotifyLockedCoinsChanged(WalletModel* walletmodel) +{ + bool invoked = QMetaObject::invokeMethod(walletmodel, "updateLockedCoins", Qt::QueuedConnection); + assert(invoked); +} + void WalletModel::subscribeToCoreSignals() { // Connect signals to wallet @@ -525,6 +551,7 @@ void WalletModel::subscribeToCoreSignals() m_handler_show_progress = m_wallet->handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2)); m_handler_watch_only_changed = m_wallet->handleWatchOnlyChanged(std::bind(NotifyWatchonlyChanged, this, std::placeholders::_1)); m_handler_can_get_addrs_changed = m_wallet->handleCanGetAddressesChanged(std::bind(NotifyCanGetAddressesChanged, this)); + m_handler_locked_coins_changed = m_wallet->handleLockedCoinsChanged(std::bind(NotifyLockedCoinsChanged, this)); } void WalletModel::unsubscribeFromCoreSignals() @@ -539,6 +566,7 @@ void WalletModel::unsubscribeFromCoreSignals() m_handler_show_progress->disconnect(); m_handler_watch_only_changed->disconnect(); m_handler_can_get_addrs_changed->disconnect(); + m_handler_locked_coins_changed->disconnect(); } // WalletModel::UnlockContext implementation @@ -633,3 +661,24 @@ uint256 WalletModel::getLastBlockProcessed() const { return m_client_model ? m_client_model->getBestBlockHash() : uint256{}; } + +CAmount WalletModel::getAvailableBalance(const CCoinControl* control) +{ + // No selected coins, return the cached balance + if (!control || !control->HasSelected()) { + const interfaces::WalletBalances& balances = getCachedBalance(); + if (control && control->IsUsingCoinJoin()) { + return balances.anonymized_balance; + } + // Coin selection cannot spend locked coins, so keep the locked share out + CAmount available_balance = balances.balance - balances.locked_balance; + // if wallet private keys are disabled, this is a watch-only wallet + // so, let's include the watch-only balance. + if (balances.have_watch_only && m_wallet->privateKeysDisabled()) { + available_balance += balances.watch_only_balance - balances.locked_watch_only_balance; + } + return available_balance; + } + // Fetch balance from the wallet, taking into account the selected coins + return wallet().getAvailableBalance(*control); +} diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 262c766ae482..f9a2493a2631 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -163,6 +163,13 @@ class WalletModel : public QObject uint256 getLastBlockProcessed() const; + // Retrieve the cached wallet balance + interfaces::WalletBalances getCachedBalance() const; + + // If coin control has selected outputs, searches the total amount inside the wallet. + // Otherwise, uses the wallet's cached available balance. + CAmount getAvailableBalance(const wallet::CCoinControl* control); + private: std::unique_ptr m_wallet; std::unique_ptr m_handler_unload; @@ -174,6 +181,7 @@ class WalletModel : public QObject std::unique_ptr m_handler_show_progress; std::unique_ptr m_handler_watch_only_changed; std::unique_ptr m_handler_can_get_addrs_changed; + std::unique_ptr m_handler_locked_coins_changed; ClientModel* m_client_model; interfaces::Node& m_node; @@ -242,6 +250,8 @@ public Q_SLOTS: void updateStatus(); /* New transaction, or transaction changed status */ void updateTransaction(); + /* Set of locked coins changed */ + void updateLockedCoins(); /* Lock existing dust outputs (called on startup and settings change) */ void lockExistingDustOutputs(); /* IS-Lock received */ diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 39f2080d388c..0d3653ffab0b 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -615,12 +615,14 @@ class WalletImpl : public Wallet result.balance = bal.m_mine_trusted; result.unconfirmed_balance = bal.m_mine_untrusted_pending; result.immature_balance = bal.m_mine_immature; + result.locked_balance = bal.m_mine_trusted_locked; result.anonymized_balance = bal.m_anonymized; result.have_watch_only = haveWatchOnly(); if (result.have_watch_only) { result.watch_only_balance = bal.m_watchonly_trusted; result.unconfirmed_watch_only_balance = bal.m_watchonly_untrusted_pending; result.immature_watch_only_balance = bal.m_watchonly_immature; + result.locked_watch_only_balance = bal.m_watchonly_trusted_locked; } result.denominated_untrusted_pending = bal.m_denominated_untrusted_pending; result.denominated_trusted = bal.m_denominated_trusted; @@ -815,6 +817,10 @@ class WalletImpl : public Wallet { return MakeHandler(m_wallet->NotifyCanGetAddressesChanged.connect(fn)); } + std::unique_ptr handleLockedCoinsChanged(LockedCoinsChangedFn fn) override + { + return MakeHandler(m_wallet->NotifyLockedCoinsChanged.connect(fn)); + } std::vector getGovernanceObjects() override { LOCK(m_wallet->cs_wallet); diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp index 309c43450a0f..54a034a1585e 100644 --- a/src/wallet/receive.cpp +++ b/src/wallet/receive.cpp @@ -327,6 +327,21 @@ Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse, } } } + + // Coins locked via LockCoin() are counted in the trusted balances + // above but cannot be selected for spending; total them separately so + // callers can tell the spendable share + for (const COutPoint& outpoint : wallet.ListLockedCoins()) { + const CWalletTx* wtx = wallet.GetWalletTx(outpoint.hash); + if (wtx == nullptr || outpoint.n >= wtx->tx->vout.size()) continue; + if (wallet.IsTxImmatureCoinBase(*wtx) || wallet.IsSpent(outpoint)) continue; + if (!CachedTxIsTrusted(wallet, *wtx, trusted_parents)) continue; + if (wallet.GetTxDepthInMainChain(*wtx) < min_depth && !(fAddLocked && wallet.IsTxLockedByInstantSend(*wtx))) continue; + const CTxOut& txout{wtx->tx->vout[outpoint.n]}; + if (avoid_reuse && wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) && wallet.IsSpentKey(txout.scriptPubKey)) continue; + ret.m_mine_trusted_locked += OutputGetCredit(wallet, txout, ISMINE_SPENDABLE); + ret.m_watchonly_trusted_locked += OutputGetCredit(wallet, txout, ISMINE_WATCH_ONLY); + } } return ret; } diff --git a/src/wallet/receive.h b/src/wallet/receive.h index 0acc4c6373e7..d9993e9e2b90 100644 --- a/src/wallet/receive.h +++ b/src/wallet/receive.h @@ -52,9 +52,11 @@ struct Balance { CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending) CAmount m_mine_immature{0}; //!< Immature coinbases in the main chain + CAmount m_mine_trusted_locked{0}; //!< Subset of m_mine_trusted locked via LockCoin CAmount m_watchonly_trusted{0}; CAmount m_watchonly_untrusted_pending{0}; CAmount m_watchonly_immature{0}; + CAmount m_watchonly_trusted_locked{0}; //!< Subset of m_watchonly_trusted locked via LockCoin CAmount m_anonymized{0}; CAmount m_denominated_trusted{0}; CAmount m_denominated_untrusted_pending{0}; diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 9c8132804272..486f9183cee5 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -218,26 +218,18 @@ CoinsResult AvailableCoins(const CWallet& wallet, // Filter by spendable outputs only if (!spendable && only_spendable) continue; - // When parsing a scriptPubKey, Solver returns the parsed pubkeys or hashes (depending on the script) - // We don't need those here, so we are leaving them in return_values_unused - std::vector> return_values_unused; - TxoutType type; + // Obtain script type + std::vector> script_solutions; + TxoutType type = Solver(output.scriptPubKey, script_solutions); - // If the Output is P2SH and spendable, we want to know if it is + // If the output is P2SH and solvable, we want to know if it is // a P2SH (legacy). We can determine this from the redeemScript. - // If the Output is not spendable, it will be classified as a P2SH (legacy), + // If the output is not solvable, it will be classified as a P2SH (legacy), // since we have no way of knowing otherwise without the redeemScript - if (output.scriptPubKey.IsPayToScriptHash() && solvable) { - CScript redeemScript; - CTxDestination destination; - if (!ExtractDestination(output.scriptPubKey, destination)) - continue; - const CScriptID& hash = CScriptID(std::get(destination)); - if (!provider->GetCScript(hash, redeemScript)) - continue; - type = Solver(redeemScript, return_values_unused); - } else { - type = Solver(output.scriptPubKey, return_values_unused); + if (type == TxoutType::SCRIPTHASH && solvable) { + CScript script; + if (!provider->GetCScript(CScriptID(uint160(script_solutions[0])), script)) continue; + type = Solver(script, script_solutions); } COutput coin(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index da4a2e08d885..cec628c1a271 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2750,7 +2750,9 @@ bool CWallet::DisplayAddress(const CTxDestination& dest) bool CWallet::LockCoin(const COutPoint& output, WalletBatch* batch) { AssertLockHeld(cs_wallet); - setLockedCoins.insert(output); + if (setLockedCoins.insert(output).second) { + NotifyLockedCoinsChanged(); + } RecalculateMixedCredit(output.hash); if (batch) { return batch->WriteLockedUTXO(output); @@ -2762,6 +2764,9 @@ bool CWallet::UnlockCoin(const COutPoint& output, WalletBatch* batch) { AssertLockHeld(cs_wallet); bool was_locked = setLockedCoins.erase(output); + if (was_locked) { + NotifyLockedCoinsChanged(); + } RecalculateMixedCredit(output.hash); if (batch && was_locked) { return batch->EraseLockedUTXO(output); @@ -2777,7 +2782,10 @@ bool CWallet::UnlockAllCoins() for (auto it = setLockedCoins.begin(); it != setLockedCoins.end(); ++it) { success &= batch.EraseLockedUTXO(*it); } - setLockedCoins.clear(); + if (!setLockedCoins.empty()) { + setLockedCoins.clear(); + NotifyLockedCoinsChanged(); + } return success; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 641065321e1e..d8a3dc53fdd3 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -976,6 +976,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati /** Keypool has new keys */ boost::signals2::signal NotifyCanGetAddressesChanged; + /** Set of coins locked via LockCoin changed */ + boost::signals2::signal NotifyLockedCoinsChanged; + /** IS-lock received */ boost::signals2::signal NotifyISLockReceived;