|
15 | 15 | #include <hash.h>
|
16 | 16 | #include <index/blockfilterindex.h>
|
17 | 17 | #include <node/coinstats.h>
|
| 18 | +#include <node/utxo_snapshot.h> |
18 | 19 | #include <policy/feerate.h>
|
19 | 20 | #include <policy/policy.h>
|
20 | 21 | #include <policy/rbf.h>
|
@@ -2245,6 +2246,113 @@ static UniValue getblockfilter(const JSONRPCRequest& request)
|
2245 | 2246 | return ret;
|
2246 | 2247 | }
|
2247 | 2248 |
|
| 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 | + |
2248 | 2356 | // clang-format off
|
2249 | 2357 | static const CRPCCommand commands[] =
|
2250 | 2358 | { // category name actor (function) argNames
|
@@ -2281,6 +2389,7 @@ static const CRPCCommand commands[] =
|
2281 | 2389 | { "hidden", "waitforblock", &waitforblock, {"blockhash","timeout"} },
|
2282 | 2390 | { "hidden", "waitforblockheight", &waitforblockheight, {"height","timeout"} },
|
2283 | 2391 | { "hidden", "syncwithvalidationinterfacequeue", &syncwithvalidationinterfacequeue, {} },
|
| 2392 | + { "hidden", "dumptxoutset", &dumptxoutset, {"path"} }, |
2284 | 2393 | };
|
2285 | 2394 | // clang-format on
|
2286 | 2395 |
|
|
0 commit comments