rpc: allow dumptxoutset to dump human-readable data

Co-authored-by: Shashwat Vangani <shaavan.github@gmail.com>
Co-authored-by: Luke Dashjr <luke-jr+git@utopios.org>
This commit is contained in:
w0xlt 2022-02-08 22:41:35 -03:00 committed by Luke Dashjr
parent 1248d0da22
commit c86d2d1cb3
4 changed files with 95 additions and 10 deletions

View File

@ -46,6 +46,7 @@
#include <util/check.h>
#include <util/fs.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
@ -2647,11 +2648,32 @@ static RPCHelpMan getblockfilter()
*/
static RPCHelpMan dumptxoutset()
{
static const std::vector<std::pair<std::string, coinascii_cb_t>> ascii_types{
{"txid", [](const COutPoint& k, const Coin& c) { return k.hash.GetHex(); }},
{"vout", [](const COutPoint& k, const Coin& c) { return util::ToString(static_cast<int32_t>(k.n)); }},
{"value", [](const COutPoint& k, const Coin& c) { return util::ToString(c.out.nValue); }},
{"coinbase", [](const COutPoint& k, const Coin& c) { return util::ToString(c.fCoinBase); }},
{"height", [](const COutPoint& k, const Coin& c) { return util::ToString(static_cast<uint32_t>(c.nHeight)); }},
{"scriptPubKey", [](const COutPoint& k, const Coin& c) { return HexStr(c.out.scriptPubKey); }},
// add any other desired items here
};
std::vector<RPCArg> ascii_args;
std::transform(std::begin(ascii_types), std::end(ascii_types), std::back_inserter(ascii_args),
[](const std::pair<std::string, coinascii_cb_t>& t) { return RPCArg{t.first, RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Info to write for a given UTXO"}; });
return RPCHelpMan{
"dumptxoutset",
"Write the serialized UTXO set to a file.",
"Write the UTXO set to a file.",
{
{"path", RPCArg::Type::STR, RPCArg::Optional::NO, "Path to the output file. If relative, will be prefixed by datadir."},
{"format", RPCArg::Type::ARR, RPCArg::DefaultHint{"compact serialized format"},
"If no argument is provided, a compact binary serialized format is used; otherwise only requested items "
"available below are written in ASCII format (if an empty array is provided, all items are written in ASCII).",
ascii_args,
RPCArgOptions{.oneline_description="format"}},
{"show_header", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to include the header line in non-serialized (ASCII) mode"},
{"separator", RPCArg::Type::STR, RPCArg::Default{","}, "Field separator to use in non-serialized (ASCII) mode"},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@ -2665,10 +2687,33 @@ static RPCHelpMan dumptxoutset()
}
},
RPCExamples{
HelpExampleCli("dumptxoutset", "utxo.dat")
HelpExampleCli("dumptxoutset", "utxo.dat") +
HelpExampleCli("dumptxoutset", "utxo.dat '[]'") +
HelpExampleCli("dumptxoutset", "utxo.dat '[\"txid\", \"vout\"]' false ':'")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
// handle optional ASCII parameters
const bool is_human_readable = !request.params[1].isNull();
const bool show_header = request.params[2].isNull() || request.params[2].get_bool();
const auto separator = request.params[3].isNull() ? MakeByteSpan(",").first(1) : MakeByteSpan(request.params[3].get_str());
std::vector<std::pair<std::string, coinascii_cb_t>> requested;
if (is_human_readable) {
const auto& arr = request.params[1].get_array();
const std::unordered_map<std::string, coinascii_cb_t> ascii_map(std::begin(ascii_types), std::end(ascii_types));
for (size_t i = 0; i < arr.size(); ++i) {
const auto it = ascii_map.find(arr[i].get_str());
if (it == std::end(ascii_map))
throw JSONRPCError(RPC_INVALID_PARAMETER, "unable to find item '"+arr[i].get_str()+"'");
requested.emplace_back(*it);
}
// if nothing was found, shows everything by default
if (requested.size() == 0)
requested = ascii_types;
}
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
@ -2682,7 +2727,7 @@ static RPCHelpMan dumptxoutset()
"move it out of the way first");
}
FILE* file{fsbridge::fopen(temppath, "wb")};
FILE* file{fsbridge::fopen(temppath, !is_human_readable ? "wb" : "w")};
AutoFile afile{file};
if (afile.IsNull()) {
throw JSONRPCError(
@ -2692,6 +2737,8 @@ static RPCHelpMan dumptxoutset()
NodeContext& node = EnsureAnyNodeContext(request.context);
UniValue result = CreateUTXOSnapshot(
is_human_readable,
show_header, separator, requested,
node, node.chainman->ActiveChainstate(), afile, path, temppath);
fs::rename(temppath, path);
@ -2702,6 +2749,10 @@ static RPCHelpMan dumptxoutset()
}
UniValue CreateUTXOSnapshot(
const bool is_human_readable,
const bool show_header,
const Span<const std::byte>& separator,
const std::vector<std::pair<std::string, coinascii_cb_t>>& requested,
NodeContext& node,
Chainstate& chainstate,
AutoFile& afile,
@ -2712,6 +2763,9 @@ UniValue CreateUTXOSnapshot(
std::optional<CCoinsStats> maybe_stats;
const CBlockIndex* tip;
// used when human readable format is requested
const auto line_separator = MakeByteSpan("\n").first(1);
{
// We need to lock cs_main to ensure that the coinsdb isn't written to
// between (i) flushing coins cache to disk (coinsdb), (ii) getting stats
@ -2742,9 +2796,20 @@ UniValue CreateUTXOSnapshot(
tip->nHeight, tip->GetBlockHash().ToString(),
fs::PathToString(path), fs::PathToString(temppath)));
SnapshotMetadata metadata{chainstate.m_chainman.GetParams().MessageStart(), tip->GetBlockHash(), maybe_stats->coins_count};
if (!is_human_readable) {
SnapshotMetadata metadata{chainstate.m_chainman.GetParams().MessageStart(), tip->GetBlockHash(), maybe_stats->coins_count};
afile << metadata;
afile << metadata;
} else if (show_header) {
afile.write(MakeByteSpan("#(blockhash " + tip->GetBlockHash().ToString() + " ) "));
for (auto it = std::begin(requested); it != std::end(requested); ++it) {
if (it != std::begin(requested)) {
afile.write(separator);
}
afile.write(MakeByteSpan(it->first));
}
afile.write(line_separator);
}
COutPoint key;
Txid last_hash;
@ -2776,12 +2841,22 @@ UniValue CreateUTXOSnapshot(
if (iter % 5000 == 0) node.rpc_interruption_point();
++iter;
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
if (key.hash != last_hash) {
write_coins_to_file(afile, last_hash, coins, written_coins_count);
last_hash = key.hash;
coins.clear();
if (!is_human_readable) {
if (key.hash != last_hash) {
write_coins_to_file(afile, last_hash, coins, written_coins_count);
last_hash = key.hash;
coins.clear();
}
coins.emplace_back(key.n, coin);
} else {
for (auto it = std::begin(requested); it != std::end(requested); ++it) {
if (it != std::begin(requested))
afile.write(separator);
afile.write(MakeByteSpan(it->second(key, coin)));
}
afile.write(line_separator);
++written_coins_count;
}
coins.emplace_back(key.n, coin);
}
pcursor->Next();
}

View File

@ -5,8 +5,10 @@
#ifndef BITCOIN_RPC_BLOCKCHAIN_H
#define BITCOIN_RPC_BLOCKCHAIN_H
#include <coins.h>
#include <consensus/amount.h>
#include <core_io.h>
#include <span.h>
#include <streams.h>
#include <sync.h>
#include <util/fs.h>
@ -26,6 +28,7 @@ struct NodeContext;
} // namespace node
static constexpr int NUM_GETBLOCKSTATS_PERCENTILES = 5;
using coinascii_cb_t = std::function<std::string(const COutPoint&, const Coin&)>;
/**
* Get the difficulty of the net wrt to the given block index.
@ -52,6 +55,10 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES],
* @return a UniValue map containing metadata about the snapshot.
*/
UniValue CreateUTXOSnapshot(
const bool is_human_readable,
const bool show_header,
const Span<const std::byte>& separator,
const std::vector<std::pair<std::string, coinascii_cb_t>>& requested,
node::NodeContext& node,
Chainstate& chainstate,
AutoFile& afile,

View File

@ -93,6 +93,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "scanblocks", 5, "options" },
{ "scanblocks", 5, "filter_false_positives" },
{ "scantxoutset", 1, "scanobjects" },
{ "dumptxoutset", 1, "format" },
{ "dumptxoutset", 2, "show_header" },
{ "addmultisigaddress", 0, "nrequired" },
{ "addmultisigaddress", 1, "keys" },
{ "createmultisig", 0, "nrequired" },

View File

@ -48,6 +48,7 @@ CreateAndActivateUTXOSnapshot(
AutoFile auto_outfile{outfile};
UniValue result = CreateUTXOSnapshot(
false, false, Span<std::byte>(), {},
node, node.chainman->ActiveChainstate(), auto_outfile, snapshot_path, snapshot_path);
LogPrintf(
"Wrote UTXO snapshot to %s: %s\n", fs::PathToString(snapshot_path.make_preferred()), result.write());