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