Skip to content

Commit

Permalink
wip: Add initial support for loading signet UTXO snapshots
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
D33r-Gee committed Oct 16, 2024
1 parent 1d08d2c commit b77f7c1
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 26 deletions.
3 changes: 3 additions & 0 deletions src/interfaces/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ class Node
//! List rpc commands.
virtual std::vector<std::string> listRpcCommands() = 0;

//! Load UTXO Snapshot.
virtual bool snapshotLoad(const std::string& path_string) = 0;

//! Set RPC timer interface if unset.
virtual void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) = 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
62 changes: 61 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 @@ -395,6 +396,65 @@ class NodeImpl : public Node
{
m_context = context;
}
bool snapshotLoad(const std::string& path_string) override
{
const fs::path path = fs::u8path(path_string);
if (!fs::exists(path)) {
LogPrintf("[loadsnapshot] Snapshot file %s does not exist\n", path.u8string());
return false;
}

AutoFile afile{fsbridge::fopen(path, "rb")};
if (afile.IsNull()) {
LogPrintf("[loadsnapshot] Failed to open snapshot file %s\n", path.u8string());
return false;
}

SnapshotMetadata metadata;
try {
afile >> metadata;
} catch (const std::exception& e) {
LogPrintf("[loadsnapshot] Failed to read snapshot metadata: %s\n", e.what());
return false;
}

const uint256& base_blockhash = metadata.m_base_blockhash;
LogPrintf("[loadsnapshot] Waiting for blockheader %s in headers chain before snapshot activation\n",
base_blockhash.ToString());

if (!m_context->chainman) {
LogPrintf("[loadsnapshot] Chainman is null\n");
return false;
}

ChainstateManager& chainman = *m_context->chainman;
CBlockIndex* snapshot_start_block = nullptr;

// Wait for the block to appear in the block index
constexpr int max_wait_seconds = 600; // 10 minutes
for (int i = 0; i < max_wait_seconds; ++i) {
snapshot_start_block = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(base_blockhash));
if (snapshot_start_block) break;
std::this_thread::sleep_for(std::chrono::seconds(1));
}

if (!snapshot_start_block) {
LogPrintf("[loadsnapshot] Timed out waiting for snapshot start blockheader %s\n", base_blockhash.ToString());
return false;
}

// Activate the snapshot
if (!chainman.ActivateSnapshot(afile, metadata, false)) {
LogPrintf("[loadsnapshot] Unable to load UTXO snapshot %s\n", path.u8string());
return false;
}

CBlockIndex* new_tip = WITH_LOCK(::cs_main, return chainman.ActiveTip());
LogPrintf("[loadsnapshot] Loaded %d coins from snapshot %s at height %d\n",
metadata.m_coins_count, new_tip->GetBlockHash().ToString(), new_tip->nHeight);

return true;
}
ArgsManager& args() { return *Assert(Assert(m_context)->args); }
ChainstateManager& chainman() { return *Assert(m_context->chainman); }
NodeContext* m_context{nullptr};
Expand Down Expand Up @@ -808,4 +868,4 @@ class ChainImpl : public Chain
namespace interfaces {
std::unique_ptr<Node> MakeNode(node::NodeContext& context) { return std::make_unique<node::NodeImpl>(context); }
std::unique_ptr<Chain> MakeChain(node::NodeContext& context) { return std::make_unique<node::ChainImpl>(context); }
} // namespace interfaces
} // namespace interfaces
57 changes: 37 additions & 20 deletions src/qml/components/ConnectionSettings.qml
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,53 @@ import QtQuick.Layouts 1.15
import "../controls"

ColumnLayout {
// TODO: Remove this once storing the snapshot path is implemented
property bool isOnboarding: false
property bool snapshotImported: false
function setSnapshotImported(imported) {
snapshotImported = imported
}
spacing: 4
Setting {
id: gotoSnapshot
Item {
// TODO: Remove this once storing the snapshot path is implemented
visible: !isOnboarding
height: visible ? implicitHeight : 0
Layout.fillWidth: true
header: qsTr("Load snapshot")
description: qsTr("Instant use with background sync")
actionItem: Item {
width: 26
height: 26
CaretRightIcon {
anchors.centerIn: parent
visible: !snapshotImported
color: gotoSnapshot.stateColor
Layout.preferredHeight: gotoSnapshot.height

Setting {
id: gotoSnapshot
visible: parent.visible
Layout.fillWidth: true
header: qsTr("Load snapshot")
description: qsTr("Instant use with background sync")
actionItem: Item {
width: 26
height: 26
CaretRightIcon {
// TODO: aligment will be fixed once Onboarding snapshot works
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
visible: !snapshotImported
color: gotoSnapshot.stateColor
}
GreenCheckIcon {
anchors.centerIn: parent
visible: snapshotImported
color: Theme.color.transparent
}
}
GreenCheckIcon {
anchors.centerIn: parent
visible: snapshotImported
color: Theme.color.transparent
onClicked: {
connectionSwipe.incrementCurrentIndex()
connectionSwipe.incrementCurrentIndex()
}
}
onClicked: {
connectionSwipe.incrementCurrentIndex()
connectionSwipe.incrementCurrentIndex()
}
}
Separator { Layout.fillWidth: true }
Separator {
Layout.fillWidth: true
// TODO: Remove this once storing the snapshot path is implemented
visible: !isOnboarding
}
Setting {
Layout.fillWidth: true
header: qsTr("Enable listening")
Expand Down
25 changes: 22 additions & 3 deletions src/qml/components/SnapshotSettings.qml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Dialogs 1.3


import "../controls"

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


// TODO: Remove simulation timer before release
Timer {
id: snapshotSimulationTimer
interval: 50 // Update every 50ms
Expand Down Expand Up @@ -78,8 +80,25 @@ ColumnLayout {
Layout.alignment: Qt.AlignCenter
text: qsTr("Choose snapshot file")
onClicked: {
settingsStack.currentIndex = 1
snapshotSimulationTimer.start()
// TODO: Connect this to snapshot loading
// settingsStack.currentIndex = 1
fileDialog.open()
}
}

FileDialog {
id: fileDialog
folder: shortcuts.home
selectMultiple: false
onAccepted: {
console.log("File chosen:", fileDialog.fileUrls)
var snapshotFileName = fileDialog.fileUrl.toString()
console.log("Snapshot file name:", snapshotFileName)
if (snapshotFileName.endsWith(".dat")) {
// optionsModel.setSnapshotDirectory(snapshotFileName)
// console.log("Snapshot directory set:", optionsModel.getSnapshotDirectory())
nodeModel.initializeSnapshot(true, snapshotFileName)
}
}
}
}
Expand Down
27 changes: 27 additions & 0 deletions src/qml/models/nodemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
#include <QMetaObject>
#include <QTimerEvent>
#include <QString>
#include <QThread>
#include <QUrl>
#include <QDebug>

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

Q_EMIT setTimeRatioListInitial();
// TODO: fix this so that it works once storing the snapshot path is implemented
Q_EMIT initializationFinished();
}

void NodeModel::startShutdownPolling()
Expand Down Expand Up @@ -166,3 +171,25 @@ void NodeModel::ConnectToNumConnectionsChangedSignal()
setNumOutboundPeers(new_num_peers.outbound_full_relay + new_num_peers.block_relay);
});
}

// Loads a snapshot from a given path using FileDialog
void NodeModel::initializeSnapshot(bool initLoadSnapshot, QString path_file) {
if (initLoadSnapshot) {
// TODO: this is to deal with FileDialog returning a QUrl
path_file = QUrl(path_file).toLocalFile();
// TODO: Remove this before release
// qDebug() << "path_file: " << path_file;
QThread* snapshot_thread = new QThread();

// Capture path_file by value
auto lambda = [this, path_file]() {
bool result = this->snapshotLoad(path_file);
Q_EMIT snapshotLoaded(result);
};

connect(snapshot_thread, &QThread::started, lambda);
connect(snapshot_thread, &QThread::finished, snapshot_thread, &QThread::deleteLater);

snapshot_thread->start();
}
}
5 changes: 5 additions & 0 deletions src/qml/models/nodemodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ class NodeModel : public QObject
Q_INVOKABLE void startNodeInitializionThread();
Q_INVOKABLE void requestShutdown();

Q_INVOKABLE void initializeSnapshot(bool initLoadSnapshot, QString path_file);
Q_INVOKABLE bool snapshotLoad(QString path_file) const { return m_node.snapshotLoad(path_file.toStdString()); }

void startShutdownPolling();
void stopShutdownPolling();

Expand All @@ -77,6 +80,8 @@ public Q_SLOTS:

void setTimeRatioList(int new_time);
void setTimeRatioListInitial();
void initializationFinished();
void snapshotLoaded(bool result);

protected:
void timerEvent(QTimerEvent* event) override;
Expand Down
18 changes: 17 additions & 1 deletion src/qml/pages/node/NodeRunner.qml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,27 @@ Page {
}
}

Component.onCompleted: nodeModel.startNodeInitializionThread();
Component.onCompleted: {
nodeModel.startNodeInitializionThread();
// TODO: Fix this so that it works once storing the snapshot path is implemented
// nodeModel.initializationFinished.connect(onInitializationFinished);
nodeModel.initializationFinished.connect(function() {
console.log("Initialization finished, initializing snapshot...")
});
}

BlockClock {
parentWidth: parent.width - 40
parentHeight: parent.height
anchors.centerIn: parent
}

function onInitializationFinished() {
// TODO: Fix this so that it works once storing the snapshot path is implemented
console.log("Initialization finished, initializing snapshot...")
// if (optionsModel.getLoadUtxo() && !optionsModel.getSnapshotLoaded()) {
// nodeModel.initializeSnapshot(true, optionsModel.getSnapshotDirectory());
// optionsModel.setSnapshotLoaded(true);
// }
}
}
4 changes: 4 additions & 0 deletions src/qml/pages/onboarding/OnboardingConnection.qml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Page {
anchors.fill: parent
interactive: false
orientation: Qt.Vertical
// TODO: Remove this once storing the snapshot path is implemented
property bool isOnboarding: true
InformationPage {
navLeftDetail: NavButton {
iconSource: "image://images/caret-left"
Expand Down Expand Up @@ -49,6 +51,8 @@ Page {
buttonMargin: 20
}
SettingsConnection {
// TODO: Remove this once storing the snapshot path is implemented
isOnboarding: connections.isOnboarding
navRightDetail: NavButton {
text: qsTr("Done")
onClicked: {
Expand Down
9 changes: 8 additions & 1 deletion src/qml/pages/settings/SettingsConnection.qml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ Item {
property alias navLeftDetail: connectionSwipe.navLeftDetail
property alias showHeader: connectionSwipe.showHeader

// TODO: Remove this once storing the snapshot path is implemented
property bool isOnboarding: false

function setSnapshotImported(imported) {
connection_settings.loadedDetailItem.setSnapshotImported(imported)
}

SwipeView {
id: connectionSwipe
// TODO: Remove this once storing the snapshot path is implemented
property bool isOnboarding: parent.isOnboarding
property alias navRightDetail: connection_settings.navRightDetail
property alias navMiddleDetail: connection_settings.navMiddleDetail
property alias navLeftDetail: connection_settings.navLeftDetail
Expand All @@ -36,7 +41,9 @@ Item {
headerText: qsTr("Connection settings")
headerMargin: 0
detailActive: true
detailItem: ConnectionSettings {}
detailItem: ConnectionSettings {
isOnboarding: connectionSwipe.isOnboarding
}
}
SettingsProxy {
onBackClicked: {
Expand Down

0 comments on commit b77f7c1

Please sign in to comment.