Skip to content

Commit 670ca05

Browse files
committed
wip: Add initial support for loading signet UTXO snapshots
Adds wiring to connect QML GUI to loading a signet UTXO snapshot via the connection settings. Modifies src/interfaces/node.h and src/node/interfaces.cpp to implement snapshot loading functionality. Current limitations: - Not integrated with onboarding process - Requires manual navigation to connection settings after initial startup Testing: 1. Start the node 2. Complete onboarding 3. Navigate to connection settings 4. Load snapshot from provided interface Note: This is a work in progress. Do not merge until fully implemented and tested.
1 parent 1d08d2c commit 670ca05

File tree

10 files changed

+198
-25
lines changed

10 files changed

+198
-25
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

+68
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,73 @@ class NodeImpl : public Node
395396
{
396397
m_context = context;
397398
}
399+
bool snapshotLoad(const std::string& path_string) override
400+
{
401+
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+
FILE* file{fsbridge::fopen(path, "rb")};
408+
409+
AutoFile afile{file};
410+
if (afile.IsNull()) {
411+
LogPrintf("[loadsnapshot] failed to open snapshot file %s\n", path_string);
412+
return false;
413+
}
414+
415+
// Read the snapshot metadata.
416+
SnapshotMetadata metadata;
417+
afile >> metadata;
418+
419+
// Get the base blockhash and look up the corresponding CBlockIndex object.
420+
uint256 base_blockhash = metadata.m_base_blockhash;
421+
int max_secs_to_wait_for_headers = 60 * 10;
422+
CBlockIndex* snapshot_start_block = nullptr;
423+
424+
LogPrintf("[loadsnapshot] waiting to see blockheader %s in headers chain before snapshot activation\n",
425+
base_blockhash.ToString());
426+
427+
if (m_context->chainman == nullptr) {
428+
LogPrintf("[loadsnapshot] m_context->chainman is null\n");
429+
return false;
430+
}
431+
432+
ChainstateManager& chainman = *m_context->chainman;
433+
434+
while (max_secs_to_wait_for_headers > 0) {
435+
LogPrintf("[loadsnapshot] base_blockhash = %s\n", base_blockhash.ToString());
436+
snapshot_start_block = WITH_LOCK(::cs_main,
437+
return chainman.m_blockman.LookupBlockIndex(base_blockhash));
438+
max_secs_to_wait_for_headers -= 1;
439+
440+
if (!snapshot_start_block) {
441+
std::this_thread::sleep_for(std::chrono::seconds(1));
442+
} else {
443+
break;
444+
}
445+
}
446+
447+
if (!snapshot_start_block) {
448+
LogPrintf("[loadsnapshot] timed out waiting for snapshot start blockheader %s\n",
449+
base_blockhash.ToString());
450+
return false;
451+
}
452+
453+
// Activate the snapshot.
454+
if (!chainman.ActivateSnapshot(afile, metadata, false)) {
455+
LogPrintf("[loadsnapshot] Unable to load UTXO snapshot %s\n", path.u8string());
456+
return false;
457+
}
458+
459+
// Get the new tip and print a log message.
460+
CBlockIndex* new_tip{WITH_LOCK(::cs_main, return chainman.ActiveTip())};
461+
LogPrintf("[loadsnashot] Loaded %d coins from snapshot %s at height %d\n",
462+
metadata.m_coins_count, new_tip->GetBlockHash().ToString(), new_tip->nHeight);
463+
464+
return true;
465+
}
398466
ArgsManager& args() { return *Assert(Assert(m_context)->args); }
399467
ChainstateManager& chainman() { return *Assert(m_context->chainman); }
400468
NodeContext* m_context{nullptr};

src/qml/components/ConnectionSettings.qml

+37-20
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,53 @@ import QtQuick.Layouts 1.15
88
import "../controls"
99

1010
ColumnLayout {
11+
// TODO: Remove this once storing the snapshot path is implemented
12+
property bool isOnboarding: false
1113
property bool snapshotImported: false
1214
function setSnapshotImported(imported) {
1315
snapshotImported = imported
1416
}
1517
spacing: 4
16-
Setting {
17-
id: gotoSnapshot
18+
Item {
19+
// TODO: Remove this once storing the snapshot path is implemented
20+
visible: !isOnboarding
21+
height: visible ? implicitHeight : 0
1822
Layout.fillWidth: true
19-
header: qsTr("Load snapshot")
20-
description: qsTr("Instant use with background sync")
21-
actionItem: Item {
22-
width: 26
23-
height: 26
24-
CaretRightIcon {
25-
anchors.centerIn: parent
26-
visible: !snapshotImported
27-
color: gotoSnapshot.stateColor
23+
Layout.preferredHeight: gotoSnapshot.height
24+
25+
Setting {
26+
id: gotoSnapshot
27+
visible: parent.visible
28+
Layout.fillWidth: true
29+
header: qsTr("Load snapshot")
30+
description: qsTr("Instant use with background sync")
31+
actionItem: Item {
32+
width: 26
33+
height: 26
34+
CaretRightIcon {
35+
// TODO: aligment will be fixed once Onboarding snapshot works
36+
anchors.right: parent.right
37+
anchors.verticalCenter: parent.verticalCenter
38+
visible: !snapshotImported
39+
color: gotoSnapshot.stateColor
40+
}
41+
GreenCheckIcon {
42+
anchors.centerIn: parent
43+
visible: snapshotImported
44+
color: Theme.color.transparent
45+
}
2846
}
29-
GreenCheckIcon {
30-
anchors.centerIn: parent
31-
visible: snapshotImported
32-
color: Theme.color.transparent
47+
onClicked: {
48+
connectionSwipe.incrementCurrentIndex()
49+
connectionSwipe.incrementCurrentIndex()
3350
}
3451
}
35-
onClicked: {
36-
connectionSwipe.incrementCurrentIndex()
37-
connectionSwipe.incrementCurrentIndex()
38-
}
3952
}
40-
Separator { Layout.fillWidth: true }
53+
Separator {
54+
Layout.fillWidth: true
55+
// TODO: Remove this once storing the snapshot path is implemented
56+
visible: !isOnboarding
57+
}
4158
Setting {
4259
Layout.fillWidth: true
4360
header: qsTr("Enable listening")

src/qml/components/SnapshotSettings.qml

+22-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import QtQuick 2.15
66
import QtQuick.Controls 2.15
77
import QtQuick.Layouts 1.15
8+
import QtQuick.Dialogs 1.3
9+
810

911
import "../controls"
1012

@@ -18,7 +20,7 @@ ColumnLayout {
1820
width: Math.min(parent.width, 450)
1921
anchors.horizontalCenter: parent.horizontalCenter
2022

21-
23+
// TODO: Remove simulation timer before release
2224
Timer {
2325
id: snapshotSimulationTimer
2426
interval: 50 // Update every 50ms
@@ -78,8 +80,25 @@ ColumnLayout {
7880
Layout.alignment: Qt.AlignCenter
7981
text: qsTr("Choose snapshot file")
8082
onClicked: {
81-
settingsStack.currentIndex = 1
82-
snapshotSimulationTimer.start()
83+
// TODO: Connect this to snapshot loading
84+
// settingsStack.currentIndex = 1
85+
fileDialog.open()
86+
}
87+
}
88+
89+
FileDialog {
90+
id: fileDialog
91+
folder: shortcuts.home
92+
selectMultiple: false
93+
onAccepted: {
94+
console.log("File chosen:", fileDialog.fileUrls)
95+
var snapshotFileName = fileDialog.fileUrl.toString()
96+
console.log("Snapshot file name:", snapshotFileName)
97+
if (snapshotFileName.endsWith(".dat")) {
98+
// optionsModel.setSnapshotDirectory(snapshotFileName)
99+
// console.log("Snapshot directory set:", optionsModel.getSnapshotDirectory())
100+
nodeModel.initializeSnapshot(true, snapshotFileName)
101+
}
83102
}
84103
}
85104
}

src/qml/models/nodemodel.cpp

+27
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
#include <QMetaObject>
1717
#include <QTimerEvent>
1818
#include <QString>
19+
#include <QThread>
20+
#include <QUrl>
21+
#include <QDebug>
1922

2023
NodeModel::NodeModel(interfaces::Node& node)
2124
: m_node{node}
@@ -121,6 +124,8 @@ void NodeModel::initializeResult(bool success, interfaces::BlockAndHeaderTipInfo
121124
setVerificationProgress(tip_info.verification_progress);
122125

123126
Q_EMIT setTimeRatioListInitial();
127+
// TODO: fix this so that it works once storing the snapshot path is implemented
128+
Q_EMIT initializationFinished();
124129
}
125130

126131
void NodeModel::startShutdownPolling()
@@ -166,3 +171,25 @@ void NodeModel::ConnectToNumConnectionsChangedSignal()
166171
setNumOutboundPeers(new_num_peers.outbound_full_relay + new_num_peers.block_relay);
167172
});
168173
}
174+
175+
// Loads a snapshot from a given path using FileDialog
176+
void NodeModel::initializeSnapshot(bool initLoadSnapshot, QString path_file) {
177+
if (initLoadSnapshot) {
178+
// TODO: this is to deal with FileDialog returning a QUrl
179+
path_file = QUrl(path_file).toLocalFile();
180+
// TODO: Remove this before release
181+
// qDebug() << "path_file: " << path_file;
182+
QThread* snapshot_thread = new QThread();
183+
184+
// Capture path_file by value
185+
auto lambda = [this, path_file]() {
186+
bool result = this->snapshotLoad(path_file);
187+
Q_EMIT snapshotLoaded(result);
188+
};
189+
190+
connect(snapshot_thread, &QThread::started, lambda);
191+
connect(snapshot_thread, &QThread::finished, snapshot_thread, &QThread::deleteLater);
192+
193+
snapshot_thread->start();
194+
}
195+
}

src/qml/models/nodemodel.h

+5
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ class NodeModel : public QObject
5959
Q_INVOKABLE void startNodeInitializionThread();
6060
Q_INVOKABLE void requestShutdown();
6161

62+
Q_INVOKABLE void initializeSnapshot(bool initLoadSnapshot, QString path_file);
63+
Q_INVOKABLE bool snapshotLoad(QString path_file) const { return m_node.snapshotLoad(path_file.toStdString()); }
64+
6265
void startShutdownPolling();
6366
void stopShutdownPolling();
6467

@@ -77,6 +80,8 @@ public Q_SLOTS:
7780

7881
void setTimeRatioList(int new_time);
7982
void setTimeRatioListInitial();
83+
void initializationFinished();
84+
void snapshotLoaded(bool result);
8085

8186
protected:
8287
void timerEvent(QTimerEvent* event) override;

src/qml/pages/node/NodeRunner.qml

+17-1
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,27 @@ Page {
2222
}
2323
}
2424

25-
Component.onCompleted: nodeModel.startNodeInitializionThread();
25+
Component.onCompleted: {
26+
nodeModel.startNodeInitializionThread();
27+
// TODO: Fix this so that it works once storing the snapshot path is implemented
28+
// nodeModel.initializationFinished.connect(onInitializationFinished);
29+
nodeModel.initializationFinished.connect(function() {
30+
console.log("Initialization finished, initializing snapshot...")
31+
});
32+
}
2633

2734
BlockClock {
2835
parentWidth: parent.width - 40
2936
parentHeight: parent.height
3037
anchors.centerIn: parent
3138
}
39+
40+
function onInitializationFinished() {
41+
// TODO: Fix this so that it works once storing the snapshot path is implemented
42+
console.log("Initialization finished, initializing snapshot...")
43+
// if (optionsModel.getLoadUtxo() && !optionsModel.getSnapshotLoaded()) {
44+
// nodeModel.initializeSnapshot(true, optionsModel.getSnapshotDirectory());
45+
// optionsModel.setSnapshotLoaded(true);
46+
// }
47+
}
3248
}

src/qml/pages/onboarding/OnboardingConnection.qml

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Page {
1717
anchors.fill: parent
1818
interactive: false
1919
orientation: Qt.Vertical
20+
// TODO: Remove this once storing the snapshot path is implemented
21+
property bool isOnboarding: true
2022
InformationPage {
2123
navLeftDetail: NavButton {
2224
iconSource: "image://images/caret-left"
@@ -49,6 +51,8 @@ Page {
4951
buttonMargin: 20
5052
}
5153
SettingsConnection {
54+
// TODO: Remove this once storing the snapshot path is implemented
55+
isOnboarding: connections.isOnboarding
5256
navRightDetail: NavButton {
5357
text: qsTr("Done")
5458
onClicked: {

src/qml/pages/settings/SettingsConnection.qml

+8-1
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,17 @@ Item {
1414
property alias navLeftDetail: connectionSwipe.navLeftDetail
1515
property alias showHeader: connectionSwipe.showHeader
1616

17+
// TODO: Remove this once storing the snapshot path is implemented
18+
property bool isOnboarding: false
19+
1720
function setSnapshotImported(imported) {
1821
connection_settings.loadedDetailItem.setSnapshotImported(imported)
1922
}
2023

2124
SwipeView {
2225
id: connectionSwipe
26+
// TODO: Remove this once storing the snapshot path is implemented
27+
property bool isOnboarding: parent.isOnboarding
2328
property alias navRightDetail: connection_settings.navRightDetail
2429
property alias navMiddleDetail: connection_settings.navMiddleDetail
2530
property alias navLeftDetail: connection_settings.navLeftDetail
@@ -36,7 +41,9 @@ Item {
3641
headerText: qsTr("Connection settings")
3742
headerMargin: 0
3843
detailActive: true
39-
detailItem: ConnectionSettings {}
44+
detailItem: ConnectionSettings {
45+
isOnboarding: connectionSwipe.isOnboarding
46+
}
4047
}
4148
SettingsProxy {
4249
onBackClicked: {

0 commit comments

Comments
 (0)