mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-11 16:17:54 +02:00
Add a "tx output spender" index
Adds an outpoint -> txid index, which can be used to find which transactions spent a given output. We use a composite key with 2 parts (suggested by @romanz): hash(spent outpoint) and tx position, with an empty value. To find the spending tx for a given outpoint, we do a prefix search (prefix being the hash of the provided outpoint), and for all keys that match this prefix we load the tx at the position specified in the key and return it, along with the block hash, if does spend the provided outpoint. To handle reorgs we just erase the keys computed from the removed block. This index is extremely useful for Lightning and more generally for layer-2 protocols that rely on chains of unpublished transactions. If enabled, this index will be used by `gettxspendingprevout` when it does not find a spending transaction in the mempool.
This commit is contained in:
@@ -313,6 +313,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||
{ "getmempoolancestors", 1, "verbose" },
|
||||
{ "getmempooldescendants", 1, "verbose" },
|
||||
{ "gettxspendingprevout", 0, "outputs" },
|
||||
{ "gettxspendingprevout", 1, "options" },
|
||||
{ "gettxspendingprevout", 1, "mempool_only" },
|
||||
{ "gettxspendingprevout", 1, "return_spending_tx" },
|
||||
{ "bumpfee", 1, "options" },
|
||||
{ "bumpfee", 1, "conf_target"},
|
||||
{ "bumpfee", 1, "fee_rate"},
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <common/args.h>
|
||||
#include <consensus/validation.h>
|
||||
#include <core_io.h>
|
||||
#include <index/txospenderindex.h>
|
||||
#include <kernel/mempool_entry.h>
|
||||
#include <net_processing.h>
|
||||
#include <netbase.h>
|
||||
@@ -775,7 +776,7 @@ static RPCHelpMan getmempoolentry()
|
||||
static RPCHelpMan gettxspendingprevout()
|
||||
{
|
||||
return RPCHelpMan{"gettxspendingprevout",
|
||||
"Scans the mempool to find transactions spending any of the given outputs",
|
||||
"Scans the mempool (and the txospenderindex, if available) to find transactions spending any of the given outputs",
|
||||
{
|
||||
{"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The transaction outputs that we want to check, and within each, the txid (string) vout (numeric).",
|
||||
{
|
||||
@@ -787,6 +788,12 @@ static RPCHelpMan gettxspendingprevout()
|
||||
},
|
||||
},
|
||||
},
|
||||
{"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
|
||||
{
|
||||
{"mempool_only", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true if txospenderindex unavailable, otherwise false"}, "If false and mempool lacks a relevant spend, use txospenderindex (throws an exception if not available)."},
|
||||
{"return_spending_tx", RPCArg::Type::BOOL, RPCArg::DefaultHint{"false"}, "If true, return the full spending tx."},
|
||||
},
|
||||
},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::ARR, "", "",
|
||||
@@ -796,12 +803,15 @@ static RPCHelpMan gettxspendingprevout()
|
||||
{RPCResult::Type::STR_HEX, "txid", "the transaction id of the checked output"},
|
||||
{RPCResult::Type::NUM, "vout", "the vout value of the checked output"},
|
||||
{RPCResult::Type::STR_HEX, "spendingtxid", /*optional=*/true, "the transaction id of the mempool transaction spending this output (omitted if unspent)"},
|
||||
{RPCResult::Type::STR_HEX, "spendingtx", /*optional=*/true, "the transaction spending this output (only if return_spending_tx is set, omitted if unspent)"},
|
||||
{RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "the hash of the spending block (omitted if unspent or the spending tx is not confirmed)"},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("gettxspendingprevout", "\"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":3}]\"")
|
||||
+ HelpExampleRpc("gettxspendingprevout", "\"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":3}]\"")
|
||||
+ HelpExampleCliNamed("gettxspendingprevout", {{"outputs", "[{\"txid\":\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\",\"vout\":3}]"}, {"return_spending_tx", true}})
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
@@ -809,6 +819,16 @@ static RPCHelpMan gettxspendingprevout()
|
||||
if (output_params.empty()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, outputs are missing");
|
||||
}
|
||||
const UniValue options{request.params[1].isNull() ? UniValue::VOBJ : request.params[1]};\
|
||||
RPCTypeCheckObj(options,
|
||||
{
|
||||
{"mempool_only", UniValueType(UniValue::VBOOL)},
|
||||
{"return_spending_tx", UniValueType(UniValue::VBOOL)},
|
||||
}, /*fAllowNull=*/true, /*fStrict=*/true);
|
||||
|
||||
const bool txospenderindex_ready{g_txospenderindex && g_txospenderindex->BlockUntilSyncedToCurrentChain()};
|
||||
const bool mempool_only{options.exists("mempool_only") ? options["mempool_only"].get_bool() : !txospenderindex_ready};
|
||||
const bool return_spending_tx{options.exists("return_spending_tx") ? options["return_spending_tx"].get_bool() : false};
|
||||
|
||||
std::vector<COutPoint> prevouts;
|
||||
prevouts.reserve(output_params.size());
|
||||
@@ -844,8 +864,27 @@ static RPCHelpMan gettxspendingprevout()
|
||||
const CTransaction* spendingTx = mempool.GetConflictTx(prevout);
|
||||
if (spendingTx != nullptr) {
|
||||
o.pushKV("spendingtxid", spendingTx->GetHash().ToString());
|
||||
if (return_spending_tx) {
|
||||
o.pushKV("spendingtx", EncodeHexTx(*spendingTx));
|
||||
}
|
||||
} else 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.", prevout.hash.GetHex(), prevout.n));
|
||||
} else {
|
||||
// no spending tx in mempool, query txospender index
|
||||
const auto spender{g_txospenderindex->FindSpender(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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.push_back(std::move(o));
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <index/blockfilterindex.h>
|
||||
#include <index/coinstatsindex.h>
|
||||
#include <index/txindex.h>
|
||||
#include <index/txospenderindex.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <interfaces/echo.h>
|
||||
#include <interfaces/init.h>
|
||||
@@ -397,6 +398,10 @@ static RPCHelpMan getindexinfo()
|
||||
result.pushKVs(SummaryToJSON(g_coin_stats_index->GetSummary(), index_name));
|
||||
}
|
||||
|
||||
if (g_txospenderindex) {
|
||||
result.pushKVs(SummaryToJSON(g_txospenderindex->GetSummary(), index_name));
|
||||
}
|
||||
|
||||
ForEachBlockFilterIndex([&result, &index_name](const BlockFilterIndex& index) {
|
||||
result.pushKVs(SummaryToJSON(index.GetSummary(), index_name));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user