mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-10 14:48:46 +02:00
Merge bitcoin/bitcoin#24539: Add a "tx output spender" index
0b96b9c600Minimize mempool lock, sync txo spender index only when and if needed (sstone)3d82ec5bddAdd a "tx output spender" index (sstone) Pull request description: This PR adds a new "tx output spender" index, which allows users to query which tx spent a given outpoint with the `gettxspendingprevout` RPC call that was added by https://github.com/bitcoin/bitcoin/pull/24408. Such an index would be extremely useful for Lightning, and probably for most layer-2 protocols that rely on chains of unpublished transactions. UPDATE: this PR is ready for review and issues have been addressed: - using a watch-only wallet instead would not work if there is a significant number of outpoints to watch (see https://github.com/bitcoin/bitcoin/pull/24539#issuecomment-1276595646) - this PR does not require `-txindex` anymore We use a composite key with 2 parts (suggested by romanz): hash(spent outpoint) and tx position, with an empty value. Average composite key size is 15 bytes. The spending tx can optionally be returned by `gettxspendingprevout` (even it `-txindex is not set`). ACKs for top commit: hodlinator: re-ACK0b96b9c600sedited: Re-ACK0b96b9c600fjahr: ACK0b96b9c600w0xlt: reACK0b96b9c600Tree-SHA512: 95c2c313ef4086e7d5bf1cf1a3c7b91cfe2bb1a0dcb4c9d3aa8a6e5bfde66aaca48d85a1f1251a780523c3e4356ec8a97fe6f5c7145bc6ccb6f820b26716ae01
This commit is contained in:
@@ -79,7 +79,7 @@ class InitTest(BitcoinTestFramework):
|
||||
if self.is_wallet_compiled():
|
||||
lines_to_terminate_after.append(b'Verifying wallet')
|
||||
|
||||
args = ['-txindex=1', '-blockfilterindex=1', '-coinstatsindex=1']
|
||||
args = ['-txindex=1', '-blockfilterindex=1', '-coinstatsindex=1', '-txospenderindex=1']
|
||||
for terminate_line in lines_to_terminate_after:
|
||||
self.log.info(f"Starting node and will terminate after line {terminate_line}")
|
||||
with node.busy_wait_for_debug_log([terminate_line]):
|
||||
@@ -133,6 +133,11 @@ class InitTest(BitcoinTestFramework):
|
||||
'error_message': 'LevelDB error: Corruption: CURRENT points to a non-existent file',
|
||||
'startup_args': ['-txindex=1'],
|
||||
},
|
||||
{
|
||||
'filepath_glob': 'indexes/txospenderindex/db/MANIFEST*',
|
||||
'error_message': 'LevelDB error: Corruption: CURRENT points to a non-existent file',
|
||||
'startup_args': ['-txospenderindex=1'],
|
||||
},
|
||||
# Removing these files does not result in a startup error:
|
||||
# 'indexes/blockfilter/basic/*.dat', 'indexes/blockfilter/basic/db/*.*', 'indexes/coinstatsindex/db/*.*',
|
||||
# 'indexes/txindex/*.log', 'indexes/txindex/CURRENT', 'indexes/txindex/LOCK'
|
||||
@@ -174,6 +179,11 @@ class InitTest(BitcoinTestFramework):
|
||||
'error_message': 'LevelDB error: Corruption',
|
||||
'startup_args': ['-txindex=1'],
|
||||
},
|
||||
{
|
||||
'filepath_glob': 'indexes/txospenderindex/db/*',
|
||||
'error_message': 'LevelDB error: Corruption',
|
||||
'startup_args': ['-txospenderindex=1'],
|
||||
},
|
||||
# Perturbing these files does not result in a startup error:
|
||||
# 'indexes/blockfilter/basic/*.dat', 'indexes/txindex/MANIFEST*', 'indexes/txindex/LOCK'
|
||||
]
|
||||
@@ -183,6 +193,7 @@ class InitTest(BitcoinTestFramework):
|
||||
err_fragment = round_info['error_message']
|
||||
startup_args = round_info['startup_args']
|
||||
target_files = list(node.chain_path.glob(file_patt))
|
||||
assert target_files, f"Failed to find expected files: {file_patt}"
|
||||
|
||||
for target_file in target_files:
|
||||
self.log.info(f"Deleting file to ensure failure {target_file}")
|
||||
@@ -209,6 +220,7 @@ class InitTest(BitcoinTestFramework):
|
||||
for dir in dirs:
|
||||
shutil.copytree(node.chain_path / dir, node.chain_path / f"{dir}_bak")
|
||||
target_files = list(node.chain_path.glob(file_patt))
|
||||
assert target_files, f"Failed to find expected files: {file_patt}"
|
||||
|
||||
for target_file in target_files:
|
||||
self.log.info(f"Perturbing file to ensure failure {target_file}")
|
||||
|
||||
186
test/functional/rpc_gettxspendingprevout.py
Executable file
186
test/functional/rpc_gettxspendingprevout.py
Executable file
@@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 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 gettxspendingprevout RPC."""
|
||||
|
||||
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 GetTxSpendingPrevoutTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 3
|
||||
self.noban_tx_relay = True
|
||||
self.extra_args = [
|
||||
["-txospenderindex"],
|
||||
["-txospenderindex"],
|
||||
[],
|
||||
]
|
||||
|
||||
def run_test(self):
|
||||
self.wallet = MiniWallet(self.nodes[0])
|
||||
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")
|
||||
# wait until spending transactions are found in the mempool of node 0, 1 and 2
|
||||
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.wait_until(lambda: self.nodes[1].gettxspendingprevout([ {'txid' : confirmed_utxo['txid'], 'vout' : 0}, {'txid' : txidA, 'vout' : 1} ]) == result)
|
||||
self.wait_until(lambda: self.nodes[2].gettxspendingprevout([ {'txid' : confirmed_utxo['txid'], 'vout' : 0}, {'txid' : txidA, 'vout' : 1} ], mempool_only=True) == result)
|
||||
|
||||
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} ])
|
||||
result = self.nodes[1].gettxspendingprevout([ {'txid' : txidA, 'vout' : 5} ])
|
||||
assert_equal(result, [ {'txid' : txidA, 'vout' : 5} ])
|
||||
result = self.nodes[2].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, "JSON value of type number for field txid is not of expected type string", 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}])
|
||||
|
||||
blockhash = self.generate(self.wallet, 1)[0]
|
||||
# spending transactions are found in the index of nodes 0 and 1 but not node 2
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : confirmed_utxo['txid'], 'vout' : 0}, {'txid' : txidA, 'vout' : 1} ], return_spending_tx=True)
|
||||
assert_equal(result, [ {'txid' : confirmed_utxo['txid'], 'vout' : 0, 'spendingtxid' : txidA, 'blockhash' : blockhash, 'spendingtx' : txA['hex']}, {'txid' : txidA, 'vout' : 1, 'spendingtxid' : txidC, 'blockhash' : blockhash, 'spendingtx' : txC['hex']} ])
|
||||
result = self.nodes[1].gettxspendingprevout([ {'txid' : confirmed_utxo['txid'], 'vout' : 0}, {'txid' : txidA, 'vout' : 1} ], return_spending_tx=True)
|
||||
assert_equal(result, [ {'txid' : confirmed_utxo['txid'], 'vout' : 0, 'spendingtxid' : txidA, 'blockhash' : blockhash, 'spendingtx' : txA['hex']}, {'txid' : txidA, 'vout' : 1, 'spendingtxid' : txidC, 'blockhash' : blockhash, 'spendingtx' : txC['hex']} ])
|
||||
result = self.nodes[2].gettxspendingprevout([{ 'txid' : confirmed_utxo['txid'], 'vout' : 0}, {'txid' : txidA, 'vout' : 1} ], return_spending_tx=True)
|
||||
assert_equal(result, [ {'txid' : confirmed_utxo['txid'], 'vout' : 0}, {'txid' : txidA, 'vout' : 1}])
|
||||
|
||||
# spending transaction is not found if we only search the mempool
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : confirmed_utxo['txid'], 'vout' : 0}, {'txid' : txidA, 'vout' : 1} ], return_spending_tx=True, mempool_only=True)
|
||||
assert_equal(result, [ {'txid' : confirmed_utxo['txid'], 'vout' : 0}, {'txid' : txidA, 'vout' : 1}])
|
||||
|
||||
self.log.info("Check that our txospenderindex is updated when a reorg replaces a spending transaction")
|
||||
confirmed_utxo = self.wallet.get_utxo(mark_as_spent = False)
|
||||
tx1 = create_tx(utxos_to_spend=[confirmed_utxo], num_outputs=1)
|
||||
blockhash = self.generate(self.wallet, 1)[0]
|
||||
# tx1 is confirmed, and indexed in txospenderindex as spending our utxo
|
||||
assert tx1["txid"] not in self.nodes[0].getrawmempool()
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : confirmed_utxo['txid'], 'vout' : 0} ], return_spending_tx=True)
|
||||
assert_equal(result, [ {'txid' : confirmed_utxo['txid'], 'vout' : 0, 'spendingtxid' : tx1["txid"], 'blockhash' : blockhash, 'spendingtx' : tx1['hex']} ])
|
||||
# replace tx1 with tx2
|
||||
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
|
||||
self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash())
|
||||
self.nodes[2].invalidateblock(self.nodes[2].getbestblockhash())
|
||||
assert tx1["txid"] in self.nodes[0].getrawmempool()
|
||||
assert tx1["txid"] in self.nodes[1].getrawmempool()
|
||||
tx2 = create_tx(utxos_to_spend=[confirmed_utxo], num_outputs=2)
|
||||
assert tx2["txid"] in self.nodes[0].getrawmempool()
|
||||
|
||||
# check that when we find tx2 when we look in the mempool for a tx spending our output
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : confirmed_utxo['txid'], 'vout' : 0} ], return_spending_tx=True)
|
||||
assert_equal(result, [ {'txid' : confirmed_utxo['txid'], 'vout' : 0, 'spendingtxid' : tx2["txid"], 'spendingtx' : tx2['hex']} ])
|
||||
|
||||
# check that our txospenderindex has been updated
|
||||
blockhash = self.generate(self.wallet, 1)[0]
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : confirmed_utxo['txid'], 'vout' : 0} ], return_spending_tx=True)
|
||||
assert_equal(result, [ {'txid' : confirmed_utxo['txid'], 'vout' : 0, 'spendingtxid' : tx2["txid"], 'blockhash' : blockhash, 'spendingtx' : tx2['hex']} ])
|
||||
|
||||
self.log.info("Check that our txospenderindex is updated when a reorg cancels a spending transaction")
|
||||
confirmed_utxo = self.wallet.get_utxo(mark_as_spent = False)
|
||||
tx1 = create_tx(utxos_to_spend=[confirmed_utxo], num_outputs=1)
|
||||
tx2 = create_tx(utxos_to_spend=[tx1["new_utxos"][0]], num_outputs=1)
|
||||
# tx1 spends our utxo, tx2 spends tx1
|
||||
blockhash = self.generate(self.wallet, 1)[0]
|
||||
# tx1 and tx2 are confirmed, and indexed in txospenderindex
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : confirmed_utxo['txid'], 'vout' : 0} ], return_spending_tx=True)
|
||||
assert_equal(result, [ {'txid' : confirmed_utxo['txid'], 'vout' : 0, 'spendingtxid' : tx1["txid"], 'blockhash' : blockhash, 'spendingtx' : tx1['hex']} ])
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : tx1['txid'], 'vout' : 0} ], return_spending_tx=True)
|
||||
assert_equal(result, [ {'txid' : tx1['txid'], 'vout' : 0, 'spendingtxid' : tx2["txid"], 'blockhash' : blockhash, 'spendingtx' : tx2['hex']} ])
|
||||
# replace tx1 with tx3
|
||||
blockhash= self.nodes[0].getbestblockhash()
|
||||
self.nodes[0].invalidateblock(blockhash)
|
||||
self.nodes[1].invalidateblock(blockhash)
|
||||
self.nodes[2].invalidateblock(blockhash)
|
||||
tx3 = create_tx(utxos_to_spend=[confirmed_utxo], num_outputs=2, fee_per_output=2000)
|
||||
assert tx3["txid"] in self.nodes[0].getrawmempool()
|
||||
assert tx1["txid"] not in self.nodes[0].getrawmempool()
|
||||
assert tx2["txid"] not in self.nodes[0].getrawmempool()
|
||||
# tx2 is not in the mempool anymore, but still in txospender index which has not been rewound yet
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : tx1['txid'], 'vout' : 0} ], return_spending_tx=True)
|
||||
assert_equal(result, [ {'txid' : tx1['txid'], 'vout' : 0, 'spendingtxid' : tx2["txid"], 'blockhash' : blockhash, 'spendingtx' : tx2['hex']} ])
|
||||
txinfo = self.nodes[0].getrawtransaction(tx2["txid"], verbose = True, blockhash = blockhash)
|
||||
assert_equal(txinfo["confirmations"], 0)
|
||||
assert_equal(txinfo["in_active_chain"], False)
|
||||
|
||||
blockhash = self.generate(self.wallet, 1)[0]
|
||||
# we check that the spending tx for tx1 is now tx3
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : confirmed_utxo['txid'], 'vout' : 0} ], return_spending_tx=True)
|
||||
assert_equal(result, [ {'txid' : confirmed_utxo['txid'], 'vout' : 0, 'spendingtxid' : tx3["txid"], 'blockhash' : blockhash, 'spendingtx' : tx3['hex']} ])
|
||||
# we check that there is no more spending tx for tx1
|
||||
result = self.nodes[0].gettxspendingprevout([ {'txid' : tx1['txid'], 'vout' : 0} ], return_spending_tx=True)
|
||||
assert_equal(result, [ {'txid' : tx1['txid'], 'vout' : 0} ])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
GetTxSpendingPrevoutTest(__file__).main()
|
||||
@@ -1,99 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2014-present 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.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])
|
||||
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, "JSON value of type number for field txid is not of expected type string", 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(__file__).main()
|
||||
@@ -100,7 +100,7 @@ class RpcMiscTest(BitcoinTestFramework):
|
||||
assert_equal(node.getindexinfo(), {})
|
||||
|
||||
# Restart the node with indices and wait for them to sync
|
||||
self.restart_node(0, ["-txindex", "-blockfilterindex", "-coinstatsindex"])
|
||||
self.restart_node(0, ["-txindex", "-blockfilterindex", "-coinstatsindex", "-txospenderindex"])
|
||||
self.wait_until(lambda: all(i["synced"] for i in node.getindexinfo().values()))
|
||||
|
||||
# Returns a list of all running indices by default
|
||||
@@ -111,10 +111,11 @@ class RpcMiscTest(BitcoinTestFramework):
|
||||
"txindex": values,
|
||||
"basic block filter index": values,
|
||||
"coinstatsindex": values,
|
||||
"txospenderindex": values
|
||||
}
|
||||
)
|
||||
# Specifying an index by name returns only the status of that index
|
||||
for i in {"txindex", "basic block filter index", "coinstatsindex"}:
|
||||
for i in {"txindex", "basic block filter index", "coinstatsindex", "txospenderindex"}:
|
||||
assert_equal(node.getindexinfo(i), {i: values})
|
||||
|
||||
# Specifying an unknown index name returns an empty result
|
||||
|
||||
@@ -373,7 +373,7 @@ BASE_SCRIPTS = [
|
||||
'feature_presegwit_node_upgrade.py',
|
||||
'feature_settings.py',
|
||||
'rpc_getdescriptorinfo.py',
|
||||
'rpc_mempool_info.py',
|
||||
'rpc_gettxspendingprevout.py',
|
||||
'rpc_help.py',
|
||||
'feature_framework_testshell.py',
|
||||
'tool_rpcauth.py',
|
||||
|
||||
Reference in New Issue
Block a user