rpc: Refactor gettxspendingprevout to be easier to parse
What changed, and why it matters
This is a code cleanup (refactor) of an internal Bitcoin Core RPC helper. It rewrites how the gettxspendingprevout command walks through a list of outpoints and builds its answer, but it does not change what the command does or what data it returns. There is no security-relevant change visible in the diff.
No security action needed; review as ordinary code-quality change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors gettxspendingprevout in src/rpc/mempool.cpp. It replaces an in-place Entry struct that carried an output field with a simpler worklist (prevouts_to_process), introduces a make_output lambda, and restructures the mempool/index lookup flow so unresolved outpoints are erased from the worklist as they are answered. The functional behavior—mempool search first, optional txospenderindex fallback, error when the index is unavailable, and the returned JSON shape—appears unchanged. No new inputs are accepted, no validation is weakened, and no resource limits are altered.
Changed components
src/rpc/mempool.cppgettxspendingprevout RPCInspect captured patch +56 / −46
diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
index 34abc3c0..5bd85236 100644
--- a/src/rpc/mempool.cpp
+++ b/src/rpc/mempool.cpp
@@ -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;
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.