|
39 | 39 | #include <script/descriptor.h> |
40 | 40 | #include <serialize.h> |
41 | 41 | #include <streams.h> |
| 42 | +#include <swiftsync.h> |
42 | 43 | #include <sync.h> |
43 | 44 | #include <tinyformat.h> |
44 | 45 | #include <txdb.h> |
@@ -3392,6 +3393,129 @@ static RPCHelpMan loadtxoutset() |
3392 | 3393 | }; |
3393 | 3394 | } |
3394 | 3395 |
|
| 3396 | +static RPCHelpMan generatetxohints() |
| 3397 | +{ |
| 3398 | + return RPCHelpMan{ |
| 3399 | + "generatetxohints", |
| 3400 | + "Build a file of hints for the state of the UTXO set at a particular height.\n" |
| 3401 | + "The purpose of said hints is to allow clients performing initial block download\n" |
| 3402 | + "to omit unnecessary disk I/O and CPU usage.\n" |
| 3403 | + "The hint file is constructed by reading in blocks sequentially and determining what outputs\n" |
| 3404 | + "will remain in the UTXO set. Network activity will be suspended during this process, and the\n" |
| 3405 | + "hint file may take a few hours to build." |
| 3406 | + "Make sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)", |
| 3407 | + { |
| 3408 | + {"path", |
| 3409 | + RPCArg::Type::STR, |
| 3410 | + RPCArg::Optional::NO, |
| 3411 | + "Path to the hint file. If relative, will be prefixed by datadir."}, |
| 3412 | + {"rollback", |
| 3413 | + RPCArg::Type::NUM, |
| 3414 | + RPCArg::Optional::OMITTED, |
| 3415 | + "The block hash or height to build the hint file up to. If none is provided, the file will be built from the current block tip.", |
| 3416 | + RPCArgOptions{ |
| 3417 | + .skip_type_check = true, |
| 3418 | + .type_str = {"", "string or numeric"},}}, |
| 3419 | + }, |
| 3420 | + RPCResult{ |
| 3421 | + RPCResult::Type::OBJ, "", "", |
| 3422 | + { |
| 3423 | + {RPCResult::Type::NUM, "height", "The stopping height encoded by the hint file."}, |
| 3424 | + {RPCResult::Type::STR, "path", "Absolute path where the file was written."}, |
| 3425 | + {RPCResult::Type::STR, "duration", "Time taken to build the file."}, |
| 3426 | + } |
| 3427 | + }, |
| 3428 | + RPCExamples{ |
| 3429 | + HelpExampleCli("--rpcclienttimeout=0 generatetxohints", "signet.hints 270000"), |
| 3430 | + }, |
| 3431 | + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue |
| 3432 | +{ |
| 3433 | + const auto start{SteadyClock::now()}; |
| 3434 | + NodeContext& node{EnsureAnyNodeContext(request.context)}; |
| 3435 | + if (node.chainman->m_blockman.IsPruneMode()) { |
| 3436 | + throw JSONRPCError(RPC_MISC_ERROR, "Creating a hint file in pruned mode is not possible."); |
| 3437 | + } |
| 3438 | + const ArgsManager& args{EnsureAnyArgsman(request.context)}; |
| 3439 | + const fs::path path = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(self.Arg<std::string_view>("path"))); |
| 3440 | + const fs::path temppath = path + ".incomplete"; |
| 3441 | + if (fs::exists(path)) { |
| 3442 | + throw JSONRPCError( |
| 3443 | + RPC_INVALID_PARAMETER, |
| 3444 | + path.utf8string() + " already exists. If you are sure this is what you want, " |
| 3445 | + "move it out of the way first"); |
| 3446 | + } |
| 3447 | + FILE* file{fsbridge::fopen(temppath, "wb")}; |
| 3448 | + AutoFile afile{file}; |
| 3449 | + if (afile.IsNull()) { |
| 3450 | + throw JSONRPCError( |
| 3451 | + RPC_INVALID_PARAMETER, |
| 3452 | + "Couldn't open file " + temppath.utf8string() + " for writing."); |
| 3453 | + } |
| 3454 | + CConnman& connman{EnsureConnman(node)}; |
| 3455 | + NetworkDisable disable_net{NetworkDisable(connman)}; |
| 3456 | + std::optional<TemporaryRollback> rollback; |
| 3457 | + if (!request.params[1].isNull()) { |
| 3458 | + const CBlockIndex* invalidate_index = ParseHashOrHeight(request.params[1], *node.chainman); |
| 3459 | + invalidate_index = WITH_LOCK(::cs_main, return node.chainman->ActiveChain().Next(invalidate_index)); |
| 3460 | + rollback.emplace(*node.chainman, *invalidate_index); |
| 3461 | + } |
| 3462 | + node.rpc_interruption_point(); |
| 3463 | + LOCK(node.chainman->GetMutex()); |
| 3464 | + CChain& active_chain{node.chainman->ActiveChain()}; |
| 3465 | + Chainstate& active_state{node.chainman->ActiveChainstate()}; |
| 3466 | + active_state.ForceFlushStateToDisk(); |
| 3467 | + const CBlockIndex* end_index{active_chain.Tip()}; |
| 3468 | + const auto tip_height{end_index->nHeight}; |
| 3469 | + LogDebug(BCLog::RPC, "Active chain best tip %d", tip_height); |
| 3470 | + swiftsync::HintsfileWriter writer{swiftsync::HintsfileWriter(afile, tip_height)}; |
| 3471 | + CBlockIndex* curr{active_chain.Next(active_chain.Genesis())}; |
| 3472 | + uint64_t total_outputs_written{0}; |
| 3473 | + while (curr) { |
| 3474 | + auto height{curr->nHeight}; |
| 3475 | + if (height % 10000 == 0) { |
| 3476 | + LogDebug(BCLog::RPC, "Wrote hints up to height %s, total outputs written %d, file size %d megabytes", height, total_outputs_written, writer.SizeMb()); |
| 3477 | + } |
| 3478 | + FlatFilePos file_pos = curr->GetBlockPos(); |
| 3479 | + std::unique_ptr<CBlock> pblock = std::make_unique<CBlock>(); |
| 3480 | + bool read = node.chainman->m_blockman.ReadBlock(*pblock, file_pos, curr->GetBlockHash()); |
| 3481 | + if (!read) { |
| 3482 | + throw JSONRPCError(RPC_DATABASE_ERROR, "Block could not be read from disk."); |
| 3483 | + } |
| 3484 | + swiftsync::BlockHintsWriter hints{}; |
| 3485 | + for (const auto& tx: pblock->vtx) { |
| 3486 | + const Txid& txid = tx->GetHash(); |
| 3487 | + for (size_t vout{}; vout < tx->vout.size(); ++vout) { |
| 3488 | + if (tx->vout[vout].scriptPubKey.IsUnspendable()) { |
| 3489 | + continue; |
| 3490 | + } |
| 3491 | + const COutPoint outpoint = COutPoint(txid, vout); |
| 3492 | + if (active_state.CoinsDB().HaveCoin(outpoint)) { |
| 3493 | + hints.PushHighBit(); |
| 3494 | + } else { |
| 3495 | + hints.PushLowBit(); |
| 3496 | + } |
| 3497 | + ++total_outputs_written; |
| 3498 | + } |
| 3499 | + } |
| 3500 | + if (!writer.WriteNextUnspents(hints, uint32_t(height))) { |
| 3501 | + throw JSONRPCError(RPC_DATABASE_ERROR, "Failed to commit changes to hint file."); |
| 3502 | + } |
| 3503 | + node.rpc_interruption_point(); |
| 3504 | + curr = active_chain.Next(curr); |
| 3505 | + } |
| 3506 | + writer.Close(); |
| 3507 | + fs::rename(temppath, path); |
| 3508 | + const auto end{SteadyClock::now()}; |
| 3509 | + const auto duration = std::chrono::duration_cast<std::chrono::minutes>(end - start).count(); |
| 3510 | + UniValue result(UniValue::VOBJ); |
| 3511 | + result.pushKV("height", tip_height); |
| 3512 | + result.pushKV("path", path.utf8string()); |
| 3513 | + result.pushKV("duration", tfm::format("%d minutes", duration)); |
| 3514 | + return result; |
| 3515 | +}, |
| 3516 | + }; |
| 3517 | +} |
| 3518 | + |
3395 | 3519 | const std::vector<RPCResult> RPCHelpForChainstate{ |
3396 | 3520 | {RPCResult::Type::NUM, "blocks", "number of blocks in this chainstate"}, |
3397 | 3521 | {RPCResult::Type::STR_HEX, "bestblockhash", "blockhash of the tip"}, |
@@ -3491,6 +3615,7 @@ void RegisterBlockchainRPCCommands(CRPCTable& t) |
3491 | 3615 | {"blockchain", &getblockfilter}, |
3492 | 3616 | {"blockchain", &dumptxoutset}, |
3493 | 3617 | {"blockchain", &loadtxoutset}, |
| 3618 | + {"blockchain", &generatetxohints}, |
3494 | 3619 | {"blockchain", &getchainstates}, |
3495 | 3620 | {"hidden", &invalidateblock}, |
3496 | 3621 | {"hidden", &reconsiderblock}, |
|
0 commit comments