mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-28 13:02:38 +02:00
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:
parent
1248d0da22
commit
c86d2d1cb3
@ -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();
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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" },
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user