|
|
|
@ -8,6 +8,7 @@
|
|
|
|
|
#include <blockfilter.h>
|
|
|
|
|
#include <chain.h>
|
|
|
|
|
#include <chainparams.h>
|
|
|
|
|
#include <clientversion.h>
|
|
|
|
|
#include <coins.h>
|
|
|
|
|
#include <common/args.h>
|
|
|
|
|
#include <consensus/amount.h>
|
|
|
|
@ -2699,6 +2700,105 @@ UniValue CreateUTXOSnapshot(
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static RPCHelpMan loadtxoutset()
|
|
|
|
|
{
|
|
|
|
|
return RPCHelpMan{
|
|
|
|
|
"loadtxoutset",
|
|
|
|
|
"Load the serialized UTXO set from disk.\n"
|
|
|
|
|
"Once this snapshot is loaded, its contents will be "
|
|
|
|
|
"deserialized into a second chainstate data structure, which is then used to sync to "
|
|
|
|
|
"the network's tip under a security model very much like `assumevalid`. "
|
|
|
|
|
"Meanwhile, the original chainstate will complete the initial block download process in "
|
|
|
|
|
"the background, eventually validating up to the block that the snapshot is based upon.\n\n"
|
|
|
|
|
|
|
|
|
|
"The result is a usable bitcoind instance that is current with the network tip in a "
|
|
|
|
|
"matter of minutes rather than hours. UTXO snapshot are typically obtained from "
|
|
|
|
|
"third-party sources (HTTP, torrent, etc.) which is reasonable since their "
|
|
|
|
|
"contents are always checked by hash.\n\n"
|
|
|
|
|
|
|
|
|
|
"You can find more information on this process in the `assumeutxo` design "
|
|
|
|
|
"document (<https://github.com/bitcoin/bitcoin/blob/master/doc/design/assumeutxo.md>).",
|
|
|
|
|
{
|
|
|
|
|
{"path",
|
|
|
|
|
RPCArg::Type::STR,
|
|
|
|
|
RPCArg::Optional::NO,
|
|
|
|
|
"path to the snapshot file. If relative, will be prefixed by datadir."},
|
|
|
|
|
},
|
|
|
|
|
RPCResult{
|
|
|
|
|
RPCResult::Type::OBJ, "", "",
|
|
|
|
|
{
|
|
|
|
|
{RPCResult::Type::NUM, "coins_loaded", "the number of coins loaded from the snapshot"},
|
|
|
|
|
{RPCResult::Type::STR_HEX, "tip_hash", "the hash of the base of the snapshot"},
|
|
|
|
|
{RPCResult::Type::NUM, "base_height", "the height of the base of the snapshot"},
|
|
|
|
|
{RPCResult::Type::STR, "path", "the absolute path that the snapshot was loaded from"},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
RPCExamples{
|
|
|
|
|
HelpExampleCli("loadtxoutset", "utxo.dat")
|
|
|
|
|
},
|
|
|
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
|
|
|
{
|
|
|
|
|
NodeContext& node = EnsureAnyNodeContext(request.context);
|
|
|
|
|
fs::path path{AbsPathForConfigVal(EnsureArgsman(node), fs::u8path(request.params[0].get_str()))};
|
|
|
|
|
|
|
|
|
|
FILE* file{fsbridge::fopen(path, "rb")};
|
|
|
|
|
AutoFile afile{file};
|
|
|
|
|
if (afile.IsNull()) {
|
|
|
|
|
throw JSONRPCError(
|
|
|
|
|
RPC_INVALID_PARAMETER,
|
|
|
|
|
"Couldn't open file " + path.u8string() + " for reading.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SnapshotMetadata metadata;
|
|
|
|
|
afile >> metadata;
|
|
|
|
|
|
|
|
|
|
uint256 base_blockhash = metadata.m_base_blockhash;
|
|
|
|
|
int max_secs_to_wait_for_headers = 60 * 10;
|
|
|
|
|
CBlockIndex* snapshot_start_block = nullptr;
|
|
|
|
|
|
|
|
|
|
LogPrintf("[snapshot] waiting to see blockheader %s in headers chain before snapshot activation\n",
|
|
|
|
|
base_blockhash.ToString());
|
|
|
|
|
|
|
|
|
|
ChainstateManager& chainman = *node.chainman;
|
|
|
|
|
|
|
|
|
|
while (max_secs_to_wait_for_headers > 0) {
|
|
|
|
|
snapshot_start_block = WITH_LOCK(::cs_main,
|
|
|
|
|
return chainman.m_blockman.LookupBlockIndex(base_blockhash));
|
|
|
|
|
max_secs_to_wait_for_headers -= 1;
|
|
|
|
|
|
|
|
|
|
if (!IsRPCRunning()) {
|
|
|
|
|
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!snapshot_start_block) {
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!snapshot_start_block) {
|
|
|
|
|
LogPrintf("[snapshot] timed out waiting for snapshot start blockheader %s\n",
|
|
|
|
|
base_blockhash.ToString());
|
|
|
|
|
throw JSONRPCError(
|
|
|
|
|
RPC_INTERNAL_ERROR,
|
|
|
|
|
"Timed out waiting for base block header to appear in headers chain");
|
|
|
|
|
}
|
|
|
|
|
if (!chainman.ActivateSnapshot(afile, metadata, false)) {
|
|
|
|
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to load UTXO snapshot " + fs::PathToString(path));
|
|
|
|
|
}
|
|
|
|
|
CBlockIndex* new_tip{WITH_LOCK(::cs_main, return chainman.ActiveTip())};
|
|
|
|
|
|
|
|
|
|
UniValue result(UniValue::VOBJ);
|
|
|
|
|
result.pushKV("coins_loaded", metadata.m_coins_count);
|
|
|
|
|
result.pushKV("tip_hash", new_tip->GetBlockHash().ToString());
|
|
|
|
|
result.pushKV("base_height", new_tip->nHeight);
|
|
|
|
|
result.pushKV("path", fs::PathToString(path));
|
|
|
|
|
return result;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RegisterBlockchainRPCCommands(CRPCTable& t)
|
|
|
|
|
{
|
|
|
|
|
static const CRPCCommand commands[]{
|
|
|
|
@ -2722,13 +2822,14 @@ void RegisterBlockchainRPCCommands(CRPCTable& t)
|
|
|
|
|
{"blockchain", &scantxoutset},
|
|
|
|
|
{"blockchain", &scanblocks},
|
|
|
|
|
{"blockchain", &getblockfilter},
|
|
|
|
|
{"blockchain", &dumptxoutset},
|
|
|
|
|
{"blockchain", &loadtxoutset},
|
|
|
|
|
{"hidden", &invalidateblock},
|
|
|
|
|
{"hidden", &reconsiderblock},
|
|
|
|
|
{"hidden", &waitfornewblock},
|
|
|
|
|
{"hidden", &waitforblock},
|
|
|
|
|
{"hidden", &waitforblockheight},
|
|
|
|
|
{"hidden", &syncwithvalidationinterfacequeue},
|
|
|
|
|
{"hidden", &dumptxoutset},
|
|
|
|
|
};
|
|
|
|
|
for (const auto& c : commands) {
|
|
|
|
|
t.appendCommand(c.name, &c);
|
|
|
|
|