Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,11 @@ class WalletLoader : public ChainClient
using LoadWalletFn = std::function<void(std::unique_ptr<Wallet> wallet)>;
virtual std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) = 0;

//! Register handler for wallet-loading messages. This callback is triggered
//! after a wallet object exists and can emit progress, but before startup
//! load work like AttachChain()/rescan necessarily completes.
virtual std::unique_ptr<Handler> handleLoadWalletLoading(LoadWalletFn fn) = 0;

//! Return pointer to internal context, useful for testing.
virtual wallet::WalletContext* context() { return nullptr; }
};
Expand Down
366 changes: 286 additions & 80 deletions src/qt/splashscreen.cpp

Large diffs are not rendered by default.

26 changes: 23 additions & 3 deletions src/qt/splashscreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#ifndef BITCOIN_QT_SPLASHSCREEN_H
#define BITCOIN_QT_SPLASHSCREEN_H

#include <QElapsedTimer>
#include <QTimer>
#include <QWidget>

#include <memory>
Expand Down Expand Up @@ -40,8 +42,11 @@ public Q_SLOTS:
/** Show message and progress */
void showMessage(const QString &message, int alignment, const QColor &color);

/** Handle wallet load notifications. */
void handleLoadWallet();
/** Set progress bar value (-1 = no sub-progress, 0-100 = phase sub-progress) */
void setProgress(int value);

/** Handle early wallet-loading notifications so startup progress can be observed. */
void handleLoadingWallet();

protected:
bool eventFilter(QObject * obj, QEvent * ev) override;
Expand All @@ -53,18 +58,33 @@ public Q_SLOTS:
void unsubscribeFromCoreSignals();
/** Initiate shutdown */
void shutdown();
/** Calculate overall progress (0.0-1.0) based on current phase and sub-progress */
qreal calcOverallProgress() const;

QPixmap pixmap;
/** Cached on GUI thread at construction for thread-safe use in cross-thread callbacks.
* Const after construction — no synchronization needed. */
const QColor messageColor;
QString curMessage;
QColor curColor;
int curAlignment{0};
int curProgress{-1};

// Phase-based progress tracking
qreal phaseStart{0.0}; // Overall progress at start of current phase
qreal phaseEnd{0.0}; // Overall progress at end of current phase
QElapsedTimer phaseTimer; // Time since current phase started
const struct PhaseInfo* m_current_phase{nullptr}; // Current phase (defined in splashscreen.cpp)
QString m_current_phase_message; // Message that triggered current phase
qreal displayProgress{0.0}; // Smoothly animated display value (0.0-1.0)
QTimer animTimer;

interfaces::Node* m_node = nullptr;
bool m_shutdown = false;
std::unique_ptr<interfaces::Handler> m_handler_init_message;
std::unique_ptr<interfaces::Handler> m_handler_show_progress;
std::unique_ptr<interfaces::Handler> m_handler_init_wallet;
std::unique_ptr<interfaces::Handler> m_handler_load_wallet;
std::unique_ptr<interfaces::Handler> m_handler_loading_wallet;
std::list<std::unique_ptr<interfaces::Wallet>> m_connected_wallets;
std::list<std::unique_ptr<interfaces::Handler>> m_connected_wallet_handlers;
};
Expand Down
1 change: 1 addition & 0 deletions src/wallet/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ struct WalletContext {
// this could introduce inconsistent lock ordering and cause deadlocks.
Mutex wallets_mutex;
std::vector<std::shared_ptr<CWallet>> wallets GUARDED_BY(wallets_mutex);
std::list<LoadWalletFn> wallet_loading_fns GUARDED_BY(wallets_mutex);
std::list<LoadWalletFn> wallet_load_fns GUARDED_BY(wallets_mutex);
interfaces::CoinJoin::Loader* coinjoin_loader{nullptr};
// Some Dash RPCs rely on WalletContext yet access NodeContext members
Expand Down
4 changes: 4 additions & 0 deletions src/wallet/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,10 @@ class WalletLoaderImpl : public WalletLoader
{
return HandleLoadWallet(m_context, std::move(fn));
}
std::unique_ptr<Handler> handleLoadWalletLoading(LoadWalletFn fn) override
{
return HandleLoadWalletLoading(m_context, std::move(fn));
}
WalletContext* context() override { return &m_context; }

WalletContext m_context;
Expand Down
17 changes: 17 additions & 0 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,21 @@ std::unique_ptr<interfaces::Handler> HandleLoadWallet(WalletContext& context, Lo
return interfaces::MakeHandler([&context, it] { LOCK(context.wallets_mutex); context.wallet_load_fns.erase(it); });
}

std::unique_ptr<interfaces::Handler> HandleLoadWalletLoading(WalletContext& context, LoadWalletFn load_wallet)
{
LOCK(context.wallets_mutex);
auto it = context.wallet_loading_fns.emplace(context.wallet_loading_fns.end(), std::move(load_wallet));
return interfaces::MakeHandler([&context, it] { LOCK(context.wallets_mutex); context.wallet_loading_fns.erase(it); });
}

void NotifyWalletLoading(WalletContext& context, const std::shared_ptr<CWallet>& wallet)
{
LOCK(context.wallets_mutex);
for (auto& load_wallet : context.wallet_loading_fns) {
load_wallet(interfaces::MakeWallet(context, wallet));
}
Comment on lines +203 to +208
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t publish an owning wallet wrapper before startup succeeds.

In src/qt/splashscreen.cpp, Lines 318-330 retain this wrapper in m_connected_wallets. Because Lines 3300-3304 fire the callback before the remaining startup path is known to succeed, any later failure here (for example AttachChain()) leaves that retained shared_ptr holding a half-initialized CWallet and its DB/resources until the splash is torn down. The loading path needs a non-owning/progress-only handle here, or an explicit failure cleanup path that releases early subscribers.

Also applies to: 3300-3304

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/wallet/wallet.cpp` around lines 203 - 208, NotifyWalletLoading is
publishing an owning wrapper (via interfaces::MakeWallet(context, wallet)) to
subscribers before wallet startup fully succeeds, which can leave a
half-initialized CWallet retained; change the behavior so the callback receives
a non-owning/progress-only handle or a weak reference instead of an owning
shared_ptr, or delay calling context.wallet_loading_fns until after startup
steps (e.g., AttachChain) succeed and only then publish the owning wrapper.
Specifically, modify NotifyWalletLoading to either pass a non-owning
observer/weak_ptr (so subscribers like splashscreen's m_connected_wallets don't
hold ownership) or add an explicit failure cleanup path that releases any early
subscribers if later startup fails; update uses of interfaces::MakeWallet and
wallet_loading_fns accordingly to reflect the non-owning handle contract.

}

void NotifyWalletLoaded(WalletContext& context, const std::shared_ptr<CWallet>& wallet)
{
LOCK(context.wallets_mutex);
Expand Down Expand Up @@ -3282,6 +3297,8 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
// Try to top up keypool. No-op if the wallet is locked.
walletInstance->TopUpKeyPool();

NotifyWalletLoading(context, walletInstance);

if (chain && !AttachChain(walletInstance, *chain, error, warnings)) {
walletInstance->m_chain_notifications_handler.reset(); // Reset this pointer so that the wallet will actually be unloaded
return nullptr;
Expand Down
2 changes: 2 additions & 0 deletions src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ std::shared_ptr<CWallet> GetWallet(WalletContext& context, const std::string& na
std::shared_ptr<CWallet> LoadWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& backup_file, const std::string& wallet_name, std::optional<bool> load_on_start, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings);
std::unique_ptr<interfaces::Handler> HandleLoadWalletLoading(WalletContext& context, LoadWalletFn load_wallet);
std::unique_ptr<interfaces::Handler> HandleLoadWallet(WalletContext& context, LoadWalletFn load_wallet);
void NotifyWalletLoading(WalletContext& context, const std::shared_ptr<CWallet>& wallet);
void NotifyWalletLoaded(WalletContext& context, const std::shared_ptr<CWallet>& wallet);
std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);

Expand Down
Loading