Skip to content

Commit

Permalink
wip: Add minimal support for loading signet UTXO snapshots
Browse files Browse the repository at this point in the history
        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
  • Loading branch information
D33r-Gee committed Jan 6, 2025
1 parent b24266c commit eb923ff
Show file tree
Hide file tree
Showing 17 changed files with 248 additions and 58 deletions.
8 changes: 8 additions & 0 deletions src/interfaces/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <net_types.h> // For banmap_t
#include <netaddress.h> // For Network
#include <netbase.h> // For ConnectionDirection
#include <node/utxo_snapshot.h> // For SnapshotMetadata
#include <support/allocators/secure.h> // For SecureString
#include <util/translation.h>

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

//! Load UTXO Snapshot.
virtual bool loadSnapshot(AutoFile& afile, const node::SnapshotMetadata& metadata, bool in_memory) = 0;

//! Set RPC timer interface if unset.
virtual void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) = 0;

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

//! Register handler for snapshot load progress.
using SnapshotLoadProgressFn = std::function<void(double progress)>;
virtual std::unique_ptr<Handler> handleSnapshotLoadProgress(SnapshotLoadProgressFn fn) = 0;

//! Register handler for wallet loader constructed messages.
using InitWalletFn = std::function<void()>;
virtual std::unique_ptr<Handler> handleInitWallet(InitWalletFn fn) = 0;
Expand Down
7 changes: 7 additions & 0 deletions src/kernel/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,13 @@ class SigNetParams : public CChainParams {

vFixedSeeds.clear();

m_assumeutxo_data = MapAssumeutxo{
{
160000,
{AssumeutxoHash{uint256S("0x5225141cb62dee63ab3be95f9b03d60801f264010b1816d4bd00618b2736e7be")}, 1278002},
},
};

base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,111);
base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,196);
base58Prefixes[SECRET_KEY] = std::vector<unsigned char>(1,239);
Expand Down
1 change: 1 addition & 0 deletions src/kernel/notifications_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Notifications
[[nodiscard]] virtual InterruptResult blockTip(SynchronizationState state, CBlockIndex& index) { return {}; }
virtual void headerTip(SynchronizationState state, int64_t height, int64_t timestamp, bool presync) {}
virtual void progress(const bilingual_str& title, int progress_percent, bool resume_possible) {}
virtual void snapshotLoadProgress(double progress) {}
virtual void warning(const bilingual_str& warning) {}

//! The flush error notification is sent to notify the user that an error
Expand Down
3 changes: 3 additions & 0 deletions src/node/interface_ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct UISignals {
boost::signals2::signal<CClientUIInterface::NotifyNetworkActiveChangedSig> NotifyNetworkActiveChanged;
boost::signals2::signal<CClientUIInterface::NotifyAlertChangedSig> NotifyAlertChanged;
boost::signals2::signal<CClientUIInterface::ShowProgressSig> ShowProgress;
boost::signals2::signal<CClientUIInterface::SnapshotLoadProgressSig> SnapshotLoadProgress;
boost::signals2::signal<CClientUIInterface::NotifyBlockTipSig> NotifyBlockTip;
boost::signals2::signal<CClientUIInterface::NotifyHeaderTipSig> NotifyHeaderTip;
boost::signals2::signal<CClientUIInterface::BannedListChangedSig> BannedListChanged;
Expand All @@ -44,6 +45,7 @@ ADD_SIGNALS_IMPL_WRAPPER(ShowProgress);
ADD_SIGNALS_IMPL_WRAPPER(NotifyBlockTip);
ADD_SIGNALS_IMPL_WRAPPER(NotifyHeaderTip);
ADD_SIGNALS_IMPL_WRAPPER(BannedListChanged);
ADD_SIGNALS_IMPL_WRAPPER(SnapshotLoadProgress);

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);}
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);}
Expand All @@ -53,6 +55,7 @@ void CClientUIInterface::NotifyNumConnectionsChanged(PeersNumByType newNumConnec
void CClientUIInterface::NotifyNetworkActiveChanged(bool networkActive) { return g_ui_signals.NotifyNetworkActiveChanged(networkActive); }
void CClientUIInterface::NotifyAlertChanged() { return g_ui_signals.NotifyAlertChanged(); }
void CClientUIInterface::ShowProgress(const std::string& title, int nProgress, bool resume_possible) { return g_ui_signals.ShowProgress(title, nProgress, resume_possible); }
void CClientUIInterface::SnapshotLoadProgress(double progress) { return g_ui_signals.SnapshotLoadProgress(progress); }
void CClientUIInterface::NotifyBlockTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyBlockTip(s, i); }
void CClientUIInterface::NotifyHeaderTip(SynchronizationState s, int64_t height, int64_t timestamp, bool presync) { return g_ui_signals.NotifyHeaderTip(s, height, timestamp, presync); }
void CClientUIInterface::BannedListChanged() { return g_ui_signals.BannedListChanged(); }
Expand Down
3 changes: 3 additions & 0 deletions src/node/interface_ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ class CClientUIInterface
*/
ADD_SIGNALS_DECL_WRAPPER(ShowProgress, void, const std::string& title, int nProgress, bool resume_possible);

/** Snapshot load progress. */
ADD_SIGNALS_DECL_WRAPPER(SnapshotLoadProgress, void, double progress);

/** New block has been accepted */
ADD_SIGNALS_DECL_WRAPPER(NotifyBlockTip, void, SynchronizationState, const CBlockIndex*);

Expand Down
11 changes: 10 additions & 1 deletion src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <node/context.h>
#include <node/interface_ui.h>
#include <node/transaction.h>
#include <node/utxo_snapshot.h>
#include <policy/feerate.h>
#include <policy/fees.h>
#include <policy/policy.h>
Expand Down Expand Up @@ -356,6 +357,10 @@ class NodeImpl : public Node
{
return MakeSignalHandler(::uiInterface.ShowProgress_connect(fn));
}
std::unique_ptr<Handler> handleSnapshotLoadProgress(SnapshotLoadProgressFn fn) override
{
return MakeSignalHandler(::uiInterface.SnapshotLoadProgress_connect(fn));
}
std::unique_ptr<Handler> handleInitWallet(InitWalletFn fn) override
{
return MakeSignalHandler(::uiInterface.InitWallet_connect(fn));
Expand Down Expand Up @@ -395,6 +400,10 @@ class NodeImpl : public Node
{
m_context = context;
}
bool loadSnapshot(AutoFile& afile, const node::SnapshotMetadata& metadata, bool in_memory) override
{
return chainman().ActivateSnapshot(afile, metadata, in_memory);
}
ArgsManager& args() { return *Assert(Assert(m_context)->args); }
ChainstateManager& chainman() { return *Assert(m_context->chainman); }
NodeContext* m_context{nullptr};
Expand Down Expand Up @@ -510,7 +519,7 @@ class RpcHandlerImpl : public Handler
class ChainImpl : public Chain
{
public:
explicit ChainImpl(NodeContext& node) : m_node(node) {}
explicit ChainImpl(node::NodeContext& node) : m_node(node) {}
std::optional<int> getHeight() override
{
const int height{WITH_LOCK(::cs_main, return chainman().ActiveChain().Height())};
Expand Down
5 changes: 5 additions & 0 deletions src/node/kernel_notifications.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ void KernelNotifications::progress(const bilingual_str& title, int progress_perc
uiInterface.ShowProgress(title.translated, progress_percent, resume_possible);
}

void KernelNotifications::snapshotLoadProgress(double progress)
{
uiInterface.SnapshotLoadProgress(progress);
}

void KernelNotifications::warning(const bilingual_str& warning)
{
DoWarning(warning);
Expand Down
2 changes: 2 additions & 0 deletions src/node/kernel_notifications.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class KernelNotifications : public kernel::Notifications

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

void snapshotLoadProgress(double progress) override;

void warning(const bilingual_str& warning) override;

void flushError(const std::string& debug_message) override;
Expand Down
17 changes: 10 additions & 7 deletions src/qml/components/ConnectionSettings.qml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ ColumnLayout {
id: root
signal next
signal gotoSnapshot
property bool snapshotImported: false
function setSnapshotImported(imported) {
snapshotImported = imported
}
property bool snapshotImportCompleted: chainModel.isSnapshotActive
property bool onboarding: false

spacing: 4
Setting {
id: gotoSnapshot
visible: !root.onboarding
Layout.fillWidth: true
header: qsTr("Load snapshot")
description: qsTr("Instant use with background sync")
Expand All @@ -26,19 +26,22 @@ ColumnLayout {
height: 26
CaretRightIcon {
anchors.centerIn: parent
visible: !snapshotImported
visible: !snapshotImportCompleted
color: gotoSnapshot.stateColor
}
GreenCheckIcon {
anchors.centerIn: parent
visible: snapshotImported
visible: snapshotImportCompleted
color: Theme.color.transparent
size: 30
}
}
onClicked: root.gotoSnapshot()
}
Separator { Layout.fillWidth: true }
Separator {
visible: !root.onboarding
Layout.fillWidth: true
}
Setting {
Layout.fillWidth: true
header: qsTr("Enable listening")
Expand Down
79 changes: 40 additions & 39 deletions src/qml/components/SnapshotSettings.qml
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,28 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Dialogs 1.3

import "../controls"

ColumnLayout {
signal snapshotImportCompleted()
property int snapshotVerificationCycles: 0
property real snapshotVerificationProgress: 0
property bool snapshotVerified: false

id: columnLayout
signal back
property bool snapshotLoading: nodeModel.snapshotLoading
property bool snapshotLoaded: nodeModel.isSnapshotLoaded
property bool snapshotImportCompleted: chainModel.isSnapshotActive
property bool onboarding: false
property bool snapshotVerified: onboarding ? false : chainModel.isSnapshotActive
property string snapshotFileName: ""
property var snapshotInfo: (snapshotVerified || snapshotLoaded) ? chainModel.getSnapshotInfo() : ({})
property string selectedFile: ""

width: Math.min(parent.width, 450)
anchors.horizontalCenter: parent.horizontalCenter


Timer {
id: snapshotSimulationTimer
interval: 50 // Update every 50ms
running: false
repeat: true
onTriggered: {
if (snapshotVerificationProgress < 1) {
snapshotVerificationProgress += 0.01
} else {
snapshotVerificationCycles++
if (snapshotVerificationCycles < 1) {
snapshotVerificationProgress = 0
} else {
running = false
snapshotVerified = true
settingsStack.currentIndex = 2
}
}
}
}

StackLayout {
id: settingsStack
currentIndex: 0
currentIndex: onboarding ? 0 : snapshotLoaded ? 2 : snapshotVerified ? 2 : snapshotLoading ? 1 : 0

ColumnLayout {
Layout.alignment: Qt.AlignHCenter
Expand Down Expand Up @@ -77,11 +61,22 @@ ColumnLayout {
Layout.bottomMargin: 20
Layout.alignment: Qt.AlignCenter
text: qsTr("Choose snapshot file")
onClicked: {
settingsStack.currentIndex = 1
snapshotSimulationTimer.start()
onClicked: fileDialog.open()
}

FileDialog {
id: fileDialog
folder: shortcuts.home
selectMultiple: false
selectExisting: true
nameFilters: ["Snapshot files (*.dat)", "All files (*)"]
onAccepted: {
selectedFile = fileUrl.toString()
snapshotFileName = selectedFile
nodeModel.snapshotLoadThread(snapshotFileName)
}
}
// TODO: Handle file error signal
}

ColumnLayout {
Expand All @@ -102,14 +97,15 @@ ColumnLayout {
Layout.leftMargin: 20
Layout.rightMargin: 20
header: qsTr("Loading Snapshot")
description: qsTr("This might take a while...")
}

ProgressIndicator {
id: progressIndicator
Layout.topMargin: 20
width: 200
height: 20
progress: snapshotVerificationProgress
progress: nodeModel.snapshotProgress
Layout.alignment: Qt.AlignCenter
progressColor: Theme.color.blue
}
Expand Down Expand Up @@ -137,8 +133,11 @@ ColumnLayout {
descriptionColor: Theme.color.neutral6
descriptionSize: 17
descriptionLineHeight: 1.1
description: qsTr("It contains transactions up to January 12, 2024. Newer transactions still need to be downloaded." +
" The data will be verified in the background.")
description: snapshotInfo && snapshotInfo["date"] ?
qsTr("It contains transactions up to %1. Newer transactions still need to be downloaded." +
" The data will be verified in the background.").arg(snapshotInfo["date"]) :
qsTr("It contains transactions up to DEBUG. Newer transactions still need to be downloaded." +
" The data will be verified in the background.")
}

ContinueButton {
Expand All @@ -147,9 +146,8 @@ ColumnLayout {
Layout.alignment: Qt.AlignCenter
text: qsTr("Done")
onClicked: {
snapshotImportCompleted()
connectionSwipe.decrementCurrentIndex()
connectionSwipe.decrementCurrentIndex()
chainModel.isSnapshotActiveChanged()
back()
}
}

Expand Down Expand Up @@ -188,14 +186,17 @@ ColumnLayout {
font.pixelSize: 14
}
CoreText {
text: qsTr("200,000")
text: snapshotInfo && snapshotInfo["height"] ?
snapshotInfo["height"] : qsTr("DEBUG")
Layout.alignment: Qt.AlignRight
font.pixelSize: 14
}
}
Separator { Layout.fillWidth: true }
CoreText {
text: qsTr("Hash: 0x1234567890abcdef...")
text: snapshotInfo && snapshotInfo["hashSerialized"] ?
qsTr("Hash: %1").arg(snapshotInfo["hashSerialized"].substring(0, 13) + "...") :
qsTr("Hash: DEBUG")
font.pixelSize: 14
}
}
Expand Down
23 changes: 23 additions & 0 deletions src/qml/models/chainmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
#include <QThread>
#include <QTime>
#include <interfaces/chain.h>
#include <node/utxo_snapshot.h>
#include <kernel/chainparams.h>
#include <validation.h>

ChainModel::ChainModel(interfaces::Chain& chain)
: m_chain{chain}
// m_params{Params()}
{
QTimer* timer = new QTimer();
connect(timer, &QTimer::timeout, this, &ChainModel::setCurrentTimeRatio);
Expand Down Expand Up @@ -101,3 +105,22 @@ void ChainModel::setCurrentTimeRatio()

Q_EMIT timeRatioListChanged();
}

// TODO: Change this once a better solution has been found.
// Using hardcoded snapshot info to display in SnapshotSettings.qml
QVariantMap ChainModel::getSnapshotInfo() {
QVariantMap snapshot_info;

const MapAssumeutxo& valid_assumeutxos_map = Params().Assumeutxo();
if (!valid_assumeutxos_map.empty()) {
const int height = valid_assumeutxos_map.rbegin()->first;
const auto& hash_serialized = valid_assumeutxos_map.rbegin()->second.hash_serialized;
int64_t date = m_chain.getBlockTime(height);

snapshot_info["height"] = height;
snapshot_info["hashSerialized"] = QString::fromStdString(hash_serialized.ToString());
snapshot_info["date"] = QDateTime::fromSecsSinceEpoch(date).toString("MMMM d yyyy");
}

return snapshot_info;
}
Loading

0 comments on commit eb923ff

Please sign in to comment.