mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-15 12:51:00 +02:00
Merge 3ba6dd6f4b into merged_master (Bitcoin PR bitcoin/bitcoin#24408)
This commit is contained in:
commit
bf065f9c8a
7 changed files with 200 additions and 0 deletions
5
doc/release-notes/release-notes-24408.md
Normal file
5
doc/release-notes/release-notes-24408.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
New RPCs
|
||||
--------
|
||||
|
||||
- A new `gettxspendingprevout` RPC has been added, which scans the mempool to find
|
||||
transactions spending any of the given outpoints. (#24408)
|
||||
|
|
@ -176,6 +176,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "setwalletflag", 1, "value" },
|
||||
{ "getmempoolancestors", 1, "verbose" },
|
||||
{ "getmempooldescendants", 1, "verbose" },
|
||||
{ "gettxspendingprevout", 0, "outputs" },
|
||||
{ "bumpfee", 1, "options" },
|
||||
{ "psbtbumpfee", 1, "options" },
|
||||
{ "logging", 0, "include" },
|
||||
|
|
|
|||
|
|
@ -597,6 +597,89 @@ static RPCHelpMan getmempoolentry()
|
|||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan gettxspendingprevout()
|
||||
{
|
||||
return RPCHelpMan{"gettxspendingprevout",
|
||||
"Scans the mempool 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).",
|
||||
{
|
||||
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
|
||||
{
|
||||
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
|
||||
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{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)"},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("gettxspendingprevout", "\"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":3}]\"")
|
||||
+ HelpExampleRpc("gettxspendingprevout", "\"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":3}]\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
RPCTypeCheckArgument(request.params[0], UniValue::VARR);
|
||||
const UniValue& output_params = request.params[0];
|
||||
if (output_params.empty()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, outputs are missing");
|
||||
}
|
||||
|
||||
std::vector<COutPoint> prevouts;
|
||||
prevouts.reserve(output_params.size());
|
||||
|
||||
for (unsigned int idx = 0; idx < output_params.size(); idx++) {
|
||||
const UniValue& o = output_params[idx].get_obj();
|
||||
|
||||
RPCTypeCheckObj(o,
|
||||
{
|
||||
{"txid", UniValueType(UniValue::VSTR)},
|
||||
{"vout", UniValueType(UniValue::VNUM)},
|
||||
}, /*fAllowNull=*/false, /*fStrict=*/true);
|
||||
|
||||
const uint256 txid(ParseHashO(o, "txid"));
|
||||
const int nOutput = find_value(o, "vout").get_int();
|
||||
if (nOutput < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative");
|
||||
}
|
||||
|
||||
prevouts.emplace_back(txid, nOutput);
|
||||
}
|
||||
|
||||
const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
|
||||
LOCK(mempool.cs);
|
||||
|
||||
UniValue result{UniValue::VARR};
|
||||
|
||||
for (const COutPoint& prevout : prevouts) {
|
||||
UniValue o(UniValue::VOBJ);
|
||||
o.pushKV("txid", prevout.hash.ToString());
|
||||
o.pushKV("vout", (uint64_t)prevout.n);
|
||||
|
||||
const CTransaction* spendingTx = mempool.GetConflictTx(prevout);
|
||||
if (spendingTx != nullptr) {
|
||||
o.pushKV("spendingtxid", spendingTx->GetHash().ToString());
|
||||
}
|
||||
|
||||
result.push_back(o);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
UniValue MempoolInfoToJSON(const CTxMemPool& pool)
|
||||
{
|
||||
// Make sure this call is atomic in the pool.
|
||||
|
|
@ -687,6 +770,7 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
|
|||
{"blockchain", &getmempoolancestors},
|
||||
{"blockchain", &getmempooldescendants},
|
||||
{"blockchain", &getmempoolentry},
|
||||
{"blockchain", &gettxspendingprevout},
|
||||
{"blockchain", &getmempoolinfo},
|
||||
{"blockchain", &getrawmempool},
|
||||
{"blockchain", &savemempool},
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
|
|||
"getmempoolancestors",
|
||||
"getmempooldescendants",
|
||||
"getmempoolentry",
|
||||
"gettxspendingprevout",
|
||||
"getmempoolinfo",
|
||||
"getmininginfo",
|
||||
"getnettotals",
|
||||
|
|
|
|||
|
|
@ -128,6 +128,12 @@ class MempoolPackagesTest(BitcoinTestFramework):
|
|||
entry = self.nodes[0].getmempoolentry(x)
|
||||
assert_equal(entry, mempool[x])
|
||||
|
||||
# Check that gettxspendingprevout is consistent with getrawmempool
|
||||
witnesstx = self.nodes[0].gettransaction(txid=x, verbose=True)['decoded']
|
||||
for tx_in in witnesstx["vin"]:
|
||||
spending_result = self.nodes[0].gettxspendingprevout([ {'txid' : tx_in["txid"], 'vout' : tx_in["vout"]} ])
|
||||
assert_equal(spending_result, [ {'txid' : tx_in["txid"], 'vout' : tx_in["vout"], 'spendingtxid' : x} ])
|
||||
|
||||
# Check that the descendant calculations are correct
|
||||
assert_equal(entry['descendantcount'], descendant_count)
|
||||
descendant_fees += entry['fees']['base']
|
||||
|
|
|
|||
102
test/functional/rpc_mempool_info.py
Executable file
102
test/functional/rpc_mempool_info.py
Executable file
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2014-2022 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test RPCs that retrieve information from the mempool."""
|
||||
|
||||
from test_framework.blocktools import COINBASE_MATURITY
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_rpc_error,
|
||||
)
|
||||
from test_framework.wallet import MiniWallet
|
||||
|
||||
|
||||
class RPCMempoolInfoTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
|
||||
def run_test(self):
|
||||
self.wallet = MiniWallet(self.nodes[0])
|
||||
self.generate(self.wallet, COINBASE_MATURITY + 1)
|
||||
self.wallet.rescan_utxos()
|
||||
confirmed_utxo = self.wallet.get_utxo()
|
||||
|
||||
# Create a tree of unconfirmed transactions in the mempool:
|
||||
# txA
|
||||
# / \
|
||||
# / \
|
||||
# / \
|
||||
# / \
|
||||
# / \
|
||||
# txB txC
|
||||
# / \ / \
|
||||
# / \ / \
|
||||
# txD txE txF txG
|
||||
# \ /
|
||||
# \ /
|
||||
# txH
|
||||
|
||||
def create_tx(**kwargs):
|
||||
return self.wallet.send_self_transfer_multi(
|
||||
from_node=self.nodes[0],
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
txA = create_tx(utxos_to_spend=[confirmed_utxo], num_outputs=2)
|
||||
txB = create_tx(utxos_to_spend=[txA["new_utxos"][0]], num_outputs=2)
|
||||
txC = create_tx(utxos_to_spend=[txA["new_utxos"][1]], num_outputs=2)
|
||||
txD = create_tx(utxos_to_spend=[txB["new_utxos"][0]], num_outputs=1)
|
||||
txE = create_tx(utxos_to_spend=[txB["new_utxos"][1]], num_outputs=1)
|
||||
txF = create_tx(utxos_to_spend=[txC["new_utxos"][0]], num_outputs=2)
|
||||
txG = create_tx(utxos_to_spend=[txC["new_utxos"][1]], num_outputs=1)
|
||||
txH = create_tx(utxos_to_spend=[txE["new_utxos"][0],txF["new_utxos"][0]], num_outputs=1)
|
||||
txidA, txidB, txidC, txidD, txidE, txidF, txidG, txidH = [
|
||||
tx["txid"] for tx in [txA, txB, txC, txD, txE, txF, txG, txH]
|
||||
]
|
||||
|
||||
mempool = self.nodes[0].getrawmempool()
|
||||
assert_equal(len(mempool), 8)
|
||||
for txid in [txidA, txidB, txidC, txidD, txidE, txidF, txidG, txidH]:
|
||||
assert_equal(txid in mempool, True)
|
||||
|
||||
self.log.info("Find transactions spending outputs")
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : confirmed_utxo['txid'], 'vout' : 0}, {'txid' : txidA, 'vout' : 1} ])
|
||||
assert_equal(result, [ {'txid' : confirmed_utxo['txid'], 'vout' : 0, 'spendingtxid' : txidA}, {'txid' : txidA, 'vout' : 1, 'spendingtxid' : txidC} ])
|
||||
|
||||
self.log.info("Find transaction spending multiple outputs")
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : txidE, 'vout' : 0}, {'txid' : txidF, 'vout' : 0} ])
|
||||
assert_equal(result, [ {'txid' : txidE, 'vout' : 0, 'spendingtxid' : txidH}, {'txid' : txidF, 'vout' : 0, 'spendingtxid' : txidH} ])
|
||||
|
||||
self.log.info("Find no transaction when output is unspent")
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : txidH, 'vout' : 0} ])
|
||||
assert_equal(result, [ {'txid' : txidH, 'vout' : 0} ])
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : txidA, 'vout' : 5} ])
|
||||
assert_equal(result, [ {'txid' : txidA, 'vout' : 5} ])
|
||||
|
||||
self.log.info("Mixed spent and unspent outputs")
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : txidB, 'vout' : 0}, {'txid' : txidG, 'vout' : 3} ])
|
||||
assert_equal(result, [ {'txid' : txidB, 'vout' : 0, 'spendingtxid' : txidD}, {'txid' : txidG, 'vout' : 3} ])
|
||||
|
||||
self.log.info("Unknown input fields")
|
||||
assert_raises_rpc_error(-3, "Unexpected key unknown", self.nodes[0].gettxspendingprevout, [{'txid' : txidC, 'vout' : 1, 'unknown' : 42}])
|
||||
|
||||
self.log.info("Invalid vout provided")
|
||||
assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", self.nodes[0].gettxspendingprevout, [{'txid' : txidA, 'vout' : -1}])
|
||||
|
||||
self.log.info("Invalid txid provided")
|
||||
assert_raises_rpc_error(-3, "Expected type string for txid, got number", self.nodes[0].gettxspendingprevout, [{'txid' : 42, 'vout' : 0}])
|
||||
|
||||
self.log.info("Missing outputs")
|
||||
assert_raises_rpc_error(-8, "Invalid parameter, outputs are missing", self.nodes[0].gettxspendingprevout, [])
|
||||
|
||||
self.log.info("Missing vout")
|
||||
assert_raises_rpc_error(-3, "Missing vout", self.nodes[0].gettxspendingprevout, [{'txid' : txidA}])
|
||||
|
||||
self.log.info("Missing txid")
|
||||
assert_raises_rpc_error(-3, "Missing txid", self.nodes[0].gettxspendingprevout, [{'vout' : 3}])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
RPCMempoolInfoTest().main()
|
||||
|
|
@ -369,6 +369,7 @@ BASE_SCRIPTS = [
|
|||
'feature_settings.py',
|
||||
'rpc_getdescriptorinfo.py',
|
||||
'rpc_mempool_entry_fee_fields_deprecation.py',
|
||||
'rpc_mempool_info.py',
|
||||
'rpc_help.py',
|
||||
'feature_dirsymlinks.py',
|
||||
'feature_help.py',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue