Skip to content

Commit 0455eb8

Browse files
Merge #7072: feat(qt): introduce Wallet::startRescan() to replace append-and-restart method in Qt wallet, enable wallet rescan for multiple loaded wallets
931707c feat(qt): enable wallet rescan for multiple loaded wallets (Kittywhiskers Van Gogh) 025df83 qt: switch over to rescan wallet activity (Kittywhiskers Van Gogh) 9e16077 qt: use `QPointer` to prevent potential dangling pointer on unload (Kittywhiskers Van Gogh) 9fed372 qt: add rescan wallet activity (Kittywhiskers Van Gogh) 8b73a88 qt: add interface method to trigger rescans (Kittywhiskers Van Gogh) 9ac357b qt: de-emphasize runtime arguments, isolate code path to wallet builds (Kittywhiskers Van Gogh) Pull request description: ## Motivation [bitcoin#23123](bitcoin#23123) removes the `-rescan` startup parameter outright and users are expected to utilise the `rescanblockchain` RPC instead. This requires us to rework how the rescan option is implemented in Dash Qt, which uses the startup parameter. This pull request replaces it by implementing the core rescan logic in the wallet interface (as opposed to trying to invoke an RPC call from the Qt wallet, which would violate separation of concerns) and then implementing a wallet activity to avoid blocking the main thread. An additional benefit of this approach is that the rescan happens immediately without the overhead of a client restart. ## Additional Information * To avoid a potential race condition where a wallet could unloaded before the rescan can be triggered, `m_rescan_wallet_model` is now a `QPointer` (which will auto-reset when the underlying entity is destroyed, [source](https://doc.qt.io/qt-6/qpointer.html)) and we do one final check in the one-shot lambda before resolving the pointer. ## Breaking Changes None expected. ## Checklist - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests **(note: N/A)** - [x] I have made corresponding changes to the documentation **(note: N/A)** - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: UdjinM6: light ACK 931707c Tree-SHA512: 0a53c3e1679fbdb84f50a553ffc8513db584827b072aa9fcc5a6f6cee7b7fcd54d971741d1528bc1914671417442c433233582910a089398eff7eb7b63d1c038
2 parents 44a7a8c + 931707c commit 0455eb8

9 files changed

Lines changed: 197 additions & 43 deletions

File tree

src/interfaces/wallet.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ namespace wallet {
4040
class CCoinControl;
4141
class CWallet;
4242
enum isminetype : unsigned int;
43+
enum class RescanStatus : uint8_t;
4344
struct CRecipient;
4445
struct WalletContext;
4546
using isminefilter = std::underlying_type<isminetype>::type;
@@ -93,6 +94,9 @@ class Wallet
9394
virtual bool changeWalletPassphrase(const SecureString& old_wallet_passphrase,
9495
const SecureString& new_wallet_passphrase) = 0;
9596

97+
//! Initiate a rescan. Returns status indicating success, failure, abort, or already rescanning.
98+
virtual wallet::RescanStatus startRescan(bool from_genesis) = 0;
99+
96100
//! Abort a rescan.
97101
virtual void abortRescan() = 0;
98102

src/qt/bitcoingui.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,8 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller, bool s
936936
m_wallet_controller = nullptr;
937937
});
938938

939+
rpcConsole->setWalletController(wallet_controller);
940+
939941
auto activity = new LoadWalletsActivity(m_wallet_controller, this);
940942
activity->load(show_loading_minimized);
941943
}

src/qt/forms/debugwindow.ui

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
<item>
8282
<widget class="QPushButton" name="btnRepair">
8383
<property name="text">
84-
<string>&amp;Wallet Repair</string>
84+
<string>&amp;Repair</string>
8585
</property>
8686
<property name="checkable">
8787
<bool>true</bool>
@@ -1728,7 +1728,7 @@
17281728
<item row="0" column="0" colspan="2">
17291729
<widget class="QLabel" name="label_repair_header">
17301730
<property name="text">
1731-
<string>Wallet repair options.</string>
1731+
<string>Repair options.</string>
17321732
</property>
17331733
<property name="wordWrap">
17341734
<bool>true</bool>
@@ -1738,7 +1738,7 @@
17381738
<item row="1" column="0" colspan="2">
17391739
<widget class="QLabel" name="label_repair_helptext">
17401740
<property name="text">
1741-
<string>The buttons below will restart the wallet with command-line options to repair the wallet, fix issues with corrupt blockchain files or missing/obsolete transactions.</string>
1741+
<string>The buttons below will trigger repair actions to fix issues with corrupt files or missing/obsolete transactions.</string>
17421742
</property>
17431743
<property name="wordWrap">
17441744
<bool>true</bool>
@@ -1758,14 +1758,14 @@
17581758
</size>
17591759
</property>
17601760
<property name="text">
1761-
<string>Rescan blockchain files 1</string>
1761+
<string>Rescan Chain</string>
17621762
</property>
17631763
</widget>
17641764
</item>
17651765
<item row="3" column="1">
17661766
<widget class="QLabel" name="label_repair_rescan1">
17671767
<property name="text">
1768-
<string>-rescan=1: Rescan the block chain for missing wallet transactions starting from wallet creation time.</string>
1768+
<string>Rescan the chain for missing wallet transactions starting from wallet creation time.</string>
17691769
</property>
17701770
<property name="wordWrap">
17711771
<bool>true</bool>
@@ -1781,14 +1781,14 @@
17811781
</size>
17821782
</property>
17831783
<property name="text">
1784-
<string>Rescan blockchain files 2</string>
1784+
<string>Rescan Chain (full)</string>
17851785
</property>
17861786
</widget>
17871787
</item>
17881788
<item row="4" column="1">
17891789
<widget class="QLabel" name="label_repair_rescan2">
17901790
<property name="text">
1791-
<string>-rescan=2: Rescan the block chain for missing wallet transactions starting from genesis block.</string>
1791+
<string>Rescan the chain for missing wallet transactions starting from genesis block.</string>
17921792
</property>
17931793
<property name="wordWrap">
17941794
<bool>true</bool>
@@ -1798,14 +1798,14 @@
17981798
<item row="5" column="0">
17991799
<widget class="QPushButton" name="btn_reindex">
18001800
<property name="text">
1801-
<string>Rebuild index</string>
1801+
<string>Rebuild Index</string>
18021802
</property>
18031803
</widget>
18041804
</item>
18051805
<item row="5" column="1">
18061806
<widget class="QLabel" name="label_repair_reindex">
18071807
<property name="text">
1808-
<string>-reindex: Rebuild block chain index from current blk000??.dat files.</string>
1808+
<string>Restarts the client to rebuild the chain index from current blk000??.dat files.</string>
18091809
</property>
18101810
<property name="wordWrap">
18111811
<bool>true</bool>

src/qt/rpcconsole.cpp

Lines changed: 63 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <qt/clientmodel.h>
2020
#include <qt/guiutil.h>
2121
#include <qt/peertablesortproxy.h>
22+
#include <qt/walletcontroller.h>
2223
#include <qt/walletmodel.h>
2324
#include <rpc/client.h>
2425
#include <rpc/server.h>
@@ -70,8 +71,6 @@ const char fontSizeSettingsKey[] = "consoleFontSize";
7071
const TrafficGraphData::GraphRange INITIAL_TRAFFIC_GRAPH_SETTING = TrafficGraphData::Range_30m;
7172

7273
// Repair parameters
73-
const QString RESCAN1("-rescan=1");
74-
const QString RESCAN2("-rescan=2");
7574
const QString REINDEX("-reindex");
7675

7776
namespace {
@@ -574,12 +573,15 @@ RPCConsole::RPCConsole(interfaces::Node& node, QWidget* parent, Qt::WindowFlags
574573
ui->WalletSelector->setVisible(false);
575574
ui->WalletSelectorLabel->setVisible(false);
576575

577-
// Wallet Repair Buttons
576+
// Repair Buttons
578577
// Disable wallet repair options that require a wallet (enable them later when a wallet is added)
579578
ui->btn_rescan1->setEnabled(false);
580579
ui->btn_rescan2->setEnabled(false);
580+
#ifdef ENABLE_WALLET
581581
connect(ui->btn_rescan1, &QPushButton::clicked, this, &RPCConsole::walletRescan1);
582582
connect(ui->btn_rescan2, &QPushButton::clicked, this, &RPCConsole::walletRescan2);
583+
connect(ui->WalletSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &RPCConsole::onWalletChanged);
584+
#endif // ENABLE_WALLET
583585
connect(ui->btn_reindex, &QPushButton::clicked, this, &RPCConsole::walletReindex);
584586

585587
// Register RPC timer interface
@@ -820,47 +822,41 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_
820822
}
821823

822824
#ifdef ENABLE_WALLET
825+
void RPCConsole::setWalletController(WalletController* wallet_controller)
826+
{
827+
m_wallet_controller = wallet_controller;
828+
}
829+
823830
void RPCConsole::addWallet(WalletModel * const walletModel)
824831
{
825832
// use name for text and wallet model for internal data object (to allow to move to a wallet id later)
826833
ui->WalletSelector->addItem(walletModel->getDisplayName(), QVariant::fromValue(walletModel));
827834
if (ui->WalletSelector->count() == 2) {
828835
// First wallet added, set to default to match wallet RPC behavior
829836
ui->WalletSelector->setCurrentIndex(1);
830-
// The only loaded wallet
831-
ui->btn_rescan1->setEnabled(true);
832-
ui->btn_rescan2->setEnabled(true);
833-
QString wallet_path = GUIUtil::PathToQString(GetWalletDir()) + QDir::separator().toLatin1();
834-
QString wallet_name = walletModel->getWalletName().isEmpty() ? "wallet.dat" : walletModel->getWalletName();
835-
ui->wallet_path->setText(wallet_path + wallet_name);
836-
} else {
837+
}
838+
839+
// Update wallet path and button states for currently selected wallet
840+
onWalletChanged();
841+
842+
// Show wallet selector when multiple wallets are loaded
843+
if (ui->WalletSelector->count() > 2) {
837844
ui->WalletSelector->setVisible(true);
838845
ui->WalletSelectorLabel->setVisible(true);
839-
// No wallet recovery for multiple loaded wallets
840-
ui->btn_rescan1->setEnabled(false);
841-
ui->btn_rescan2->setEnabled(false);
842-
ui->wallet_path->clear();
843846
}
844847
}
845848

846849
void RPCConsole::removeWallet(WalletModel * const walletModel)
847850
{
848851
ui->WalletSelector->removeItem(ui->WalletSelector->findData(QVariant::fromValue(walletModel)));
852+
853+
// Update wallet path and button states for currently selected wallet (or clear/disable if none)
854+
onWalletChanged();
855+
856+
// Hide wallet selector when back to single wallet
849857
if (ui->WalletSelector->count() == 2) {
850858
ui->WalletSelector->setVisible(false);
851859
ui->WalletSelectorLabel->setVisible(false);
852-
// Back to the only loaded wallet
853-
ui->btn_rescan1->setEnabled(true);
854-
ui->btn_rescan2->setEnabled(true);
855-
WalletModel* wallet_model = ui->WalletSelector->itemData(1).value<WalletModel*>();
856-
QString wallet_path = GUIUtil::PathToQString(GetWalletDir()) + QDir::separator().toLatin1();
857-
QString wallet_name = wallet_model->getWalletName().isEmpty() ? "wallet.dat" : wallet_model->getWalletName();
858-
ui->wallet_path->setText(wallet_path + wallet_name);
859-
} else {
860-
// No wallet recovery for multiple loaded wallets
861-
ui->btn_rescan1->setEnabled(false);
862-
ui->btn_rescan2->setEnabled(false);
863-
ui->wallet_path->clear();
864860
}
865861
}
866862

@@ -869,6 +865,24 @@ void RPCConsole::setCurrentWallet(WalletModel* const wallet_model)
869865
QVariant data = QVariant::fromValue(wallet_model);
870866
ui->WalletSelector->setCurrentIndex(ui->WalletSelector->findData(data));
871867
}
868+
869+
void RPCConsole::onWalletChanged()
870+
{
871+
WalletModel* wallet_model = ui->WalletSelector->currentData().value<WalletModel*>();
872+
if (wallet_model) {
873+
QString wallet_path = GUIUtil::PathToQString(GetWalletDir()) + QDir::separator().toLatin1();
874+
QString wallet_name = wallet_model->getWalletName().isEmpty() ? "wallet.dat" : wallet_model->getWalletName();
875+
ui->wallet_path->setText(wallet_path + wallet_name);
876+
// Enable rescan buttons when a valid wallet is selected
877+
ui->btn_rescan1->setEnabled(true);
878+
ui->btn_rescan2->setEnabled(true);
879+
} else {
880+
ui->wallet_path->clear();
881+
// Disable rescan buttons when no wallet is selected (e.g., "(none)")
882+
ui->btn_rescan1->setEnabled(false);
883+
ui->btn_rescan2->setEnabled(false);
884+
}
885+
}
872886
#endif
873887

874888
static QString categoryClass(int category)
@@ -917,17 +931,36 @@ void RPCConsole::setFontSize(int newSize)
917931
ui->messagesWidget->verticalScrollBar()->setValue(oldPosFactor * ui->messagesWidget->verticalScrollBar()->maximum());
918932
}
919933

920-
/** Restart wallet with "-rescan=1" */
934+
#ifdef ENABLE_WALLET
935+
void RPCConsole::walletRescan(bool from_genesis)
936+
{
937+
if (!m_wallet_controller) {
938+
QMessageBox::critical(this, PACKAGE_NAME, QObject::tr("Error: Wallet controller not available."));
939+
return;
940+
}
941+
942+
WalletModel* wallet_model{ui->WalletSelector->currentData().value<WalletModel*>()};
943+
if (!wallet_model) {
944+
QMessageBox::critical(this, PACKAGE_NAME, QObject::tr("Error: Rescan failed. Wallet not loaded."));
945+
return;
946+
}
947+
948+
auto activity = new RescanWalletActivity(m_wallet_controller, this);
949+
activity->rescan(wallet_model, from_genesis);
950+
}
951+
952+
/** Rescan wallet from wallet creation */
921953
void RPCConsole::walletRescan1()
922954
{
923-
buildParameterlist(RESCAN1);
955+
walletRescan(/*from_genesis=*/false);
924956
}
925957

926-
/** Restart wallet with "-rescan=2" */
958+
/** Rescan wallet from genesis block */
927959
void RPCConsole::walletRescan2()
928960
{
929-
buildParameterlist(RESCAN2);
961+
walletRescan(/*from_genesis=*/true);
930962
}
963+
#endif
931964

932965
/** Restart wallet with "-reindex" */
933966
void RPCConsole::walletReindex()
@@ -952,8 +985,6 @@ void RPCConsole::buildParameterlist(QString arg)
952985
}
953986

954987
// Remove existing repair-options
955-
args.removeAll(RESCAN1);
956-
args.removeAll(RESCAN2);
957988
args.removeAll(REINDEX);
958989

959990
// Append repair parameter to command line.

src/qt/rpcconsole.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
class ClientModel;
2525
class RPCExecutor;
2626
class RPCTimerInterface;
27+
class WalletController;
2728
class WalletModel;
2829

2930
namespace interfaces {
@@ -58,6 +59,7 @@ class RPCConsole: public QWidget
5859
void setClientModel(ClientModel *model = nullptr, int bestblock_height = 0, int64_t bestblock_date = 0, uint256 bestblock_hash = uint256(), double verification_progress = 0.0);
5960

6061
#ifdef ENABLE_WALLET
62+
void setWalletController(WalletController* wallet_controller);
6163
void addWallet(WalletModel* const walletModel);
6264
void removeWallet(WalletModel* const walletModel);
6365
#endif // ENABLE_WALLET
@@ -117,10 +119,12 @@ public Q_SLOTS:
117119
void fontSmaller();
118120
void setFontSize(int newSize);
119121

120-
/** Wallet repair options */
122+
/** Repair options */
123+
void walletReindex();
124+
#ifdef ENABLE_WALLET
121125
void walletRescan1();
122126
void walletRescan2();
123-
void walletReindex();
127+
#endif // ENABLE_WALLET
124128

125129
/** Append the message to the message widget */
126130
void message(int category, const QString &msg) { message(category, msg, false); }
@@ -174,6 +178,12 @@ public Q_SLOTS:
174178
void setButtonIcons();
175179
/** Reload some themes related widgets */
176180
void reloadThemedWidgets();
181+
#ifdef ENABLE_WALLET
182+
/** Initiate a wallet rescan */
183+
void walletRescan(bool from_genesis);
184+
/** Update wallet UI when selected wallet changes */
185+
void onWalletChanged();
186+
#endif // ENABLE_WALLET
177187

178188
enum ColumnWidths
179189
{
@@ -188,6 +198,7 @@ public Q_SLOTS:
188198
interfaces::Node& m_node;
189199
Ui::RPCConsole* const ui;
190200
ClientModel *clientModel = nullptr;
201+
WalletController* m_wallet_controller{nullptr};
191202
QButtonGroup* pageButtons = nullptr;
192203
QStringList history;
193204
int historyPtr = 0;

src/qt/walletcontroller.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,3 +514,47 @@ void RestoreWalletActivity::finish()
514514

515515
Q_EMIT finished();
516516
}
517+
518+
RescanWalletActivity::RescanWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
519+
: WalletControllerActivity(wallet_controller, parent_widget)
520+
{
521+
}
522+
523+
void RescanWalletActivity::rescan(WalletModel* wallet_model, bool from_genesis)
524+
{
525+
m_rescan_wallet_model = wallet_model;
526+
527+
QTimer::singleShot(0, worker(), [this, from_genesis] {
528+
if (m_rescan_wallet_model) {
529+
// Emits its own progress bar
530+
m_rescan_status = m_rescan_wallet_model->wallet().startRescan(from_genesis);
531+
} else {
532+
// Wallet was closed before rescan could start
533+
m_rescan_status = wallet::RescanStatus::FAILURE;
534+
}
535+
QTimer::singleShot(0, this, &RescanWalletActivity::finish);
536+
});
537+
}
538+
539+
void RescanWalletActivity::finish()
540+
{
541+
switch (m_rescan_status) {
542+
case wallet::RescanStatus::BUSY:
543+
QMessageBox::warning(m_parent_widget, tr("Rescan unavailable"), tr("Wallet is currently rescanning. Abort existing rescan or wait."));
544+
Q_EMIT rescanFailed();
545+
break;
546+
case wallet::RescanStatus::FAILURE:
547+
QMessageBox::critical(m_parent_widget, tr("Rescan wallet failed"), tr("Rescan failed. Potentially corrupted data files."));
548+
Q_EMIT rescanFailed();
549+
break;
550+
case wallet::RescanStatus::SUCCESS:
551+
Q_EMIT rescanComplete();
552+
break;
553+
case wallet::RescanStatus::USER_ABORT:
554+
QMessageBox::information(m_parent_widget, tr("Rescan aborted"), tr("Wallet rescan was aborted."));
555+
Q_EMIT rescanFailed();
556+
break;
557+
}
558+
559+
Q_EMIT finished();
560+
}

0 commit comments

Comments
 (0)