Skip to content

Commit 8beb50a

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. Uses SnapshotSettings.qml to allow user interaction. Modifies src/interfaces/node.h, src/node/interfaces.cpp and chainparams.cpp (temporarily for signet snapshot testing) to expose 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 implemented yet Testing: 1. Start the node 2. Complete onboarding 3. Navigate to connection settings 4. Load snapshot from provided interface
1 parent bc72124 commit 8beb50a

File tree

11 files changed

+209
-66
lines changed

11 files changed

+209
-66
lines changed

Diff for: src/interfaces/node.h

+6
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ 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+
205+
//! Get snapshot progress.
206+
virtual double getSnapshotProgress() = 0;
207+
202208
//! Set RPC timer interface if unset.
203209
virtual void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) = 0;
204210

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

+35-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,9 +396,42 @@ class NodeImpl : public Node
395396
{
396397
m_context = context;
397398
}
399+
double getSnapshotProgress() override { return m_snapshot_progress.load(); }
400+
bool snapshotLoad(const std::string& path_string) override
401+
{
402+
const fs::path path = fs::u8path(path_string);
403+
if (!fs::exists(path)) { return false; }
404+
405+
AutoFile afile{fsbridge::fopen(path, "rb")};
406+
if (afile.IsNull()) { return false; }
407+
408+
SnapshotMetadata metadata;
409+
try {
410+
afile >> metadata;
411+
} catch (const std::exception& e) { return false; }
412+
413+
const uint256& base_blockhash = metadata.m_base_blockhash;
414+
415+
if (!m_context->chainman) { return false; }
416+
417+
ChainstateManager& chainman = *m_context->chainman;
418+
CBlockIndex* snapshot_start_block = nullptr;
419+
420+
// Wait for the block to appear in the block index
421+
//TODO: remove this once another method is implemented
422+
constexpr int max_wait_seconds = 600; // 10 minutes
423+
for (int i = 0; i < max_wait_seconds; ++i) {
424+
snapshot_start_block = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(base_blockhash));
425+
if (snapshot_start_block) break;
426+
std::this_thread::sleep_for(std::chrono::seconds(1));
427+
}
428+
429+
return chainman.ActivateSnapshot(afile, metadata, false);
430+
}
398431
ArgsManager& args() { return *Assert(Assert(m_context)->args); }
399432
ChainstateManager& chainman() { return *Assert(m_context->chainman); }
400433
NodeContext* m_context{nullptr};
434+
std::atomic<double> m_snapshot_progress{0.0};
401435
};
402436

403437
bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<RecursiveMutex>& lock, const CChain& active, const BlockManager& blockman)
@@ -510,7 +544,7 @@ class RpcHandlerImpl : public Handler
510544
class ChainImpl : public Chain
511545
{
512546
public:
513-
explicit ChainImpl(NodeContext& node) : m_node(node) {}
547+
explicit ChainImpl(node::NodeContext& node) : m_node(node) {}
514548
std::optional<int> getHeight() override
515549
{
516550
const int height{WITH_LOCK(::cs_main, return chainman().ActiveChain().Height())};

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

+17-6
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,21 @@ ColumnLayout {
1111
id: root
1212
signal next
1313
signal gotoSnapshot
14-
property bool snapshotImported: false
15-
function setSnapshotImported(imported) {
16-
snapshotImported = imported
14+
property bool snapshotImportCompleted: false
15+
property bool onboarding: false
16+
17+
Component.onCompleted: {
18+
if (!onboarding) {
19+
snapshotImportCompleted = chainModel.isSnapshotActive
20+
} else {
21+
snapshotImportCompleted = false
22+
}
1723
}
24+
1825
spacing: 4
1926
Setting {
2027
id: gotoSnapshot
28+
visible: !root.onboarding
2129
Layout.fillWidth: true
2230
header: qsTr("Load snapshot")
2331
description: qsTr("Instant use with background sync")
@@ -26,19 +34,22 @@ ColumnLayout {
2634
height: 26
2735
CaretRightIcon {
2836
anchors.centerIn: parent
29-
visible: !snapshotImported
37+
visible: !snapshotImportCompleted
3038
color: gotoSnapshot.stateColor
3139
}
3240
GreenCheckIcon {
3341
anchors.centerIn: parent
34-
visible: snapshotImported
42+
visible: snapshotImportCompleted
3543
color: Theme.color.transparent
3644
size: 30
3745
}
3846
}
3947
onClicked: root.gotoSnapshot()
4048
}
41-
Separator { Layout.fillWidth: true }
49+
Separator {
50+
visible: !root.onboarding
51+
Layout.fillWidth: true
52+
}
4253
Setting {
4354
Layout.fillWidth: true
4455
header: qsTr("Enable listening")

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

+45-49
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,28 @@
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 {
12-
signal snapshotImportCompleted()
13-
property int snapshotVerificationCycles: 0
14-
property real snapshotVerificationProgress: 0
15-
property bool snapshotVerified: false
16-
1713
id: columnLayout
14+
signal back
15+
property bool snapshotLoading: nodeModel.snapshotLoading
16+
property bool snapshotLoaded: nodeModel.isSnapshotLoaded
17+
property bool snapshotImportCompleted: chainModel.isSnapshotActive
18+
property bool onboarding: false
19+
property bool snapshotVerified: onboarding ? false : chainModel.isSnapshotActive
20+
property string snapshotFileName: ""
21+
property var snapshotInfo: ({})
22+
property string selectedFile: ""
23+
1824
width: Math.min(parent.width, 450)
1925
anchors.horizontalCenter: parent.horizontalCenter
2026

21-
22-
Timer {
23-
id: snapshotSimulationTimer
24-
interval: 50 // Update every 50ms
25-
running: false
26-
repeat: true
27-
onTriggered: {
28-
if (snapshotVerificationProgress < 1) {
29-
snapshotVerificationProgress += 0.01
30-
} else {
31-
snapshotVerificationCycles++
32-
if (snapshotVerificationCycles < 1) {
33-
snapshotVerificationProgress = 0
34-
} else {
35-
running = false
36-
snapshotVerified = true
37-
settingsStack.currentIndex = 2
38-
}
39-
}
40-
}
41-
}
42-
4327
StackLayout {
4428
id: settingsStack
45-
currentIndex: 0
29+
currentIndex: onboarding ? 0 : snapshotLoaded ? 2 : snapshotVerified ? 2 : snapshotLoading ? 1 : 0
4630

4731
ColumnLayout {
4832
Layout.alignment: Qt.AlignHCenter
@@ -77,11 +61,22 @@ ColumnLayout {
7761
Layout.bottomMargin: 20
7862
Layout.alignment: Qt.AlignCenter
7963
text: qsTr("Choose snapshot file")
80-
onClicked: {
81-
settingsStack.currentIndex = 1
82-
snapshotSimulationTimer.start()
64+
onClicked: fileDialog.open()
65+
}
66+
67+
FileDialog {
68+
id: fileDialog
69+
folder: shortcuts.home
70+
selectMultiple: false
71+
selectExisting: true
72+
nameFilters: ["Snapshot files (*.dat)", "All files (*)"]
73+
onAccepted: {
74+
selectedFile = fileUrl.toString()
75+
snapshotFileName = selectedFile
76+
nodeModel.initializeSnapshot(true, snapshotFileName)
8377
}
8478
}
79+
// TODO: Handle file error signal
8580
}
8681

8782
ColumnLayout {
@@ -102,17 +97,10 @@ ColumnLayout {
10297
Layout.leftMargin: 20
10398
Layout.rightMargin: 20
10499
header: qsTr("Loading Snapshot")
100+
description: qsTr("This might take a while...")
105101
}
106102

107-
ProgressIndicator {
108-
id: progressIndicator
109-
Layout.topMargin: 20
110-
width: 200
111-
height: 20
112-
progress: snapshotVerificationProgress
113-
Layout.alignment: Qt.AlignCenter
114-
progressColor: Theme.color.blue
115-
}
103+
// TODO: add progress indicator once the snapshot progress is implemented
116104
}
117105

118106
ColumnLayout {
@@ -137,20 +125,19 @@ ColumnLayout {
137125
descriptionColor: Theme.color.neutral6
138126
descriptionSize: 17
139127
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.")
128+
description: snapshotInfo && snapshotInfo["date"] ?
129+
qsTr("It contains transactions up to %1. Newer transactions still need to be downloaded." +
130+
" The data will be verified in the background.").arg(snapshotInfo["date"]) :
131+
qsTr("It contains transactions up to DEBUG. Newer transactions still need to be downloaded." +
132+
" The data will be verified in the background.")
142133
}
143134

144135
ContinueButton {
145136
Layout.preferredWidth: Math.min(300, columnLayout.width - 2 * Layout.leftMargin)
146137
Layout.topMargin: 40
147138
Layout.alignment: Qt.AlignCenter
148139
text: qsTr("Done")
149-
onClicked: {
150-
snapshotImportCompleted()
151-
connectionSwipe.decrementCurrentIndex()
152-
connectionSwipe.decrementCurrentIndex()
153-
}
140+
onClicked: back()
154141
}
155142

156143
Setting {
@@ -188,16 +175,25 @@ ColumnLayout {
188175
font.pixelSize: 14
189176
}
190177
CoreText {
191-
text: qsTr("200,000")
178+
text: snapshotInfo && snapshotInfo["height"] ?
179+
snapshotInfo["height"] : qsTr("DEBUG")
192180
Layout.alignment: Qt.AlignRight
193181
font.pixelSize: 14
194182
}
195183
}
196184
Separator { Layout.fillWidth: true }
197185
CoreText {
198-
text: qsTr("Hash: 0x1234567890abcdef...")
186+
text: snapshotInfo && snapshotInfo["hashSerialized"] ?
187+
qsTr("Hash: %1").arg(snapshotInfo["hashSerialized"].substring(0, 13) + "...") :
188+
qsTr("Hash: DEBUG")
199189
font.pixelSize: 14
200190
}
191+
192+
Component.onCompleted: {
193+
if (snapshotVerified || snapshotLoaded) {
194+
snapshotInfo = chainModel.getSnapshotInfo()
195+
}
196+
}
201197
}
202198
}
203199
}

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)