|  | 
| 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