Skip to content

Commit ce585a9

Browse files
jamesobtheStack
andcommitted
rpc: add loadtxoutset
Co-authored-by: Sebastian Falbesoner <[email protected]>
1 parent 62ac519 commit ce585a9

File tree

4 files changed

+123
-2
lines changed

4 files changed

+123
-2
lines changed

doc/design/assumeutxo.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Assumeutxo is a feature that allows fast bootstrapping of a validating bitcoind
44
instance with a very similar security model to assumevalid.
55

6-
The RPC commands `dumptxoutset` and `loadtxoutset` (yet to be merged) are used to
6+
The RPC commands `dumptxoutset` and `loadtxoutset` are used to
77
respectively generate and load UTXO snapshots. The utility script
88
`./contrib/devtools/utxo_snapshot.sh` may be of use.
99

doc/release-notes-27596.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,22 @@ When using assumeutxo with `-prune`, the prune budget may be exceeded if it is s
55
lower than 1100MB (i.e. `MIN_DISK_SPACE_FOR_BLOCK_FILES * 2`). Prune budget is normally
66
split evenly across each chainstate, unless the resulting prune budget per chainstate
77
is beneath `MIN_DISK_SPACE_FOR_BLOCK_FILES` in which case that value will be used.
8+
9+
RPC
10+
---
11+
12+
`loadtxoutset` has been added, which allows loading a UTXO snapshot of the format
13+
generated by `dumptxoutset`. Once this snapshot is loaded, its contents will be
14+
deserialized into a second chainstate data structure, which is then used to sync to
15+
the network's tip under a security model very much like `assumevalid`.
16+
17+
Meanwhile, the original chainstate will complete the initial block download process in
18+
the background, eventually validating up to the block that the snapshot is based upon.
19+
20+
The result is a usable bitcoind instance that is current with the network tip in a
21+
matter of minutes rather than hours. UTXO snapshot are typically obtained via
22+
third-party sources (HTTP, torrent, etc.) which is reasonable since their contents
23+
are always checked by hash.
24+
25+
You can find more information on this process in the `assumeutxo` design
26+
document (<https://github.com/bitcoin/bitcoin/blob/master/doc/design/assumeutxo.md>).

src/rpc/blockchain.cpp

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
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+
27022802
void 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);

src/test/fuzz/rpc.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ const std::vector<std::string> RPC_COMMANDS_NOT_SAFE_FOR_FUZZING{
8080
"gettxoutproof", // avoid prohibitively slow execution
8181
"importmempool", // avoid reading from disk
8282
"importwallet", // avoid reading from disk
83+
"loadtxoutset", // avoid reading from disk
8384
"loadwallet", // avoid reading from disk
8485
"savemempool", // disabled as a precautionary measure: may take a file path argument in the future
8586
"setban", // avoid DNS lookups

0 commit comments

Comments
 (0)