rpc: Refactor gettxspendingprevout to be easier to parse

Co-authored-by: Matias Furszyfer <matiasfurszyfer@protonmail.com>
This commit is contained in:
Fabian Jahr 2026-02-20 12:23:19 +01:00
parent ca45461ddb
commit d236415649
No known key found for this signature in database
GPG Key ID: 145F599ECE70D26C

View File

@ -950,14 +950,13 @@ static RPCHelpMan gettxspendingprevout()
const bool mempool_only{options.exists("mempool_only") ? options["mempool_only"].get_bool() : !g_txospenderindex};
const bool return_spending_tx{options.exists("return_spending_tx") ? options["return_spending_tx"].get_bool() : false};
// Worklist of outpoints to resolve
struct Entry {
const COutPoint prevout;
const UniValue& input;
UniValue output;
COutPoint outpoint;
const UniValue* raw;
};
std::vector<Entry> prevouts;
prevouts.reserve(output_params.size());
std::vector<Entry> prevouts_to_process;
prevouts_to_process.reserve(output_params.size());
for (unsigned int idx = 0; idx < output_params.size(); idx++) {
const UniValue& o = output_params[idx].get_obj();
@ -972,57 +971,68 @@ static RPCHelpMan gettxspendingprevout()
if (nOutput < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative");
}
prevouts.emplace_back(COutPoint{txid, uint32_t(nOutput)}, o, UniValue{});
prevouts_to_process.emplace_back(COutPoint{txid, static_cast<uint32_t>(nOutput)}, &o);
}
// search the mempool first
bool missing_from_mempool{false};
auto make_output = [return_spending_tx](const Entry& prevout, const CTransaction* spending_tx = nullptr) {
UniValue o{*prevout.raw};
if (spending_tx) {
o.pushKV("spendingtxid", spending_tx->GetHash().ToString());
if (return_spending_tx) {
o.pushKV("spendingtx", EncodeHexTx(*spending_tx));
}
}
return o;
};
UniValue result{UniValue::VARR};
// Search the mempool first
{
const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
LOCK(mempool.cs);
for (auto& entry : prevouts) {
const CTransaction* spendingTx = mempool.GetConflictTx(entry.prevout);
if (spendingTx != nullptr) {
UniValue o{entry.input};
o.pushKV("spendingtxid", spendingTx->GetHash().ToString());
if (return_spending_tx) {
o.pushKV("spendingtx", EncodeHexTx(*spendingTx));
}
entry.output = std::move(o);
} else {
missing_from_mempool = true;
// Make the result if the spending tx appears in the mempool or this is a mempool_only request
for (auto it = prevouts_to_process.begin(); it != prevouts_to_process.end(); ) {
const CTransaction* spending_tx{mempool.GetConflictTx(it->outpoint)};
// If the outpoint is not spent in the mempool and this is not a mempool-only
// request, we cannot answer it yet.
if (!spending_tx && !mempool_only) {
++it;
continue;
}
result.push_back(make_output(*it, spending_tx));
it = prevouts_to_process.erase(it);
}
}
// if search is not limited to the mempool and no spender was found for an outpoint, search the txospenderindex
// we call g_txospenderindex->BlockUntilSyncedToCurrentChain() only if g_txospenderindex is going to be used
UniValue result{UniValue::VARR};
bool txospenderindex_ready{mempool_only || !missing_from_mempool || (g_txospenderindex && g_txospenderindex->BlockUntilSyncedToCurrentChain())};
for (auto& entry : prevouts) {
if (!entry.output.isNull()) {
result.push_back(std::move(entry.output));
continue;
// Return early if all requests have been handled by the mempool search
if (prevouts_to_process.empty()) {
return result;
}
// At this point the request was not limited to the mempool and some outpoints remain
// unresolved. We now rely on the index to determine whether they were spent or not.
if (!g_txospenderindex || !g_txospenderindex->BlockUntilSyncedToCurrentChain()) {
throw JSONRPCError(RPC_MISC_ERROR, "Mempool lacks a relevant spend, and txospenderindex is unavailable.");
}
for (const auto& prevout : prevouts_to_process) {
const auto spender{g_txospenderindex->FindSpender(prevout.outpoint)};
if (!spender) {
throw JSONRPCError(RPC_MISC_ERROR, spender.error());
}
UniValue o{entry.input};
if (mempool_only) {
// do nothing, caller has selected to only query the mempool
} else if (!txospenderindex_ready) {
throw JSONRPCError(RPC_MISC_ERROR, strprintf("No spending tx for the outpoint %s:%d in mempool, and txospenderindex is unavailable.", entry.prevout.hash.GetHex(), entry.prevout.n));
if (const auto& spender_opt{spender.value()}) {
UniValue o{make_output(prevout, spender_opt->tx.get())};
o.pushKV("blockhash", spender_opt->block_hash.GetHex());
result.push_back(std::move(o));
} else {
// no spending tx in mempool, query txospender index
const auto spender{g_txospenderindex->FindSpender(entry.prevout)};
if (!spender) {
throw JSONRPCError(RPC_MISC_ERROR, spender.error());
}
if (spender.value()) {
o.pushKV("spendingtxid", spender.value()->tx->GetHash().GetHex());
o.pushKV("blockhash", spender.value()->block_hash.GetHex());
if (return_spending_tx) {
o.pushKV("spendingtx", EncodeHexTx(*spender.value()->tx));
}
}
// Only return the input outpoint itself, which indicates it is unspent.
result.push_back(make_output(prevout));
}
result.push_back(std::move(o));
}
return result;