Skip to content

Commit 57cf74c

Browse files
committed
rpc: add dumptxoutset
Allows the creation of a UTXO snapshot to disk.
1 parent 92fafb3 commit 57cf74c

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed

src/rpc/blockchain.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <hash.h>
1616
#include <index/blockfilterindex.h>
1717
#include <node/coinstats.h>
18+
#include <node/utxo_snapshot.h>
1819
#include <policy/feerate.h>
1920
#include <policy/policy.h>
2021
#include <policy/rbf.h>
@@ -2245,6 +2246,113 @@ static UniValue getblockfilter(const JSONRPCRequest& request)
22452246
return ret;
22462247
}
22472248

2249+
/**
2250+
* Serialize the UTXO set to a file for loading elsewhere.
2251+
*
2252+
* @see SnapshotMetadata
2253+
*/
2254+
UniValue dumptxoutset(const JSONRPCRequest& request)
2255+
{
2256+
RPCHelpMan{
2257+
"dumptxoutset",
2258+
"\nWrite the serialized UTXO set to disk.\n"
2259+
"Incidentally flushes the latest coinsdb (leveldb) to disk.\n",
2260+
{
2261+
{"path",
2262+
RPCArg::Type::STR,
2263+
RPCArg::Optional::NO,
2264+
/* default_val */ "",
2265+
"path to the output file. If relative, will be prefixed by datadir."},
2266+
},
2267+
RPCResult{
2268+
"{\n"
2269+
" \"coins_written\": n, (numeric) the number of coins written in the snapshot\n"
2270+
" \"base_hash\": \"...\", (string) the hash of the base of the snapshot\n"
2271+
" \"base_height\": n, (string) the height of the base of the snapshot\n"
2272+
" \"path\": \"...\" (string) the absolute path that the snapshot was written to\n"
2273+
"]\n"
2274+
},
2275+
RPCExamples{
2276+
HelpExampleCli("dumptxoutset", "utxo.dat")
2277+
}
2278+
}.Check(request);
2279+
2280+
fs::path path = fs::absolute(request.params[0].get_str(), GetDataDir());
2281+
// Write to a temporary path and then move into `path` on completion
2282+
// to avoid confusion due to an interruption.
2283+
fs::path temppath = fs::absolute(request.params[0].get_str() + ".incomplete", GetDataDir());
2284+
2285+
if (fs::exists(path)) {
2286+
throw JSONRPCError(
2287+
RPC_INVALID_PARAMETER,
2288+
path.string() + " already exists. If you are sure this is what you want, "
2289+
"move it out of the way first");
2290+
}
2291+
2292+
FILE* file{fsbridge::fopen(temppath, "wb")};
2293+
CAutoFile afile{file, SER_DISK, CLIENT_VERSION};
2294+
std::unique_ptr<CCoinsViewCursor> pcursor;
2295+
CCoinsStats stats;
2296+
CBlockIndex* tip;
2297+
2298+
{
2299+
// We need to lock cs_main to ensure that the coinsdb isn't written to
2300+
// between (i) flushing coins cache to disk (coinsdb), (ii) getting stats
2301+
// based upon the coinsdb, and (iii) constructing a cursor to the
2302+
// coinsdb for use below this block.
2303+
//
2304+
// Cursors returned by leveldb iterate over snapshots, so the contents
2305+
// of the pcursor will not be affected by simultaneous writes during
2306+
// use below this block.
2307+
//
2308+
// See discussion here:
2309+
// https://github.com/bitcoin/bitcoin/pull/15606#discussion_r274479369
2310+
//
2311+
LOCK(::cs_main);
2312+
2313+
::ChainstateActive().ForceFlushStateToDisk();
2314+
2315+
if (!GetUTXOStats(&::ChainstateActive().CoinsDB(), stats)) {
2316+
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
2317+
}
2318+
2319+
pcursor = std::unique_ptr<CCoinsViewCursor>(::ChainstateActive().CoinsDB().Cursor());
2320+
tip = LookupBlockIndex(stats.hashBlock);
2321+
CHECK_NONFATAL(tip);
2322+
}
2323+
2324+
SnapshotMetadata metadata{tip->GetBlockHash(), stats.coins_count, tip->nChainTx};
2325+
2326+
afile << metadata;
2327+
2328+
COutPoint key;
2329+
Coin coin;
2330+
unsigned int iter{0};
2331+
2332+
while (pcursor->Valid()) {
2333+
if (iter % 5000 == 0 && !IsRPCRunning()) {
2334+
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down");
2335+
}
2336+
++iter;
2337+
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
2338+
afile << key;
2339+
afile << coin;
2340+
}
2341+
2342+
pcursor->Next();
2343+
}
2344+
2345+
afile.fclose();
2346+
fs::rename(temppath, path);
2347+
2348+
UniValue result(UniValue::VOBJ);
2349+
result.pushKV("coins_written", stats.coins_count);
2350+
result.pushKV("base_hash", tip->GetBlockHash().ToString());
2351+
result.pushKV("base_height", tip->nHeight);
2352+
result.pushKV("path", path.string());
2353+
return result;
2354+
}
2355+
22482356
// clang-format off
22492357
static const CRPCCommand commands[] =
22502358
{ // category name actor (function) argNames
@@ -2281,6 +2389,7 @@ static const CRPCCommand commands[] =
22812389
{ "hidden", "waitforblock", &waitforblock, {"blockhash","timeout"} },
22822390
{ "hidden", "waitforblockheight", &waitforblockheight, {"height","timeout"} },
22832391
{ "hidden", "syncwithvalidationinterfacequeue", &syncwithvalidationinterfacequeue, {} },
2392+
{ "hidden", "dumptxoutset", &dumptxoutset, {"path"} },
22842393
};
22852394
// clang-format on
22862395

0 commit comments

Comments
 (0)