Skip to content

Commit eb923ff

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 to src/node/interfaces.cpp Also modifies src/validation.cpp to enable showing the snapshot loading progress. And touches 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 working via notifications Testing: 1. Start the node 2. Complete onboarding 3. Navigate to connection settings 4. Load snapshot from provided interface
1 parent b24266c commit eb923ff

17 files changed

+248
-58
lines changed

src/interfaces/node.h

+8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <net_types.h> // For banmap_t
1212
#include <netaddress.h> // For Network
1313
#include <netbase.h> // For ConnectionDirection
14+
#include <node/utxo_snapshot.h> // For SnapshotMetadata
1415
#include <support/allocators/secure.h> // For SecureString
1516
#include <util/translation.h>
1617

@@ -199,6 +200,9 @@ class Node
199200
//! List rpc commands.
200201
virtual std::vector<std::string> listRpcCommands() = 0;
201202

203+
//! Load UTXO Snapshot.
204+
virtual bool loadSnapshot(AutoFile& afile, const node::SnapshotMetadata& metadata, bool in_memory) = 0;
205+
202206
//! Set RPC timer interface if unset.
203207
virtual void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) = 0;
204208

@@ -234,6 +238,10 @@ class Node
234238
using ShowProgressFn = std::function<void(const std::string& title, int progress, bool resume_possible)>;
235239
virtual std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) = 0;
236240

241+
//! Register handler for snapshot load progress.
242+
using SnapshotLoadProgressFn = std::function<void(double progress)>;
243+
virtual std::unique_ptr<Handler> handleSnapshotLoadProgress(SnapshotLoadProgressFn fn) = 0;
244+
237245
//! Register handler for wallet loader constructed messages.
238246
using InitWalletFn = std::function<void()>;
239247
virtual std::unique_ptr<Handler> handleInitWallet(InitWalletFn fn) = 0;

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/kernel/notifications_interface.h

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class Notifications
4040
[[nodiscard]] virtual InterruptResult blockTip(SynchronizationState state, CBlockIndex& index) { return {}; }
4141
virtual void headerTip(SynchronizationState state, int64_t height, int64_t timestamp, bool presync) {}
4242
virtual void progress(const bilingual_str& title, int progress_percent, bool resume_possible) {}
43+
virtual void snapshotLoadProgress(double progress) {}
4344
virtual void warning(const bilingual_str& warning) {}
4445

4546
//! The flush error notification is sent to notify the user that an error

src/node/interface_ui.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ struct UISignals {
2121
boost::signals2::signal<CClientUIInterface::NotifyNetworkActiveChangedSig> NotifyNetworkActiveChanged;
2222
boost::signals2::signal<CClientUIInterface::NotifyAlertChangedSig> NotifyAlertChanged;
2323
boost::signals2::signal<CClientUIInterface::ShowProgressSig> ShowProgress;
24+
boost::signals2::signal<CClientUIInterface::SnapshotLoadProgressSig> SnapshotLoadProgress;
2425
boost::signals2::signal<CClientUIInterface::NotifyBlockTipSig> NotifyBlockTip;
2526
boost::signals2::signal<CClientUIInterface::NotifyHeaderTipSig> NotifyHeaderTip;
2627
boost::signals2::signal<CClientUIInterface::BannedListChangedSig> BannedListChanged;
@@ -44,6 +45,7 @@ ADD_SIGNALS_IMPL_WRAPPER(ShowProgress);
4445
ADD_SIGNALS_IMPL_WRAPPER(NotifyBlockTip);
4546
ADD_SIGNALS_IMPL_WRAPPER(NotifyHeaderTip);
4647
ADD_SIGNALS_IMPL_WRAPPER(BannedListChanged);
48+
ADD_SIGNALS_IMPL_WRAPPER(SnapshotLoadProgress);
4749

4850
bool CClientUIInterface::ThreadSafeMessageBox(const bilingual_str& message, const std::string& caption, unsigned int style) { return g_ui_signals.ThreadSafeMessageBox(message, caption, style).value_or(false);}
4951
bool CClientUIInterface::ThreadSafeQuestion(const bilingual_str& message, const std::string& non_interactive_message, const std::string& caption, unsigned int style) { return g_ui_signals.ThreadSafeQuestion(message, non_interactive_message, caption, style).value_or(false);}
@@ -53,6 +55,7 @@ void CClientUIInterface::NotifyNumConnectionsChanged(PeersNumByType newNumConnec
5355
void CClientUIInterface::NotifyNetworkActiveChanged(bool networkActive) { return g_ui_signals.NotifyNetworkActiveChanged(networkActive); }
5456
void CClientUIInterface::NotifyAlertChanged() { return g_ui_signals.NotifyAlertChanged(); }
5557
void CClientUIInterface::ShowProgress(const std::string& title, int nProgress, bool resume_possible) { return g_ui_signals.ShowProgress(title, nProgress, resume_possible); }
58+
void CClientUIInterface::SnapshotLoadProgress(double progress) { return g_ui_signals.SnapshotLoadProgress(progress); }
5659
void CClientUIInterface::NotifyBlockTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyBlockTip(s, i); }
5760
void CClientUIInterface::NotifyHeaderTip(SynchronizationState s, int64_t height, int64_t timestamp, bool presync) { return g_ui_signals.NotifyHeaderTip(s, height, timestamp, presync); }
5861
void CClientUIInterface::BannedListChanged() { return g_ui_signals.BannedListChanged(); }

src/node/interface_ui.h

+3
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ class CClientUIInterface
109109
*/
110110
ADD_SIGNALS_DECL_WRAPPER(ShowProgress, void, const std::string& title, int nProgress, bool resume_possible);
111111

112+
/** Snapshot load progress. */
113+
ADD_SIGNALS_DECL_WRAPPER(SnapshotLoadProgress, void, double progress);
114+
112115
/** New block has been accepted */
113116
ADD_SIGNALS_DECL_WRAPPER(NotifyBlockTip, void, SynchronizationState, const CBlockIndex*);
114117

src/node/interfaces.cpp

+10-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>
@@ -356,6 +357,10 @@ class NodeImpl : public Node
356357
{
357358
return MakeSignalHandler(::uiInterface.ShowProgress_connect(fn));
358359
}
360+
std::unique_ptr<Handler> handleSnapshotLoadProgress(SnapshotLoadProgressFn fn) override
361+
{
362+
return MakeSignalHandler(::uiInterface.SnapshotLoadProgress_connect(fn));
363+
}
359364
std::unique_ptr<Handler> handleInitWallet(InitWalletFn fn) override
360365
{
361366
return MakeSignalHandler(::uiInterface.InitWallet_connect(fn));
@@ -395,6 +400,10 @@ class NodeImpl : public Node
395400
{
396401
m_context = context;
397402
}
403+
bool loadSnapshot(AutoFile& afile, const node::SnapshotMetadata& metadata, bool in_memory) override
404+
{
405+
return chainman().ActivateSnapshot(afile, metadata, in_memory);
406+
}
398407
ArgsManager& args() { return *Assert(Assert(m_context)->args); }
399408
ChainstateManager& chainman() { return *Assert(m_context->chainman); }
400409
NodeContext* m_context{nullptr};
@@ -510,7 +519,7 @@ class RpcHandlerImpl : public Handler
510519
class ChainImpl : public Chain
511520
{
512521
public:
513-
explicit ChainImpl(NodeContext& node) : m_node(node) {}
522+
explicit ChainImpl(node::NodeContext& node) : m_node(node) {}
514523
std::optional<int> getHeight() override
515524
{
516525
const int height{WITH_LOCK(::cs_main, return chainman().ActiveChain().Height())};

src/node/kernel_notifications.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ void KernelNotifications::progress(const bilingual_str& title, int progress_perc
7878
uiInterface.ShowProgress(title.translated, progress_percent, resume_possible);
7979
}
8080

81+
void KernelNotifications::snapshotLoadProgress(double progress)
82+
{
83+
uiInterface.SnapshotLoadProgress(progress);
84+
}
85+
8186
void KernelNotifications::warning(const bilingual_str& warning)
8287
{
8388
DoWarning(warning);

src/node/kernel_notifications.h

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class KernelNotifications : public kernel::Notifications
3131

3232
void progress(const bilingual_str& title, int progress_percent, bool resume_possible) override;
3333

34+
void snapshotLoadProgress(double progress) override;
35+
3436
void warning(const bilingual_str& warning) override;
3537

3638
void flushError(const std::string& debug_message) override;

src/qml/components/ConnectionSettings.qml

+10-7
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ ColumnLayout {
1111
id: root
1212
signal next
1313
signal gotoSnapshot
14-
property bool snapshotImported: false
15-
function setSnapshotImported(imported) {
16-
snapshotImported = imported
17-
}
14+
property bool snapshotImportCompleted: chainModel.isSnapshotActive
15+
property bool onboarding: false
16+
1817
spacing: 4
1918
Setting {
2019
id: gotoSnapshot
20+
visible: !root.onboarding
2121
Layout.fillWidth: true
2222
header: qsTr("Load snapshot")
2323
description: qsTr("Instant use with background sync")
@@ -26,19 +26,22 @@ ColumnLayout {
2626
height: 26
2727
CaretRightIcon {
2828
anchors.centerIn: parent
29-
visible: !snapshotImported
29+
visible: !snapshotImportCompleted
3030
color: gotoSnapshot.stateColor
3131
}
3232
GreenCheckIcon {
3333
anchors.centerIn: parent
34-
visible: snapshotImported
34+
visible: snapshotImportCompleted
3535
color: Theme.color.transparent
3636
size: 30
3737
}
3838
}
3939
onClicked: root.gotoSnapshot()
4040
}
41-
Separator { Layout.fillWidth: true }
41+
Separator {
42+
visible: !root.onboarding
43+
Layout.fillWidth: true
44+
}
4245
Setting {
4346
Layout.fillWidth: true
4447
header: qsTr("Enable listening")

src/qml/components/SnapshotSettings.qml

+40-39
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: (snapshotVerified || snapshotLoaded) ? chainModel.getSnapshotInfo() : ({})
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.snapshotLoadThread(snapshotFileName)
8377
}
8478
}
79+
// TODO: Handle file error signal
8580
}
8681

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

107103
ProgressIndicator {
108104
id: progressIndicator
109105
Layout.topMargin: 20
110106
width: 200
111107
height: 20
112-
progress: snapshotVerificationProgress
108+
progress: nodeModel.snapshotProgress
113109
Layout.alignment: Qt.AlignCenter
114110
progressColor: Theme.color.blue
115111
}
@@ -137,8 +133,11 @@ ColumnLayout {
137133
descriptionColor: Theme.color.neutral6
138134
descriptionSize: 17
139135
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.")
136+
description: snapshotInfo && snapshotInfo["date"] ?
137+
qsTr("It contains transactions up to %1. Newer transactions still need to be downloaded." +
138+
" The data will be verified in the background.").arg(snapshotInfo["date"]) :
139+
qsTr("It contains transactions up to DEBUG. Newer transactions still need to be downloaded." +
140+
" The data will be verified in the background.")
142141
}
143142

144143
ContinueButton {
@@ -147,9 +146,8 @@ ColumnLayout {
147146
Layout.alignment: Qt.AlignCenter
148147
text: qsTr("Done")
149148
onClicked: {
150-
snapshotImportCompleted()
151-
connectionSwipe.decrementCurrentIndex()
152-
connectionSwipe.decrementCurrentIndex()
149+
chainModel.isSnapshotActiveChanged()
150+
back()
153151
}
154152
}
155153

@@ -188,14 +186,17 @@ ColumnLayout {
188186
font.pixelSize: 14
189187
}
190188
CoreText {
191-
text: qsTr("200,000")
189+
text: snapshotInfo && snapshotInfo["height"] ?
190+
snapshotInfo["height"] : qsTr("DEBUG")
192191
Layout.alignment: Qt.AlignRight
193192
font.pixelSize: 14
194193
}
195194
}
196195
Separator { Layout.fillWidth: true }
197196
CoreText {
198-
text: qsTr("Hash: 0x1234567890abcdef...")
197+
text: snapshotInfo && snapshotInfo["hashSerialized"] ?
198+
qsTr("Hash: %1").arg(snapshotInfo["hashSerialized"].substring(0, 13) + "...") :
199+
qsTr("Hash: DEBUG")
199200
font.pixelSize: 14
200201
}
201202
}

src/qml/models/chainmodel.cpp

+23
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,22 @@ void ChainModel::setCurrentTimeRatio()
101105

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

0 commit comments

Comments
 (0)