From f1e94dc21648ff3cc6bf5e602c0765793103faec Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Fri, 31 Jan 2025 10:41:24 -0800 Subject: [PATCH 1/3] cpp: Implement UTXO snapshot loading interface and progress tracking - Extend node interface with virtual functions for UTXO snapshot loading - Add signal mechanism to monitor snapshot loading progress - Include predefined signet UTXO dataset in chainparams for validation --- src/interfaces/node.h | 8 ++++++++ src/kernel/chainparams.cpp | 7 +++++++ src/kernel/notifications_interface.h | 1 + src/node/interface_ui.cpp | 3 +++ src/node/interface_ui.h | 3 +++ src/node/interfaces.cpp | 11 ++++++++++- src/node/kernel_notifications.cpp | 5 +++++ src/node/kernel_notifications.h | 2 ++ src/validation.cpp | 4 ++++ 9 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 1aab36927b..b50643db22 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -11,6 +11,7 @@ #include // For banmap_t #include // For Network #include // For ConnectionDirection +#include // For SnapshotMetadata #include // For SecureString #include @@ -208,6 +209,9 @@ class Node //! List rpc commands. virtual std::vector listRpcCommands() = 0; + //! Load UTXO Snapshot. + virtual bool loadSnapshot(AutoFile& afile, const node::SnapshotMetadata& metadata, bool in_memory) = 0; + //! Set RPC timer interface if unset. virtual void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) = 0; @@ -243,6 +247,10 @@ class Node using ShowProgressFn = std::function; virtual std::unique_ptr handleShowProgress(ShowProgressFn fn) = 0; + //! Register handler for snapshot load progress. + using SnapshotLoadProgressFn = std::function; + virtual std::unique_ptr handleSnapshotLoadProgress(SnapshotLoadProgressFn fn) = 0; + //! Register handler for wallet loader constructed messages. using InitWalletFn = std::function; virtual std::unique_ptr handleInitWallet(InitWalletFn fn) = 0; diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index 733a3339b3..635dc40f9f 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -371,6 +371,13 @@ class SigNetParams : public CChainParams { vFixedSeeds.clear(); + m_assumeutxo_data = MapAssumeutxo{ + { + 160000, + {AssumeutxoHash{uint256S("0x5225141cb62dee63ab3be95f9b03d60801f264010b1816d4bd00618b2736e7be")}, 1278002}, + }, + }; + base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,111); base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,196); base58Prefixes[SECRET_KEY] = std::vector(1,239); diff --git a/src/kernel/notifications_interface.h b/src/kernel/notifications_interface.h index c5e77b0df9..b8002dedd2 100644 --- a/src/kernel/notifications_interface.h +++ b/src/kernel/notifications_interface.h @@ -40,6 +40,7 @@ class Notifications [[nodiscard]] virtual InterruptResult blockTip(SynchronizationState state, CBlockIndex& index) { return {}; } virtual void headerTip(SynchronizationState state, int64_t height, int64_t timestamp, bool presync) {} virtual void progress(const bilingual_str& title, int progress_percent, bool resume_possible) {} + virtual void snapshotLoadProgress(double progress) {} virtual void warning(const bilingual_str& warning) {} //! The flush error notification is sent to notify the user that an error diff --git a/src/node/interface_ui.cpp b/src/node/interface_ui.cpp index a1664f1bad..a57a559146 100644 --- a/src/node/interface_ui.cpp +++ b/src/node/interface_ui.cpp @@ -21,6 +21,7 @@ struct UISignals { boost::signals2::signal NotifyNetworkActiveChanged; boost::signals2::signal NotifyAlertChanged; boost::signals2::signal ShowProgress; + boost::signals2::signal SnapshotLoadProgress; boost::signals2::signal NotifyBlockTip; boost::signals2::signal NotifyHeaderTip; boost::signals2::signal BannedListChanged; @@ -44,6 +45,7 @@ ADD_SIGNALS_IMPL_WRAPPER(ShowProgress); ADD_SIGNALS_IMPL_WRAPPER(NotifyBlockTip); ADD_SIGNALS_IMPL_WRAPPER(NotifyHeaderTip); ADD_SIGNALS_IMPL_WRAPPER(BannedListChanged); +ADD_SIGNALS_IMPL_WRAPPER(SnapshotLoadProgress); bool CClientUIInterface::ThreadSafeMessageBox(const bilingual_str& message, const std::string& caption, unsigned int style) { return g_ui_signals.ThreadSafeMessageBox(message, caption, style).value_or(false);} bool CClientUIInterface::ThreadSafeQuestion(const bilingual_str& message, const std::string& non_interactive_message, const std::string& caption, unsigned int style) { return g_ui_signals.ThreadSafeQuestion(message, non_interactive_message, caption, style).value_or(false);} @@ -53,6 +55,7 @@ void CClientUIInterface::NotifyNumConnectionsChanged(PeersNumByType newNumConnec void CClientUIInterface::NotifyNetworkActiveChanged(bool networkActive) { return g_ui_signals.NotifyNetworkActiveChanged(networkActive); } void CClientUIInterface::NotifyAlertChanged() { return g_ui_signals.NotifyAlertChanged(); } void CClientUIInterface::ShowProgress(const std::string& title, int nProgress, bool resume_possible) { return g_ui_signals.ShowProgress(title, nProgress, resume_possible); } +void CClientUIInterface::SnapshotLoadProgress(double progress) { return g_ui_signals.SnapshotLoadProgress(progress); } void CClientUIInterface::NotifyBlockTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyBlockTip(s, i); } void CClientUIInterface::NotifyHeaderTip(SynchronizationState s, int64_t height, int64_t timestamp, bool presync) { return g_ui_signals.NotifyHeaderTip(s, height, timestamp, presync); } void CClientUIInterface::BannedListChanged() { return g_ui_signals.BannedListChanged(); } diff --git a/src/node/interface_ui.h b/src/node/interface_ui.h index ded59925d0..cc90fde75b 100644 --- a/src/node/interface_ui.h +++ b/src/node/interface_ui.h @@ -110,6 +110,9 @@ class CClientUIInterface */ ADD_SIGNALS_DECL_WRAPPER(ShowProgress, void, const std::string& title, int nProgress, bool resume_possible); + /** Snapshot load progress. */ + ADD_SIGNALS_DECL_WRAPPER(SnapshotLoadProgress, void, double progress); + /** New block has been accepted */ ADD_SIGNALS_DECL_WRAPPER(NotifyBlockTip, void, SynchronizationState, const CBlockIndex*); diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 3dd867cb8d..4d88822a8a 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -378,6 +379,10 @@ class NodeImpl : public Node { return MakeSignalHandler(::uiInterface.ShowProgress_connect(fn)); } + std::unique_ptr handleSnapshotLoadProgress(SnapshotLoadProgressFn fn) override + { + return MakeSignalHandler(::uiInterface.SnapshotLoadProgress_connect(fn)); + } std::unique_ptr handleInitWallet(InitWalletFn fn) override { return MakeSignalHandler(::uiInterface.InitWallet_connect(fn)); @@ -417,6 +422,10 @@ class NodeImpl : public Node { m_context = context; } + bool loadSnapshot(AutoFile& afile, const node::SnapshotMetadata& metadata, bool in_memory) override + { + return chainman().ActivateSnapshot(afile, metadata, in_memory); + } ArgsManager& args() { return *Assert(Assert(m_context)->args); } ChainstateManager& chainman() { return *Assert(m_context->chainman); } NodeContext* m_context{nullptr}; @@ -532,7 +541,7 @@ class RpcHandlerImpl : public Handler class ChainImpl : public Chain { public: - explicit ChainImpl(NodeContext& node) : m_node(node) {} + explicit ChainImpl(node::NodeContext& node) : m_node(node) {} std::optional getHeight() override { const int height{WITH_LOCK(::cs_main, return chainman().ActiveChain().Height())}; diff --git a/src/node/kernel_notifications.cpp b/src/node/kernel_notifications.cpp index 7224127c72..e41308dd50 100644 --- a/src/node/kernel_notifications.cpp +++ b/src/node/kernel_notifications.cpp @@ -78,6 +78,11 @@ void KernelNotifications::progress(const bilingual_str& title, int progress_perc uiInterface.ShowProgress(title.translated, progress_percent, resume_possible); } +void KernelNotifications::snapshotLoadProgress(double progress) +{ + uiInterface.SnapshotLoadProgress(progress); +} + void KernelNotifications::warning(const bilingual_str& warning) { DoWarning(warning); diff --git a/src/node/kernel_notifications.h b/src/node/kernel_notifications.h index b2dfc03398..4e9b9f77db 100644 --- a/src/node/kernel_notifications.h +++ b/src/node/kernel_notifications.h @@ -31,6 +31,8 @@ class KernelNotifications : public kernel::Notifications void progress(const bilingual_str& title, int progress_percent, bool resume_possible) override; + void snapshotLoadProgress(double progress) override; + void warning(const bilingual_str& warning) override; void flushError(const std::string& debug_message) override; diff --git a/src/validation.cpp b/src/validation.cpp index ed9889d9dd..c67cb91178 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -5261,6 +5261,10 @@ bool ChainstateManager::PopulateAndValidateSnapshot( --coins_left; ++coins_processed; + // Show Snapshot Loading progress + double progress = static_cast(coins_processed) / static_cast(coins_count); + GetNotifications().snapshotLoadProgress(progress); + if (coins_processed % 1000000 == 0) { LogPrintf("[snapshot] %d coins loaded (%.2f%%, %.2f MB)\n", coins_processed, From c73bd202b17625f41597b50442552bc8f1dc2a47 Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Fri, 31 Jan 2025 11:27:34 -0800 Subject: [PATCH 2/3] qml: Implement UTXO snapshot loading infrastructure - Add SnapshotQml class to encapsulate snapshot loading operations - Integrate QML properties for UTXO snapshot management - Wire up snapshot loading functionality with node interface --- src/Makefile.qt.include | 3 + src/qml/components/ConnectionSettings.qml | 2 +- src/qml/models/chainmodel.cpp | 30 ++++++++ src/qml/models/chainmodel.h | 6 +- src/qml/models/nodemodel.cpp | 69 +++++++++++++++++++ src/qml/models/nodemodel.h | 33 ++++++++- src/qml/models/snapshotqml.cpp | 40 +++++++++++ src/qml/models/snapshotqml.h | 26 +++++++ src/qml/pages/settings/SettingsConnection.qml | 9 --- 9 files changed, 204 insertions(+), 14 deletions(-) create mode 100644 src/qml/models/snapshotqml.cpp create mode 100644 src/qml/models/snapshotqml.h diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 134cadc8b8..36c0ef2f29 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -47,6 +47,7 @@ QT_MOC_CPP = \ qml/models/moc_peerlistsortproxy.cpp \ qml/models/moc_transaction.cpp \ qml/models/moc_sendrecipient.cpp \ + qml/models/moc_snapshotqml.cpp \ qml/models/moc_walletlistmodel.cpp \ qml/models/moc_walletqmlmodel.cpp \ qml/models/moc_walletqmlmodel.cpp \ @@ -138,6 +139,7 @@ BITCOIN_QT_H = \ qml/models/peerlistsortproxy.h \ qml/models/transaction.h \ qml/models/sendrecipient.h \ + qml/models/snapshotqml.h \ qml/models/walletlistmodel.h \ qml/models/walletqmlmodel.h \ qml/models/walletqmlmodeltransaction.h \ @@ -339,6 +341,7 @@ BITCOIN_QML_BASE_CPP = \ qml/models/peerlistsortproxy.cpp \ qml/models/transaction.cpp \ qml/models/sendrecipient.cpp \ + qml/models/snapshotqml.cpp \ qml/models/walletlistmodel.cpp \ qml/models/walletqmlmodel.cpp \ qml/models/walletqmlmodeltransaction.cpp \ diff --git a/src/qml/components/ConnectionSettings.qml b/src/qml/components/ConnectionSettings.qml index 7bb9846a81..c2c3fd054f 100644 --- a/src/qml/components/ConnectionSettings.qml +++ b/src/qml/components/ConnectionSettings.qml @@ -39,7 +39,7 @@ ColumnLayout { onClicked: root.gotoSnapshot() } Separator { - visible: !root.onboarding && !snapshotImportCompleted + visible: !root.onboarding && !snapshotImportCompleted && !root.isIBDCompleted Layout.fillWidth: true } Setting { diff --git a/src/qml/models/chainmodel.cpp b/src/qml/models/chainmodel.cpp index aeffe99599..978046ebff 100644 --- a/src/qml/models/chainmodel.cpp +++ b/src/qml/models/chainmodel.cpp @@ -9,9 +9,13 @@ #include #include #include +#include +#include +#include ChainModel::ChainModel(interfaces::Chain& chain) : m_chain{chain} + // m_params{Params()} { QTimer* timer = new QTimer(); connect(timer, &QTimer::timeout, this, &ChainModel::setCurrentTimeRatio); @@ -101,3 +105,29 @@ void ChainModel::setCurrentTimeRatio() Q_EMIT timeRatioListChanged(); } + +// TODO: Change this once a better solution has been found. +// Using hardcoded snapshot info to display in SnapshotSettings.qml +QVariantMap ChainModel::getSnapshotInfo() { + QVariantMap snapshot_info; + + const MapAssumeutxo& valid_assumeutxos_map = Params().Assumeutxo(); + if (!valid_assumeutxos_map.empty()) { + const int height = valid_assumeutxos_map.rbegin()->first; + const auto& hash_serialized = valid_assumeutxos_map.rbegin()->second.hash_serialized; + int64_t date = m_chain.getBlockTime(height); + + QString fullHash = QString::fromStdString(hash_serialized.ToString()); + + int midPoint = fullHash.length() / 2; + QString firstHalf = fullHash.left(midPoint); + QString secondHalf = fullHash.mid(midPoint); + + snapshot_info["height"] = height; + snapshot_info["hashSerializedFirstHalf"] = firstHalf; + snapshot_info["hashSerializedSecondHalf"] = secondHalf; + snapshot_info["date"] = QDateTime::fromSecsSinceEpoch(date).toString("MMMM d yyyy"); + } + + return snapshot_info; +} diff --git a/src/qml/models/chainmodel.h b/src/qml/models/chainmodel.h index 9318510eda..6a5124be7f 100644 --- a/src/qml/models/chainmodel.h +++ b/src/qml/models/chainmodel.h @@ -27,6 +27,7 @@ class ChainModel : public QObject Q_PROPERTY(quint64 assumedBlockchainSize READ assumedBlockchainSize CONSTANT) Q_PROPERTY(quint64 assumedChainstateSize READ assumedChainstateSize CONSTANT) Q_PROPERTY(QVariantList timeRatioList READ timeRatioList NOTIFY timeRatioListChanged) + Q_PROPERTY(bool isSnapshotActive READ isSnapshotActive NOTIFY isSnapshotActiveChanged) public: explicit ChainModel(interfaces::Chain& chain); @@ -36,11 +37,13 @@ class ChainModel : public QObject quint64 assumedBlockchainSize() const { return m_assumed_blockchain_size; }; quint64 assumedChainstateSize() const { return m_assumed_chainstate_size; }; QVariantList timeRatioList() const { return m_time_ratio_list; }; - + bool isSnapshotActive() const { return m_chain.hasAssumedValidChain(); }; int timestampAtMeridian(); void setCurrentTimeRatio(); + Q_INVOKABLE QVariantMap getSnapshotInfo(); + public Q_SLOTS: void setTimeRatioList(int new_time); void setTimeRatioListInitial(); @@ -48,6 +51,7 @@ public Q_SLOTS: Q_SIGNALS: void timeRatioListChanged(); void currentNetworkNameChanged(); + void isSnapshotActiveChanged(); private: QString m_current_network_name; diff --git a/src/qml/models/nodemodel.cpp b/src/qml/models/nodemodel.cpp index 8ccc532016..7d684eac2d 100644 --- a/src/qml/models/nodemodel.cpp +++ b/src/qml/models/nodemodel.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include @@ -14,14 +15,19 @@ #include #include +#include #include #include +#include +#include +#include NodeModel::NodeModel(interfaces::Node& node) : m_node{node} { ConnectToBlockTipSignal(); ConnectToNumConnectionsChangedSignal(); + ConnectToSnapshotLoadProgressSignal(); } void NodeModel::setBlockTipHeight(int new_height) @@ -80,6 +86,14 @@ void NodeModel::setVerificationProgress(double new_progress) if (new_progress != m_verification_progress) { setRemainingSyncTime(new_progress); + if (new_progress >= 0.00001) { + setHeadersSynced(true); + } + + if (new_progress >= 0.999) { + setIsIBDCompleted(true); + } + m_verification_progress = new_progress; Q_EMIT verificationProgressChanged(); } @@ -176,3 +190,58 @@ QString NodeModel::defaultProxyAddress() { return QString::fromStdString(m_node.defaultProxyAddress()); } + +void NodeModel::ConnectToSnapshotLoadProgressSignal() +{ + assert(!m_handler_snapshot_load_progress); + + m_handler_snapshot_load_progress = m_node.handleSnapshotLoadProgress( + [this](double progress) { + setSnapshotProgress(progress); + }); +} + +void NodeModel::snapshotLoadThread(QString path_file) { + m_snapshot_loading = true; + Q_EMIT snapshotLoadingChanged(); + + path_file = QUrl(path_file).toLocalFile(); + + QThread* snapshot_thread = QThread::create([this, path_file]() { + SnapshotQml loader(m_node, path_file); + bool result = loader.processPath(); + if (!result) { + m_snapshot_loading = false; + Q_EMIT snapshotLoadingChanged(); + } else { + m_snapshot_loaded = true; + Q_EMIT snapshotLoaded(result); + Q_EMIT snapshotLoadingChanged(); + } + }); + + connect(snapshot_thread, &QThread::finished, snapshot_thread, &QThread::deleteLater); + + snapshot_thread->start(); +} + +void NodeModel::setSnapshotProgress(double new_progress) { + if (new_progress != m_snapshot_progress) { + m_snapshot_progress = new_progress; + Q_EMIT snapshotProgressChanged(); + } +} + +void NodeModel::setHeadersSynced(bool new_synced) { + if (new_synced != m_headers_synced) { + m_headers_synced = new_synced; + Q_EMIT headersSyncedChanged(); + } +} + +void NodeModel::setIsIBDCompleted(bool new_completed) { + if (new_completed != m_is_ibd_completed) { + m_is_ibd_completed = new_completed; + Q_EMIT isIBDCompletedChanged(); + } +} diff --git a/src/qml/models/nodemodel.h b/src/qml/models/nodemodel.h index 8603c44d36..17e4504835 100644 --- a/src/qml/models/nodemodel.h +++ b/src/qml/models/nodemodel.h @@ -34,6 +34,11 @@ class NodeModel : public QObject Q_PROPERTY(double verificationProgress READ verificationProgress NOTIFY verificationProgressChanged) Q_PROPERTY(bool pause READ pause WRITE setPause NOTIFY pauseChanged) Q_PROPERTY(bool faulted READ errorState WRITE setErrorState NOTIFY errorStateChanged) + Q_PROPERTY(double snapshotProgress READ snapshotProgress WRITE setSnapshotProgress NOTIFY snapshotProgressChanged) + Q_PROPERTY(bool snapshotLoading READ snapshotLoading NOTIFY snapshotLoadingChanged) + Q_PROPERTY(bool isSnapshotLoaded READ isSnapshotLoaded NOTIFY snapshotLoaded) + Q_PROPERTY(bool headersSynced READ headersSynced WRITE setHeadersSynced NOTIFY headersSyncedChanged) + Q_PROPERTY(bool isIBDCompleted READ isIBDCompleted WRITE setIsIBDCompleted NOTIFY isIBDCompletedChanged) public: explicit NodeModel(interfaces::Node& node); @@ -52,6 +57,14 @@ class NodeModel : public QObject void setPause(bool new_pause); bool errorState() const { return m_faulted; } void setErrorState(bool new_error); + bool isSnapshotLoaded() const { return m_snapshot_loaded; } + double snapshotProgress() const { return m_snapshot_progress; } + void setSnapshotProgress(double new_progress); + bool snapshotLoading() const { return m_snapshot_loading; } + bool headersSynced() const { return m_headers_synced; } + void setHeadersSynced(bool new_synced); + bool isIBDCompleted() const { return m_is_ibd_completed; } + void setIsIBDCompleted(bool new_completed); Q_INVOKABLE float getTotalBytesReceived() const { return (float)m_node.getTotalBytesRecv(); } Q_INVOKABLE float getTotalBytesSent() const { return (float)m_node.getTotalBytesSent(); } @@ -59,6 +72,8 @@ class NodeModel : public QObject Q_INVOKABLE void startNodeInitializionThread(); Q_INVOKABLE void requestShutdown(); + Q_INVOKABLE void snapshotLoadThread(QString path_file); + void startShutdownPolling(); void stopShutdownPolling(); @@ -80,7 +95,13 @@ public Q_SLOTS: void setTimeRatioList(int new_time); void setTimeRatioListInitial(); - + void initializationFinished(); + void snapshotLoaded(bool result); + void snapshotProgressChanged(); + void snapshotLoadingChanged(); + void showProgress(const QString& title, int progress); + void headersSyncedChanged(); + void isIBDCompletedChanged(); protected: void timerEvent(QTimerEvent* event) override; @@ -93,17 +114,23 @@ public Q_SLOTS: double m_verification_progress{0.0}; bool m_pause{false}; bool m_faulted{false}; - + double m_snapshot_progress{0.0}; int m_shutdown_polling_timer_id{0}; + int m_snapshot_timer_id{0}; + bool m_snapshot_loading{false}; + bool m_snapshot_loaded{false}; + bool m_headers_synced{false}; + bool m_is_ibd_completed{false}; QVector> m_block_process_time; interfaces::Node& m_node; std::unique_ptr m_handler_notify_block_tip; std::unique_ptr m_handler_notify_num_peers_changed; - + std::unique_ptr m_handler_snapshot_load_progress; void ConnectToBlockTipSignal(); void ConnectToNumConnectionsChangedSignal(); + void ConnectToSnapshotLoadProgressSignal(); }; #endif // BITCOIN_QML_MODELS_NODEMODEL_H diff --git a/src/qml/models/snapshotqml.cpp b/src/qml/models/snapshotqml.cpp new file mode 100644 index 0000000000..6265aba417 --- /dev/null +++ b/src/qml/models/snapshotqml.cpp @@ -0,0 +1,40 @@ +// Copyright (c) 2024 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include + +SnapshotQml::SnapshotQml(interfaces::Node& node, QString path) + : m_node(node), m_path(path) {} + +bool SnapshotQml::processPath() +{ + const fs::path path = fs::u8path(m_path.toStdString()); + if (!fs::exists(path)) { + return false; + } + + FILE* snapshot_file{fsbridge::fopen(path, "rb")}; + AutoFile afile{snapshot_file}; + if (afile.IsNull()) { + return false; + } + + node::SnapshotMetadata metadata; + try { + afile >> metadata; + } catch (const std::exception& e) { + return false; + } + + bool result = m_node.loadSnapshot(afile, metadata, false); + if (!result) { + return false; + } + return true; +} \ No newline at end of file diff --git a/src/qml/models/snapshotqml.h b/src/qml/models/snapshotqml.h new file mode 100644 index 0000000000..75e4758d7e --- /dev/null +++ b/src/qml/models/snapshotqml.h @@ -0,0 +1,26 @@ +// Copyright (c) 2024 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QML_MODELS_SNAPSHOTQML_H +#define BITCOIN_QML_MODELS_SNAPSHOTQML_H + +#include +#include + +#include + +class SnapshotQml : public QObject +{ + Q_OBJECT +public: + SnapshotQml(interfaces::Node& node, QString path); + + bool processPath(); + +private: + interfaces::Node& m_node; + QString m_path; +}; + +#endif // BITCOIN_QML_MODELS_SNAPSHOTQML_H diff --git a/src/qml/pages/settings/SettingsConnection.qml b/src/qml/pages/settings/SettingsConnection.qml index bd15c28bad..da5593c58d 100644 --- a/src/qml/pages/settings/SettingsConnection.qml +++ b/src/qml/pages/settings/SettingsConnection.qml @@ -99,14 +99,5 @@ Page { onBack: stack.pop() } } - Component { - id: generateSnapshotSettings - SettingsSnapshotGen { - onboarding: root.onboarding - generateSnapshot: true - isSnapshotGenerated: ( nodeModel.isSnapshotFileExists() || nodeModel.isSnapshotGenerated ) - onBack: stack.pop() - } - } } } From 77ff309a0dce01b4e966942b9ea67133bc87f962 Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Mon, 5 May 2025 15:29:34 -0700 Subject: [PATCH 3/3] qml: Enable snapshot load path setting during onboarding - Update visibility conditions in ConnectionSettings and SnapshotSettings to enable loading during onboarding. - Introduce new methods in NodeModel for handling snapshot file paths and loading logic based on header sync status. - Ensure snapshot loading is triggered appropriately when headers are synced and a snapshot file path is set. - Adds error handling on snapshot failure. --- src/qml/components/ConnectionSettings.qml | 4 ++-- src/qml/components/SnapshotSettings.qml | 15 +++++++------ src/qml/models/nodemodel.cpp | 26 ++++++++++++++++++++++- src/qml/models/nodemodel.h | 12 ++++++++++- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/qml/components/ConnectionSettings.qml b/src/qml/components/ConnectionSettings.qml index c2c3fd054f..6c4ae8c5fd 100644 --- a/src/qml/components/ConnectionSettings.qml +++ b/src/qml/components/ConnectionSettings.qml @@ -17,7 +17,7 @@ ColumnLayout { spacing: 4 Setting { id: gotoSnapshot - visible: !root.onboarding && !snapshotImportCompleted && !root.isIBDCompleted + visible: !snapshotImportCompleted && !root.isIBDCompleted Layout.fillWidth: true header: qsTr("Load snapshot") description: qsTr("Instant use with background sync") @@ -39,7 +39,7 @@ ColumnLayout { onClicked: root.gotoSnapshot() } Separator { - visible: !root.onboarding && !snapshotImportCompleted && !root.isIBDCompleted + visible: !snapshotImportCompleted && !root.isIBDCompleted Layout.fillWidth: true } Setting { diff --git a/src/qml/components/SnapshotSettings.qml b/src/qml/components/SnapshotSettings.qml index 3481af9e79..4eeacf4c33 100644 --- a/src/qml/components/SnapshotSettings.qml +++ b/src/qml/components/SnapshotSettings.qml @@ -21,7 +21,7 @@ ColumnLayout { property var snapshotInfo: (snapshotVerified || snapshotLoaded) ? chainModel.getSnapshotInfo() : ({}) property string selectedFile: "" property bool headersSynced: nodeModel.headersSynced - property bool snapshotError: false + property bool snapshotError: nodeModel.snapshotError width: Math.min(parent.width, 450) anchors.horizontalCenter: parent.horizontalCenter @@ -60,7 +60,7 @@ ColumnLayout { Layout.topMargin: 20 color: Theme.color.neutral6 font.pixelSize: 17 - visible: !headersSynced + visible: !headersSynced && !onboarding text: !headersSynced ? qsTr("Please wait for headers to sync before loading a snapshot.") : qsTr("") @@ -76,7 +76,7 @@ ColumnLayout { Layout.bottomMargin: 20 Layout.alignment: Qt.AlignCenter text: qsTr("Choose snapshot file") - enabled: headersSynced + enabled: headersSynced || onboarding onClicked: fileDialog.open() } @@ -89,8 +89,11 @@ ColumnLayout { onAccepted: { selectedFile = fileUrl.toString() snapshotFileName = selectedFile - if (!nodeModel.snapshotLoadThread(snapshotFileName)) { - snapshotError = true + if (!onboarding) { + nodeModel.snapshotLoadThread(snapshotFileName) + } else { + nodeModel.setSnapshotFilePath(snapshotFileName) + back() } } } @@ -259,7 +262,7 @@ ColumnLayout { Layout.alignment: Qt.AlignCenter text: qsTr("OK") onClicked: { - snapshotError = false + nodeModel.setSnapshotError(false) back() } } diff --git a/src/qml/models/nodemodel.cpp b/src/qml/models/nodemodel.cpp index 7d684eac2d..e16e06cd66 100644 --- a/src/qml/models/nodemodel.cpp +++ b/src/qml/models/nodemodel.cpp @@ -86,7 +86,7 @@ void NodeModel::setVerificationProgress(double new_progress) if (new_progress != m_verification_progress) { setRemainingSyncTime(new_progress); - if (new_progress >= 0.00001) { + if (new_progress >= 0.00014) { setHeadersSynced(true); } @@ -213,6 +213,8 @@ void NodeModel::snapshotLoadThread(QString path_file) { if (!result) { m_snapshot_loading = false; Q_EMIT snapshotLoadingChanged(); + m_snapshot_error = true; + Q_EMIT snapshotErrorChanged(); } else { m_snapshot_loaded = true; Q_EMIT snapshotLoaded(result); @@ -236,6 +238,7 @@ void NodeModel::setHeadersSynced(bool new_synced) { if (new_synced != m_headers_synced) { m_headers_synced = new_synced; Q_EMIT headersSyncedChanged(); + checkAndLoadSnapshot(); } } @@ -245,3 +248,24 @@ void NodeModel::setIsIBDCompleted(bool new_completed) { Q_EMIT isIBDCompletedChanged(); } } + +void NodeModel::setSnapshotFilePath(const QString& new_path) { + if (new_path != m_snapshot_file_path) { + m_snapshot_file_path = new_path; + Q_EMIT snapshotFilePathChanged(); + } +} + +void NodeModel::checkAndLoadSnapshot() +{ + if (m_headers_synced && !m_snapshot_file_path.isEmpty()) { + snapshotLoadThread(m_snapshot_file_path); + } +} + +void NodeModel::setSnapshotError(bool new_error) { + if (new_error != m_snapshot_error) { + m_snapshot_error = new_error; + Q_EMIT snapshotErrorChanged(); + } +} diff --git a/src/qml/models/nodemodel.h b/src/qml/models/nodemodel.h index 17e4504835..eac30239e3 100644 --- a/src/qml/models/nodemodel.h +++ b/src/qml/models/nodemodel.h @@ -39,6 +39,8 @@ class NodeModel : public QObject Q_PROPERTY(bool isSnapshotLoaded READ isSnapshotLoaded NOTIFY snapshotLoaded) Q_PROPERTY(bool headersSynced READ headersSynced WRITE setHeadersSynced NOTIFY headersSyncedChanged) Q_PROPERTY(bool isIBDCompleted READ isIBDCompleted WRITE setIsIBDCompleted NOTIFY isIBDCompletedChanged) + Q_PROPERTY(QString snapshotFilePath READ snapshotFilePath WRITE setSnapshotFilePath NOTIFY snapshotFilePathChanged) + Q_PROPERTY(bool snapshotError READ snapshotError WRITE setSnapshotError NOTIFY snapshotErrorChanged) public: explicit NodeModel(interfaces::Node& node); @@ -65,6 +67,10 @@ class NodeModel : public QObject void setHeadersSynced(bool new_synced); bool isIBDCompleted() const { return m_is_ibd_completed; } void setIsIBDCompleted(bool new_completed); + QString snapshotFilePath() const { return m_snapshot_file_path; } + Q_INVOKABLE void setSnapshotFilePath(const QString& new_path); + bool snapshotError() const { return m_snapshot_error; } + Q_INVOKABLE void setSnapshotError(bool new_error); Q_INVOKABLE float getTotalBytesReceived() const { return (float)m_node.getTotalBytesRecv(); } Q_INVOKABLE float getTotalBytesSent() const { return (float)m_node.getTotalBytesSent(); } @@ -73,6 +79,7 @@ class NodeModel : public QObject Q_INVOKABLE void requestShutdown(); Q_INVOKABLE void snapshotLoadThread(QString path_file); + Q_INVOKABLE void checkAndLoadSnapshot(); void startShutdownPolling(); void stopShutdownPolling(); @@ -102,6 +109,8 @@ public Q_SLOTS: void showProgress(const QString& title, int progress); void headersSyncedChanged(); void isIBDCompletedChanged(); + void snapshotFilePathChanged(); + void snapshotErrorChanged(); protected: void timerEvent(QTimerEvent* event) override; @@ -121,7 +130,8 @@ public Q_SLOTS: bool m_snapshot_loaded{false}; bool m_headers_synced{false}; bool m_is_ibd_completed{false}; - + QString m_snapshot_file_path; + bool m_snapshot_error{false}; QVector> m_block_process_time; interfaces::Node& m_node;