diff --git a/.gitmodules b/.gitmodules
index fabdfe5061..03c4756751 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
[submodule "bitcoin"]
path = bitcoin
- url = https://github.com/bitcoin/bitcoin
+ url = https://github.com/D33r-Gee/bitcoin
diff --git a/bitcoin b/bitcoin
index 8ffbd7b778..8bc4035570 160000
--- a/bitcoin
+++ b/bitcoin
@@ -1 +1 @@
-Subproject commit 8ffbd7b778600aa1e824027f1e675929a4240856
+Subproject commit 8bc4035570acfe512c756c6de30d6440d322d514
diff --git a/qml/bitcoin_qml.qrc b/qml/bitcoin_qml.qrc
index 2a1a9c4432..dbdcf885e1 100644
--- a/qml/bitcoin_qml.qrc
+++ b/qml/bitcoin_qml.qrc
@@ -16,6 +16,7 @@
components/ProxySettings.qml
components/StorageLocations.qml
components/Separator.qml
+ components/SnapshotLoadSettings.qml
components/StorageOptions.qml
components/StorageSettings.qml
components/ThemeSettings.qml
@@ -29,6 +30,7 @@
controls/CoreTextField.qml
controls/ExternalLink.qml
controls/FocusBorder.qml
+ controls/GreenCheckIcon.qml
controls/Header.qml
controls/Icon.qml
controls/IconButton.qml
@@ -79,6 +81,7 @@
pages/settings/SettingsDeveloper.qml
pages/settings/SettingsDisplay.qml
pages/settings/SettingsProxy.qml
+ pages/settings/SettingsSnapshotLoad.qml
pages/settings/SettingsStorage.qml
pages/settings/SettingsTheme.qml
pages/wallet/Activity.qml
@@ -115,6 +118,9 @@
res/icons/check.png
res/icons/copy.png
res/icons/coinbase.png
+ res/icons/circle-file.png
+ res/icons/circle-green-check.png
+ res/icons/circle-red-cross.png
res/icons/cross.png
res/icons/ellipsis.png
res/icons/error.png
@@ -122,6 +128,7 @@
res/icons/flip-vertical.png
res/icons/gear.png
res/icons/gear-outline.png
+ res/icons/green-check.png
res/icons/hidden.png
res/icons/info.png
res/icons/lock.png
diff --git a/qml/components/ConnectionSettings.qml b/qml/components/ConnectionSettings.qml
index 385cb5ab45..8fe7e1a130 100644
--- a/qml/components/ConnectionSettings.qml
+++ b/qml/components/ConnectionSettings.qml
@@ -10,7 +10,38 @@ import "../controls"
ColumnLayout {
id: root
signal next
+ signal gotoSnapshot
+ property bool onboarding: false
+ property bool snapshotImportCompleted: onboarding ? false : chainModel.isSnapshotActive
+ property bool isIBDCompleted: nodeModel.isIBDCompleted
spacing: 4
+ Setting {
+ id: gotoSnapshot
+ visible: !snapshotImportCompleted && !root.isIBDCompleted
+ Layout.fillWidth: true
+ header: qsTr("Load snapshot")
+ description: qsTr("Instant use with background sync")
+ actionItem: Item {
+ width: 26
+ height: 26
+ CaretRightIcon {
+ anchors.centerIn: parent
+ visible: !snapshotImportCompleted
+ color: gotoSnapshot.stateColor
+ }
+ GreenCheckIcon {
+ anchors.centerIn: parent
+ visible: snapshotImportCompleted
+ color: Theme.color.transparent
+ size: 30
+ }
+ }
+ onClicked: root.gotoSnapshot()
+ }
+ Separator {
+ visible: !snapshotImportCompleted && !root.isIBDCompleted
+ Layout.fillWidth: true
+ }
Setting {
Layout.fillWidth: true
header: qsTr("Enable listening")
diff --git a/qml/components/SnapshotLoadSettings.qml b/qml/components/SnapshotLoadSettings.qml
new file mode 100644
index 0000000000..cfcf3a97a9
--- /dev/null
+++ b/qml/components/SnapshotLoadSettings.qml
@@ -0,0 +1,269 @@
+// Copyright (c) 2023-present The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Dialogs
+
+import "../controls"
+
+ColumnLayout {
+ id: columnLayout
+ signal back
+ property bool snapshotLoading: nodeModel.snapshotLoading
+ property bool snapshotLoaded: nodeModel.isSnapshotLoaded
+ property bool snapshotImportCompleted: onboarding ? false : chainModel.isSnapshotActive
+ property bool onboarding: false
+ property bool snapshotVerified: onboarding ? false : chainModel.isSnapshotActive
+ property string snapshotFileName: ""
+ property var snapshotInfo: (snapshotVerified || snapshotLoaded) ? chainModel.getSnapshotInfo() : ({})
+ property string selectedFile: ""
+ property bool headersSynced: nodeModel.headersSynced
+ property bool snapshotError: nodeModel.snapshotError
+
+ width: Math.min(parent.width, 450)
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ StackLayout {
+ id: settingsStack
+ currentIndex: onboarding ? 0 : snapshotLoaded ? 2 : snapshotVerified ? 2 : snapshotLoading ? 1 : snapshotError ? 3 : 0
+
+ ColumnLayout {
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredWidth: Math.min(parent.width, 450)
+
+ Image {
+ Layout.alignment: Qt.AlignCenter
+ source: "image://images/circle-file"
+
+ sourceSize.width: 200
+ sourceSize.height: 200
+ }
+
+ Header {
+ Layout.fillWidth: true
+ Layout.topMargin: 20
+ headerBold: true
+ header: qsTr("Load snapshot")
+ descriptionBold: false
+ descriptionColor: Theme.color.neutral6
+ descriptionSize: 17
+ descriptionLineHeight: 1.1
+ description: qsTr("You can start using the application more quickly by loading a recent transaction snapshot." +
+ " It will be automatically verified in the background.")
+ }
+
+ CoreText {
+ Layout.fillWidth: true
+ Layout.topMargin: 20
+ color: Theme.color.neutral6
+ font.pixelSize: 17
+ visible: !headersSynced && !onboarding
+ text: !headersSynced
+ ? qsTr("Please wait for headers to sync before loading a snapshot.")
+ : qsTr("")
+ wrap: true
+ wrapMode: Text.WordWrap
+ }
+
+ ContinueButton {
+ Layout.preferredWidth: Math.min(300, columnLayout.width - 2 * Layout.leftMargin)
+ Layout.topMargin: 40
+ Layout.leftMargin: 20
+ Layout.rightMargin: Layout.leftMargin
+ Layout.bottomMargin: 20
+ Layout.alignment: Qt.AlignCenter
+ text: qsTr("Choose snapshot file")
+ enabled: headersSynced || onboarding
+ onClicked: fileDialog.open()
+ }
+
+ FileDialog {
+ id: fileDialog
+ currentFolder: optionsModel.getDefaultDataDirectory
+ nameFilters: ["Snapshot files (*.dat)", "All files (*)"]
+ onAccepted: {
+ snapshotFileName = selectedFile
+ console.log("snapshotFileName", snapshotFileName)
+ if (!onboarding) {
+ nodeModel.snapshotLoadThread(snapshotFileName)
+ } else {
+ nodeModel.setSnapshotFilePath(snapshotFileName)
+ back()
+ }
+ }
+ }
+ }
+
+ ColumnLayout {
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredWidth: Math.min(parent.width, 450)
+
+ Image {
+ Layout.alignment: Qt.AlignCenter
+ source: "image://images/circle-file"
+
+ sourceSize.width: 200
+ sourceSize.height: 200
+ }
+
+ Header {
+ Layout.fillWidth: true
+ Layout.topMargin: 20
+ Layout.leftMargin: 20
+ Layout.rightMargin: 20
+ header: qsTr("Loading Snapshot")
+ description: qsTr("This might take a while...")
+ }
+
+ ProgressIndicator {
+ id: progressIndicator
+ Layout.topMargin: 20
+ width: 200
+ height: 20
+ progress: nodeModel.snapshotProgress
+ Layout.alignment: Qt.AlignCenter
+ progressColor: Theme.color.blue
+ }
+ }
+
+ ColumnLayout {
+ id: loadedSnapshotColumn
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredWidth: Math.min(parent.width, 450)
+
+ Image {
+ Layout.alignment: Qt.AlignCenter
+ source: "image://images/circle-green-check"
+
+ sourceSize.width: 60
+ sourceSize.height: 60
+ }
+
+ Header {
+ Layout.fillWidth: true
+ Layout.topMargin: 20
+ headerBold: true
+ header: qsTr("Snapshot Loaded")
+ descriptionBold: false
+ descriptionColor: Theme.color.neutral6
+ descriptionSize: 17
+ descriptionLineHeight: 1.1
+ description: snapshotInfo && snapshotInfo["date"] ?
+ qsTr("It contains unspent transactions up to %1. Next, transactions will be verified and newer transactions downloaded.").arg(snapshotInfo["date"]) :
+ qsTr("It contains transactions up to DEBUG. Newer transactions still need to be downloaded." +
+ " The data will be verified in the background.")
+ }
+
+ ContinueButton {
+ Layout.preferredWidth: Math.min(300, columnLayout.width - 2 * Layout.leftMargin)
+ Layout.topMargin: 40
+ Layout.alignment: Qt.AlignCenter
+ text: qsTr("Done")
+ onClicked: {
+ chainModel.isSnapshotActiveChanged()
+ back()
+ }
+ }
+
+ Setting {
+ id: viewDetails
+ Layout.alignment: Qt.AlignCenter
+ header: qsTr("View details")
+ actionItem: CaretRightIcon {
+ id: caretIcon
+ color: viewDetails.stateColor
+ rotation: viewDetails.expanded ? 90 : 0
+ Behavior on rotation { NumberAnimation { duration: 200 } }
+ }
+
+ property bool expanded: false
+
+ onClicked: {
+ expanded = !expanded
+ }
+ }
+
+ ColumnLayout {
+ id: detailsContent
+ visible: viewDetails.expanded
+ Layout.preferredWidth: Math.min(300, parent.width - 2 * Layout.leftMargin)
+ Layout.alignment: Qt.AlignCenter
+ Layout.leftMargin: 80
+ Layout.rightMargin: 80
+ Layout.topMargin: 10
+ spacing: 10
+ // TODO: make sure the block height number aligns right
+ RowLayout {
+ CoreText {
+ text: qsTr("Block Height:")
+ Layout.alignment: Qt.AlignLeft
+ font.pixelSize: 14
+ }
+ CoreText {
+ text: snapshotInfo && snapshotInfo["height"] ?
+ snapshotInfo["height"] : qsTr("DEBUG")
+ Layout.alignment: Qt.AlignRight
+ font.pixelSize: 14
+ }
+ }
+ Separator { Layout.fillWidth: true }
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: 5
+ CoreText {
+ text: qsTr("Hash:")
+ font.pixelSize: 14
+ }
+ CoreText {
+ text: snapshotInfo && snapshotInfo["hashSerializedFirstHalf"] ?
+ snapshotInfo["hashSerializedFirstHalf"] + "\n" + snapshotInfo["hashSerializedSecondHalf"] :
+ qsTr("DEBUG")
+ Layout.fillWidth: true
+ font.pixelSize: 14
+ textFormat: Text.PlainText
+ }
+ }
+ }
+ }
+
+ ColumnLayout {
+ id: snapshotErrorColumn
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredWidth: Math.min(parent.width, 450)
+
+ Image {
+ Layout.alignment: Qt.AlignCenter
+ source: "image://images/circle-red-cross"
+
+ sourceSize.width: 60
+ sourceSize.height: 60
+ }
+
+ Header {
+ Layout.fillWidth: true
+ Layout.topMargin: 20
+ headerBold: true
+ header: qsTr("Snapshot Could Not Be Verified")
+ descriptionBold: false
+ descriptionColor: Theme.color.neutral6
+ descriptionSize: 17
+ descriptionLineHeight: 1.1
+ description: qsTr("There was a problem with the transactions in the snapshot. Please try a different file.")
+ }
+
+ ContinueButton {
+ Layout.preferredWidth: Math.min(300, columnLayout.width - 2 * Layout.leftMargin)
+ Layout.topMargin: 40
+ Layout.alignment: Qt.AlignCenter
+ text: qsTr("OK")
+ onClicked: {
+ nodeModel.setSnapshotError(false)
+ back()
+ }
+ }
+ }
+ }
+}
diff --git a/qml/controls/GreenCheckIcon.qml b/qml/controls/GreenCheckIcon.qml
new file mode 100644
index 0000000000..02977857b2
--- /dev/null
+++ b/qml/controls/GreenCheckIcon.qml
@@ -0,0 +1,11 @@
+// Copyright (c) 2023 - present The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+Icon {
+ source: "image://images/green-check"
+ size: 30
+}
diff --git a/qml/controls/Header.qml b/qml/controls/Header.qml
index f3c4c0c3e3..ece49234d2 100644
--- a/qml/controls/Header.qml
+++ b/qml/controls/Header.qml
@@ -25,6 +25,7 @@ ColumnLayout {
property int subtextSize: 15
property color subtextColor: Theme.color.neutral9
property bool wrap: true
+ property real descriptionLineHeight: 1
spacing: 0
Loader {
@@ -60,6 +61,7 @@ ColumnLayout {
text: root.description
horizontalAlignment: root.center ? Text.AlignHCenter : Text.AlignLeft
wrapMode: wrap ? Text.WordWrap : Text.NoWrap
+ lineHeight: root.descriptionLineHeight
Behavior on color {
ColorAnimation { duration: 150 }
diff --git a/qml/controls/ProgressIndicator.qml b/qml/controls/ProgressIndicator.qml
index 117a4baebb..9d6d62d329 100644
--- a/qml/controls/ProgressIndicator.qml
+++ b/qml/controls/ProgressIndicator.qml
@@ -7,6 +7,7 @@ import QtQuick.Controls 2.15
Control {
property real progress: 0
+ property color progressColor: Theme.color.orange
Behavior on progress {
NumberAnimation {
easing.type: Easing.Bezier
@@ -26,7 +27,7 @@ Control {
width: contentItem.width
height: contentItem.height
radius: contentItem.radius
- color: Theme.color.orange
+ color: progressColor
}
}
}
diff --git a/qml/controls/Theme.qml b/qml/controls/Theme.qml
index f57e152cbd..3c7621c2b5 100644
--- a/qml/controls/Theme.qml
+++ b/qml/controls/Theme.qml
@@ -27,6 +27,7 @@ Control {
required property color blue
required property color amber
required property color purple
+ required property color transparent
required property color neutral0
required property color neutral1
required property color neutral2
@@ -59,6 +60,7 @@ Control {
blue: "#3CA3DE"
amber: "#C9B500"
purple: "#C075DC"
+ transparent: "#00000000"
neutral0: "#000000"
neutral1: "#1A1A1A"
neutral2: "#2D2D2D"
@@ -91,6 +93,7 @@ Control {
blue: "#2D9CDB"
amber: "#C9B500"
purple: "#BB6BD9"
+ transparent: "#00000000"
neutral0: "#FFFFFF"
neutral1: "#F8F8F8"
neutral2: "#F4F4F4"
diff --git a/qml/models/chainmodel.cpp b/qml/models/chainmodel.cpp
index ce3a6b12ae..4e56066898 100644
--- a/qml/models/chainmodel.cpp
+++ b/qml/models/chainmodel.cpp
@@ -9,6 +9,9 @@
#include
#include
#include
+#include
+#include
+#include
using interfaces::FoundBlock;
@@ -106,3 +109,39 @@ 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;
+
+ std::vector available_heights = Params().GetAvailableSnapshotHeights();
+ if (!available_heights.empty()) {
+ // Get the highest height snapshot (last element in the vector)
+ const int height = available_heights.back();
+ std::optional maybe_snapshot = Params().AssumeutxoForHeight(height);
+
+ if (maybe_snapshot.has_value()) {
+ const auto& latest_snapshot = maybe_snapshot.value();
+ const auto& hash_serialized = latest_snapshot.hash_serialized;
+
+ // Get block time using the interfaces pattern
+ uint256 block_hash{m_chain.getBlockHash(height)};
+ int64_t block_time;
+ m_chain.findBlock(block_hash, FoundBlock().time(block_time));
+
+ 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(block_time).toString("MMMM d yyyy");
+ }
+ }
+
+ return snapshot_info;
+}
diff --git a/qml/models/chainmodel.h b/qml/models/chainmodel.h
index 9318510eda..6a5124be7f 100644
--- a/qml/models/chainmodel.h
+++ b/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/qml/models/nodemodel.cpp b/qml/models/nodemodel.cpp
index be6fc4adba..f3cd870b48 100644
--- a/qml/models/nodemodel.cpp
+++ b/qml/models/nodemodel.cpp
@@ -14,14 +14,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 +85,14 @@ void NodeModel::setVerificationProgress(double new_progress)
if (new_progress != m_verification_progress) {
setRemainingSyncTime(new_progress);
+ if (new_progress >= 0.00014) {
+ setHeadersSynced(true);
+ }
+
+ if (new_progress >= 0.999) {
+ setIsIBDCompleted(true);
+ }
+
m_verification_progress = new_progress;
Q_EMIT verificationProgressChanged();
}
@@ -190,3 +203,89 @@ QString NodeModel::defaultProxyAddress()
{
return QString::fromStdString(std::string(DEFAULT_PROXY_HOST) + ":" + util::ToString(DEFAULT_PROXY_PORT));
}
+
+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]() {
+ const fs::path path_file_fs = fs::u8path(path_file.toStdString());
+ auto snapshot = m_node.loadSnapshot(path_file_fs);
+
+ bool result = snapshot ? snapshot->activate() : false;
+
+ if (!result) {
+ QMetaObject::invokeMethod(this, [this]() {
+ m_snapshot_loading = false;
+ Q_EMIT snapshotLoadingChanged();
+ m_snapshot_error = true;
+ Q_EMIT snapshotErrorChanged();
+ }, Qt::QueuedConnection);
+ } else {
+ QMetaObject::invokeMethod(this, [this, result]() {
+ m_snapshot_loaded = true;
+ Q_EMIT snapshotLoaded(result);
+ Q_EMIT snapshotLoadingChanged();
+ }, Qt::QueuedConnection);
+ }
+ });
+
+ 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();
+ checkAndLoadSnapshot();
+ }
+}
+
+void NodeModel::setIsIBDCompleted(bool new_completed) {
+ if (new_completed != m_is_ibd_completed) {
+ m_is_ibd_completed = 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/qml/models/nodemodel.h b/qml/models/nodemodel.h
index 993da098a5..00f45569db 100644
--- a/qml/models/nodemodel.h
+++ b/qml/models/nodemodel.h
@@ -37,6 +37,13 @@ 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)
+ 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);
@@ -55,6 +62,18 @@ 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);
+ 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(); }
@@ -62,6 +81,9 @@ class NodeModel : public QObject
Q_INVOKABLE void startNodeInitializionThread();
Q_INVOKABLE void requestShutdown();
+ Q_INVOKABLE void snapshotLoadThread(QString path_file);
+ Q_INVOKABLE void checkAndLoadSnapshot();
+
void startShutdownPolling();
void stopShutdownPolling();
@@ -83,6 +105,15 @@ 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();
+ void snapshotFilePathChanged();
+ void snapshotErrorChanged();
protected:
void timerEvent(QTimerEvent* event) override;
@@ -96,17 +127,25 @@ 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};
+ QString m_snapshot_file_path;
+ bool m_snapshot_error{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/qml/pages/settings/SettingsConnection.qml b/qml/pages/settings/SettingsConnection.qml
index d180fa2ff2..726a62c7a1 100644
--- a/qml/pages/settings/SettingsConnection.qml
+++ b/qml/pages/settings/SettingsConnection.qml
@@ -12,6 +12,7 @@ Page {
id: root
signal back
property bool onboarding: false
+ property bool snapshotImportCompleted: onboarding ? false : chainModel.isSnapshotActive
background: null
PageStack {
id: stack
@@ -31,6 +32,9 @@ Page {
detailActive: true
detailItem: ConnectionSettings {
onNext: stack.push(proxySettings)
+ onGotoSnapshot: stack.push(loadSnapshotSettings)
+ snapshotImportCompleted: root.snapshotImportCompleted
+ onboarding: root.onboarding
}
states: [
@@ -87,5 +91,13 @@ Page {
onBack: stack.pop()
}
}
+ Component {
+ id: loadSnapshotSettings
+ SettingsSnapshotLoad {
+ onboarding: root.onboarding
+ snapshotImportCompleted: root.snapshotImportCompleted
+ onBack: stack.pop()
+ }
+ }
}
}
diff --git a/qml/pages/settings/SettingsSnapshotLoad.qml b/qml/pages/settings/SettingsSnapshotLoad.qml
new file mode 100644
index 0000000000..cd308ede94
--- /dev/null
+++ b/qml/pages/settings/SettingsSnapshotLoad.qml
@@ -0,0 +1,38 @@
+// Copyright (c) 2024-present The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+import "../../controls"
+import "../../components"
+
+Page {
+ signal back
+ property bool onboarding: false
+ property bool snapshotImportCompleted: onboarding ? false : chainModel.isSnapshotActive
+
+ id: root
+
+ background: null
+ implicitWidth: 450
+ leftPadding: 20
+ rightPadding: 20
+ topPadding: 30
+
+ header: NavigationBar2 {
+ leftItem: NavButton {
+ iconSource: "image://images/caret-left"
+ text: qsTr("Back")
+ onClicked: root.back()
+ }
+ }
+ SnapshotLoadSettings {
+ width: Math.min(parent.width, 450)
+ anchors.horizontalCenter: parent.horizontalCenter
+ onboarding: root.onboarding
+ snapshotImportCompleted: root.snapshotImportCompleted
+ onBack: root.back()
+ }
+}
diff --git a/qml/res/icons/circle-file.png b/qml/res/icons/circle-file.png
new file mode 100644
index 0000000000..14a776e6d5
Binary files /dev/null and b/qml/res/icons/circle-file.png differ
diff --git a/qml/res/icons/circle-green-check.png b/qml/res/icons/circle-green-check.png
new file mode 100644
index 0000000000..25bb20e00f
Binary files /dev/null and b/qml/res/icons/circle-green-check.png differ
diff --git a/qml/res/icons/circle-red-cross.png b/qml/res/icons/circle-red-cross.png
new file mode 100644
index 0000000000..9a7aad3a46
Binary files /dev/null and b/qml/res/icons/circle-red-cross.png differ
diff --git a/qml/res/icons/green-check.png b/qml/res/icons/green-check.png
new file mode 100644
index 0000000000..acce5e1a21
Binary files /dev/null and b/qml/res/icons/green-check.png differ
diff --git a/qml/res/src/circle-file.svg b/qml/res/src/circle-file.svg
new file mode 100644
index 0000000000..d8af3949d8
--- /dev/null
+++ b/qml/res/src/circle-file.svg
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/qml/res/src/circle-green-check.svg b/qml/res/src/circle-green-check.svg
new file mode 100644
index 0000000000..d56c175fd4
--- /dev/null
+++ b/qml/res/src/circle-green-check.svg
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/qml/res/src/green-check.svg b/qml/res/src/green-check.svg
new file mode 100644
index 0000000000..95f7c80ead
--- /dev/null
+++ b/qml/res/src/green-check.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file