Skip to content

Commit 0b00908

Browse files
committed
rpc: Generate UTXO set hints
1 parent 0c9e2b2 commit 0b00908

File tree

1 file changed

+105
-0
lines changed

1 file changed

+105
-0
lines changed

src/rpc/blockchain.cpp

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include <script/descriptor.h>
4040
#include <serialize.h>
4141
#include <streams.h>
42+
#include <swiftsync.h>
4243
#include <sync.h>
4344
#include <tinyformat.h>
4445
#include <txdb.h>
@@ -3396,6 +3397,109 @@ static RPCHelpMan loadtxoutset()
33963397
};
33973398
}
33983399

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 an hour or more 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+
},
3417+
RPCResult{
3418+
RPCResult::Type::OBJ, "", "",
3419+
{
3420+
{RPCResult::Type::NUM, "height", "height of the blockchain the file encodes UTXO hints for."},
3421+
}
3422+
},
3423+
RPCExamples{
3424+
HelpExampleCli("-rpcclienttimeout=0 generateutxohints", "hints.bin")
3425+
},
3426+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
3427+
{
3428+
using swiftsync::Hintfile;
3429+
NodeContext& node = EnsureAnyNodeContext(request.context);
3430+
if (node.chainman->m_blockman.IsPruneMode()) {
3431+
throw JSONRPCError(RPC_MISC_ERROR, "Creating a hint file in pruned mode is not possible.");
3432+
}
3433+
3434+
const ArgsManager& args = EnsureAnyArgsman(request.context);
3435+
const fs::path path = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(self.Arg<std::string_view>("path")));
3436+
const fs::path temppath = path + ".incomplete";
3437+
if (fs::exists(path)) {
3438+
throw JSONRPCError(
3439+
RPC_INVALID_PARAMETER,
3440+
path.utf8string() + " already exists. If you are sure this is what you want, "
3441+
"move it out of the way first");
3442+
}
3443+
FILE* file{fsbridge::fopen(temppath, "wb")};
3444+
AutoFile afile{file};
3445+
if (afile.IsNull()) {
3446+
throw JSONRPCError(
3447+
RPC_INVALID_PARAMETER,
3448+
"Couldn't open file " + temppath.utf8string() + " for writing.");
3449+
}
3450+
3451+
CConnman& connman = EnsureConnman(node);
3452+
NetworkDisable disable_net = NetworkDisable(connman);
3453+
ChainstateManager& chainman = EnsureChainman(node);
3454+
LOCK(chainman.GetMutex());
3455+
CChain& active_chain = chainman.ActiveChain();
3456+
Chainstate& active_state = chainman.ActiveChainstate();
3457+
active_state.ForceFlushStateToDisk();
3458+
CBlockIndex* end_index = active_chain.Tip();
3459+
auto tip_height = end_index->nHeight;
3460+
LogDebug(BCLog::RPC, "Active chain best tip %d", tip_height);
3461+
Hintfile hintfile = Hintfile::CreateNew(afile, end_index->GetBlockHash(), tip_height);
3462+
CBlockIndex* curr = active_chain.Next(active_chain.Genesis());
3463+
3464+
while (curr) {
3465+
auto height = curr->nHeight;
3466+
if (height % 10000 == 0) {
3467+
LogDebug(BCLog::RPC, "Wrote hints up to height (%s)", height);
3468+
}
3469+
FlatFilePos file_pos = curr->GetBlockPos();
3470+
std::unique_ptr<CBlock> pblock = std::make_unique<CBlock>();
3471+
bool read = chainman.m_blockman.ReadBlock(*pblock, file_pos, curr->GetBlockHash());
3472+
if (!read) {
3473+
throw JSONRPCError(RPC_DATABASE_ERROR, "Block could not be read from disk.");
3474+
}
3475+
std::vector<uint64_t> unspent_offsets;
3476+
uint64_t curr_offset = 0;
3477+
for (const auto& tx: pblock->vtx) {
3478+
const Txid& txid = tx->GetHash();
3479+
for (size_t vout = 0; vout < tx->vout.size(); vout++) {
3480+
COutPoint outpoint = COutPoint(txid, vout);
3481+
if (active_state.CoinsDB().HaveCoin(outpoint)) {
3482+
unspent_offsets.push_back(curr_offset);
3483+
curr_offset = 0;
3484+
}
3485+
curr_offset++;
3486+
}
3487+
}
3488+
if (!hintfile.WriteNextBlock(unspent_offsets)) {
3489+
throw JSONRPCError(RPC_DATABASE_ERROR, "Failed to commit changes to hint file.");
3490+
}
3491+
node.rpc_interruption_point();
3492+
curr = active_chain.Next(curr);
3493+
}
3494+
fs::rename(temppath, path);
3495+
3496+
UniValue result(UniValue::VOBJ);
3497+
result.pushKV("height", tip_height);
3498+
return result;
3499+
},
3500+
};
3501+
}
3502+
33993503
const std::vector<RPCResult> RPCHelpForChainstate{
34003504
{RPCResult::Type::NUM, "blocks", "number of blocks in this chainstate"},
34013505
{RPCResult::Type::STR_HEX, "bestblockhash", "blockhash of the tip"},
@@ -3496,6 +3600,7 @@ void RegisterBlockchainRPCCommands(CRPCTable& t)
34963600
{"blockchain", &getblockfilter},
34973601
{"blockchain", &dumptxoutset},
34983602
{"blockchain", &loadtxoutset},
3603+
{"blockchain", &genhints},
34993604
{"blockchain", &getchainstates},
35003605
{"hidden", &invalidateblock},
35013606
{"hidden", &reconsiderblock},

0 commit comments

Comments
 (0)