aboutsummaryrefslogtreecommitdiff
path: root/src/rpc/blockchain.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc/blockchain.cpp')
-rw-r--r--src/rpc/blockchain.cpp177
1 files changed, 176 insertions, 1 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index f4d88e4209..0f4941b40c 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -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,178 @@ 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;
+},
+ };
+}
+
+const std::vector<RPCResult> RPCHelpForChainstate{
+ {RPCResult::Type::NUM, "blocks", "number of blocks in this chainstate"},
+ {RPCResult::Type::STR_HEX, "bestblockhash", "blockhash of the tip"},
+ {RPCResult::Type::NUM, "difficulty", "difficulty of the tip"},
+ {RPCResult::Type::NUM, "verificationprogress", "progress towards the network tip"},
+ {RPCResult::Type::STR_HEX, "snapshot_blockhash", /*optional=*/true, "the base block of the snapshot this chainstate is based on, if any"},
+ {RPCResult::Type::NUM, "coins_db_cache_bytes", "size of the coinsdb cache"},
+ {RPCResult::Type::NUM, "coins_tip_cache_bytes", "size of the coinstip cache"},
+};
+
+static RPCHelpMan getchainstates()
+{
+return RPCHelpMan{
+ "getchainstates",
+ "\nReturn information about chainstates.\n",
+ {},
+ RPCResult{
+ RPCResult::Type::OBJ, "", "", {
+ {RPCResult::Type::NUM, "headers", "the number of headers seen so far"},
+ {RPCResult::Type::OBJ, "normal", /*optional=*/true, "fully validated chainstate containing blocks this node has validated starting from the genesis block", RPCHelpForChainstate},
+ {RPCResult::Type::OBJ, "snapshot", /*optional=*/true, "only present if an assumeutxo snapshot is loaded. Partially validated chainstate containing blocks this node has validated starting from the snapshot. After the snapshot is validated (when the 'normal' chainstate advances far enough to validate it), this chainstate will replace and become the 'normal' chainstate.", RPCHelpForChainstate},
+ }
+ },
+ RPCExamples{
+ HelpExampleCli("getchainstates", "")
+ + HelpExampleRpc("getchainstates", "")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ LOCK(cs_main);
+ UniValue obj(UniValue::VOBJ);
+
+ NodeContext& node = EnsureAnyNodeContext(request.context);
+ ChainstateManager& chainman = *node.chainman;
+
+ auto make_chain_data = [&](const Chainstate& cs) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
+ AssertLockHeld(::cs_main);
+ UniValue data(UniValue::VOBJ);
+ if (!cs.m_chain.Tip()) {
+ return data;
+ }
+ const CChain& chain = cs.m_chain;
+ const CBlockIndex* tip = chain.Tip();
+
+ data.pushKV("blocks", (int)chain.Height());
+ data.pushKV("bestblockhash", tip->GetBlockHash().GetHex());
+ data.pushKV("difficulty", (double)GetDifficulty(tip));
+ data.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), tip));
+ data.pushKV("coins_db_cache_bytes", cs.m_coinsdb_cache_size_bytes);
+ data.pushKV("coins_tip_cache_bytes", cs.m_coinstip_cache_size_bytes);
+ if (cs.m_from_snapshot_blockhash) {
+ data.pushKV("snapshot_blockhash", cs.m_from_snapshot_blockhash->ToString());
+ }
+ return data;
+ };
+
+ if (chainman.GetAll().size() > 1) {
+ for (Chainstate* chainstate : chainman.GetAll()) {
+ obj.pushKV(
+ chainstate->m_from_snapshot_blockhash ? "snapshot" : "normal",
+ make_chain_data(*chainstate));
+ }
+ } else {
+ obj.pushKV("normal", make_chain_data(chainman.ActiveChainstate()));
+ }
+ obj.pushKV("headers", chainman.m_best_header ? chainman.m_best_header->nHeight : -1);
+
+ return obj;
+}
+ };
+}
+
+
void RegisterBlockchainRPCCommands(CRPCTable& t)
{
static const CRPCCommand commands[]{
@@ -2722,13 +2895,15 @@ void RegisterBlockchainRPCCommands(CRPCTable& t)
{"blockchain", &scantxoutset},
{"blockchain", &scanblocks},
{"blockchain", &getblockfilter},
+ {"blockchain", &dumptxoutset},
+ {"blockchain", &loadtxoutset},
+ {"blockchain", &getchainstates},
{"hidden", &invalidateblock},
{"hidden", &reconsiderblock},
{"hidden", &waitfornewblock},
{"hidden", &waitforblock},
{"hidden", &waitforblockheight},
{"hidden", &syncwithvalidationinterfacequeue},
- {"hidden", &dumptxoutset},
};
for (const auto& c : commands) {
t.appendCommand(c.name, &c);