Skip to content

Commit 90dc0dd

Browse files
committed
wip: Add minimal support for loading signet UTXO snapshots
Adds minimal wiring to connect QML GUI to loading a signet UTXO snapshot via the connection settings. Modifies src/interfaces/node.h, src/node/interfaces.cpp and chainparams.cpp (temporarily for signet snapshot testing) to implement snapshot loading functionality through the node model. Current limitations: - Not integrated with onboarding process - Requires manual navigation to connection settings after initial startup - Snapshot verification progress is not working yet Testing: 1. Start the node 2. Complete onboarding 3. Navigate to connection settings 4. Load snapshot from provided interface
1 parent 1d028d7 commit 90dc0dd

File tree

8 files changed

+175
-8
lines changed

8 files changed

+175
-8
lines changed

src/interfaces/node.h

+3
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ class Node
199199
//! List rpc commands.
200200
virtual std::vector<std::string> listRpcCommands() = 0;
201201

202+
//! Load UTXO Snapshot.
203+
virtual bool snapshotLoad(const std::string& path_string) = 0;
204+
202205
//! Set RPC timer interface if unset.
203206
virtual void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) = 0;
204207

src/kernel/chainparams.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,13 @@ class SigNetParams : public CChainParams {
371371

372372
vFixedSeeds.clear();
373373

374+
m_assumeutxo_data = MapAssumeutxo{
375+
{
376+
160000,
377+
{AssumeutxoHash{uint256S("0x5225141cb62dee63ab3be95f9b03d60801f264010b1816d4bd00618b2736e7be")}, 1278002},
378+
},
379+
};
380+
374381
base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,111);
375382
base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,196);
376383
base58Prefixes[SECRET_KEY] = std::vector<unsigned char>(1,239);

src/node/interfaces.cpp

+60
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <node/context.h>
3030
#include <node/interface_ui.h>
3131
#include <node/transaction.h>
32+
#include <node/utxo_snapshot.h>
3233
#include <policy/feerate.h>
3334
#include <policy/fees.h>
3435
#include <policy/policy.h>
@@ -395,6 +396,65 @@ class NodeImpl : public Node
395396
{
396397
m_context = context;
397398
}
399+
bool snapshotLoad(const std::string& path_string) override
400+
{
401+
const fs::path path = fs::u8path(path_string);
402+
if (!fs::exists(path)) {
403+
LogPrintf("[loadsnapshot] Snapshot file %s does not exist\n", path.u8string());
404+
return false;
405+
}
406+
407+
AutoFile afile{fsbridge::fopen(path, "rb")};
408+
if (afile.IsNull()) {
409+
LogPrintf("[loadsnapshot] Failed to open snapshot file %s\n", path.u8string());
410+
return false;
411+
}
412+
413+
SnapshotMetadata metadata;
414+
try {
415+
afile >> metadata;
416+
} catch (const std::exception& e) {
417+
LogPrintf("[loadsnapshot] Failed to read snapshot metadata: %s\n", e.what());
418+
return false;
419+
}
420+
421+
const uint256& base_blockhash = metadata.m_base_blockhash;
422+
LogPrintf("[loadsnapshot] Waiting for blockheader %s in headers chain before snapshot activation\n",
423+
base_blockhash.ToString());
424+
425+
if (!m_context->chainman) {
426+
LogPrintf("[loadsnapshot] Chainman is null\n");
427+
return false;
428+
}
429+
430+
ChainstateManager& chainman = *m_context->chainman;
431+
CBlockIndex* snapshot_start_block = nullptr;
432+
433+
// Wait for the block to appear in the block index
434+
constexpr int max_wait_seconds = 600; // 10 minutes
435+
for (int i = 0; i < max_wait_seconds; ++i) {
436+
snapshot_start_block = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(base_blockhash));
437+
if (snapshot_start_block) break;
438+
std::this_thread::sleep_for(std::chrono::seconds(1));
439+
}
440+
441+
if (!snapshot_start_block) {
442+
LogPrintf("[loadsnapshot] Timed out waiting for snapshot start blockheader %s\n", base_blockhash.ToString());
443+
return false;
444+
}
445+
446+
// Activate the snapshot
447+
if (!chainman.ActivateSnapshot(afile, metadata, false)) {
448+
LogPrintf("[loadsnapshot] Unable to load UTXO snapshot %s\n", path.u8string());
449+
return false;
450+
}
451+
452+
CBlockIndex* new_tip = WITH_LOCK(::cs_main, return chainman.ActiveTip());
453+
LogPrintf("[loadsnapshot] Loaded %d coins from snapshot %s at height %d\n",
454+
metadata.m_coins_count, new_tip->GetBlockHash().ToString(), new_tip->nHeight);
455+
456+
return true;
457+
}
398458
ArgsManager& args() { return *Assert(Assert(m_context)->args); }
399459
ChainstateManager& chainman() { return *Assert(m_context->chainman); }
400460
NodeContext* m_context{nullptr};

src/qml/components/ConnectionSettings.qml

+11-1
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,19 @@ ColumnLayout {
1111
id: root
1212
signal next
1313
property bool snapshotImported: false
14+
property bool onboarding: false
15+
16+
Component.onCompleted: {
17+
snapshotImported = false
18+
}
19+
1420
function setSnapshotImported(imported) {
1521
snapshotImported = imported
1622
}
1723
spacing: 4
1824
Setting {
1925
id: gotoSnapshot
26+
visible: !root.onboarding
2027
Layout.fillWidth: true
2128
header: qsTr("Load snapshot")
2229
description: qsTr("Instant use with background sync")
@@ -39,7 +46,10 @@ ColumnLayout {
3946
connectionSwipe.incrementCurrentIndex()
4047
}
4148
}
42-
Separator { Layout.fillWidth: true }
49+
Separator {
50+
visible: !root.onboarding
51+
Layout.fillWidth: true
52+
}
4353
Setting {
4454
Layout.fillWidth: true
4555
header: qsTr("Enable listening")

src/qml/components/SnapshotSettings.qml

+49-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import QtQuick 2.15
66
import QtQuick.Controls 2.15
77
import QtQuick.Layouts 1.15
8+
import QtQuick.Dialogs 1.3
89

910
import "../controls"
1011

@@ -18,7 +19,7 @@ ColumnLayout {
1819
width: Math.min(parent.width, 450)
1920
anchors.horizontalCenter: parent.horizontalCenter
2021

21-
22+
// TODO: Remove this once the verification progress is available
2223
Timer {
2324
id: snapshotSimulationTimer
2425
interval: 50 // Update every 50ms
@@ -29,7 +30,7 @@ ColumnLayout {
2930
snapshotVerificationProgress += 0.01
3031
} else {
3132
snapshotVerificationCycles++
32-
if (snapshotVerificationCycles < 1) {
33+
if (snapshotVerificationCycles < 3) {
3334
snapshotVerificationProgress = 0
3435
} else {
3536
running = false
@@ -42,7 +43,7 @@ ColumnLayout {
4243

4344
StackLayout {
4445
id: settingsStack
45-
currentIndex: 0
46+
currentIndex: snapshotVerified ? 2 : 0
4647

4748
ColumnLayout {
4849
Layout.alignment: Qt.AlignHCenter
@@ -78,8 +79,24 @@ ColumnLayout {
7879
Layout.alignment: Qt.AlignCenter
7980
text: qsTr("Choose snapshot file")
8081
onClicked: {
81-
settingsStack.currentIndex = 1
82-
snapshotSimulationTimer.start()
82+
fileDialog.open()
83+
}
84+
}
85+
86+
FileDialog {
87+
id: fileDialog
88+
folder: shortcuts.home
89+
selectMultiple: false
90+
onAccepted: {
91+
console.log("File chosen:", fileDialog.fileUrls)
92+
var snapshotFileName = fileDialog.fileUrl.toString()
93+
console.log("Snapshot file name:", snapshotFileName)
94+
if (snapshotFileName.endsWith(".dat")) {
95+
// optionsModel.setSnapshotDirectory(snapshotFileName)
96+
// console.log("Snapshot directory set:", optionsModel.getSnapshotDirectory())
97+
nodeModel.initializeSnapshot(true, snapshotFileName)
98+
settingsStack.currentIndex = 1
99+
}
83100
}
84101
}
85102
}
@@ -109,10 +126,29 @@ ColumnLayout {
109126
Layout.topMargin: 20
110127
width: 200
111128
height: 20
112-
progress: snapshotVerificationProgress
129+
// TODO: uncomment this once the verification progress is available
130+
// progress: nodeModel.verificationProgress
131+
progress: 0
113132
Layout.alignment: Qt.AlignCenter
114133
progressColor: Theme.color.blue
115134
}
135+
136+
Connections {
137+
target: nodeModel
138+
// TODO: uncomment this once the verification progress is available
139+
// function onVerificationProgressChanged() {
140+
// progressIndicator.progress = nodeModel.verificationProgress
141+
// }
142+
function onSnapshotLoaded(success) {
143+
if (success) {
144+
progressIndicator.progress = 1
145+
settingsStack.currentIndex = 2 // Move to the "Snapshot Loaded" page
146+
} else {
147+
// Handle snapshot loading failure
148+
console.error("Snapshot loading failed")
149+
}
150+
}
151+
}
116152
}
117153

118154
ColumnLayout {
@@ -137,6 +173,7 @@ ColumnLayout {
137173
descriptionColor: Theme.color.neutral6
138174
descriptionSize: 17
139175
descriptionLineHeight: 1.1
176+
// TODO: Update this description once the snapshot is verified
140177
description: qsTr("It contains transactions up to January 12, 2024. Newer transactions still need to be downloaded." +
141178
" The data will be verified in the background.")
142179
}
@@ -153,6 +190,9 @@ ColumnLayout {
153190
}
154191
}
155192

193+
// TODO: Update this with the actual snapshot details
194+
// TODO: uncomment this once the snapshot details are available
195+
/*
156196
Setting {
157197
id: viewDetails
158198
Layout.alignment: Qt.AlignCenter
@@ -188,17 +228,20 @@ ColumnLayout {
188228
font.pixelSize: 14
189229
}
190230
CoreText {
231+
// TODO: Update this with the actual block height
191232
text: qsTr("200,000")
192233
Layout.alignment: Qt.AlignRight
193234
font.pixelSize: 14
194235
}
195236
}
196237
Separator { Layout.fillWidth: true }
197238
CoreText {
239+
// TODO: Update this with the actual snapshot file hash
198240
text: qsTr("Hash: 0x1234567890abcdef...")
199241
font.pixelSize: 14
200242
}
201243
}
244+
*/
202245
}
203246
}
204247
}

src/qml/models/nodemodel.cpp

+33
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <interfaces/node.h>
88
#include <net.h>
99
#include <node/interface_ui.h>
10+
#include <node/utxo_snapshot.h>
1011
#include <validation.h>
1112

1213
#include <cassert>
@@ -16,6 +17,8 @@
1617
#include <QMetaObject>
1718
#include <QTimerEvent>
1819
#include <QString>
20+
#include <QUrl>
21+
#include <QThread>
1922

2023
NodeModel::NodeModel(interfaces::Node& node)
2124
: m_node{node}
@@ -166,3 +169,33 @@ void NodeModel::ConnectToNumConnectionsChangedSignal()
166169
setNumOutboundPeers(new_num_peers.outbound_full_relay + new_num_peers.block_relay);
167170
});
168171
}
172+
173+
// Loads a snapshot from a given path using FileDialog
174+
void NodeModel::initializeSnapshot(bool initLoadSnapshot, QString path_file) {
175+
if (initLoadSnapshot) {
176+
// TODO: this is to deal with FileDialog returning a QUrl
177+
path_file = QUrl(path_file).toLocalFile();
178+
// TODO: Remove this before release
179+
// qDebug() << "path_file: " << path_file;
180+
QThread* snapshot_thread = new QThread();
181+
182+
// Capture path_file by value
183+
auto lambda = [this, path_file]() {
184+
bool result = this->snapshotLoad(path_file);
185+
Q_EMIT snapshotLoaded(result);
186+
};
187+
188+
connect(snapshot_thread, &QThread::started, lambda);
189+
connect(snapshot_thread, &QThread::finished, snapshot_thread, &QThread::deleteLater);
190+
191+
snapshot_thread->start();
192+
}
193+
}
194+
195+
void NodeModel::setSnapshotProgress(double new_snapshot_progress)
196+
{
197+
if (new_snapshot_progress != m_snapshot_progress) {
198+
m_snapshot_progress = new_snapshot_progress;
199+
Q_EMIT snapshotProgressChanged();
200+
}
201+
}

src/qml/models/nodemodel.h

+11-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class NodeModel : public QObject
3434
Q_PROPERTY(double verificationProgress READ verificationProgress NOTIFY verificationProgressChanged)
3535
Q_PROPERTY(bool pause READ pause WRITE setPause NOTIFY pauseChanged)
3636
Q_PROPERTY(bool faulted READ errorState WRITE setErrorState NOTIFY errorStateChanged)
37+
Q_PROPERTY(double snapshotProgress READ snapshotProgress NOTIFY snapshotProgressChanged)
3738

3839
public:
3940
explicit NodeModel(interfaces::Node& node);
@@ -52,13 +53,19 @@ class NodeModel : public QObject
5253
void setPause(bool new_pause);
5354
bool errorState() const { return m_faulted; }
5455
void setErrorState(bool new_error);
56+
double snapshotProgress() const { return m_snapshot_progress; }
57+
void setSnapshotProgress(double new_snapshot_progress);
58+
bool isSnapshotLoaded() const;
5559

5660
Q_INVOKABLE float getTotalBytesReceived() const { return (float)m_node.getTotalBytesRecv(); }
5761
Q_INVOKABLE float getTotalBytesSent() const { return (float)m_node.getTotalBytesSent(); }
5862

5963
Q_INVOKABLE void startNodeInitializionThread();
6064
Q_INVOKABLE void requestShutdown();
6165

66+
Q_INVOKABLE void initializeSnapshot(bool initLoadSnapshot, QString path_file);
67+
Q_INVOKABLE bool snapshotLoad(QString path_file) const { return m_node.snapshotLoad(path_file.toStdString()); }
68+
6269
void startShutdownPolling();
6370
void stopShutdownPolling();
6471

@@ -77,6 +84,9 @@ public Q_SLOTS:
7784

7885
void setTimeRatioList(int new_time);
7986
void setTimeRatioListInitial();
87+
void initializationFinished();
88+
void snapshotLoaded(bool result);
89+
void snapshotProgressChanged();
8090

8191
protected:
8292
void timerEvent(QTimerEvent* event) override;
@@ -90,7 +100,7 @@ public Q_SLOTS:
90100
double m_verification_progress{0.0};
91101
bool m_pause{false};
92102
bool m_faulted{false};
93-
103+
double m_snapshot_progress{0.0};
94104
int m_shutdown_polling_timer_id{0};
95105

96106
QVector<QPair<int, double>> m_block_process_time;

src/qml/pages/settings/SettingsConnection.qml

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Item {
3535
detailActive: true
3636
detailItem: ConnectionSettings {
3737
onNext: connectionSwipe.incrementCurrentIndex()
38+
onboarding: root.onboarding
3839
}
3940

4041
states: [

0 commit comments

Comments
 (0)