I don't like this default. This RPC is already kind of dangerous, it could roll back and forth the chain for hundred thousands of blocks (if executed a few years after it's released), which could take days
That's a good point. Given a choice of whether the default behavior might create a useless snapshot that can't be loaded or take the node offline trying to produce a really old snapshot, that makes me think there should probably not be a default value at all and the user should have to explicitly specify the behavior they want.
Would suggest adding a type parameter for the user to specify whether they want the latest snapshot or to roll back to a historic snapshot, and a rollback option to optionally specify the height or hash of a historic snapshot. Usage would look like:
# create the a snapshot of the latest utxo set
bitcoin-cli dumptxoutset utxo.dat latest
# roll back and create a snapshot of the latest utxo set that can be loaded
bitcoin-cli dumptxoutset utxo.dat rollback
# roll back to the specified height and create snapshot of that utxo set
bitcoin-cli -named dumptxoutset utxo.dat rollback=853456
And a possible implementation would be:
<details><summary>diff</summary>
<p>
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -2673,12 +2673,17 @@ static RPCHelpMan dumptxoutset()
return RPCHelpMan{
"dumptxoutset",
"Write the serialized UTXO set to a file. This can be used in loadtxoutset afterwards if this snapshot height is supported in the chainparams as well.\n\n"
- "Unless the requested height is the current tip, the node will roll back to the requested height and network activity will be suspended during this process."
+ "Unless the requested height is the current tip, the node will roll back to the requested height and network activity will be suspended during this process. "
"Because of this it is discouraged to interact with the node in any other way during the execution of this call to avoid inconsistent results and race conditions, particularly RPCs that interact with blockstorage.",
{
{"path", RPCArg::Type::STR, RPCArg::Optional::NO, "Path to the output file. If relative, will be prefixed by datadir."},
- {"height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the latest valid snapshot height"},
- "Height of the UTXO set file. Note: The further this number is from the tip, the longer this process will take. Consider setting a higher -rpcclienttimeout value in this case."
+ {"type", RPCArg::Type::STR, RPCArg::Default(""), "The type of snapshot to create. Can be \"latest\" to create a snapshot of the current UTXO set or \"rollback\" to temporarily roll back the state of the node to a historical block before creating the snapshot of a historical UTXO set. This parameter can be omitted if a separate \"rollback\" named parameter is specified indicating the height or hash of a specific historical block. If \"rollback\" is specified and separate \"rollback\" named parameter is not specified, this will roll back to the latest valid snapshot block that currently be loaded with loadtxoutset."},
+ {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
+ {
+ {"rollback", RPCArg::Type::NUM, RPCArg::Optional::OMITTED,
+ "Height or hash of the block to roll back to before creating the snapshot. Note: The further this number is from the tip, the longer this process will take. Consider setting a higher -rpcclienttimeout value in this case.",
+ RPCArgOptions{.skip_type_check = true, .type_str = {"", "string or numeric"}}},
+ },
},
},
RPCResult{
@@ -2693,10 +2698,33 @@ static RPCHelpMan dumptxoutset()
}
},
RPCExamples{
- HelpExampleCli("dumptxoutset", "utxo.dat")
+ HelpExampleCli("dumptxoutset", "utxo.dat latest") +
+ HelpExampleCli("dumptxoutset", "utxo.dat rollback") +
+ HelpExampleCli("-named dumptxoutset", R"(utxo.dat rollback=853456)")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
+ NodeContext& node = EnsureAnyNodeContext(request.context);
+ const CBlockIndex* tip{WITH_LOCK(::cs_main, return node.chainman->ActiveChain().Tip())};
+ const CBlockIndex* target_index{nullptr};
+ const std::string snapshot_type{self.Arg<std::string>("type")};
+ const UniValue options{request.params[2].isNull() ? UniValue::VOBJ : request.params[2]};
+ if (options.exists("rollback")) {
+ if (!snapshot_type.empty() && snapshot_type != "rollback") {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid snapshot type \"%s\" specified with rollback option", snapshot_type));
+ }
+ target_index = ParseHashOrHeight(options["rollback"], *node.chainman);
+ } else if (snapshot_type == "rollback") {
+ auto snapshot_heights = node.chainman->GetParams().GetAvailableSnapshotHeights();
+ CHECK_NONFATAL(snapshot_heights.size() > 0);
+ auto max_height = std::max_element(snapshot_heights.begin(), snapshot_heights.end());
+ target_index = ParseHashOrHeight(*max_height, *node.chainman);
+ } else if (snapshot_type == "latest") {
+ target_index = tip;
+ } else {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid snapshot type \"%s\" specified. Please specify \"rollback\" or \"latest\"", snapshot_type));
+ }
+
const ArgsManager& args{EnsureAnyArgsman(request.context)};
const fs::path path = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str()));
// Write to a temporary path and then move into `path` on completion
@@ -2718,19 +2746,7 @@ static RPCHelpMan dumptxoutset()
"Couldn't open file " + temppath.utf8string() + " for writing.");
}
- NodeContext& node = EnsureAnyNodeContext(request.context);
CConnman& connman = EnsureConnman(node);
-
- const CBlockIndex* target_index{};
- if (request.params[1].isNull()) {
- auto snapshot_heights = node.chainman->GetParams().GetAvailableSnapshotHeights();
- auto max_height = std::max_element(snapshot_heights.begin(), snapshot_heights.end());
- target_index = ParseHashOrHeight(*max_height, *node.chainman);
- } else {
- target_index = ParseHashOrHeight(request.params[1], *node.chainman);
- }
-
- const auto tip{WITH_LOCK(::cs_main, return node.chainman->ActiveChain().Tip())};
const CBlockIndex* invalidate_index{nullptr};
std::unique_ptr<NetworkDisable> disable_network;
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -185,7 +185,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "gettxoutproof", 0, "txids" },
{ "gettxoutsetinfo", 1, "hash_or_height" },
{ "gettxoutsetinfo", 2, "use_index"},
- { "dumptxoutset", 1, "height"},
+ { "dumptxoutset", 2, "options" },
+ { "dumptxoutset", 2, "rollback" },
{ "lockunspent", 0, "unlock" },
{ "lockunspent", 1, "transactions" },
{ "lockunspent", 2, "persistent" },
</p>
</details>