diff options
author | Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> | 2022-08-15 19:35:52 +0100 |
---|---|---|
committer | Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> | 2022-08-15 19:38:17 +0100 |
commit | 6d4889a694da12e03ef4d18bef2ff60d1d170f18 (patch) | |
tree | fc3d86245521743eaa9ec83992e17ecc05f750cd | |
parent | 867f5fd1b3564259116ac2edbba4f557d87ded07 (diff) | |
parent | 4584d300a40bfd84517072f7a6eee114fb7cab08 (diff) |
Merge bitcoin-core/gui#598: Avoid recalculating the wallet balance - use model cache
4584d300a40bfd84517072f7a6eee114fb7cab08 GUI: remove now unneeded 'm_balances' field from overviewpage (furszy)
050e8b139145d6991e740b0e5f2b3364663dd348 GUI: 'getAvailableBalance', use cached balance if the user did not select UTXO manually (furszy)
96e3264a82c51b456703f500bd98e8cb98115697 GUI: use cached balance in overviewpage and sendcoinsdialog (furszy)
321335bf0292034d79afa6c44f7f072942b6cc3c GUI: add getter for WalletModel::m_cached_balances field (furszy)
e62958dc81d215a1c56318d0914dfd9a33d45973 GUI: sendCoinsDialog, remove duplicate wallet().getBalances() call (furszy)
Pull request description:
As per the title says, we are recalculating the entire wallet balance on different situations calling to `wallet().getBalances()`, when should instead make use of the wallet model cached balance.
This has the benefits of (1) not spending resources calculating a balance that we already have cached, and (2) avoid blocking the main thread for a long time, in case of big wallets, walking through the entire wallet's tx map more than what it's really needed.
Changes:
1) Fix: `SendCoinsDialog` was calling `wallet().getBalances()` twice during `setModel`.
2) Use the cached balance if the user did not select any UTXO manually inside the wallet model `getAvailableBalance` call.
-----------------------
As an extra note, this work born in [#25005](https://github.com/bitcoin/bitcoin/pull/25005) but grew out of scope of it.
ACKs for top commit:
jarolrod:
ACK 4584d300a40bfd84517072f7a6eee114fb7cab08
hebasto:
re-ACK 4584d300a40bfd84517072f7a6eee114fb7cab08, only suggested changes and commit message formatting since my [recent](https://github.com/bitcoin-core/gui/pull/598#pullrequestreview-1071268192) review.
Tree-SHA512: 6633ce7f9a82a3e46e75aa7295df46c80a4cd4a9f3305427af203c9bc8670573fa8a1927f14a279260c488cc975a08d238faba2e9751588086fea1dcf8ea2b28
-rw-r--r-- | src/qt/overviewpage.cpp | 23 | ||||
-rw-r--r-- | src/qt/overviewpage.h | 1 | ||||
-rw-r--r-- | src/qt/sendcoinsdialog.cpp | 12 | ||||
-rw-r--r-- | src/qt/sendcoinsdialog.h | 2 | ||||
-rw-r--r-- | src/qt/test/wallettests.cpp | 28 | ||||
-rw-r--r-- | src/qt/walletmodel.cpp | 20 | ||||
-rw-r--r-- | src/qt/walletmodel.h | 7 |
7 files changed, 54 insertions, 39 deletions
diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index a8133f481e..85a3c36f39 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -147,8 +147,6 @@ OverviewPage::OverviewPage(const PlatformStyle *platformStyle, QWidget *parent) { ui->setupUi(this); - m_balances.balance = -1; - // use a SingleColorIcon for the "out of sync warning" icon QIcon icon = m_platform_style->SingleColorIcon(QStringLiteral(":/icons/warning")); ui->labelTransactionsStatus->setIcon(icon); @@ -177,8 +175,9 @@ void OverviewPage::handleTransactionClicked(const QModelIndex &index) void OverviewPage::setPrivacy(bool privacy) { m_privacy = privacy; - if (m_balances.balance != -1) { - setBalance(m_balances); + const auto& balances = walletModel->getCachedBalance(); + if (balances.balance != -1) { + setBalance(balances); } ui->listTransactions->setVisible(!m_privacy); @@ -197,7 +196,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::formatWithPrivacy(unit, balances.watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); @@ -276,14 +274,13 @@ void OverviewPage::setWalletModel(WalletModel *model) ui->listTransactions->setModelColumn(TransactionTableModel::ToAddress); // 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); connect(model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &OverviewPage::updateDisplayUnit); - updateWatchOnlyLabels(wallet.haveWatchOnly() && !model->wallet().privateKeysDisabled()); + interfaces::Wallet& wallet = model->wallet(); + updateWatchOnlyLabels(wallet.haveWatchOnly() && !wallet.privateKeysDisabled()); connect(model, &WalletModel::notifyWatchonlyChanged, [this](bool showWatchOnly) { updateWatchOnlyLabels(showWatchOnly && !walletModel->wallet().privateKeysDisabled()); }); @@ -306,10 +303,10 @@ void OverviewPage::changeEvent(QEvent* e) void OverviewPage::updateDisplayUnit() { - if(walletModel && walletModel->getOptionsModel()) - { - if (m_balances.balance != -1) { - setBalance(m_balances); + if (walletModel && walletModel->getOptionsModel()) { + const auto& balances = walletModel->getCachedBalance(); + if (balances.balance != -1) { + setBalance(balances); } // Update txdelegate->unit with the current unit diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index 058df1a8c5..56f45907db 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -52,7 +52,6 @@ private: Ui::OverviewPage *ui; ClientModel *clientModel; WalletModel *walletModel; - interfaces::WalletBalances m_balances; bool m_privacy{false}; const PlatformStyle* m_platform_style; diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index bd44d12781..a75c1098a5 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -164,11 +164,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); @@ -711,9 +709,9 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances) } } -void SendCoinsDialog::updateDisplayUnit() +void SendCoinsDialog::refreshBalance() { - setBalance(model->wallet().getBalances()); + setBalance(model->getCachedBalance()); ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); updateSmartFeeLabel(); } @@ -786,7 +784,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<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); if (e && !e->isHidden() && e != entry) { diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 400503d0c0..b58d4690a0 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -97,7 +97,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 b8ae62ecab..7e39829038 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -129,6 +129,13 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st QVERIFY(text.indexOf(QString::fromStdString(expectError)) != -1); } +void CompareBalance(WalletModel& walletModel, CAmount expected_balance, QLabel* balance_label_to_check) +{ + BitcoinUnit unit = walletModel.getOptionsModel()->getDisplayUnit(); + QString balanceComparison = 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 @@ -195,15 +202,10 @@ void TestGUI(interfaces::Node& node) sendCoinsDialog.setModel(&walletModel); transactionView.setModel(&walletModel); - { - // Check balance in send dialog - QLabel* balanceLabel = sendCoinsDialog.findChild<QLabel*>("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<QLabel*>("labelBalance")); // Send two transactions, and verify they are added to transaction list. TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel(); @@ -223,12 +225,8 @@ void TestGUI(interfaces::Node& node) // Check current balance on OverviewPage OverviewPage overviewPage(platformStyle.get()); overviewPage.setWalletModel(&walletModel); - QLabel* balanceLabel = overviewPage.findChild<QLabel*>("labelBalance"); - QString balanceText = balanceLabel->text().trimmed(); - BitcoinUnit unit = walletModel.getOptionsModel()->getDisplayUnit(); - CAmount balance = walletModel.wallet().getBalance(); - QString balanceComparison = BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::SeparatorStyle::ALWAYS); - QCOMPARE(balanceText, balanceComparison); + walletModel.pollBalanceChanged(); // Manual balance polling update + CompareBalance(walletModel, walletModel.wallet().getBalance(), overviewPage.findChild<QLabel*>("labelBalance")); // Check Request Payment button ReceiveCoinsDialog receiveCoinsDialog(platformStyle.get()); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index ac6c8bea46..c6f3f5b00c 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -67,6 +67,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. @@ -120,12 +124,17 @@ 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 @@ -194,7 +203,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact return DuplicateAddress; } - 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) { @@ -602,3 +613,8 @@ uint256 WalletModel::getLastBlockProcessed() const { return m_client_model ? m_client_model->getBestBlockHash() : uint256{}; } + +CAmount WalletModel::getAvailableBalance(const CCoinControl* control) +{ + return control && control->HasSelected() ? wallet().getAvailableBalance(*control) : getCachedBalance().balance; +} diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 0184fb8ec2..73dfe0386a 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -155,6 +155,13 @@ public: 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<interfaces::Wallet> m_wallet; std::unique_ptr<interfaces::Handler> m_handler_unload; |