Skip to content

Commit fff3782

Browse files
committed
qml: Implement snapshot loading functionality
- Added snapshot loading capabilities to NodeModel, including progress tracking and error handling. - Introduced SnapshotQml class for processing snapshot files. - Enhanced ChainModel with methods to retrieve snapshot information and check if a snapshot is active. - Updated QML properties to reflect snapshot loading state and progress. - Included necessary includes for snapshot handling in relevant files.
1 parent 98f206e commit fff3782

File tree

8 files changed

+253
-4
lines changed

8 files changed

+253
-4
lines changed

bitcoin

qml/bitcoin.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <qml/models/peerdetailsmodel.h>
3737
#include <qml/models/peerlistsortproxy.h>
3838
#include <qml/models/sendrecipient.h>
39+
#include <qml/models/snapshotqml.h>
3940
#include <qml/models/walletlistmodel.h>
4041
#include <qml/models/walletqmlmodel.h>
4142
#include <qml/models/walletqmlmodeltransaction.h>

qml/models/chainmodel.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
#include <QThread>
1010
#include <QTime>
1111
#include <interfaces/chain.h>
12+
#include <node/utxo_snapshot.h>
13+
#include <kernel/chainparams.h>
14+
#include <validation.h>
1215

1316
using interfaces::FoundBlock;
1417

@@ -106,3 +109,39 @@ void ChainModel::setCurrentTimeRatio()
106109

107110
Q_EMIT timeRatioListChanged();
108111
}
112+
113+
// TODO: Change this once a better solution has been found.
114+
// Using hardcoded snapshot info to display in SnapshotSettings.qml
115+
QVariantMap ChainModel::getSnapshotInfo() {
116+
QVariantMap snapshot_info;
117+
118+
std::vector<int> available_heights = Params().GetAvailableSnapshotHeights();
119+
if (!available_heights.empty()) {
120+
// Get the highest height snapshot (last element in the vector)
121+
const int height = available_heights.back();
122+
std::optional<AssumeutxoData> maybe_snapshot = Params().AssumeutxoForHeight(height);
123+
124+
if (maybe_snapshot.has_value()) {
125+
const auto& latest_snapshot = maybe_snapshot.value();
126+
const auto& hash_serialized = latest_snapshot.hash_serialized;
127+
128+
// Get block time using the interfaces pattern
129+
uint256 block_hash{m_chain.getBlockHash(height)};
130+
int64_t block_time;
131+
m_chain.findBlock(block_hash, FoundBlock().time(block_time));
132+
133+
QString fullHash = QString::fromStdString(hash_serialized.ToString());
134+
135+
int midPoint = fullHash.length() / 2;
136+
QString firstHalf = fullHash.left(midPoint);
137+
QString secondHalf = fullHash.mid(midPoint);
138+
139+
snapshot_info["height"] = height;
140+
snapshot_info["hashSerializedFirstHalf"] = firstHalf;
141+
snapshot_info["hashSerializedSecondHalf"] = secondHalf;
142+
snapshot_info["date"] = QDateTime::fromSecsSinceEpoch(block_time).toString("MMMM d yyyy");
143+
}
144+
}
145+
146+
return snapshot_info;
147+
}

qml/models/chainmodel.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class ChainModel : public QObject
2727
Q_PROPERTY(quint64 assumedBlockchainSize READ assumedBlockchainSize CONSTANT)
2828
Q_PROPERTY(quint64 assumedChainstateSize READ assumedChainstateSize CONSTANT)
2929
Q_PROPERTY(QVariantList timeRatioList READ timeRatioList NOTIFY timeRatioListChanged)
30+
Q_PROPERTY(bool isSnapshotActive READ isSnapshotActive NOTIFY isSnapshotActiveChanged)
3031

3132
public:
3233
explicit ChainModel(interfaces::Chain& chain);
@@ -36,18 +37,21 @@ class ChainModel : public QObject
3637
quint64 assumedBlockchainSize() const { return m_assumed_blockchain_size; };
3738
quint64 assumedChainstateSize() const { return m_assumed_chainstate_size; };
3839
QVariantList timeRatioList() const { return m_time_ratio_list; };
39-
40+
bool isSnapshotActive() const { return m_chain.hasAssumedValidChain(); };
4041
int timestampAtMeridian();
4142

4243
void setCurrentTimeRatio();
4344

45+
Q_INVOKABLE QVariantMap getSnapshotInfo();
46+
4447
public Q_SLOTS:
4548
void setTimeRatioList(int new_time);
4649
void setTimeRatioListInitial();
4750

4851
Q_SIGNALS:
4952
void timeRatioListChanged();
5053
void currentNetworkNameChanged();
54+
void isSnapshotActiveChanged();
5155

5256
private:
5357
QString m_current_network_name;

qml/models/nodemodel.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include <qml/models/nodemodel.h>
6+
#include <qml/models/snapshotqml.h>
67

78
#include <interfaces/node.h>
89
#include <net.h>
@@ -14,14 +15,19 @@
1415

1516
#include <QDateTime>
1617
#include <QMetaObject>
18+
#include <QObject>
1719
#include <QTimerEvent>
1820
#include <QString>
21+
#include <QUrl>
22+
#include <QThread>
23+
#include <QDebug>
1924

2025
NodeModel::NodeModel(interfaces::Node& node)
2126
: m_node{node}
2227
{
2328
ConnectToBlockTipSignal();
2429
ConnectToNumConnectionsChangedSignal();
30+
ConnectToSnapshotLoadProgressSignal();
2531
}
2632

2733
void NodeModel::setBlockTipHeight(int new_height)
@@ -80,6 +86,14 @@ void NodeModel::setVerificationProgress(double new_progress)
8086
if (new_progress != m_verification_progress) {
8187
setRemainingSyncTime(new_progress);
8288

89+
if (new_progress >= 0.00014) {
90+
setHeadersSynced(true);
91+
}
92+
93+
if (new_progress >= 0.999) {
94+
setIsIBDCompleted(true);
95+
}
96+
8397
m_verification_progress = new_progress;
8498
Q_EMIT verificationProgressChanged();
8599
}
@@ -190,3 +204,82 @@ QString NodeModel::defaultProxyAddress()
190204
{
191205
return QString::fromStdString(std::string(DEFAULT_PROXY_HOST) + ":" + util::ToString(DEFAULT_PROXY_PORT));
192206
}
207+
208+
void NodeModel::ConnectToSnapshotLoadProgressSignal()
209+
{
210+
assert(!m_handler_snapshot_load_progress);
211+
212+
m_handler_snapshot_load_progress = m_node.handleSnapshotLoadProgress(
213+
[this](double progress) {
214+
setSnapshotProgress(progress);
215+
});
216+
}
217+
218+
void NodeModel::snapshotLoadThread(QString path_file) {
219+
m_snapshot_loading = true;
220+
Q_EMIT snapshotLoadingChanged();
221+
222+
path_file = QUrl(path_file).toLocalFile();
223+
224+
QThread* snapshot_thread = QThread::create([this, path_file]() {
225+
SnapshotQml loader(m_node, path_file);
226+
bool result = loader.processPath();
227+
if (!result) {
228+
m_snapshot_loading = false;
229+
Q_EMIT snapshotLoadingChanged();
230+
m_snapshot_error = true;
231+
Q_EMIT snapshotErrorChanged();
232+
} else {
233+
m_snapshot_loaded = true;
234+
Q_EMIT snapshotLoaded(result);
235+
Q_EMIT snapshotLoadingChanged();
236+
}
237+
});
238+
239+
connect(snapshot_thread, &QThread::finished, snapshot_thread, &QThread::deleteLater);
240+
241+
snapshot_thread->start();
242+
}
243+
244+
void NodeModel::setSnapshotProgress(double new_progress) {
245+
if (new_progress != m_snapshot_progress) {
246+
m_snapshot_progress = new_progress;
247+
Q_EMIT snapshotProgressChanged();
248+
}
249+
}
250+
251+
void NodeModel::setHeadersSynced(bool new_synced) {
252+
if (new_synced != m_headers_synced) {
253+
m_headers_synced = new_synced;
254+
Q_EMIT headersSyncedChanged();
255+
checkAndLoadSnapshot();
256+
}
257+
}
258+
259+
void NodeModel::setIsIBDCompleted(bool new_completed) {
260+
if (new_completed != m_is_ibd_completed) {
261+
m_is_ibd_completed = new_completed;
262+
Q_EMIT isIBDCompletedChanged();
263+
}
264+
}
265+
266+
void NodeModel::setSnapshotFilePath(const QString& new_path) {
267+
if (new_path != m_snapshot_file_path) {
268+
m_snapshot_file_path = new_path;
269+
Q_EMIT snapshotFilePathChanged();
270+
}
271+
}
272+
273+
void NodeModel::checkAndLoadSnapshot()
274+
{
275+
if (m_headers_synced && !m_snapshot_file_path.isEmpty()) {
276+
snapshotLoadThread(m_snapshot_file_path);
277+
}
278+
}
279+
280+
void NodeModel::setSnapshotError(bool new_error) {
281+
if (new_error != m_snapshot_error) {
282+
m_snapshot_error = new_error;
283+
Q_EMIT snapshotErrorChanged();
284+
}
285+
}

qml/models/nodemodel.h

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ class NodeModel : public QObject
3737
Q_PROPERTY(double verificationProgress READ verificationProgress NOTIFY verificationProgressChanged)
3838
Q_PROPERTY(bool pause READ pause WRITE setPause NOTIFY pauseChanged)
3939
Q_PROPERTY(bool faulted READ errorState WRITE setErrorState NOTIFY errorStateChanged)
40+
Q_PROPERTY(double snapshotProgress READ snapshotProgress WRITE setSnapshotProgress NOTIFY snapshotProgressChanged)
41+
Q_PROPERTY(bool snapshotLoading READ snapshotLoading NOTIFY snapshotLoadingChanged)
42+
Q_PROPERTY(bool isSnapshotLoaded READ isSnapshotLoaded NOTIFY snapshotLoaded)
43+
Q_PROPERTY(bool headersSynced READ headersSynced WRITE setHeadersSynced NOTIFY headersSyncedChanged)
44+
Q_PROPERTY(bool isIBDCompleted READ isIBDCompleted WRITE setIsIBDCompleted NOTIFY isIBDCompletedChanged)
45+
Q_PROPERTY(QString snapshotFilePath READ snapshotFilePath WRITE setSnapshotFilePath NOTIFY snapshotFilePathChanged)
46+
Q_PROPERTY(bool snapshotError READ snapshotError WRITE setSnapshotError NOTIFY snapshotErrorChanged)
4047

4148
public:
4249
explicit NodeModel(interfaces::Node& node);
@@ -55,13 +62,28 @@ class NodeModel : public QObject
5562
void setPause(bool new_pause);
5663
bool errorState() const { return m_faulted; }
5764
void setErrorState(bool new_error);
65+
bool isSnapshotLoaded() const { return m_snapshot_loaded; }
66+
double snapshotProgress() const { return m_snapshot_progress; }
67+
void setSnapshotProgress(double new_progress);
68+
bool snapshotLoading() const { return m_snapshot_loading; }
69+
bool headersSynced() const { return m_headers_synced; }
70+
void setHeadersSynced(bool new_synced);
71+
bool isIBDCompleted() const { return m_is_ibd_completed; }
72+
void setIsIBDCompleted(bool new_completed);
73+
QString snapshotFilePath() const { return m_snapshot_file_path; }
74+
Q_INVOKABLE void setSnapshotFilePath(const QString& new_path);
75+
bool snapshotError() const { return m_snapshot_error; }
76+
Q_INVOKABLE void setSnapshotError(bool new_error);
5877

5978
Q_INVOKABLE float getTotalBytesReceived() const { return (float)m_node.getTotalBytesRecv(); }
6079
Q_INVOKABLE float getTotalBytesSent() const { return (float)m_node.getTotalBytesSent(); }
6180

6281
Q_INVOKABLE void startNodeInitializionThread();
6382
Q_INVOKABLE void requestShutdown();
6483

84+
Q_INVOKABLE void snapshotLoadThread(QString path_file);
85+
Q_INVOKABLE void checkAndLoadSnapshot();
86+
6587
void startShutdownPolling();
6688
void stopShutdownPolling();
6789

@@ -83,6 +105,15 @@ public Q_SLOTS:
83105

84106
void setTimeRatioList(int new_time);
85107
void setTimeRatioListInitial();
108+
void initializationFinished();
109+
void snapshotLoaded(bool result);
110+
void snapshotProgressChanged();
111+
void snapshotLoadingChanged();
112+
void showProgress(const QString& title, int progress);
113+
void headersSyncedChanged();
114+
void isIBDCompletedChanged();
115+
void snapshotFilePathChanged();
116+
void snapshotErrorChanged();
86117

87118
protected:
88119
void timerEvent(QTimerEvent* event) override;
@@ -96,17 +127,25 @@ public Q_SLOTS:
96127
double m_verification_progress{0.0};
97128
bool m_pause{false};
98129
bool m_faulted{false};
99-
130+
double m_snapshot_progress{0.0};
100131
int m_shutdown_polling_timer_id{0};
132+
int m_snapshot_timer_id{0};
133+
bool m_snapshot_loading{false};
134+
bool m_snapshot_loaded{false};
135+
bool m_headers_synced{false};
136+
bool m_is_ibd_completed{false};
137+
QString m_snapshot_file_path;
138+
bool m_snapshot_error{false};
101139

102140
QVector<QPair<int, double>> m_block_process_time;
103141

104142
interfaces::Node& m_node;
105143
std::unique_ptr<interfaces::Handler> m_handler_notify_block_tip;
106144
std::unique_ptr<interfaces::Handler> m_handler_notify_num_peers_changed;
107-
145+
std::unique_ptr<interfaces::Handler> m_handler_snapshot_load_progress;
108146
void ConnectToBlockTipSignal();
109147
void ConnectToNumConnectionsChangedSignal();
148+
void ConnectToSnapshotLoadProgressSignal();
110149
};
111150

112151
#endif // BITCOIN_QML_MODELS_NODEMODEL_H

qml/models/snapshotqml.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2024 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <qml/models/snapshotqml.h>
6+
7+
#include <interfaces/node.h>
8+
#include <node/context.h>
9+
#include <node/utxo_snapshot.h>
10+
#include <clientversion.h>
11+
#include <validation.h>
12+
#include <util/fs.h>
13+
#include <util/fs_helpers.h>
14+
#include <streams.h>
15+
#include <QString>
16+
#include <QObject>
17+
18+
SnapshotQml::SnapshotQml(interfaces::Node& node, QString path)
19+
: m_node(node), m_path(path) {}
20+
21+
bool SnapshotQml::processPath()
22+
{
23+
ChainstateManager& chainman = *m_node.context()->chainman;
24+
const fs::path path = fs::u8path(m_path.toStdString());
25+
if (!fs::exists(path)) {
26+
return false;
27+
}
28+
29+
FILE* snapshot_file{fsbridge::fopen(path, "rb")};
30+
AutoFile afile{snapshot_file};
31+
if (afile.IsNull()) {
32+
return false;
33+
}
34+
35+
node::SnapshotMetadata metadata{chainman.GetParams().MessageStart()};
36+
try {
37+
afile >> metadata;
38+
} catch (const std::exception& e) {
39+
return false;
40+
}
41+
42+
bool result = m_node.loadSnapshot(afile, metadata, false);
43+
if (!result) {
44+
return false;
45+
}
46+
return true;
47+
}

qml/models/snapshotqml.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) 2024 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_QML_MODELS_SNAPSHOTQML_H
6+
#define BITCOIN_QML_MODELS_SNAPSHOTQML_H
7+
8+
#include <interfaces/handler.h>
9+
#include <interfaces/node.h>
10+
11+
#include <QObject>
12+
13+
class SnapshotQml : public QObject
14+
{
15+
Q_OBJECT
16+
public:
17+
SnapshotQml(interfaces::Node& node, QString path);
18+
19+
bool processPath();
20+
21+
private:
22+
interfaces::Node& m_node;
23+
QString m_path;
24+
};
25+
26+
#endif // BITCOIN_QML_MODELS_SNAPSHOTQML_H

0 commit comments

Comments
 (0)