|
29 | 29 | #include <node/context.h>
|
30 | 30 | #include <node/interface_ui.h>
|
31 | 31 | #include <node/transaction.h>
|
| 32 | +#include <node/utxo_snapshot.h> |
32 | 33 | #include <policy/feerate.h>
|
33 | 34 | #include <policy/fees.h>
|
34 | 35 | #include <policy/policy.h>
|
|
58 | 59 | #include <memory>
|
59 | 60 | #include <optional>
|
60 | 61 | #include <utility>
|
61 |
| - |
| 62 | +#include <regex> |
62 | 63 | #include <boost/signals2/signal.hpp>
|
63 | 64 |
|
64 | 65 | using interfaces::BlockTip;
|
@@ -395,6 +396,99 @@ class NodeImpl : public Node
|
395 | 396 | {
|
396 | 397 | m_context = context;
|
397 | 398 | }
|
| 399 | + SnapshotProgressFn m_snapshot_progress_callback{nullptr}; |
| 400 | + void setSnapshotProgressCallback(SnapshotProgressFn fn) override |
| 401 | + { |
| 402 | + m_snapshot_progress_callback = std::move(fn); |
| 403 | + } |
| 404 | + void notifySnapshotProgress(double progress) override |
| 405 | + { |
| 406 | + if (m_snapshot_progress_callback) m_snapshot_progress_callback(progress); |
| 407 | + } |
| 408 | + bool snapshotLoad(const std::string& path_string) override |
| 409 | + { |
| 410 | + // Set up log message parsing |
| 411 | + LogInstance().PushBackCallback([this](const std::string& str) { |
| 412 | + static const std::regex progress_regex(R"(\[snapshot\] (\d+) coins loaded \(([0-9.]+)%.*\))"); |
| 413 | + static const std::regex sync_progress_regex(R"(Synchronizing blockheaders, height: (\d+) \(~([\d.]+)%\))"); |
| 414 | + |
| 415 | + std::smatch matches; |
| 416 | + if (std::regex_search(str, matches, progress_regex)) { |
| 417 | + try { |
| 418 | + double percentage = std::stod(matches[2]); |
| 419 | + // Convert percentage to 0-1 range and notify through callback |
| 420 | + notifySnapshotProgress(percentage / 100.0); |
| 421 | + } catch (...) { |
| 422 | + // Handle parsing errors |
| 423 | + } |
| 424 | + } else if (std::regex_search(str, matches, sync_progress_regex)) { |
| 425 | + try { |
| 426 | + double percentage = std::stod(matches[2]); |
| 427 | + // Convert percentage to 0-1 range and notify through callback |
| 428 | + notifySnapshotProgress(percentage / 100.0); |
| 429 | + } catch (...) { |
| 430 | + // Handle parsing errors |
| 431 | + } |
| 432 | + } |
| 433 | + }); |
| 434 | + |
| 435 | + const fs::path path = fs::u8path(path_string); |
| 436 | + if (!fs::exists(path)) { |
| 437 | + LogPrintf("[loadsnapshot] Snapshot file %s does not exist\n", path.u8string()); |
| 438 | + return false; |
| 439 | + } |
| 440 | + |
| 441 | + AutoFile afile{fsbridge::fopen(path, "rb")}; |
| 442 | + if (afile.IsNull()) { |
| 443 | + LogPrintf("[loadsnapshot] Failed to open snapshot file %s\n", path.u8string()); |
| 444 | + return false; |
| 445 | + } |
| 446 | + |
| 447 | + SnapshotMetadata metadata; |
| 448 | + try { |
| 449 | + afile >> metadata; |
| 450 | + } catch (const std::exception& e) { |
| 451 | + LogPrintf("[loadsnapshot] Failed to read snapshot metadata: %s\n", e.what()); |
| 452 | + return false; |
| 453 | + } |
| 454 | + |
| 455 | + const uint256& base_blockhash = metadata.m_base_blockhash; |
| 456 | + LogPrintf("[loadsnapshot] Waiting for blockheader %s in headers chain before snapshot activation\n", |
| 457 | + base_blockhash.ToString()); |
| 458 | + |
| 459 | + if (!m_context->chainman) { |
| 460 | + LogPrintf("[loadsnapshot] Chainman is null\n"); |
| 461 | + return false; |
| 462 | + } |
| 463 | + |
| 464 | + ChainstateManager& chainman = *m_context->chainman; |
| 465 | + CBlockIndex* snapshot_start_block = nullptr; |
| 466 | + |
| 467 | + // Wait for the block to appear in the block index |
| 468 | + constexpr int max_wait_seconds = 600; // 10 minutes |
| 469 | + for (int i = 0; i < max_wait_seconds; ++i) { |
| 470 | + snapshot_start_block = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(base_blockhash)); |
| 471 | + if (snapshot_start_block) break; |
| 472 | + std::this_thread::sleep_for(std::chrono::seconds(1)); |
| 473 | + } |
| 474 | + |
| 475 | + if (!snapshot_start_block) { |
| 476 | + LogPrintf("[loadsnapshot] Timed out waiting for snapshot start blockheader %s\n", base_blockhash.ToString()); |
| 477 | + return false; |
| 478 | + } |
| 479 | + |
| 480 | + // Activate the snapshot |
| 481 | + if (!chainman.ActivateSnapshot(afile, metadata, false)) { |
| 482 | + LogPrintf("[loadsnapshot] Unable to load UTXO snapshot %s\n", path.u8string()); |
| 483 | + return false; |
| 484 | + } |
| 485 | + |
| 486 | + CBlockIndex* new_tip = WITH_LOCK(::cs_main, return chainman.ActiveTip()); |
| 487 | + LogPrintf("[loadsnapshot] Loaded %d coins from snapshot %s at height %d\n", |
| 488 | + metadata.m_coins_count, new_tip->GetBlockHash().ToString(), new_tip->nHeight); |
| 489 | + |
| 490 | + return true; |
| 491 | + } |
398 | 492 | ArgsManager& args() { return *Assert(Assert(m_context)->args); }
|
399 | 493 | ChainstateManager& chainman() { return *Assert(m_context->chainman); }
|
400 | 494 | NodeContext* m_context{nullptr};
|
@@ -510,7 +604,7 @@ class RpcHandlerImpl : public Handler
|
510 | 604 | class ChainImpl : public Chain
|
511 | 605 | {
|
512 | 606 | public:
|
513 |
| - explicit ChainImpl(NodeContext& node) : m_node(node) {} |
| 607 | + explicit ChainImpl(node::NodeContext& node) : m_node(node) {} |
514 | 608 | std::optional<int> getHeight() override
|
515 | 609 | {
|
516 | 610 | const int height{WITH_LOCK(::cs_main, return chainman().ActiveChain().Height())};
|
|
0 commit comments