diff options
Diffstat (limited to 'test/functional')
-rwxr-xr-x | test/functional/feature_config_args.py | 94 | ||||
-rwxr-xr-x | test/functional/feature_signet.py | 3 | ||||
-rwxr-xr-x | test/functional/interface_usdt_mempool.py | 4 | ||||
-rwxr-xr-x | test/functional/mempool_compatibility.py | 2 | ||||
-rwxr-xr-x | test/functional/mempool_package_limits.py | 3 | ||||
-rwxr-xr-x | test/functional/mempool_packages.py | 15 | ||||
-rwxr-xr-x | test/functional/mempool_persist.py | 1 | ||||
-rwxr-xr-x | test/functional/p2p_compactblocks.py | 88 | ||||
-rwxr-xr-x | test/functional/rpc_getblockfrompeer.py | 4 | ||||
-rwxr-xr-x | test/functional/rpc_invalid_address_message.py | 6 | ||||
-rwxr-xr-x | test/functional/rpc_validateaddress.py | 203 | ||||
-rwxr-xr-x | test/functional/test_framework/test_node.py | 4 | ||||
-rw-r--r-- | test/functional/test_framework/util.py | 20 | ||||
-rw-r--r-- | test/functional/test_framework/wallet.py | 7 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 3 | ||||
-rwxr-xr-x | test/functional/wallet_abandonconflict.py | 12 | ||||
-rwxr-xr-x | test/functional/wallet_basic.py | 33 | ||||
-rwxr-xr-x | test/functional/wallet_conflicts.py | 127 |
18 files changed, 591 insertions, 38 deletions
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index f9730b48c5..2257605870 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -5,9 +5,14 @@ """Test various command line arguments and configuration file parameters.""" import os +import pathlib +import re +import sys +import tempfile import time from test_framework.test_framework import BitcoinTestFramework +from test_framework.test_node import ErrorMatch from test_framework import util @@ -74,7 +79,7 @@ class ConfArgsTest(BitcoinTestFramework): util.write_config(main_conf_file_path, n=0, chain='', extra_config=f'includeconf={inc_conf_file_path}\n') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('acceptnonstdtxn=1\n') - self.nodes[0].assert_start_raises_init_error(extra_args=[f"-conf={main_conf_file_path}"], expected_msg='Error: acceptnonstdtxn is not currently supported for main chain') + self.nodes[0].assert_start_raises_init_error(extra_args=[f"-conf={main_conf_file_path}", "-allowignoredconf"], expected_msg='Error: acceptnonstdtxn is not currently supported for main chain') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('nono\n') @@ -108,6 +113,41 @@ class ConfArgsTest(BitcoinTestFramework): with open(inc_conf_file2_path, 'w', encoding='utf-8') as conf: conf.write('') # clear + def test_config_file_log(self): + # Disable this test for windows currently because trying to override + # the default datadir through the environment does not seem to work. + if sys.platform == "win32": + return + + self.log.info('Test that correct configuration path is changed when configuration file changes the datadir') + + # Create a temporary directory that will be treated as the default data + # directory by bitcoind. + env, default_datadir = util.get_temp_default_datadir(pathlib.Path(self.options.tmpdir, "test_config_file_log")) + default_datadir.mkdir(parents=True) + + # Write a bitcoin.conf file in the default data directory containing a + # datadir= line pointing at the node datadir. + node = self.nodes[0] + conf_text = pathlib.Path(node.bitcoinconf).read_text() + conf_path = default_datadir / "bitcoin.conf" + conf_path.write_text(f"datadir={node.datadir}\n{conf_text}") + + # Drop the node -datadir= argument during this test, because if it is + # specified it would take precedence over the datadir setting in the + # config file. + node_args = node.args + node.args = [arg for arg in node.args if not arg.startswith("-datadir=")] + + # Check that correct configuration file path is actually logged + # (conf_path, not node.bitcoinconf) + with self.nodes[0].assert_debug_log(expected_msgs=[f"Config file: {conf_path}"]): + self.start_node(0, ["-allowignoredconf"], env=env) + self.stop_node(0) + + # Restore node arguments after the test + node.args = node_args + def test_invalid_command_line_options(self): self.nodes[0].assert_start_raises_init_error( expected_msg='Error: Error parsing command line arguments: Can not set -proxy with no value. Please specify value with -proxy=value.', @@ -282,6 +322,55 @@ class ConfArgsTest(BitcoinTestFramework): unexpected_msgs=seednode_ignored): self.restart_node(0, extra_args=[connect_arg, '-seednode=fakeaddress2']) + def test_ignored_conf(self): + self.log.info('Test error is triggered when the datadir in use contains a bitcoin.conf file that would be ignored ' + 'because a conflicting -conf file argument is passed.') + node = self.nodes[0] + with tempfile.NamedTemporaryFile(dir=self.options.tmpdir, mode="wt", delete=False) as temp_conf: + temp_conf.write(f"datadir={node.datadir}\n") + node.assert_start_raises_init_error([f"-conf={temp_conf.name}"], re.escape( + f'Error: Data directory "{node.datadir}" contains a "bitcoin.conf" file which is ignored, because a ' + f'different configuration file "{temp_conf.name}" from command line argument "-conf={temp_conf.name}" ' + f'is being used instead.') + r"[\s\S]*", match=ErrorMatch.FULL_REGEX) + + # Test that passing a redundant -conf command line argument pointing to + # the same bitcoin.conf that would be loaded anyway does not trigger an + # error. + self.start_node(0, [f'-conf={node.datadir}/bitcoin.conf']) + self.stop_node(0) + + def test_ignored_default_conf(self): + # Disable this test for windows currently because trying to override + # the default datadir through the environment does not seem to work. + if sys.platform == "win32": + return + + self.log.info('Test error is triggered when bitcoin.conf in the default data directory sets another datadir ' + 'and it contains a different bitcoin.conf file that would be ignored') + + # Create a temporary directory that will be treated as the default data + # directory by bitcoind. + env, default_datadir = util.get_temp_default_datadir(pathlib.Path(self.options.tmpdir, "home")) + default_datadir.mkdir(parents=True) + + # Write a bitcoin.conf file in the default data directory containing a + # datadir= line pointing at the node datadir. This will trigger a + # startup error because the node datadir contains a different + # bitcoin.conf that would be ignored. + node = self.nodes[0] + (default_datadir / "bitcoin.conf").write_text(f"datadir={node.datadir}\n") + + # Drop the node -datadir= argument during this test, because if it is + # specified it would take precedence over the datadir setting in the + # config file. + node_args = node.args + node.args = [arg for arg in node.args if not arg.startswith("-datadir=")] + node.assert_start_raises_init_error([], re.escape( + f'Error: Data directory "{node.datadir}" contains a "bitcoin.conf" file which is ignored, because a ' + f'different configuration file "{default_datadir}/bitcoin.conf" from data directory "{default_datadir}" ' + f'is being used instead.') + r"[\s\S]*", env=env, match=ErrorMatch.FULL_REGEX) + node.args = node_args + def run_test(self): self.test_log_buffer() self.test_args_log() @@ -290,7 +379,10 @@ class ConfArgsTest(BitcoinTestFramework): self.test_connect_with_seednode() self.test_config_file_parser() + self.test_config_file_log() self.test_invalid_command_line_options() + self.test_ignored_conf() + self.test_ignored_default_conf() # Remove the -datadir argument so it doesn't override the config file self.nodes[0].args = [arg for arg in self.nodes[0].args if not arg.startswith("-datadir")] diff --git a/test/functional/feature_signet.py b/test/functional/feature_signet.py index b41fe378af..a90a2a8e5e 100755 --- a/test/functional/feature_signet.py +++ b/test/functional/feature_signet.py @@ -76,6 +76,9 @@ class SignetBasicTest(BitcoinTestFramework): self.log.info("test that signet logs the network magic on node start") with self.nodes[0].assert_debug_log(["Signet derived magic (message start)"]): self.restart_node(0) + self.stop_node(0) + self.nodes[0].assert_start_raises_init_error(extra_args=["-signetchallenge=abc"], expected_msg="Error: -signetchallenge must be hex, not 'abc'.") + self.nodes[0].assert_start_raises_init_error(extra_args=["-signetchallenge=abc"] * 2, expected_msg="Error: -signetchallenge cannot be multiple values.") if __name__ == '__main__': diff --git a/test/functional/interface_usdt_mempool.py b/test/functional/interface_usdt_mempool.py index ec2f9e4e77..542849d558 100755 --- a/test/functional/interface_usdt_mempool.py +++ b/test/functional/interface_usdt_mempool.py @@ -173,6 +173,7 @@ class MempoolTracepointTest(BitcoinTestFramework): self.log.info("Ensuring mempool:added event was handled successfully...") assert_equal(EXPECTED_ADDED_EVENTS, handled_added_events) + self.generate(self.wallet, 1) def removed_test(self): """Expire a transaction from the mempool and make sure the tracepoint returns @@ -223,6 +224,7 @@ class MempoolTracepointTest(BitcoinTestFramework): self.log.info("Ensuring mempool:removed event was handled successfully...") assert_equal(EXPECTED_REMOVED_EVENTS, handled_removed_events) + self.generate(self.wallet, 1) def replaced_test(self): """Replace one and two transactions in the mempool and make sure the tracepoint @@ -280,6 +282,7 @@ class MempoolTracepointTest(BitcoinTestFramework): self.log.info("Ensuring mempool:replaced event was handled successfully...") assert_equal(EXPECTED_REPLACED_EVENTS, handled_replaced_events) + self.generate(self.wallet, 1) def rejected_test(self): """Create an invalid transaction and make sure the tracepoint returns @@ -321,6 +324,7 @@ class MempoolTracepointTest(BitcoinTestFramework): self.log.info("Ensuring mempool:rejected event was handled successfully...") assert_equal(EXPECTED_REJECTED_EVENTS, handled_rejected_events) + self.generate(self.wallet, 1) def run_test(self): """Tests the mempool:added, mempool:removed, mempool:replaced, diff --git a/test/functional/mempool_compatibility.py b/test/functional/mempool_compatibility.py index a7bdc49695..7337802aea 100755 --- a/test/functional/mempool_compatibility.py +++ b/test/functional/mempool_compatibility.py @@ -47,12 +47,12 @@ class MempoolCompatibilityTest(BitcoinTestFramework): # unbroadcasted_tx won't pass old_node's `MemPoolAccept::PreChecks`. self.connect_nodes(0, 1) self.sync_blocks() - self.stop_node(1) self.log.info("Add a transaction to mempool on old node and shutdown") old_tx_hash = new_wallet.send_self_transfer(from_node=old_node)["txid"] assert old_tx_hash in old_node.getrawmempool() self.stop_node(0) + self.stop_node(1) self.log.info("Move mempool.dat from old to new node") old_node_mempool = os.path.join(old_node.datadir, self.chain, 'mempool.dat') diff --git a/test/functional/mempool_package_limits.py b/test/functional/mempool_package_limits.py index 6143ae83de..81451bf2a5 100755 --- a/test/functional/mempool_package_limits.py +++ b/test/functional/mempool_package_limits.py @@ -46,8 +46,7 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): def run_test(self): self.wallet = MiniWallet(self.nodes[0]) # Add enough mature utxos to the wallet so that all txs spend confirmed coins. - self.generate(self.wallet, 35) - self.generate(self.nodes[0], COINBASE_MATURITY) + self.generate(self.wallet, COINBASE_MATURITY + 35) self.test_chain_limits() self.test_desc_count_limits() diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py index 0387282862..95f7939412 100755 --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -7,7 +7,6 @@ from decimal import Decimal from test_framework.messages import ( - COIN, DEFAULT_ANCESTOR_LIMIT, DEFAULT_DESCENDANT_LIMIT, ) @@ -26,9 +25,6 @@ assert CUSTOM_DESCENDANT_LIMIT >= CUSTOM_ANCESTOR_LIMIT class MempoolPackagesTest(BitcoinTestFramework): - def add_options(self, parser): - self.add_wallet_options(parser) - def set_test_params(self): self.num_nodes = 2 self.extra_args = [ @@ -47,10 +43,6 @@ class MempoolPackagesTest(BitcoinTestFramework): self.wallet = MiniWallet(self.nodes[0]) self.wallet.rescan_utxos() - if self.is_specified_wallet_compiled(): - self.nodes[0].createwallet("watch_wallet", disable_private_keys=True) - self.nodes[0].importaddress(self.wallet.get_address()) - peer_inv_store = self.nodes[0].add_p2p_connection(P2PTxInvStore()) # keep track of invs # DEFAULT_ANCESTOR_LIMIT transactions off a confirmed tx should be fine @@ -63,13 +55,6 @@ class MempoolPackagesTest(BitcoinTestFramework): ancestor_vsize += t["tx"].get_vsize() ancestor_fees += t["fee"] self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=t["hex"]) - # Check that listunspent ancestor{count, size, fees} yield the correct results - if self.is_specified_wallet_compiled(): - wallet_unspent = self.nodes[0].listunspent(minconf=0) - this_unspent = next(utxo_info for utxo_info in wallet_unspent if utxo_info["txid"] == t["txid"]) - assert_equal(this_unspent['ancestorcount'], i + 1) - assert_equal(this_unspent['ancestorsize'], ancestor_vsize) - assert_equal(this_unspent['ancestorfees'], ancestor_fees * COIN) # Wait until mempool transactions have passed initial broadcast (sent inv and received getdata) # Otherwise, getrawmempool may be inconsistent with getmempoolentry if unbroadcast changes in between diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py index f818801136..8f74d9de20 100755 --- a/test/functional/mempool_persist.py +++ b/test/functional/mempool_persist.py @@ -191,6 +191,7 @@ class MempoolPersistTest(BitcoinTestFramework): def test_persist_unbroadcast(self): node0 = self.nodes[0] self.start_node(0) + self.start_node(2) # clear out mempool self.generate(node0, 1, sync_fun=self.no_op) diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index 23eeea50bc..d6c06fdeed 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -105,6 +105,10 @@ class TestP2PConn(P2PInterface): self.last_message.pop("headers", None) self.last_message.pop("cmpctblock", None) + def clear_getblocktxn(self): + with p2p_lock: + self.last_message.pop("getblocktxn", None) + def get_headers(self, locator, hashstop): msg = msg_getheaders() msg.locator.vHave = locator @@ -745,7 +749,7 @@ class CompactBlocksTest(BitcoinTestFramework): peer.get_headers(locator=[int(tip, 16)], hashstop=0) peer.send_and_ping(msg_sendcmpct(announce=True, version=2)) - def test_compactblock_reconstruction_multiple_peers(self, stalling_peer, delivery_peer): + def test_compactblock_reconstruction_stalling_peer(self, stalling_peer, delivery_peer): node = self.nodes[0] assert len(self.utxos) @@ -823,12 +827,85 @@ class CompactBlocksTest(BitcoinTestFramework): hb_test_node.send_and_ping(msg_sendcmpct(announce=False, version=2)) assert_highbandwidth_states(self.nodes[0], hb_to=True, hb_from=False) + def test_compactblock_reconstruction_parallel_reconstruction(self, stalling_peer, delivery_peer, inbound_peer, outbound_peer): + """ All p2p connections are inbound except outbound_peer. We test that ultimate parallel slot + can only be taken by an outbound node unless prior attempts were done by an outbound + """ + node = self.nodes[0] + assert len(self.utxos) + + def announce_cmpct_block(node, peer, txn_count): + utxo = self.utxos.pop(0) + block = self.build_block_with_transactions(node, utxo, txn_count) + + cmpct_block = HeaderAndShortIDs() + cmpct_block.initialize_from_block(block) + msg = msg_cmpctblock(cmpct_block.to_p2p()) + peer.send_and_ping(msg) + with p2p_lock: + assert "getblocktxn" in peer.last_message + return block, cmpct_block + + for name, peer in [("delivery", delivery_peer), ("inbound", inbound_peer), ("outbound", outbound_peer)]: + self.log.info(f"Setting {name} as high bandwidth peer") + block, cmpct_block = announce_cmpct_block(node, peer, 1) + msg = msg_blocktxn() + msg.block_transactions.blockhash = block.sha256 + msg.block_transactions.transactions = block.vtx[1:] + peer.send_and_ping(msg) + assert_equal(int(node.getbestblockhash(), 16), block.sha256) + peer.clear_getblocktxn() + + # Test the simple parallel download case... + for num_missing in [1, 5, 20]: + + # Remaining low-bandwidth peer is stalling_peer, who announces first + assert_equal([peer['bip152_hb_to'] for peer in node.getpeerinfo()], [False, True, True, True]) + + block, cmpct_block = announce_cmpct_block(node, stalling_peer, num_missing) + + delivery_peer.send_and_ping(msg_cmpctblock(cmpct_block.to_p2p())) + with p2p_lock: + # The second peer to announce should still get a getblocktxn + assert "getblocktxn" in delivery_peer.last_message + assert int(node.getbestblockhash(), 16) != block.sha256 + + inbound_peer.send_and_ping(msg_cmpctblock(cmpct_block.to_p2p())) + with p2p_lock: + # The third inbound peer to announce should *not* get a getblocktxn + assert "getblocktxn" not in inbound_peer.last_message + assert int(node.getbestblockhash(), 16) != block.sha256 + + outbound_peer.send_and_ping(msg_cmpctblock(cmpct_block.to_p2p())) + with p2p_lock: + # The third peer to announce should get a getblocktxn if outbound + assert "getblocktxn" in outbound_peer.last_message + assert int(node.getbestblockhash(), 16) != block.sha256 + + # Second peer completes the compact block first + msg = msg_blocktxn() + msg.block_transactions.blockhash = block.sha256 + msg.block_transactions.transactions = block.vtx[1:] + delivery_peer.send_and_ping(msg) + assert_equal(int(node.getbestblockhash(), 16), block.sha256) + + # Nothing bad should happen if we get a late fill from the first peer... + stalling_peer.send_and_ping(msg) + self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) + + delivery_peer.clear_getblocktxn() + inbound_peer.clear_getblocktxn() + outbound_peer.clear_getblocktxn() + + def run_test(self): self.wallet = MiniWallet(self.nodes[0]) # Setup the p2p connections self.segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn()) self.additional_segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn()) + self.onemore_inbound_node = self.nodes[0].add_p2p_connection(TestP2PConn()) + self.outbound_node = self.nodes[0].add_outbound_p2p_connection(TestP2PConn(), p2p_idx=3, connection_type="outbound-full-relay") # We will need UTXOs to construct transactions in later tests. self.make_utxos() @@ -838,6 +915,8 @@ class CompactBlocksTest(BitcoinTestFramework): self.log.info("Testing SENDCMPCT p2p message... ") self.test_sendcmpct(self.segwit_node) self.test_sendcmpct(self.additional_segwit_node) + self.test_sendcmpct(self.onemore_inbound_node) + self.test_sendcmpct(self.outbound_node) self.log.info("Testing compactblock construction...") self.test_compactblock_construction(self.segwit_node) @@ -860,8 +939,11 @@ class CompactBlocksTest(BitcoinTestFramework): self.log.info("Testing handling of incorrect blocktxn responses...") self.test_incorrect_blocktxn_response(self.segwit_node) - self.log.info("Testing reconstructing compact blocks from all peers...") - self.test_compactblock_reconstruction_multiple_peers(self.segwit_node, self.additional_segwit_node) + self.log.info("Testing reconstructing compact blocks with a stalling peer...") + self.test_compactblock_reconstruction_stalling_peer(self.segwit_node, self.additional_segwit_node) + + self.log.info("Testing reconstructing compact blocks from multiple peers...") + self.test_compactblock_reconstruction_parallel_reconstruction(stalling_peer=self.segwit_node, inbound_peer=self.onemore_inbound_node, delivery_peer=self.additional_segwit_node, outbound_peer=self.outbound_node) # Test that if we submitblock to node1, we'll get a compact block # announcement to all peers. diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py index 2f093bebff..1ab1023cf1 100755 --- a/test/functional/rpc_getblockfrompeer.py +++ b/test/functional/rpc_getblockfrompeer.py @@ -117,9 +117,11 @@ class GetBlockFromPeerTest(BitcoinTestFramework): assert_raises_rpc_error(-1, error_msg, self.nodes[1].getblockfrompeer, blockhash, node1_interface_id) self.log.info("Connect pruned node") - # We need to generate more blocks to be able to prune self.connect_nodes(0, 2) pruned_node = self.nodes[2] + self.sync_blocks([self.nodes[0], pruned_node]) + + # We need to generate more blocks to be able to prune self.generate(self.nodes[0], 400, sync_fun=self.no_op) self.sync_blocks([self.nodes[0], pruned_node]) pruneheight = pruned_node.pruneblockchain(300) diff --git a/test/functional/rpc_invalid_address_message.py b/test/functional/rpc_invalid_address_message.py index fd282a9bc1..6759b69dd1 100755 --- a/test/functional/rpc_invalid_address_message.py +++ b/test/functional/rpc_invalid_address_message.py @@ -63,12 +63,12 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework): def test_validateaddress(self): # Invalid Bech32 - self.check_invalid(BECH32_INVALID_SIZE, 'Invalid Bech32 address data size') + self.check_invalid(BECH32_INVALID_SIZE, "Invalid Bech32 address program size (41 bytes)") self.check_invalid(BECH32_INVALID_PREFIX, 'Invalid or unsupported Segwit (Bech32) or Base58 encoding.') self.check_invalid(BECH32_INVALID_BECH32, 'Version 1+ witness address must use Bech32m checksum') self.check_invalid(BECH32_INVALID_BECH32M, 'Version 0 witness address must use Bech32 checksum') self.check_invalid(BECH32_INVALID_VERSION, 'Invalid Bech32 address witness version') - self.check_invalid(BECH32_INVALID_V0_SIZE, 'Invalid Bech32 v0 address data size') + self.check_invalid(BECH32_INVALID_V0_SIZE, "Invalid Bech32 v0 address program size (21 bytes), per BIP141") self.check_invalid(BECH32_TOO_LONG, 'Bech32 string too long', list(range(90, 108))) self.check_invalid(BECH32_ONE_ERROR, 'Invalid Bech32 checksum', [9]) self.check_invalid(BECH32_TWO_ERRORS, 'Invalid Bech32 checksum', [22, 43]) @@ -105,7 +105,7 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework): def test_getaddressinfo(self): node = self.nodes[0] - assert_raises_rpc_error(-5, "Invalid Bech32 address data size", node.getaddressinfo, BECH32_INVALID_SIZE) + assert_raises_rpc_error(-5, "Invalid Bech32 address program size (41 bytes)", node.getaddressinfo, BECH32_INVALID_SIZE) assert_raises_rpc_error(-5, "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", node.getaddressinfo, BECH32_INVALID_PREFIX) assert_raises_rpc_error(-5, "Invalid or unsupported Base58-encoded address.", node.getaddressinfo, BASE58_INVALID_PREFIX) assert_raises_rpc_error(-5, "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", node.getaddressinfo, INVALID_ADDRESS) diff --git a/test/functional/rpc_validateaddress.py b/test/functional/rpc_validateaddress.py new file mode 100755 index 0000000000..d87ba098c3 --- /dev/null +++ b/test/functional/rpc_validateaddress.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023 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 validateaddress for main chain""" + +from test_framework.test_framework import BitcoinTestFramework + +from test_framework.util import assert_equal + +INVALID_DATA = [ + # BIP 173 + ( + "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", + "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # Invalid hrp + [], + ), + ("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", "Invalid Bech32 checksum", [41]), + ( + "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", + "Version 1+ witness address must use Bech32m checksum", + [], + ), + ( + "bc1rw5uspcuh", + "Version 1+ witness address must use Bech32m checksum", # Invalid program length + [], + ), + ( + "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", + "Version 1+ witness address must use Bech32m checksum", # Invalid program length + [], + ), + ( + "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", + "Invalid Bech32 v0 address program size (16 bytes), per BIP141", + [], + ), + ( + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", + "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # tb1, Mixed case + [], + ), + ( + "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3t4", + "Invalid character or mixed case", # bc1, Mixed case, not in BIP 173 test vectors + [40], + ), + ( + "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", + "Version 1+ witness address must use Bech32m checksum", # Wrong padding + [], + ), + ( + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", + "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # tb1, Non-zero padding in 8-to-5 conversion + [], + ), + ("bc1gmk9yu", "Empty Bech32 data section", []), + # BIP 350 + ( + "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut", + "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # Invalid human-readable part + [], + ), + ( + "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd", + "Version 1+ witness address must use Bech32m checksum", # Invalid checksum (Bech32 instead of Bech32m) + [], + ), + ( + "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf", + "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # tb1, Invalid checksum (Bech32 instead of Bech32m) + [], + ), + ( + "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL", + "Version 1+ witness address must use Bech32m checksum", # Invalid checksum (Bech32 instead of Bech32m) + [], + ), + ( + "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh", + "Version 0 witness address must use Bech32 checksum", # Invalid checksum (Bech32m instead of Bech32) + [], + ), + ( + "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47", + "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # tb1, Invalid checksum (Bech32m instead of Bech32) + [], + ), + ( + "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4", + "Invalid Base 32 character", # Invalid character in checksum + [59], + ), + ( + "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R", + "Invalid Bech32 address witness version", + [], + ), + ("bc1pw5dgrnzv", "Invalid Bech32 address program size (1 byte)", []), + ( + "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav", + "Invalid Bech32 address program size (41 bytes)", + [], + ), + ( + "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", + "Invalid Bech32 v0 address program size (16 bytes), per BIP141", + [], + ), + ( + "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq", + "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # tb1, Mixed case + [], + ), + ( + "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf", + "Invalid padding in Bech32 data section", # zero padding of more than 4 bits + [], + ), + ( + "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j", + "Invalid or unsupported Segwit (Bech32) or Base58 encoding.", # tb1, Non-zero padding in 8-to-5 conversion + [], + ), + ("bc1gmk9yu", "Empty Bech32 data section", []), +] +VALID_DATA = [ + # BIP 350 + ( + "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", + "0014751e76e8199196d454941c45d1b3a323f1433bd6", + ), + # ( + # "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", + # "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", + # ), + ( + "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", + "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", + ), + ( + "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", + "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6", + ), + ("BC1SW50QGDZ25J", "6002751e"), + ("bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", "5210751e76e8199196d454941c45d1b3a323"), + # ( + # "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", + # "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", + # ), + ( + "bc1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvses5wp4dt", + "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", + ), + # ( + # "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c", + # "5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", + # ), + ( + "bc1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvses7epu4h", + "5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", + ), + ( + "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0", + "512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + ), +] + + +class ValidateAddressMainTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.chain = "" # main + self.num_nodes = 1 + self.extra_args = [["-prune=899"]] * self.num_nodes + + def check_valid(self, addr, spk): + info = self.nodes[0].validateaddress(addr) + assert_equal(info["isvalid"], True) + assert_equal(info["scriptPubKey"], spk) + assert "error" not in info + assert "error_locations" not in info + + def check_invalid(self, addr, error_str, error_locations): + res = self.nodes[0].validateaddress(addr) + assert_equal(res["isvalid"], False) + assert_equal(res["error"], error_str) + assert_equal(res["error_locations"], error_locations) + + def test_validateaddress(self): + for (addr, error, locs) in INVALID_DATA: + self.check_invalid(addr, error, locs) + for (addr, spk) in VALID_DATA: + self.check_valid(addr, spk) + + def run_test(self): + self.test_validateaddress() + + +if __name__ == "__main__": + ValidateAddressMainTest().main() diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 56abe5f26a..51bd697e81 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -190,7 +190,7 @@ class TestNode(): assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection") return getattr(RPCOverloadWrapper(self.rpc, descriptors=self.descriptors), name) - def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, **kwargs): + def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, env=None, **kwargs): """Start the node.""" if extra_args is None: extra_args = self.extra_args @@ -213,6 +213,8 @@ class TestNode(): # add environment variable LIBC_FATAL_STDERR_=1 so that libc errors are written to stderr and not the terminal subp_env = dict(os.environ, LIBC_FATAL_STDERR_="1") + if env is not None: + subp_env.update(env) self.process = subprocess.Popen(self.args + extra_args, env=subp_env, stdout=stdout, stderr=stderr, cwd=cwd, **kwargs) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 2c227922c5..d3b3e4d536 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -12,13 +12,15 @@ import inspect import json import logging import os +import pathlib import random import re +import sys import time from . import coverage from .authproxy import AuthServiceProxy, JSONRPCException -from typing import Callable, Optional +from typing import Callable, Optional, Tuple logger = logging.getLogger("TestFramework.utils") @@ -419,6 +421,22 @@ def get_datadir_path(dirname, n): return os.path.join(dirname, "node" + str(n)) +def get_temp_default_datadir(temp_dir: pathlib.Path) -> Tuple[dict, pathlib.Path]: + """Return os-specific environment variables that can be set to make the + GetDefaultDataDir() function return a datadir path under the provided + temp_dir, as well as the complete path it would return.""" + if sys.platform == "win32": + env = dict(APPDATA=str(temp_dir)) + datadir = temp_dir / "Bitcoin" + else: + env = dict(HOME=str(temp_dir)) + if sys.platform == "darwin": + datadir = temp_dir / "Library/Application Support/Bitcoin" + else: + datadir = temp_dir / ".bitcoin" + return env, datadir + + def append_config(datadir, options): with open(os.path.join(datadir, "bitcoin.conf"), 'a', encoding='utf8') as f: for option in options: diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index 64606b818b..1d546e12bd 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -218,10 +218,12 @@ class MiniWallet: txid: get the first utxo we find from a specific transaction """ self._utxos = sorted(self._utxos, key=lambda k: (k['value'], -k['height'])) # Put the largest utxo last + blocks_height = self._test_node.getblockchaininfo()['blocks'] + mature_coins = list(filter(lambda utxo: not utxo['coinbase'] or COINBASE_MATURITY - 1 <= blocks_height - utxo['height'], self._utxos)) if txid: utxo_filter: Any = filter(lambda utxo: txid == utxo['txid'], self._utxos) else: - utxo_filter = reversed(self._utxos) # By default the largest utxo + utxo_filter = reversed(mature_coins) # By default the largest utxo if vout is not None: utxo_filter = filter(lambda utxo: vout == utxo['vout'], utxo_filter) index = self._utxos.index(next(utxo_filter)) @@ -233,7 +235,8 @@ class MiniWallet: def get_utxos(self, *, include_immature_coinbase=False, mark_as_spent=True): """Returns the list of all utxos and optionally mark them as spent""" if not include_immature_coinbase: - utxo_filter = filter(lambda utxo: not utxo['coinbase'] or COINBASE_MATURITY <= utxo['confirmations'], self._utxos) + blocks_height = self._test_node.getblockchaininfo()['blocks'] + utxo_filter = filter(lambda utxo: not utxo['coinbase'] or COINBASE_MATURITY - 1 <= blocks_height - utxo['height'], self._utxos) else: utxo_filter = self._utxos utxos = deepcopy(list(utxo_filter)) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 3dc22ccde3..5895e1de84 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -171,6 +171,7 @@ BASE_SCRIPTS = [ 'wallet_fast_rescan.py --descriptors', 'interface_zmq.py', 'rpc_invalid_address_message.py', + 'rpc_validateaddress.py', 'interface_bitcoin_cli.py --legacy-wallet', 'interface_bitcoin_cli.py --descriptors', 'feature_bind_extra.py', @@ -195,6 +196,8 @@ BASE_SCRIPTS = [ 'wallet_watchonly.py --legacy-wallet', 'wallet_watchonly.py --usecli --legacy-wallet', 'wallet_reorgsrestore.py', + 'wallet_conflicts.py --legacy-wallet', + 'wallet_conflicts.py --descriptors', 'interface_http.py', 'interface_rpc.py', 'interface_usdt_coinselection.py', diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py index 934f44588d..2691507773 100755 --- a/test/functional/wallet_abandonconflict.py +++ b/test/functional/wallet_abandonconflict.py @@ -226,20 +226,16 @@ class AbandonConflictTest(BitcoinTestFramework): assert_equal(double_spend["walletconflicts"], [txAB1]) # Verify that B and C's 10 BTC outputs are available for spending again because AB1 is now conflicted + assert_equal(alice.gettransaction(txAB1)["confirmations"], -1) newbalance = alice.getbalance() assert_equal(newbalance, balance + Decimal("20")) balance = newbalance - # There is currently a minor bug around this and so this test doesn't work. See Issue #7315 - # Invalidate the block with the double spend and B's 10 BTC output should no longer be available - # Don't think C's should either + # Invalidate the block with the double spend. B & C's 10 BTC outputs should no longer be available self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) + assert_equal(alice.gettransaction(txAB1)["confirmations"], 0) newbalance = alice.getbalance() - #assert_equal(newbalance, balance - Decimal("10")) - self.log.info("If balance has not declined after invalidateblock then out of mempool wallet tx which is no longer") - self.log.info("conflicted has not resumed causing its inputs to be seen as spent. See Issue #7315") - assert_equal(balance, newbalance) - + assert_equal(newbalance, balance - Decimal("20")) if __name__ == '__main__': AbandonConflictTest().main() diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index e23ec7bc01..a1b805c09e 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -8,6 +8,10 @@ from itertools import product from test_framework.blocktools import COINBASE_MATURITY from test_framework.descriptors import descsum_create +from test_framework.messages import ( + COIN, + DEFAULT_ANCESTOR_LIMIT, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_array_result, @@ -17,6 +21,7 @@ from test_framework.util import ( find_vout_for_address, ) from test_framework.wallet_util import test_address +from test_framework.wallet import MiniWallet NOT_A_NUMBER_OR_STRING = "Amount is not a number or string" OUT_OF_RANGE = "Amount out of range" @@ -784,6 +789,34 @@ class WalletTest(BitcoinTestFramework): zeroconf_wallet.sendtoaddress(zeroconf_wallet.getnewaddress(), Decimal('0.5')) + self.test_chain_listunspent() + + def test_chain_listunspent(self): + if not self.options.descriptors: + return + self.wallet = MiniWallet(self.nodes[0]) + self.nodes[0].get_wallet_rpc(self.default_wallet_name).sendtoaddress(self.wallet.get_address(), "5") + self.generate(self.wallet, 1, sync_fun=self.no_op) + self.nodes[0].createwallet("watch_wallet", disable_private_keys=True) + watch_wallet = self.nodes[0].get_wallet_rpc("watch_wallet") + watch_wallet.importaddress(self.wallet.get_address()) + + # DEFAULT_ANCESTOR_LIMIT transactions off a confirmed tx should be fine + chain = self.wallet.create_self_transfer_chain(chain_length=DEFAULT_ANCESTOR_LIMIT) + ancestor_vsize = 0 + ancestor_fees = Decimal(0) + + for i, t in enumerate(chain): + ancestor_vsize += t["tx"].get_vsize() + ancestor_fees += t["fee"] + self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=t["hex"]) + # Check that listunspent ancestor{count, size, fees} yield the correct results + wallet_unspent = watch_wallet.listunspent(minconf=0) + this_unspent = next(utxo_info for utxo_info in wallet_unspent if utxo_info["txid"] == t["txid"]) + assert_equal(this_unspent['ancestorcount'], i + 1) + assert_equal(this_unspent['ancestorsize'], ancestor_vsize) + assert_equal(this_unspent['ancestorfees'], ancestor_fees * COIN) + if __name__ == '__main__': WalletTest().main() diff --git a/test/functional/wallet_conflicts.py b/test/functional/wallet_conflicts.py new file mode 100755 index 0000000000..802b718cd5 --- /dev/null +++ b/test/functional/wallet_conflicts.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023 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 that wallet correctly tracks transactions that have been conflicted by blocks, particularly during reorgs. +""" + +from decimal import Decimal + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, +) + +class TxConflicts(BitcoinTestFramework): + def add_options(self, parser): + self.add_wallet_options(parser) + + def set_test_params(self): + self.num_nodes = 3 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def get_utxo_of_value(self, from_tx_id, search_value): + return next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(from_tx_id)["details"] if tx_out["amount"] == Decimal(f"{search_value}")) + + def run_test(self): + self.log.info("Send tx from which to conflict outputs later") + txid_conflict_from_1 = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) + txid_conflict_from_2 = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) + self.generate(self.nodes[0], 1) + self.sync_blocks() + + self.log.info("Disconnect nodes to broadcast conflicts on their respective chains") + self.disconnect_nodes(0, 1) + self.disconnect_nodes(2, 1) + + self.log.info("Create transactions that conflict with each other") + output_A = self.get_utxo_of_value(from_tx_id=txid_conflict_from_1, search_value=10) + output_B = self.get_utxo_of_value(from_tx_id=txid_conflict_from_2, search_value=10) + + # First create a transaction that consumes both A and B outputs. + # + # | tx1 | -----> | | | | + # | AB_parent_tx | ----> | Child_Tx | + # | tx2 | -----> | | | | + # + inputs_tx_AB_parent = [{"txid": txid_conflict_from_1, "vout": output_A}, {"txid": txid_conflict_from_2, "vout": output_B}] + tx_AB_parent = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs_tx_AB_parent, {self.nodes[0].getnewaddress(): Decimal("19.99998")})) + + # Secondly, create two transactions: One consuming output_A, and another one consuming output_B + # + # | tx1 | -----> | Tx_A_1 | + # ---------------- + # | tx2 | -----> | Tx_B_1 | + # + inputs_tx_A_1 = [{"txid": txid_conflict_from_1, "vout": output_A}] + inputs_tx_B_1 = [{"txid": txid_conflict_from_2, "vout": output_B}] + tx_A_1 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs_tx_A_1, {self.nodes[0].getnewaddress(): Decimal("9.99998")})) + tx_B_1 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs_tx_B_1, {self.nodes[0].getnewaddress(): Decimal("9.99998")})) + + self.log.info("Broadcast conflicted transaction") + txid_AB_parent = self.nodes[0].sendrawtransaction(tx_AB_parent["hex"]) + self.generate(self.nodes[0], 1, sync_fun=self.no_op) + + # Now that 'AB_parent_tx' was broadcast, build 'Child_Tx' + output_c = self.get_utxo_of_value(from_tx_id=txid_AB_parent, search_value=19.99998) + inputs_tx_C_child = [({"txid": txid_AB_parent, "vout": output_c})] + + tx_C_child = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs_tx_C_child, {self.nodes[0].getnewaddress() : Decimal("19.99996")})) + tx_C_child_txid = self.nodes[0].sendrawtransaction(tx_C_child["hex"]) + self.generate(self.nodes[0], 1, sync_fun=self.no_op) + + self.log.info("Broadcast conflicting tx to node 1 and generate a longer chain") + conflicting_txid_A = self.nodes[1].sendrawtransaction(tx_A_1["hex"]) + self.generate(self.nodes[1], 4, sync_fun=self.no_op) + conflicting_txid_B = self.nodes[1].sendrawtransaction(tx_B_1["hex"]) + self.generate(self.nodes[1], 4, sync_fun=self.no_op) + + self.log.info("Connect nodes 0 and 1, trigger reorg and ensure that the tx is effectively conflicted") + self.connect_nodes(0, 1) + self.sync_blocks([self.nodes[0], self.nodes[1]]) + conflicted_AB_tx = self.nodes[0].gettransaction(txid_AB_parent) + tx_C_child = self.nodes[0].gettransaction(tx_C_child_txid) + conflicted_A_tx = self.nodes[0].gettransaction(conflicting_txid_A) + + self.log.info("Verify, after the reorg, that Tx_A was accepted, and tx_AB and its Child_Tx are conflicting now") + # Tx A was accepted, Tx AB was not. + assert conflicted_AB_tx["confirmations"] < 0 + assert conflicted_A_tx["confirmations"] > 0 + + # Conflicted tx should have confirmations set to the confirmations of the most conflicting tx + assert_equal(-conflicted_AB_tx["confirmations"], conflicted_A_tx["confirmations"]) + # Child should inherit conflicted state from parent + assert_equal(-tx_C_child["confirmations"], conflicted_A_tx["confirmations"]) + # Check the confirmations of the conflicting transactions + assert_equal(conflicted_A_tx["confirmations"], 8) + assert_equal(self.nodes[0].gettransaction(conflicting_txid_B)["confirmations"], 4) + + self.log.info("Now generate a longer chain that does not contain any tx") + # Node2 chain without conflicts + self.generate(self.nodes[2], 15, sync_fun=self.no_op) + + # Connect node0 and node2 and wait reorg + self.connect_nodes(0, 2) + self.sync_blocks() + conflicted = self.nodes[0].gettransaction(txid_AB_parent) + tx_C_child = self.nodes[0].gettransaction(tx_C_child_txid) + + self.log.info("Test that formerly conflicted transaction are inactive after reorg") + # Former conflicted tx should be unconfirmed as it hasn't been yet rebroadcast + assert_equal(conflicted["confirmations"], 0) + # Former conflicted child tx should be unconfirmed as it hasn't been rebroadcast + assert_equal(tx_C_child["confirmations"], 0) + # Rebroadcast former conflicted tx and check it confirms smoothly + self.nodes[2].sendrawtransaction(conflicted["hex"]) + self.generate(self.nodes[2], 1) + self.sync_blocks() + former_conflicted = self.nodes[0].gettransaction(txid_AB_parent) + assert_equal(former_conflicted["confirmations"], 1) + assert_equal(former_conflicted["blockheight"], 217) + +if __name__ == '__main__': + TxConflicts().main() |