Skip to content

Commit 0eba513

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 c97d2a1 commit 0eba513

File tree

11 files changed

+239
-28
lines changed

11 files changed

+239
-28
lines changed

Diff for: 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

Diff for: 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);

Diff for: src/node/interfaces.cpp

+61-1
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};
@@ -510,7 +570,7 @@ class RpcHandlerImpl : public Handler
510570
class ChainImpl : public Chain
511571
{
512572
public:
513-
explicit ChainImpl(NodeContext& node) : m_node(node) {}
573+
explicit ChainImpl(node::NodeContext& node) : m_node(node) {}
514574
std::optional<int> getHeight() override
515575
{
516576
const int height{WITH_LOCK(::cs_main, return chainman().ActiveChain().Height())};

Diff for: src/qml/components/ConnectionSettings.qml

+16-2
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,24 @@ import "../controls"
1010
ColumnLayout {
1111
id: root
1212
signal next
13-
property bool snapshotImported: false
13+
property bool snapshotImported: onboarding ? false : chainModel.isSnapshotActive
14+
property bool onboarding: false
15+
16+
Component.onCompleted: {
17+
if (!onboarding) {
18+
snapshotImported = chainModel.isSnapshotActive
19+
} else {
20+
snapshotImported = false
21+
}
22+
}
23+
1424
function setSnapshotImported(imported) {
1525
snapshotImported = imported
1626
}
1727
spacing: 4
1828
Setting {
1929
id: gotoSnapshot
30+
visible: !root.onboarding
2031
Layout.fillWidth: true
2132
header: qsTr("Load snapshot")
2233
description: qsTr("Instant use with background sync")
@@ -40,7 +51,10 @@ ColumnLayout {
4051
connectionSwipe.incrementCurrentIndex()
4152
}
4253
}
43-
Separator { Layout.fillWidth: true }
54+
Separator {
55+
visible: !root.onboarding
56+
Layout.fillWidth: true
57+
}
4458
Setting {
4559
Layout.fillWidth: true
4660
header: qsTr("Enable listening")

Diff for: src/qml/components/SnapshotSettings.qml

+83-11
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,25 @@
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

1112
ColumnLayout {
13+
property bool snapshotLoading: false
1214
signal snapshotImportCompleted()
1315
property int snapshotVerificationCycles: 0
1416
property real snapshotVerificationProgress: 0
15-
property bool snapshotVerified: false
17+
property bool onboarding: false
18+
property bool snapshotVerified: onboarding ? false : chainModel.isSnapshotActive
19+
property string snapshotFileName: ""
20+
property var snapshotInfo: ({})
1621

1722
id: columnLayout
1823
width: Math.min(parent.width, 450)
1924
anchors.horizontalCenter: parent.horizontalCenter
2025

21-
26+
// TODO: Remove this once the verification progress is available
2227
Timer {
2328
id: snapshotSimulationTimer
2429
interval: 50 // Update every 50ms
@@ -29,7 +34,7 @@ ColumnLayout {
2934
snapshotVerificationProgress += 0.01
3035
} else {
3136
snapshotVerificationCycles++
32-
if (snapshotVerificationCycles < 1) {
37+
if (snapshotVerificationCycles < 3) {
3338
snapshotVerificationProgress = 0
3439
} else {
3540
running = false
@@ -42,7 +47,16 @@ ColumnLayout {
4247

4348
StackLayout {
4449
id: settingsStack
45-
currentIndex: 0
50+
currentIndex: onboarding ? 0 : snapshotVerified ? 2 : 0
51+
52+
function startLoading() {
53+
snapshotLoading = true;
54+
settingsStack.currentIndex = 1; // Navigate to loading page
55+
}
56+
57+
function stopLoading() {
58+
snapshotLoading = false;
59+
}
4660

4761
ColumnLayout {
4862
Layout.alignment: Qt.AlignHCenter
@@ -78,8 +92,22 @@ ColumnLayout {
7892
Layout.alignment: Qt.AlignCenter
7993
text: qsTr("Choose snapshot file")
8094
onClicked: {
81-
settingsStack.currentIndex = 1
82-
snapshotSimulationTimer.start()
95+
fileDialog.open()
96+
}
97+
}
98+
99+
FileDialog {
100+
id: fileDialog
101+
folder: shortcuts.home
102+
selectMultiple: false
103+
onAccepted: {
104+
console.log("File chosen:", fileDialog.fileUrls)
105+
snapshotFileName = fileDialog.fileUrl.toString()
106+
console.log("Snapshot file name:", snapshotFileName)
107+
if (snapshotFileName.endsWith(".dat")) {
108+
nodeModel.initializeSnapshot(true, snapshotFileName)
109+
settingsStack.startLoading()
110+
}
83111
}
84112
}
85113
}
@@ -102,17 +130,49 @@ ColumnLayout {
102130
Layout.leftMargin: 20
103131
Layout.rightMargin: 20
104132
header: qsTr("Loading Snapshot")
133+
description: qsTr("This might take a while...")
105134
}
106135

136+
// TODO: uncomment this once snapshot verification progress is available
137+
/*
107138
ProgressIndicator {
108139
id: progressIndicator
109140
Layout.topMargin: 20
110141
width: 200
111142
height: 20
112-
progress: snapshotVerificationProgress
143+
// TODO: uncomment this once the verification progress is available
144+
// progress: nodeModel.verificationProgress
145+
progress: 0
146+
// progress: nodeModel.snapshotProgress
113147
Layout.alignment: Qt.AlignCenter
114148
progressColor: Theme.color.blue
115149
}
150+
*/
151+
152+
Connections {
153+
target: nodeModel
154+
// TODO: uncomment this once the verification progress is available
155+
// function onVerificationProgressChanged() {
156+
// progressIndicator.progress = nodeModel.verificationProgress
157+
// }
158+
// function onSnapshotProgressChanged() {
159+
// progressIndicator.progress = nodeModel.snapshotProgress
160+
// }
161+
162+
function onSnapshotLoaded(success) {
163+
if (success) {
164+
chainModel.isSnapshotActiveChanged()
165+
snapshotVerified = chainModel.isSnapshotActive
166+
snapshotInfo = chainModel.getSnapshotInfo()
167+
settingsStack.stopLoading()
168+
// progressIndicator.progress = 1
169+
settingsStack.currentIndex = 2 // Move to the "Snapshot Loaded" page
170+
} else {
171+
// Handle snapshot loading failure
172+
console.error("Snapshot loading failed")
173+
}
174+
}
175+
}
116176
}
117177

118178
ColumnLayout {
@@ -137,8 +197,11 @@ ColumnLayout {
137197
descriptionColor: Theme.color.neutral6
138198
descriptionSize: 17
139199
descriptionLineHeight: 1.1
140-
description: qsTr("It contains transactions up to January 12, 2024. Newer transactions still need to be downloaded." +
141-
" The data will be verified in the background.")
200+
description: snapshotInfo && snapshotInfo["date"] ?
201+
qsTr("It contains transactions up to %1. Newer transactions still need to be downloaded." +
202+
" The data will be verified in the background.").arg(snapshotInfo["date"]) :
203+
qsTr("It contains transactions up to DEBUG. Newer transactions still need to be downloaded." +
204+
" The data will be verified in the background.")
142205
}
143206

144207
ContinueButton {
@@ -188,16 +251,25 @@ ColumnLayout {
188251
font.pixelSize: 14
189252
}
190253
CoreText {
191-
text: qsTr("200,000")
254+
text: snapshotInfo && snapshotInfo["height"] ?
255+
snapshotInfo["height"] : qsTr("DEBUG")
192256
Layout.alignment: Qt.AlignRight
193257
font.pixelSize: 14
194258
}
195259
}
196260
Separator { Layout.fillWidth: true }
197261
CoreText {
198-
text: qsTr("Hash: 0x1234567890abcdef...")
262+
text: snapshotInfo && snapshotInfo["hashSerialized"] ?
263+
qsTr("Hash: %1").arg(snapshotInfo["hashSerialized"].substring(0, 13) + "...") :
264+
qsTr("Hash: DEBUG")
199265
font.pixelSize: 14
200266
}
267+
268+
Component.onCompleted: {
269+
if (snapshotVerified) {
270+
snapshotInfo = chainModel.getSnapshotInfo()
271+
}
272+
}
201273
}
202274
}
203275
}

Diff for: src/qml/models/chainmodel.cpp

+22
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@
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
ChainModel::ChainModel(interfaces::Chain& chain)
1417
: m_chain{chain}
18+
// m_params{Params()}
1519
{
1620
QTimer* timer = new QTimer();
1721
connect(timer, &QTimer::timeout, this, &ChainModel::setCurrentTimeRatio);
@@ -101,3 +105,21 @@ void ChainModel::setCurrentTimeRatio()
101105

102106
Q_EMIT timeRatioListChanged();
103107
}
108+
109+
// Using hardcoded snapshot info to display in SnapshotSettings.qml
110+
QVariantMap ChainModel::getSnapshotInfo() {
111+
QVariantMap snapshot_info;
112+
113+
const MapAssumeutxo& valid_assumeutxos_map = Params().Assumeutxo();
114+
if (!valid_assumeutxos_map.empty()) {
115+
const int height = valid_assumeutxos_map.rbegin()->first;
116+
const auto& hash_serialized = valid_assumeutxos_map.rbegin()->second.hash_serialized;
117+
int64_t date = m_chain.getBlockTime(height);
118+
119+
snapshot_info["height"] = height;
120+
snapshot_info["hashSerialized"] = QString::fromStdString(hash_serialized.ToString());
121+
snapshot_info["date"] = QDateTime::fromSecsSinceEpoch(date).toString("MMMM d yyyy");
122+
}
123+
124+
return snapshot_info;
125+
}

Diff for: src/qml/models/chainmodel.h

+5-1
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;

0 commit comments

Comments
 (0)