Skip to content

Commit 5d0756c

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 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 working, could be improved Testing: 1. Start the node 2. Complete onboarding 3. Navigate to connection settings 4. Load snapshot from provided interface
1 parent c97d2a1 commit 5d0756c

File tree

11 files changed

+302
-29
lines changed

11 files changed

+302
-29
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

+88-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,95 @@ 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)) {
404+
LogPrintf("[loadsnapshot] Snapshot file %s does not exist\n", path.u8string());
405+
return false;
406+
}
407+
408+
AutoFile afile{fsbridge::fopen(path, "rb")};
409+
if (afile.IsNull()) {
410+
LogPrintf("[loadsnapshot] Failed to open snapshot file %s\n", path.u8string());
411+
return false;
412+
}
413+
414+
SnapshotMetadata metadata;
415+
try {
416+
afile >> metadata;
417+
} catch (const std::exception& e) {
418+
LogPrintf("[loadsnapshot] Failed to read snapshot metadata: %s\n", e.what());
419+
return false;
420+
}
421+
422+
const uint256& base_blockhash = metadata.m_base_blockhash;
423+
LogPrintf("[loadsnapshot] Waiting for blockheader %s in headers chain before snapshot activation\n",
424+
base_blockhash.ToString());
425+
426+
if (!m_context->chainman) {
427+
LogPrintf("[loadsnapshot] Chainman is null\n");
428+
return false;
429+
}
430+
431+
ChainstateManager& chainman = *m_context->chainman;
432+
CBlockIndex* snapshot_start_block = nullptr;
433+
434+
// Wait for the block to appear in the block index
435+
constexpr int max_wait_seconds = 600; // 10 minutes
436+
for (int i = 0; i < max_wait_seconds; ++i) {
437+
snapshot_start_block = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(base_blockhash));
438+
if (snapshot_start_block) break;
439+
std::this_thread::sleep_for(std::chrono::seconds(1));
440+
}
441+
442+
// Snapshot Progress GUI display
443+
COutPoint outpoint;
444+
Coin coin;
445+
const uint64_t coins_count = metadata.m_coins_count;
446+
uint64_t coins_left = metadata.m_coins_count;
447+
448+
LogPrintf("[loadsnapshot] Loading %d coins from snapshot %s\n", coins_count, base_blockhash.ToString());
449+
int64_t coins_processed{0};
450+
m_snapshot_progress.store(0.0);
451+
452+
while (coins_left > 0) {
453+
--coins_left;
454+
++coins_processed;
455+
456+
if (coins_processed > 0) {
457+
double progress = static_cast<float>(coins_processed) / static_cast<float>(coins_count);
458+
m_snapshot_progress.store(progress);
459+
if (coins_processed % 1000000 == 0) {
460+
LogPrintf("[loadsnapshot] Progress: %.2f%% (%d/%d coins)\n",
461+
progress * 100, coins_processed, coins_count);
462+
}
463+
}
464+
}
465+
m_snapshot_progress.store(1.0);
466+
467+
if (!snapshot_start_block) {
468+
LogPrintf("[loadsnapshot] Timed out waiting for snapshot start blockheader %s\n", base_blockhash.ToString());
469+
return false;
470+
}
471+
472+
// Activate the snapshot
473+
if (!chainman.ActivateSnapshot(afile, metadata, false)) {
474+
LogPrintf("[loadsnapshot] Unable to load UTXO snapshot %s\n", path.u8string());
475+
return false;
476+
}
477+
478+
CBlockIndex* new_tip = WITH_LOCK(::cs_main, return chainman.ActiveTip());
479+
LogPrintf("[loadsnapshot] Loaded %d coins from snapshot %s at height %d\n",
480+
metadata.m_coins_count, new_tip->GetBlockHash().ToString(), new_tip->nHeight);
481+
482+
return true;
483+
}
398484
ArgsManager& args() { return *Assert(Assert(m_context)->args); }
399485
ChainstateManager& chainman() { return *Assert(m_context->chainman); }
400486
NodeContext* m_context{nullptr};
487+
std::atomic<double> m_snapshot_progress{0.0};
401488
};
402489

403490
bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<RecursiveMutex>& lock, const CChain& active, const BlockManager& blockman)
@@ -510,7 +597,7 @@ class RpcHandlerImpl : public Handler
510597
class ChainImpl : public Chain
511598
{
512599
public:
513-
explicit ChainImpl(NodeContext& node) : m_node(node) {}
600+
explicit ChainImpl(node::NodeContext& node) : m_node(node) {}
514601
std::optional<int> getHeight() override
515602
{
516603
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

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

12+
// This QML component manages the snapshot loading process in the GUI.
13+
// It provides visual feedback to the user about the snapshot's loading state.
14+
1115
ColumnLayout {
16+
// The snapshotLoading property indicates if the snapshot is currently being loaded.
17+
// When true, the UI will show a loading indicator.
18+
property bool snapshotLoading: nodeModel.snapshotLoading
1219
signal snapshotImportCompleted()
1320
property int snapshotVerificationCycles: 0
1421
property real snapshotVerificationProgress: 0
15-
property bool snapshotVerified: false
22+
property bool onboarding: false
23+
24+
// The snapshotVerified property indicates if the snapshot has been successfully loaded and verified.
25+
// When true, the UI will transition to the "Snapshot Loaded" page.
26+
property bool snapshotVerified: onboarding ? false : chainModel.isSnapshotActive
27+
28+
// The snapshotFileName property holds the name of the snapshot file being loaded.
29+
// It is set when a file is selected in the FileDialog.
30+
property string snapshotFileName: ""
31+
32+
// The snapshotInfo property holds information about the loaded snapshot.
33+
// It is updated when the snapshot is loaded and verified.
34+
property var snapshotInfo: ({})
1635

1736
id: columnLayout
1837
width: Math.min(parent.width, 450)
1938
anchors.horizontalCenter: parent.horizontalCenter
2039

21-
40+
// The Timer component simulates snapshot verification progress for testing purposes.
41+
// It updates the snapshotVerificationProgress property, which can be used to display a progress bar.
2242
Timer {
2343
id: snapshotSimulationTimer
2444
interval: 50 // Update every 50ms
@@ -29,7 +49,7 @@ ColumnLayout {
2949
snapshotVerificationProgress += 0.01
3050
} else {
3151
snapshotVerificationCycles++
32-
if (snapshotVerificationCycles < 1) {
52+
if (snapshotVerificationCycles < 3) {
3353
snapshotVerificationProgress = 0
3454
} else {
3555
running = false
@@ -40,9 +60,11 @@ ColumnLayout {
4060
}
4161
}
4262

63+
// The StackLayout component manages the different pages of the snapshot settings UI.
64+
// It determines which page to display based on the snapshotLoading and snapshotVerified properties.
4365
StackLayout {
4466
id: settingsStack
45-
currentIndex: 0
67+
currentIndex: onboarding ? 0 : snapshotVerified ? 2 : snapshotLoading ? 1 : 0
4668

4769
ColumnLayout {
4870
Layout.alignment: Qt.AlignHCenter
@@ -69,6 +91,8 @@ ColumnLayout {
6991
" It will be automatically verified in the background.")
7092
}
7193

94+
// The ContinueButton component is used to trigger the snapshot file selection process.
95+
// When clicked, it opens a FileDialog for the user to choose a snapshot file.
7296
ContinueButton {
7397
Layout.preferredWidth: Math.min(300, columnLayout.width - 2 * Layout.leftMargin)
7498
Layout.topMargin: 40
@@ -78,8 +102,25 @@ ColumnLayout {
78102
Layout.alignment: Qt.AlignCenter
79103
text: qsTr("Choose snapshot file")
80104
onClicked: {
81-
settingsStack.currentIndex = 1
82-
snapshotSimulationTimer.start()
105+
fileDialog.open()
106+
}
107+
}
108+
109+
// The FileDialog component is used to allow the user to select a snapshot file from their system.
110+
FileDialog {
111+
id: fileDialog
112+
folder: shortcuts.home
113+
selectMultiple: false
114+
onAccepted: {
115+
console.log("File chosen:", fileDialog.fileUrls)
116+
snapshotFileName = fileDialog.fileUrl.toString()
117+
console.log("Snapshot file name:", snapshotFileName)
118+
if (snapshotFileName.endsWith(".dat")) {
119+
nodeModel.initializeSnapshot(true, snapshotFileName)
120+
// nodeModel.presyncProgress
121+
} else {
122+
console.error("Snapshot loading failed")
123+
}
83124
}
84125
}
85126
}
@@ -102,17 +143,40 @@ ColumnLayout {
102143
Layout.leftMargin: 20
103144
Layout.rightMargin: 20
104145
header: qsTr("Loading Snapshot")
146+
description: qsTr("This might take a while...")
105147
}
106148

149+
// The ProgressIndicator component displays the progress of the snapshot verification process.
107150
ProgressIndicator {
108151
id: progressIndicator
109152
Layout.topMargin: 20
110153
width: 200
111154
height: 20
112-
progress: snapshotVerificationProgress
155+
progress: nodeModel.snapshotProgress
113156
Layout.alignment: Qt.AlignCenter
114157
progressColor: Theme.color.blue
115158
}
159+
160+
// The Connections component listens for signals from the nodeModel
161+
// to update the UI based on snapshot loading progress.
162+
Connections {
163+
target: nodeModel
164+
function onSnapshotProgressChanged() {
165+
progressIndicator.progress = nodeModel.snapshotProgress
166+
}
167+
168+
function onSnapshotLoaded(success) {
169+
if (success) {
170+
chainModel.isSnapshotActiveChanged()
171+
snapshotVerified = chainModel.isSnapshotActive
172+
snapshotInfo = chainModel.getSnapshotInfo()
173+
settingsStack.currentIndex = 2 // Move to the "Snapshot Loaded" page
174+
} else {
175+
// Handle snapshot loading failure
176+
console.error("Snapshot loading failed")
177+
}
178+
}
179+
}
116180
}
117181

118182
ColumnLayout {
@@ -137,8 +201,11 @@ ColumnLayout {
137201
descriptionColor: Theme.color.neutral6
138202
descriptionSize: 17
139203
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.")
204+
description: snapshotInfo && snapshotInfo["date"] ?
205+
qsTr("It contains transactions up to %1. Newer transactions still need to be downloaded." +
206+
" The data will be verified in the background.").arg(snapshotInfo["date"]) :
207+
qsTr("It contains transactions up to DEBUG. Newer transactions still need to be downloaded." +
208+
" The data will be verified in the background.")
142209
}
143210

144211
ContinueButton {
@@ -153,6 +220,7 @@ ColumnLayout {
153220
}
154221
}
155222

223+
// The Setting component provides a toggleable view for detailed snapshot information.
156224
Setting {
157225
id: viewDetails
158226
Layout.alignment: Qt.AlignCenter
@@ -188,16 +256,26 @@ ColumnLayout {
188256
font.pixelSize: 14
189257
}
190258
CoreText {
191-
text: qsTr("200,000")
259+
text: snapshotInfo && snapshotInfo["height"] ?
260+
snapshotInfo["height"] : qsTr("DEBUG")
192261
Layout.alignment: Qt.AlignRight
193262
font.pixelSize: 14
194263
}
195264
}
196265
Separator { Layout.fillWidth: true }
197266
CoreText {
198-
text: qsTr("Hash: 0x1234567890abcdef...")
267+
// The CoreText component displays the hash of the loaded snapshot.
268+
text: snapshotInfo && snapshotInfo["hashSerialized"] ?
269+
qsTr("Hash: %1").arg(snapshotInfo["hashSerialized"].substring(0, 13) + "...") :
270+
qsTr("Hash: DEBUG")
199271
font.pixelSize: 14
200272
}
273+
274+
Component.onCompleted: {
275+
if (snapshotVerified) {
276+
snapshotInfo = chainModel.getSnapshotInfo()
277+
}
278+
}
201279
}
202280
}
203281
}

0 commit comments

Comments
 (0)