aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/design/assumeutxo.md2
-rw-r--r--doc/release-notes-27596.md19
-rw-r--r--src/rpc/blockchain.cpp103
-rw-r--r--src/test/fuzz/rpc.cpp1
4 files changed, 123 insertions, 2 deletions
diff --git a/doc/design/assumeutxo.md b/doc/design/assumeutxo.md
index 1492877e62..8068a93f27 100644
--- a/doc/design/assumeutxo.md
+++ b/doc/design/assumeutxo.md
@@ -3,7 +3,7 @@
Assumeutxo is a feature that allows fast bootstrapping of a validating bitcoind
instance with a very similar security model to assumevalid.
-The RPC commands `dumptxoutset` and `loadtxoutset` (yet to be merged) are used to
+The RPC commands `dumptxoutset` and `loadtxoutset` are used to
respectively generate and load UTXO snapshots. The utility script
`./contrib/devtools/utxo_snapshot.sh` may be of use.
diff --git a/doc/release-notes-27596.md b/doc/release-notes-27596.md
index 4f96adb0f3..da96b36189 100644
--- a/doc/release-notes-27596.md
+++ b/doc/release-notes-27596.md
@@ -5,3 +5,22 @@ When using assumeutxo with `-prune`, the prune budget may be exceeded if it is s
lower than 1100MB (i.e. `MIN_DISK_SPACE_FOR_BLOCK_FILES * 2`). Prune budget is normally
split evenly across each chainstate, unless the resulting prune budget per chainstate
is beneath `MIN_DISK_SPACE_FOR_BLOCK_FILES` in which case that value will be used.
+
+RPC
+---
+
+`loadtxoutset` has been added, which allows loading a UTXO snapshot of the format
+generated by `dumptxoutset`. 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.
+
+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 via
+third-party sources (HTTP, torrent, etc.) which is reasonable since their contents
+are always checked by hash.
+
+You can find more information on this process in the `assumeutxo` design
+document (<https://github.com/bitcoin/bitcoin/blob/master/doc/design/assumeutxo.md>).
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index f4d88e4209..c7ffa0a0b2 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,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);
diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp
index 7e9a18e1d0..2ef3fe1b47 100644
--- a/src/test/fuzz/rpc.cpp
+++ b/src/test/fuzz/rpc.cpp
@@ -80,6 +80,7 @@ const std::vector<std::string> RPC_COMMANDS_NOT_SAFE_FOR_FUZZING{
"gettxoutproof", // avoid prohibitively slow execution
"importmempool", // avoid reading from disk
"importwallet", // avoid reading from disk
+ "loadtxoutset", // avoid reading from disk
"loadwallet", // avoid reading from disk
"savemempool", // disabled as a precautionary measure: may take a file path argument in the future
"setban", // avoid DNS lookups