diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 34abc3c08e4..5bd85236d77 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 prevouts; - prevouts.reserve(output_params.size()); - + std::vector 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(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;