RPC: Add type parameter to dumptxoutset

pull/29553/head
Fabian Jahr 8 months ago
parent fccf4f91d2
commit 993cafe7e4
No known key found for this signature in database
GPG Key ID: F13D1E9D890798CD

@ -2648,6 +2648,25 @@ static RPCHelpMan getblockfilter()
};
}
/**
* RAII class that disables the network in its constructor and enables it in its
* destructor.
*/
class NetworkDisable
{
CConnman& m_connman;
public:
NetworkDisable(CConnman& connman) : m_connman(connman) {
m_connman.SetNetworkActive(false);
if (m_connman.GetNetworkActive()) {
throw JSONRPCError(RPC_MISC_ERROR, "Network activity could not be suspended.");
}
};
~NetworkDisable() {
m_connman.SetNetworkActive(true);
};
};
/**
* Serialize the UTXO set to a file for loading elsewhere.
*
@ -2660,6 +2679,14 @@ static RPCHelpMan dumptxoutset()
"Write the serialized UTXO set to a file.",
{
{"path", RPCArg::Type::STR, RPCArg::Optional::NO, "Path to the output file. If relative, will be prefixed by datadir."},
{"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{
RPCResult::Type::OBJ, "", "",
@ -2673,10 +2700,33 @@ static RPCHelpMan dumptxoutset()
}
},
RPCExamples{
HelpExampleCli("dumptxoutset", "utxo.dat")
HelpExampleCli("-rpcclienttimeout=0 dumptxoutset", "utxo.dat latest") +
HelpExampleCli("-rpcclienttimeout=0 dumptxoutset", "utxo.dat rollback") +
HelpExampleCli("-rpcclienttimeout=0 -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
@ -2698,11 +2748,55 @@ static RPCHelpMan dumptxoutset()
"Couldn't open file " + temppath.utf8string() + " for writing.");
}
NodeContext& node = EnsureAnyNodeContext(request.context);
CConnman& connman = EnsureConnman(node);
const CBlockIndex* invalidate_index{nullptr};
std::unique_ptr<NetworkDisable> disable_network;
// If the user wants to dump the txoutset of the current tip, we don't have
// to roll back at all
if (target_index != tip) {
// If the node is running in pruned mode we ensure all necessary block
// data is available before starting to roll back.
if (node.chainman->m_blockman.IsPruneMode()) {
LOCK(node.chainman->GetMutex());
const CBlockIndex* current_tip{node.chainman->ActiveChain().Tip()};
const CBlockIndex* first_block{node.chainman->m_blockman.GetFirstBlock(*current_tip, /*status_mask=*/BLOCK_HAVE_MASK)};
if (first_block->nHeight > target_index->nHeight) {
throw JSONRPCError(RPC_MISC_ERROR, "Could not roll back to requested height since necessary block data is already pruned.");
}
}
// Suspend network activity for the duration of the process when we are
// rolling back the chain to get a utxo set from a past height. We do
// this so we don't punish peers that send us that send us data that
// seems wrong in this temporary state. For example a normal new block
// would be classified as a block connecting an invalid block.
disable_network = std::make_unique<NetworkDisable>(connman);
invalidate_index = WITH_LOCK(::cs_main, return node.chainman->ActiveChain().Next(target_index));
InvalidateBlock(*node.chainman, invalidate_index->GetBlockHash());
const CBlockIndex* new_tip_index{WITH_LOCK(::cs_main, return node.chainman->ActiveChain().Tip())};
// In case there is any issue with a block being read from disk we need
// to stop here, otherwise the dump could still be created for the wrong
// height.
// The new tip could also not be the target block if we have a stale
// sister block of invalidate_index. This block (or a descendant) would
// be activated as the new tip and we would not get to new_tip_index.
if (new_tip_index != target_index) {
ReconsiderBlock(*node.chainman, invalidate_index->GetBlockHash());
throw JSONRPCError(RPC_MISC_ERROR, "Could not roll back to requested height, reverting to tip.");
}
}
UniValue result = CreateUTXOSnapshot(
node, node.chainman->ActiveChainstate(), afile, path, temppath);
fs::rename(temppath, path);
if (invalidate_index) {
ReconsiderBlock(*node.chainman, invalidate_index->GetBlockHash());
}
result.pushKV("path", path.utf8string());
return result;
},

@ -187,6 +187,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "gettxoutproof", 0, "txids" },
{ "gettxoutsetinfo", 1, "hash_or_height" },
{ "gettxoutsetinfo", 2, "use_index"},
{ "dumptxoutset", 2, "options" },
{ "dumptxoutset", 2, "rollback" },
{ "lockunspent", 0, "unlock" },
{ "lockunspent", 1, "transactions" },
{ "lockunspent", 2, "persistent" },

@ -295,7 +295,7 @@ class AssumeutxoTest(BitcoinTestFramework):
assert_equal(n1.getblockcount(), START_HEIGHT)
self.log.info(f"Creating a UTXO snapshot at height {SNAPSHOT_BASE_HEIGHT}")
dump_output = n0.dumptxoutset('utxos.dat')
dump_output = n0.dumptxoutset('utxos.dat', "latest")
self.log.info("Test loading snapshot when the node tip is on the same block as the snapshot")
assert_equal(n0.getblockcount(), SNAPSHOT_BASE_HEIGHT)

@ -27,7 +27,7 @@ class DumptxoutsetTest(BitcoinTestFramework):
self.generate(node, COINBASE_MATURITY)
FILENAME = 'txoutset.dat'
out = node.dumptxoutset(FILENAME)
out = node.dumptxoutset(FILENAME, "latest")
expected_path = node.datadir_path / self.chain / FILENAME
assert expected_path.is_file()
@ -51,10 +51,10 @@ class DumptxoutsetTest(BitcoinTestFramework):
# Specifying a path to an existing or invalid file will fail.
assert_raises_rpc_error(
-8, '{} already exists'.format(FILENAME), node.dumptxoutset, FILENAME)
-8, '{} already exists'.format(FILENAME), node.dumptxoutset, FILENAME, "latest")
invalid_path = node.datadir_path / "invalid" / "path"
assert_raises_rpc_error(
-8, "Couldn't open file {}.incomplete for writing".format(invalid_path), node.dumptxoutset, invalid_path)
-8, "Couldn't open file {}.incomplete for writing".format(invalid_path), node.dumptxoutset, invalid_path, "latest")
if __name__ == '__main__':

@ -93,7 +93,7 @@ class AssumeutxoTest(BitcoinTestFramework):
self.log.info(
f"Creating a UTXO snapshot at height {SNAPSHOT_BASE_HEIGHT}")
dump_output = n0.dumptxoutset('utxos.dat')
dump_output = n0.dumptxoutset('utxos.dat', "latest")
assert_equal(
dump_output['txoutset_hash'],

Loading…
Cancel
Save