88#include < blockfilter.h>
99#include < chain.h>
1010#include < chainparams.h>
11+ #include < clientversion.h>
1112#include < coins.h>
1213#include < common/args.h>
1314#include < consensus/amount.h>
@@ -2699,6 +2700,105 @@ UniValue CreateUTXOSnapshot(
26992700 return result;
27002701}
27012702
2703+ static RPCHelpMan loadtxoutset ()
2704+ {
2705+ return RPCHelpMan{
2706+ " loadtxoutset" ,
2707+ " Load the serialized UTXO set from disk.\n "
2708+ " Once this snapshot is loaded, its contents will be "
2709+ " deserialized into a second chainstate data structure, which is then used to sync to "
2710+ " the network's tip under a security model very much like `assumevalid`. "
2711+ " Meanwhile, the original chainstate will complete the initial block download process in "
2712+ " the background, eventually validating up to the block that the snapshot is based upon.\n\n "
2713+
2714+ " The result is a usable bitcoind instance that is current with the network tip in a "
2715+ " matter of minutes rather than hours. UTXO snapshot are typically obtained from "
2716+ " third-party sources (HTTP, torrent, etc.) which is reasonable since their "
2717+ " contents are always checked by hash.\n\n "
2718+
2719+ " You can find more information on this process in the `assumeutxo` design "
2720+ " document (<https://github.com/bitcoin/bitcoin/blob/master/doc/design/assumeutxo.md>)." ,
2721+ {
2722+ {" path" ,
2723+ RPCArg::Type::STR,
2724+ RPCArg::Optional::NO,
2725+ " path to the snapshot file. If relative, will be prefixed by datadir." },
2726+ },
2727+ RPCResult{
2728+ RPCResult::Type::OBJ, " " , " " ,
2729+ {
2730+ {RPCResult::Type::NUM, " coins_loaded" , " the number of coins loaded from the snapshot" },
2731+ {RPCResult::Type::STR_HEX, " tip_hash" , " the hash of the base of the snapshot" },
2732+ {RPCResult::Type::NUM, " base_height" , " the height of the base of the snapshot" },
2733+ {RPCResult::Type::STR, " path" , " the absolute path that the snapshot was loaded from" },
2734+ }
2735+ },
2736+ RPCExamples{
2737+ HelpExampleCli (" loadtxoutset" , " utxo.dat" )
2738+ },
2739+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2740+ {
2741+ NodeContext& node = EnsureAnyNodeContext (request.context );
2742+ fs::path path{AbsPathForConfigVal (EnsureArgsman (node), fs::u8path (request.params [0 ].get_str ()))};
2743+
2744+ FILE* file{fsbridge::fopen (path, " rb" )};
2745+ AutoFile afile{file};
2746+ if (afile.IsNull ()) {
2747+ throw JSONRPCError (
2748+ RPC_INVALID_PARAMETER,
2749+ " Couldn't open file " + path.u8string () + " for reading." );
2750+ }
2751+
2752+ SnapshotMetadata metadata;
2753+ afile >> metadata;
2754+
2755+ uint256 base_blockhash = metadata.m_base_blockhash ;
2756+ int max_secs_to_wait_for_headers = 60 * 10 ;
2757+ CBlockIndex* snapshot_start_block = nullptr ;
2758+
2759+ LogPrintf (" [snapshot] waiting to see blockheader %s in headers chain before snapshot activation\n " ,
2760+ base_blockhash.ToString ());
2761+
2762+ ChainstateManager& chainman = *node.chainman ;
2763+
2764+ while (max_secs_to_wait_for_headers > 0 ) {
2765+ snapshot_start_block = WITH_LOCK (::cs_main,
2766+ return chainman.m_blockman .LookupBlockIndex (base_blockhash));
2767+ max_secs_to_wait_for_headers -= 1 ;
2768+
2769+ if (!IsRPCRunning ()) {
2770+ throw JSONRPCError (RPC_CLIENT_NOT_CONNECTED, " Shutting down" );
2771+ }
2772+
2773+ if (!snapshot_start_block) {
2774+ std::this_thread::sleep_for (std::chrono::seconds (1 ));
2775+ } else {
2776+ break ;
2777+ }
2778+ }
2779+
2780+ if (!snapshot_start_block) {
2781+ LogPrintf (" [snapshot] timed out waiting for snapshot start blockheader %s\n " ,
2782+ base_blockhash.ToString ());
2783+ throw JSONRPCError (
2784+ RPC_INTERNAL_ERROR,
2785+ " Timed out waiting for base block header to appear in headers chain" );
2786+ }
2787+ if (!chainman.ActivateSnapshot (afile, metadata, false )) {
2788+ throw JSONRPCError (RPC_INTERNAL_ERROR, " Unable to load UTXO snapshot " + fs::PathToString (path));
2789+ }
2790+ CBlockIndex* new_tip{WITH_LOCK (::cs_main, return chainman.ActiveTip ())};
2791+
2792+ UniValue result (UniValue::VOBJ);
2793+ result.pushKV (" coins_loaded" , metadata.m_coins_count );
2794+ result.pushKV (" tip_hash" , new_tip->GetBlockHash ().ToString ());
2795+ result.pushKV (" base_height" , new_tip->nHeight );
2796+ result.pushKV (" path" , fs::PathToString (path));
2797+ return result;
2798+ },
2799+ };
2800+ }
2801+
27022802void RegisterBlockchainRPCCommands (CRPCTable& t)
27032803{
27042804 static const CRPCCommand commands[]{
@@ -2722,13 +2822,14 @@ void RegisterBlockchainRPCCommands(CRPCTable& t)
27222822 {" blockchain" , &scantxoutset},
27232823 {" blockchain" , &scanblocks},
27242824 {" blockchain" , &getblockfilter},
2825+ {" blockchain" , &dumptxoutset},
2826+ {" blockchain" , &loadtxoutset},
27252827 {" hidden" , &invalidateblock},
27262828 {" hidden" , &reconsiderblock},
27272829 {" hidden" , &waitfornewblock},
27282830 {" hidden" , &waitforblock},
27292831 {" hidden" , &waitforblockheight},
27302832 {" hidden" , &syncwithvalidationinterfacequeue},
2731- {" hidden" , &dumptxoutset},
27322833 };
27332834 for (const auto & c : commands) {
27342835 t.appendCommand (c.name , &c);
0 commit comments