Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/interfaces/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <net_types.h>
#include <netaddress.h>
#include <netbase.h>
#include <interfaces/snapshot.h>
#include <node/utxo_snapshot.h>
#include <support/allocators/secure.h>
#include <util/translation.h>

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

//! UTXO Snapshot interface.
virtual std::unique_ptr<Snapshot> snapshot(const fs::path& path) = 0;

//! Get unspent output associated with a transaction.
virtual std::optional<Coin> getUnspentOutput(const COutPoint& output) = 0;

Expand Down Expand Up @@ -233,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
43 changes: 43 additions & 0 deletions src/interfaces/snapshot.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) 2024 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_INTERFACES_SNAPSHOT_H
#define BITCOIN_INTERFACES_SNAPSHOT_H

#include <memory>
#include <optional>
#include <uint256.h>

class CBlockIndex;
namespace node {
class SnapshotMetadata;
}

namespace interfaces {

//! Interface for managing UTXO snapshots.
class Snapshot
{
public:
virtual ~Snapshot() = default;

//! Activate the snapshot, making it the active chainstate.
virtual bool activate() = 0;

//! Get the snapshot metadata.
virtual const node::SnapshotMetadata& getMetadata() const = 0;

//! Get the activation result (block index of the snapshot base).
virtual std::optional<const CBlockIndex*> getActivationResult() const = 0;

//! Get the last error message from activation attempt.
virtual std::string getLastError() const = 0;

//! Get the path of the snapshot.
virtual fs::path getPath() const = 0;
};

} // namespace interfaces

#endif // BITCOIN_INTERFACES_SNAPSHOT_H
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, const CBlockIndex& index, double verification_progress) { 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 warningSet(Warning id, const bilingual_str& message) {}
virtual void warningUnset(Warning id) {}

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 @@ -23,6 +23,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 @@ -43,6 +44,7 @@ ADD_SIGNALS_IMPL_WRAPPER(NotifyNumConnectionsChanged);
ADD_SIGNALS_IMPL_WRAPPER(NotifyNetworkActiveChanged);
ADD_SIGNALS_IMPL_WRAPPER(NotifyAlertChanged);
ADD_SIGNALS_IMPL_WRAPPER(ShowProgress);
ADD_SIGNALS_IMPL_WRAPPER(SnapshotLoadProgress);
ADD_SIGNALS_IMPL_WRAPPER(NotifyBlockTip);
ADD_SIGNALS_IMPL_WRAPPER(NotifyHeaderTip);
ADD_SIGNALS_IMPL_WRAPPER(BannedListChanged);
Expand All @@ -55,6 +57,7 @@ void CClientUIInterface::NotifyNumConnectionsChanged(int newNumConnections) { re
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& block, double verification_progress) { return g_ui_signals.NotifyBlockTip(s, block, verification_progress); }
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 @@ -102,6 +102,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& block, double verification_progress);

Expand Down
83 changes: 83 additions & 0 deletions src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <interfaces/handler.h>
#include <interfaces/mining.h>
#include <interfaces/node.h>
#include <interfaces/snapshot.h>
#include <interfaces/types.h>
#include <interfaces/wallet.h>
#include <kernel/chain.h>
Expand All @@ -38,6 +39,7 @@
#include <node/kernel_notifications.h>
#include <node/transaction.h>
#include <node/types.h>
#include <node/utxo_snapshot.h>
#include <node/warnings.h>
#include <policy/feerate.h>
#include <policy/fees.h>
Expand Down Expand Up @@ -80,6 +82,7 @@ using interfaces::Handler;
using interfaces::MakeSignalHandler;
using interfaces::Mining;
using interfaces::Node;
using interfaces::Snapshot;
using interfaces::WalletLoader;
using node::BlockAssembler;
using node::BlockWaitOptions;
Expand All @@ -89,6 +92,78 @@ namespace node {
// All members of the classes in this namespace are intentionally public, as the
// classes themselves are private.
namespace {
class SnapshotImpl : public interfaces::Snapshot
{
public:
SnapshotImpl(ChainstateManager& chainman, const fs::path& path, bool in_memory)
: m_chainman(chainman), m_path(path), m_in_memory(in_memory), m_metadata(chainman.GetParams().MessageStart()) {}

bool activate() override
{
m_last_error.clear();

if (!fs::exists(m_path)) {
m_last_error = "Snapshot file does not exist";
return false;
}

FILE* snapshot_file{fsbridge::fopen(m_path, "rb")};
AutoFile afile{snapshot_file};
if (afile.IsNull()) {
m_last_error = "Unable to open snapshot file for reading";
return false;
}

try {
afile >> m_metadata;
} catch (const std::ios_base::failure& e) {
m_last_error = strprintf("Unable to parse metadata: %s", e.what());
return false;
}

auto result = m_chainman.ActivateSnapshot(afile, m_metadata, m_in_memory);
if (result.has_value()) {
m_activation_result = result.value();
return true;
} else {
m_activation_result = std::nullopt;
m_last_error = util::ErrorString(result).original;
return false;
}
}

const node::SnapshotMetadata& getMetadata() const override
{
return m_metadata;
}

std::optional<const CBlockIndex*> getActivationResult() const override
{
if (m_activation_result.has_value()) {
return m_activation_result;
}
return std::nullopt;
}

std::string getLastError() const override
{
return m_last_error;
}

fs::path getPath() const override
{
return m_path;
}

private:
ChainstateManager& m_chainman;
fs::path m_path;
bool m_in_memory;
node::SnapshotMetadata m_metadata;
std::optional<const CBlockIndex*> m_activation_result;
std::string m_last_error;
};

#ifdef ENABLE_EXTERNAL_SIGNER
class ExternalSignerImpl : public interfaces::ExternalSigner
{
Expand Down Expand Up @@ -356,6 +431,10 @@ class NodeImpl : public Node
return ::tableRPC.execute(req);
}
std::vector<std::string> listRpcCommands() override { return ::tableRPC.listCommands(); }
std::unique_ptr<interfaces::Snapshot> snapshot(const fs::path& path) override
{
return std::make_unique<SnapshotImpl>(chainman(), path, /*in_memory=*/ false);
}
std::optional<Coin> getUnspentOutput(const COutPoint& output) override
{
LOCK(::cs_main);
Expand Down Expand Up @@ -385,6 +464,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
5 changes: 5 additions & 0 deletions src/node/kernel_notifications.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,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::warningSet(kernel::Warning id, const bilingual_str& message)
{
if (m_warnings.Set(id, message)) {
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 @@ -41,6 +41,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 warningSet(kernel::Warning id, const bilingual_str& message) override;

void warningUnset(kernel::Warning id) override;
Expand Down
19 changes: 16 additions & 3 deletions src/qt/bitcoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
#include <interfaces/handler.h>
#include <interfaces/init.h>
#include <interfaces/node.h>
#include <interfaces/snapshot.h>
#include <logging.h>
#include <validation.h>
#include <node/context.h>
#include <node/interface_ui.h>
#include <noui.h>
Expand All @@ -35,7 +37,6 @@
#include <util/string.h>
#include <util/threadnames.h>
#include <util/translation.h>
#include <validation.h>

#ifdef ENABLE_WALLET
#include <qt/paymentserver.h>
Expand Down Expand Up @@ -319,6 +320,12 @@ void BitcoinApplication::InitPruneSetting(int64_t prune_MiB)
optionsModel->SetPruneTargetGB(PruneMiBtoGB(prune_MiB));
}

void BitcoinApplication::setSnapshotPath(const QString& snapshot_path)
{
qDebug() << "setSnapshotPath called with:" << snapshot_path;
m_snapshot_path = snapshot_path;
}

void BitcoinApplication::requestInitialize()
{
qDebug() << __func__ << ": Requesting initialize";
Expand Down Expand Up @@ -386,7 +393,7 @@ void BitcoinApplication::initializeResult(bool success, interfaces::BlockAndHead

// Log this only after AppInitMain finishes, as then logging setup is guaranteed complete
qInfo() << "Platform customization:" << platformStyle->getName();
clientModel = new ClientModel(node(), optionsModel);
clientModel = new ClientModel(node(), optionsModel, this, m_snapshot_path);
window->setClientModel(clientModel, &tip_info);

// If '-min' option passed, start window minimized (iconified) or minimized to tray
Expand Down Expand Up @@ -580,8 +587,9 @@ int GuiMain(int argc, char* argv[])
// User language is set up: pick a data directory
bool did_show_intro = false;
int64_t prune_MiB = 0; // Intro dialog prune configuration
QString snapshot_path; // Intro dialog snapshot path
// Gracefully exit if the user cancels
if (!Intro::showIfNeeded(did_show_intro, prune_MiB)) return EXIT_SUCCESS;
if (!Intro::showIfNeeded(did_show_intro, prune_MiB, snapshot_path)) return EXIT_SUCCESS;

/// 6-7. Parse bitcoin.conf, determine network, switch to network specific
/// options, and create datadir and settings.json.
Expand Down Expand Up @@ -662,6 +670,11 @@ int GuiMain(int argc, char* argv[])
app.InitPruneSetting(prune_MiB);
}

// Store snapshot path for later loading after node initialization
if (!snapshot_path.isEmpty()) {
app.setSnapshotPath(snapshot_path);
}

try
{
app.createWindow(networkStyle.data());
Expand Down
15 changes: 15 additions & 0 deletions src/qt/bitcoin.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

#include <QApplication>

enum class SynchronizationState;

class BitcoinGUI;
class ClientModel;
class NetworkStyle;
Expand Down Expand Up @@ -48,6 +50,12 @@ class BitcoinApplication: public QApplication
[[nodiscard]] bool createOptionsModel(bool resetSettings);
/// Initialize prune setting
void InitPruneSetting(int64_t prune_MiB);
/// Set snapshot path for loading after node initialization
void setSnapshotPath(const QString& snapshot_path);
/// Test method to manually trigger snapshot loading (for debugging)
void testLoadSnapshot();
/// Force load snapshot regardless of sync status (for debugging)
void forceLoadSnapshot();
/// Create main window
void createWindow(const NetworkStyle *networkStyle);
/// Create splash screen
Expand All @@ -66,6 +74,10 @@ class BitcoinApplication: public QApplication
/// Setup platform style
void setupPlatformStyle();

void loadSnapshotIfNeeded();
bool areHeadersSynced() const;
void onHeaderTipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, bool presync);

interfaces::Node& node() const { assert(m_node); return *m_node; }

public Q_SLOTS:
Expand Down Expand Up @@ -103,6 +115,9 @@ public Q_SLOTS:
std::unique_ptr<QWidget> shutdownWindow;
SplashScreen* m_splash = nullptr;
std::unique_ptr<interfaces::Node> m_node;
QString m_snapshot_path;
std::unique_ptr<interfaces::Handler> m_header_tip_handler;
QTimer* m_sync_check_timer;

void startThread();
};
Expand Down
Loading