Merge bitcoin/bitcoin#34749: rpc: Refactor gettxspendingprevout to be easier to parse

d236415649 rpc: Refactor gettxspendingprevout to be easier to parse (Fabian Jahr)

Pull request description:

  This refactoring commit was originally part of #34635 but the the change was some-what controversial: https://github.com/bitcoin/bitcoin/pull/34635#discussion_r2833541912 and the discussion was blocking the rest of the uncontroversial changes from moving forward.

  It looks like #34748 is trying to achieve something similar now, we'll have to see how the different approached converge but at least there seems to be some more conceptual interest than appeared on #34635 originally.

ACKs for top commit:
  hodlinator:
    ACK d236415649
  sedited:
    Re-ACK d236415649
  furszy:
    utACK d236415649

Tree-SHA512: 3df70bb8f143247f66d52430b3d8a4ecc5f0b80aa68ef3fc9082739b6f1be02fa9ef88ff134bb01034c9b6006400342e71fa8dc0a203d8e13b4bf6e21d1b05a8
This commit is contained in:
merge-script
2026-03-12 13:44:43 +01:00

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;