diff options
Diffstat (limited to 'test/functional')
43 files changed, 593 insertions, 127 deletions
diff --git a/test/functional/.gitignore b/test/functional/.gitignore index cb41d94423..0d20b6487c 100644 --- a/test/functional/.gitignore +++ b/test/functional/.gitignore @@ -1,2 +1 @@ *.pyc -cache diff --git a/test/functional/feature_assumeutxo.py b/test/functional/feature_assumeutxo.py index a212704311..2995ece42f 100755 --- a/test/functional/feature_assumeutxo.py +++ b/test/functional/feature_assumeutxo.py @@ -9,6 +9,7 @@ to a hash that has been compiled into bitcoind. The assumeutxo value generated and used here is committed to in `CRegTestParams::m_assumeutxo_data` in `src/kernel/chainparams.cpp`. """ +import time from shutil import rmtree from dataclasses import dataclass @@ -16,12 +17,22 @@ from test_framework.blocktools import ( create_block, create_coinbase ) -from test_framework.messages import tx_from_hex +from test_framework.messages import ( + CBlockHeader, + from_hex, + msg_headers, + tx_from_hex +) +from test_framework.p2p import ( + P2PInterface, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_approx, assert_equal, assert_raises_rpc_error, + sha256sum_file, + try_rpc, ) from test_framework.wallet import ( getnewdestination, @@ -247,6 +258,74 @@ class AssumeutxoTest(BitcoinTestFramework): node1.submitheader(main_block1) node1.submitheader(main_block2) + def test_sync_from_assumeutxo_node(self, snapshot): + """ + This test verifies that: + 1. An IBD node can sync headers from an AssumeUTXO node at any time. + 2. IBD nodes do not request historical blocks from AssumeUTXO nodes while they are syncing the background-chain. + 3. The assumeUTXO node dynamically adjusts the network services it offers according to its state. + 4. IBD nodes can fully sync from AssumeUTXO nodes after they finish the background-chain sync. + """ + self.log.info("Testing IBD-sync from assumeUTXO node") + # Node2 starts clean and loads the snapshot. + # Node3 starts clean and seeks to sync-up from snapshot_node. + miner = self.nodes[0] + snapshot_node = self.nodes[2] + ibd_node = self.nodes[3] + + # Start test fresh by cleaning up node directories + for node in (snapshot_node, ibd_node): + self.stop_node(node.index) + rmtree(node.chain_path) + self.start_node(node.index, extra_args=self.extra_args[node.index]) + + # Sync-up headers chain on snapshot_node to load snapshot + headers_provider_conn = snapshot_node.add_p2p_connection(P2PInterface()) + headers_provider_conn.wait_for_getheaders() + msg = msg_headers() + for block_num in range(1, miner.getblockcount()+1): + msg.headers.append(from_hex(CBlockHeader(), miner.getblockheader(miner.getblockhash(block_num), verbose=False))) + headers_provider_conn.send_message(msg) + + # Ensure headers arrived + default_value = {'status': ''} # No status + headers_tip_hash = miner.getbestblockhash() + self.wait_until(lambda: next(filter(lambda x: x['hash'] == headers_tip_hash, snapshot_node.getchaintips()), default_value)['status'] == "headers-only") + snapshot_node.disconnect_p2ps() + + # Load snapshot + snapshot_node.loadtxoutset(snapshot['path']) + + # Connect nodes and verify the ibd_node can sync-up the headers-chain from the snapshot_node + self.connect_nodes(ibd_node.index, snapshot_node.index) + snapshot_block_hash = snapshot['base_hash'] + self.wait_until(lambda: next(filter(lambda x: x['hash'] == snapshot_block_hash, ibd_node.getchaintips()), default_value)['status'] == "headers-only") + + # Once the headers-chain is synced, the ibd_node must avoid requesting historical blocks from the snapshot_node. + # If it does request such blocks, the snapshot_node will ignore requests it cannot fulfill, causing the ibd_node + # to stall. This stall could last for up to 10 min, ultimately resulting in an abrupt disconnection due to the + # ibd_node's perceived unresponsiveness. + time.sleep(3) # Sleep here because we can't detect when a node avoids requesting blocks from other peer. + assert_equal(len(ibd_node.getpeerinfo()[0]['inflight']), 0) + + # Now disconnect nodes and finish background chain sync + self.disconnect_nodes(ibd_node.index, snapshot_node.index) + self.connect_nodes(snapshot_node.index, miner.index) + self.sync_blocks(nodes=(miner, snapshot_node)) + # Check the base snapshot block was stored and ensure node signals full-node service support + self.wait_until(lambda: not try_rpc(-1, "Block not available (not fully downloaded)", snapshot_node.getblock, snapshot_block_hash)) + self.wait_until(lambda: 'NETWORK' in snapshot_node.getnetworkinfo()['localservicesnames']) + + # Now that the snapshot_node is synced, verify the ibd_node can sync from it + self.connect_nodes(snapshot_node.index, ibd_node.index) + assert 'NETWORK' in ibd_node.getpeerinfo()[0]['servicesnames'] + self.sync_blocks(nodes=(ibd_node, snapshot_node)) + + def assert_only_network_limited_service(self, node): + node_services = node.getnetworkinfo()['localservicesnames'] + assert 'NETWORK' not in node_services + assert 'NETWORK_LIMITED' in node_services + def run_test(self): """ Bring up two (disconnected) nodes, mine some new blocks on the first, @@ -295,7 +374,7 @@ class AssumeutxoTest(BitcoinTestFramework): assert_equal(n1.getblockcount(), START_HEIGHT) self.log.info(f"Creating a UTXO snapshot at height {SNAPSHOT_BASE_HEIGHT}") - dump_output = n0.dumptxoutset('utxos.dat') + dump_output = n0.dumptxoutset('utxos.dat', "latest") self.log.info("Test loading snapshot when the node tip is on the same block as the snapshot") assert_equal(n0.getblockcount(), SNAPSHOT_BASE_HEIGHT) @@ -320,12 +399,16 @@ class AssumeutxoTest(BitcoinTestFramework): for n in self.nodes: assert_equal(n.getblockchaininfo()["headers"], SNAPSHOT_BASE_HEIGHT) - assert_equal( - dump_output['txoutset_hash'], - "a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27") - assert_equal(dump_output["nchaintx"], blocks[SNAPSHOT_BASE_HEIGHT].chain_tx) assert_equal(n0.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT) + def check_dump_output(output): + assert_equal( + output['txoutset_hash'], + "a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27") + assert_equal(output["nchaintx"], blocks[SNAPSHOT_BASE_HEIGHT].chain_tx) + + check_dump_output(dump_output) + # Mine more blocks on top of the snapshot that n1 hasn't yet seen. This # will allow us to test n1's sync-to-tip on top of a snapshot. self.generate(n0, nblocks=100, sync_fun=self.no_op) @@ -335,6 +418,39 @@ class AssumeutxoTest(BitcoinTestFramework): assert_equal(n0.getblockchaininfo()["blocks"], FINAL_HEIGHT) + self.log.info(f"Check that dumptxoutset works for past block heights") + # rollback defaults to the snapshot base height + dump_output2 = n0.dumptxoutset('utxos2.dat', "rollback") + check_dump_output(dump_output2) + assert_equal(sha256sum_file(dump_output['path']), sha256sum_file(dump_output2['path'])) + + # Rollback with specific height + dump_output3 = n0.dumptxoutset('utxos3.dat', rollback=SNAPSHOT_BASE_HEIGHT) + check_dump_output(dump_output3) + assert_equal(sha256sum_file(dump_output['path']), sha256sum_file(dump_output3['path'])) + + # Specified height that is not a snapshot height + prev_snap_height = SNAPSHOT_BASE_HEIGHT - 1 + dump_output4 = n0.dumptxoutset(path='utxos4.dat', rollback=prev_snap_height) + assert_equal( + dump_output4['txoutset_hash'], + "8a1db0d6e958ce0d7c963bc6fc91ead596c027129bacec68acc40351037b09d7") + assert sha256sum_file(dump_output['path']) != sha256sum_file(dump_output4['path']) + + # Use a hash instead of a height + prev_snap_hash = n0.getblockhash(prev_snap_height) + dump_output5 = n0.dumptxoutset('utxos5.dat', rollback=prev_snap_hash) + assert_equal(sha256sum_file(dump_output4['path']), sha256sum_file(dump_output5['path'])) + + # TODO: This is a hack to set m_best_header to the correct value after + # dumptxoutset/reconsiderblock. Otherwise the wrong error messages are + # returned in following tests. It can be removed once this bug is + # fixed. See also https://github.com/bitcoin/bitcoin/issues/26245 + self.restart_node(0, ["-reindex"]) + + # Ensure n0 is back at the tip + assert_equal(n0.getblockchaininfo()["blocks"], FINAL_HEIGHT) + self.test_snapshot_with_less_work(dump_output['path']) self.test_invalid_mempool_state(dump_output['path']) self.test_invalid_snapshot_scenarios(dump_output['path']) @@ -343,6 +459,9 @@ class AssumeutxoTest(BitcoinTestFramework): self.test_snapshot_block_invalidated(dump_output['path']) self.test_snapshot_not_on_most_work_chain(dump_output['path']) + # Prune-node sanity check + assert 'NETWORK' not in n1.getnetworkinfo()['localservicesnames'] + self.log.info(f"Loading snapshot into second node from {dump_output['path']}") # This node's tip is on an ancestor block of the snapshot, which should # be the normal case @@ -350,6 +469,10 @@ class AssumeutxoTest(BitcoinTestFramework): assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT) assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT) + self.log.info("Confirm that local services remain unchanged") + # Since n1 is a pruned node, the 'NETWORK' service flag must always be unset. + self.assert_only_network_limited_service(n1) + self.log.info("Check that UTXO-querying RPCs operate on snapshot chainstate") snapshot_hash = loaded['tip_hash'] snapshot_num_coins = loaded['coins_loaded'] @@ -362,7 +485,7 @@ class AssumeutxoTest(BitcoinTestFramework): # find coinbase output at snapshot height on node0 and scan for it on node1, # where the block is not available, but the snapshot was loaded successfully coinbase_tx = n0.getblock(snapshot_hash, verbosity=2)['tx'][0] - assert_raises_rpc_error(-1, "Block not found on disk", n1.getblock, snapshot_hash) + assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", n1.getblock, snapshot_hash) coinbase_output_descriptor = coinbase_tx['vout'][0]['scriptPubKey']['desc'] scan_result = n1.scantxoutset('start', [coinbase_output_descriptor]) assert_equal(scan_result['success'], True) @@ -434,7 +557,7 @@ class AssumeutxoTest(BitcoinTestFramework): self.log.info("Submit a spending transaction for a snapshot chainstate coin to the mempool") # spend the coinbase output of the first block that is not available on node1 spend_coin_blockhash = n1.getblockhash(START_HEIGHT + 1) - assert_raises_rpc_error(-1, "Block not found on disk", n1.getblock, spend_coin_blockhash) + assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", n1.getblock, spend_coin_blockhash) prev_tx = n0.getblock(spend_coin_blockhash, 3)['tx'][0] prevout = {"txid": prev_tx['txid'], "vout": 0, "scriptPubKey": prev_tx['vout'][0]['scriptPubKey']['hex']} privkey = n0.get_deterministic_priv_key().key @@ -453,6 +576,9 @@ class AssumeutxoTest(BitcoinTestFramework): self.restart_node(1, extra_args=[ f"-stopatheight={PAUSE_HEIGHT}", *self.extra_args[1]]) + # Upon restart during snapshot tip sync, the node must remain in 'limited' mode. + self.assert_only_network_limited_service(n1) + # Finally connect the nodes and let them sync. # # Set `wait_for_connect=False` to avoid a race between performing connection @@ -469,6 +595,9 @@ class AssumeutxoTest(BitcoinTestFramework): self.log.info("Restarted node before snapshot validation completed, reloading...") self.restart_node(1, extra_args=self.extra_args[1]) + # Upon restart, the node must remain in 'limited' mode + self.assert_only_network_limited_service(n1) + # Send snapshot block to n1 out of order. This makes the test less # realistic because normally the snapshot block is one of the last # blocks downloaded, but its useful to test because it triggers more @@ -487,6 +616,10 @@ class AssumeutxoTest(BitcoinTestFramework): self.log.info("Ensuring background validation completes") self.wait_until(lambda: len(n1.getchainstates()['chainstates']) == 1) + # Since n1 is a pruned node, it will not signal NODE_NETWORK after + # completing the background sync. + self.assert_only_network_limited_service(n1) + # Ensure indexes have synced. completed_idx_state = { 'basic block filter index': COMPLETE_IDX, @@ -517,12 +650,18 @@ class AssumeutxoTest(BitcoinTestFramework): self.log.info("-- Testing all indexes + reindex") assert_equal(n2.getblockcount(), START_HEIGHT) + assert 'NETWORK' in n2.getnetworkinfo()['localservicesnames'] # sanity check self.log.info(f"Loading snapshot into third node from {dump_output['path']}") loaded = n2.loadtxoutset(dump_output['path']) assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT) assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT) + # Even though n2 is a full node, it will unset the 'NETWORK' service flag during snapshot loading. + # This indicates other peers that the node will temporarily not provide historical blocks. + self.log.info("Check node2 updated the local services during snapshot load") + self.assert_only_network_limited_service(n2) + for reindex_arg in ['-reindex=1', '-reindex-chainstate=1']: self.log.info(f"Check that restarting with {reindex_arg} will delete the snapshot chainstate") self.restart_node(2, extra_args=[reindex_arg, *self.extra_args[2]]) @@ -546,6 +685,11 @@ class AssumeutxoTest(BitcoinTestFramework): msg = "Unable to load UTXO snapshot: Can't activate a snapshot-based chainstate more than once" assert_raises_rpc_error(-32603, msg, n2.loadtxoutset, dump_output['path']) + # Upon restart, the node must stay in 'limited' mode until the background + # chain sync completes. + self.restart_node(2, extra_args=self.extra_args[2]) + self.assert_only_network_limited_service(n2) + self.connect_nodes(0, 2) self.wait_until(lambda: n2.getchainstates()['chainstates'][-1]['blocks'] == FINAL_HEIGHT) self.sync_blocks(nodes=(n0, n2)) @@ -553,6 +697,9 @@ class AssumeutxoTest(BitcoinTestFramework): self.log.info("Ensuring background validation completes") self.wait_until(lambda: len(n2.getchainstates()['chainstates']) == 1) + # Once background chain sync completes, the full node must start offering historical blocks again. + self.wait_until(lambda: {'NETWORK', 'NETWORK_LIMITED'}.issubset(n2.getnetworkinfo()['localservicesnames'])) + completed_idx_state = { 'basic block filter index': COMPLETE_IDX, 'coinstatsindex': COMPLETE_IDX, @@ -587,6 +734,9 @@ class AssumeutxoTest(BitcoinTestFramework): self.test_snapshot_in_a_divergent_chain(dump_output['path']) + # The following test cleans node2 and node3 chain directories. + self.test_sync_from_assumeutxo_node(snapshot=dump_output) + @dataclass class Block: hash: str diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py index 03a9f666b2..32ad3ad271 100755 --- a/test/functional/feature_assumevalid.py +++ b/test/functional/feature_assumevalid.py @@ -139,8 +139,8 @@ class AssumeValidTest(BitcoinTestFramework): height += 1 # Start node1 and node2 with assumevalid so they accept a block with a bad signature. - self.start_node(1, extra_args=["-assumevalid=" + hex(block102.sha256)]) - self.start_node(2, extra_args=["-assumevalid=" + hex(block102.sha256)]) + self.start_node(1, extra_args=["-assumevalid=" + block102.hash]) + self.start_node(2, extra_args=["-assumevalid=" + block102.hash]) p2p0 = self.nodes[0].add_p2p_connection(BaseNode()) p2p0.send_header_for_blocks(self.blocks[0:2000]) diff --git a/test/functional/feature_blocksxor.py b/test/functional/feature_blocksxor.py index 88e0244cd4..7698a66ec4 100755 --- a/test/functional/feature_blocksxor.py +++ b/test/functional/feature_blocksxor.py @@ -3,14 +3,15 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test support for XORed block data and undo files (`-blocksxor` option).""" -import os from test_framework.test_framework import BitcoinTestFramework -from test_framework.test_node import ErrorMatch +from test_framework.test_node import ( + ErrorMatch, + NULL_BLK_XOR_KEY, +) from test_framework.util import ( assert_equal, assert_greater_than, - read_xor_key, util_xor, ) from test_framework.wallet import MiniWallet @@ -40,7 +41,7 @@ class BlocksXORTest(BitcoinTestFramework): self.log.info("Shut down node and un-XOR block/undo files manually") self.stop_node(0) - xor_key = read_xor_key(node=node) + xor_key = node.read_xor_key() for data_file in sorted(block_files + undo_files): self.log.debug(f"Rewriting file {data_file}...") with open(data_file, 'rb+') as f: @@ -54,11 +55,13 @@ class BlocksXORTest(BitcoinTestFramework): match=ErrorMatch.PARTIAL_REGEX) self.log.info("Delete XOR key, restart node with '-blocksxor=0', check blk*.dat/rev*.dat file integrity") - os.remove(node.blocks_path / 'xor.dat') + node.blocks_key_path.unlink() self.start_node(0, extra_args=['-blocksxor=0']) # checklevel=2 -> verify block validity + undo data # nblocks=0 -> verify all blocks node.verifychain(checklevel=2, nblocks=0) + self.log.info("Check that blocks XOR key is recreated") + assert_equal(node.read_xor_key(), NULL_BLK_XOR_KEY) if __name__ == '__main__': diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index bb20e2baa8..44c7edf962 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -153,6 +153,13 @@ class ConfArgsTest(BitcoinTestFramework): expected_msg='Error: Error parsing command line arguments: Can not set -proxy with no value. Please specify value with -proxy=value.', extra_args=['-proxy'], ) + # Provide a value different from 1 to the -wallet negated option + if self.is_wallet_compiled(): + for value in [0, 'not_a_boolean']: + self.nodes[0].assert_start_raises_init_error( + expected_msg="Error: Invalid value detected for '-wallet' or '-nowallet'. '-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets", + extra_args=[f'-nowallet={value}'], + ) def test_log_buffer(self): self.stop_node(0) diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index a3dcb7afda..83627ff5c2 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -4,7 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test fee estimation code.""" from copy import deepcopy -from decimal import Decimal +from decimal import Decimal, ROUND_DOWN import os import random import time @@ -40,7 +40,7 @@ def small_txpuzzle_randfee( # Exponentially distributed from 1-128 * fee_increment rand_fee = float(fee_increment) * (1.1892 ** random.randint(0, 28)) # Total fee ranges from min_fee to min_fee + 127*fee_increment - fee = min_fee - fee_increment + satoshi_round(rand_fee) + fee = min_fee - fee_increment + satoshi_round(rand_fee, rounding=ROUND_DOWN) utxos_to_spend = [] total_in = Decimal("0.00000000") while total_in <= (amount + fee) and len(conflist) > 0: diff --git a/test/functional/feature_minchainwork.py b/test/functional/feature_minchainwork.py index 8327a0477b..34228f6f38 100755 --- a/test/functional/feature_minchainwork.py +++ b/test/functional/feature_minchainwork.py @@ -110,7 +110,7 @@ class MinimumChainWorkTest(BitcoinTestFramework): self.stop_node(0) self.nodes[0].assert_start_raises_init_error( ["-minimumchainwork=test"], - expected_msg='Error: Invalid non-hex (test) minimum chain work value specified', + expected_msg='Error: Invalid minimum work specified (test), must be up to 64 hex digits', ) diff --git a/test/functional/feature_reindex.py b/test/functional/feature_reindex.py index 2961a2d356..1ebfe82da5 100755 --- a/test/functional/feature_reindex.py +++ b/test/functional/feature_reindex.py @@ -14,7 +14,6 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import MAGIC_BYTES from test_framework.util import ( assert_equal, - read_xor_key, util_xor, ) @@ -43,7 +42,7 @@ class ReindexTest(BitcoinTestFramework): # we're generating them rather than getting them from peers), so to # test out-of-order handling, swap blocks 1 and 2 on disk. blk0 = self.nodes[0].blocks_path / "blk00000.dat" - xor_dat = read_xor_key(node=self.nodes[0]) + xor_dat = self.nodes[0].read_xor_key() with open(blk0, 'r+b') as bf: # Read at least the first few blocks (including genesis) diff --git a/test/functional/feature_settings.py b/test/functional/feature_settings.py index 2189eac7dd..a7294944bf 100755 --- a/test/functional/feature_settings.py +++ b/test/functional/feature_settings.py @@ -13,11 +13,32 @@ from test_framework.util import assert_equal class SettingsTest(BitcoinTestFramework): + def add_options(self, parser): + self.add_wallet_options(parser) + def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.wallet_names = [] + def test_wallet_settings(self, settings_path): + if not self.is_wallet_compiled(): + return + + self.log.info("Testing wallet settings..") + node = self.nodes[0] + # Create wallet to use it during tests + self.start_node(0) + node.createwallet(wallet_name='w1') + self.stop_node(0) + + # Verify wallet settings can only be strings. Either names or paths. Not booleans, nums nor anything else. + for wallets_data in [[10], [True], [[]], [{}], ["w1", 10], ["w1", False]]: + with settings_path.open("w") as fp: + json.dump({"wallet": wallets_data}, fp) + node.assert_start_raises_init_error(expected_msg="Error: Invalid value detected for '-wallet' or '-nowallet'. '-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets", + extra_args=[f'-settings={settings_path}']) + def run_test(self): node, = self.nodes settings = node.chain_path / "settings.json" @@ -86,6 +107,8 @@ class SettingsTest(BitcoinTestFramework): self.start_node(0, extra_args=[f"-settings={altsettings}"]) self.stop_node(0) + self.test_wallet_settings(settings) + if __name__ == '__main__': SettingsTest(__file__).main() diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index e7113f8335..30bf97185a 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -381,6 +381,9 @@ class TestBitcoinCli(BitcoinTestFramework): assert_raises_process_error(1, "Could not connect to the server", self.nodes[0].cli('-rpcwait', '-rpcwaittimeout=5').echo) assert_greater_than_or_equal(time.time(), start_time + 5) + self.log.info("Test that only one of -addrinfo, -generate, -getinfo, -netinfo may be specified at a time") + assert_raises_process_error(1, "Only one of -getinfo, -netinfo may be specified", self.nodes[0].cli('-getinfo', '-netinfo').send_cli) + if __name__ == '__main__': TestBitcoinCli(__file__).main() diff --git a/test/functional/interface_usdt_coinselection.py b/test/functional/interface_usdt_coinselection.py index dc40986a75..f684848aed 100755 --- a/test/functional/interface_usdt_coinselection.py +++ b/test/functional/interface_usdt_coinselection.py @@ -181,7 +181,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework): # 5. aps_create_tx_internal (type 4) wallet.sendtoaddress(wallet.getnewaddress(), 10) events = self.get_tracepoints([1, 2, 3, 1, 4]) - success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events) + success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events) assert_equal(success, True) assert_greater_than(change_pos, -1) @@ -190,7 +190,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework): # 1. normal_create_tx_internal (type 2) assert_raises_rpc_error(-6, "Insufficient funds", wallet.sendtoaddress, wallet.getnewaddress(), 102 * 50) events = self.get_tracepoints([2]) - success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events) + success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events) assert_equal(success, False) self.log.info("Explicitly enabling APS results in 2 tracepoints") @@ -200,7 +200,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework): wallet.setwalletflag("avoid_reuse") wallet.sendtoaddress(address=wallet.getnewaddress(), amount=10, avoid_reuse=True) events = self.get_tracepoints([1, 2]) - success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events) + success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events) assert_equal(success, True) assert_equal(use_aps, None) @@ -213,7 +213,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework): # 5. aps_create_tx_internal (type 4) wallet.sendtoaddress(address=wallet.getnewaddress(), amount=wallet.getbalance(), subtractfeefromamount=True, avoid_reuse=False) events = self.get_tracepoints([1, 2, 3, 1, 4]) - success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events) + success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events) assert_equal(success, True) assert_equal(change_pos, -1) @@ -223,7 +223,7 @@ class CoinSelectionTracepointTest(BitcoinTestFramework): # 2. normal_create_tx_internal (type 2) wallet.sendtoaddress(address=wallet.getnewaddress(), amount=wallet.getbalance(), subtractfeefromamount=True) events = self.get_tracepoints([1, 2]) - success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events) + success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events) assert_equal(success, True) assert_equal(change_pos, -1) diff --git a/test/functional/mempool_package_rbf.py b/test/functional/mempool_package_rbf.py index 9b4269f0a0..f4d57262f2 100755 --- a/test/functional/mempool_package_rbf.py +++ b/test/functional/mempool_package_rbf.py @@ -189,7 +189,7 @@ class PackageRBFTest(BitcoinTestFramework): package_hex4, package_txns4 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE) node.submitpackage(package_hex4) self.assert_mempool_contents(expected=package_txns4) - package_hex5, package_txns5 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE) + package_hex5, _package_txns5 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE, child_fee=DEFAULT_CHILD_FEE) pkg_results5 = node.submitpackage(package_hex5) assert 'package RBF failed: package feerate is less than or equal to parent feerate' in pkg_results5["package_msg"] self.assert_mempool_contents(expected=package_txns4) @@ -336,16 +336,16 @@ class PackageRBFTest(BitcoinTestFramework): self.assert_mempool_contents(expected=expected_txns) # Now make conflicting packages for each coin - package_hex1, package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE) + package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE) package_result = node.submitpackage(package_hex1) assert_equal(f"package RBF failed: {parent_result['tx'].rehash()} has 2 descendants, max 1 allowed", package_result["package_msg"]) - package_hex2, package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE) + package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE) package_result = node.submitpackage(package_hex2) assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has both ancestor and descendant, exceeding cluster limit of 2", package_result["package_msg"]) - package_hex3, package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE) + package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE) package_result = node.submitpackage(package_hex3) assert_equal(f"package RBF failed: {grandchild_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"]) @@ -389,15 +389,15 @@ class PackageRBFTest(BitcoinTestFramework): self.assert_mempool_contents(expected=expected_txns) # Now make conflicting packages for each coin - package_hex1, package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE) + package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE) package_result = node.submitpackage(package_hex1) assert_equal(f"package RBF failed: {parent1_result['tx'].rehash()} is not the only parent of child {child_result['tx'].rehash()}", package_result["package_msg"]) - package_hex2, package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE) + package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE) package_result = node.submitpackage(package_hex2) assert_equal(f"package RBF failed: {parent2_result['tx'].rehash()} is not the only parent of child {child_result['tx'].rehash()}", package_result["package_msg"]) - package_hex3, package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE) + package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE) package_result = node.submitpackage(package_hex3) assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"]) @@ -443,15 +443,15 @@ class PackageRBFTest(BitcoinTestFramework): self.assert_mempool_contents(expected=expected_txns) # Now make conflicting packages for each coin - package_hex1, package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE) + package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE) package_result = node.submitpackage(package_hex1) assert_equal(f"package RBF failed: {parent_result['tx'].rehash()} has 2 descendants, max 1 allowed", package_result["package_msg"]) - package_hex2, package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE) + package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE) package_result = node.submitpackage(package_hex2) assert_equal(f"package RBF failed: {child1_result['tx'].rehash()} is not the only child of parent {parent_result['tx'].rehash()}", package_result["package_msg"]) - package_hex3, package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE) + package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE) package_result = node.submitpackage(package_hex3) assert_equal(f"package RBF failed: {child2_result['tx'].rehash()} is not the only child of parent {parent_result['tx'].rehash()}", package_result["package_msg"]) @@ -519,7 +519,7 @@ class PackageRBFTest(BitcoinTestFramework): # Package 2 feerate is below the feerate of directly conflicted parent, so it fails even though # total fees are higher than the original package - package_hex2, package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"), child_fee=DEFAULT_CHILD_FEE) + package_hex2, _package_txns2 = self.create_simple_package(coin, parent_fee=DEFAULT_CHILD_FEE - Decimal("0.00000001"), child_fee=DEFAULT_CHILD_FEE) pkg_results2 = node.submitpackage(package_hex2) assert_equal(pkg_results2["package_msg"], 'package RBF failed: insufficient feerate: does not improve feerate diagram') self.assert_mempool_contents(expected=package_txns1) diff --git a/test/functional/mempool_sigoplimit.py b/test/functional/mempool_sigoplimit.py index 4656176a75..47df0c614a 100755 --- a/test/functional/mempool_sigoplimit.py +++ b/test/functional/mempool_sigoplimit.py @@ -154,7 +154,7 @@ class BytesPerSigOpTest(BitcoinTestFramework): return (tx_utxo, tx) tx_parent_utxo, tx_parent = create_bare_multisig_tx() - tx_child_utxo, tx_child = create_bare_multisig_tx(tx_parent_utxo) + _tx_child_utxo, tx_child = create_bare_multisig_tx(tx_parent_utxo) # Separately, the parent tx is ok parent_individual_testres = self.nodes[0].testmempoolaccept([tx_parent.serialize().hex()])[0] diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index c0df120c65..aca71933ec 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -159,6 +159,15 @@ class MiningTest(BitcoinTestFramework): bad_block.solve() assert_raises_rpc_error(-25, 'time-timewarp-attack', lambda: node.submitheader(hexdata=CBlockHeader(bad_block).serialize().hex())) + self.log.info("Test timewarp protection boundary") + bad_block.nTime = t + MAX_FUTURE_BLOCK_TIME - MAX_TIMEWARP - 1 + bad_block.solve() + assert_raises_rpc_error(-25, 'time-timewarp-attack', lambda: node.submitheader(hexdata=CBlockHeader(bad_block).serialize().hex())) + + bad_block.nTime = t + MAX_FUTURE_BLOCK_TIME - MAX_TIMEWARP + bad_block.solve() + node.submitheader(hexdata=CBlockHeader(bad_block).serialize().hex()) + def run_test(self): node = self.nodes[0] self.wallet = MiniWallet(node) diff --git a/test/functional/p2p_1p1c_network.py b/test/functional/p2p_1p1c_network.py index c3cdb3e0b3..f9e782f524 100755 --- a/test/functional/p2p_1p1c_network.py +++ b/test/functional/p2p_1p1c_network.py @@ -107,7 +107,7 @@ class PackageRelayTest(BitcoinTestFramework): # 3: 2-parent-1-child package. Both parents are above mempool min feerate. No package submission happens. # We require packages to be child-with-unconfirmed-parents and only allow 1-parent-1-child packages. - package_hex_3, parent_31, parent_32, child_3 = self.create_package_2p1c(self.wallet) + package_hex_3, parent_31, _parent_32, child_3 = self.create_package_2p1c(self.wallet) # 4: parent + child package where the child spends 2 different outputs from the parent. package_hex_4, parent_4, child_4 = self.create_package_2outs(self.wallet) diff --git a/test/functional/p2p_headers_sync_with_minchainwork.py b/test/functional/p2p_headers_sync_with_minchainwork.py index 9055232cf3..6e7b4b399e 100755 --- a/test/functional/p2p_headers_sync_with_minchainwork.py +++ b/test/functional/p2p_headers_sync_with_minchainwork.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2019-2022 The Bitcoin Core developers +# Copyright (c) 2019-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 that we reject low difficulty headers to prevent our block tree from filling up with useless bloat""" @@ -21,6 +21,8 @@ from test_framework.blocktools import ( from test_framework.util import assert_equal +import time + NODE1_BLOCKS_REQUIRED = 15 NODE2_BLOCKS_REQUIRED = 2047 @@ -48,6 +50,10 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework): self.connect_nodes(0, 2) self.connect_nodes(0, 3) + def mocktime_all(self, time): + for n in self.nodes: + n.setmocktime(time) + def test_chains_sync_when_long_enough(self): self.log.info("Generate blocks on the node with no required chainwork, and verify nodes 1 and 2 have no new headers in their headers tree") with self.nodes[1].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[2].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[3].assert_debug_log(expected_msgs=["Synchronizing blockheaders, height: 14"]): @@ -149,7 +155,9 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework): self.reconnect_all() + self.mocktime_all(int(time.time())) # Temporarily hold time to avoid internal timeouts self.sync_blocks(timeout=300) # Ensure tips eventually agree + self.mocktime_all(0) def run_test(self): diff --git a/test/functional/p2p_ibd_stalling.py b/test/functional/p2p_ibd_stalling.py index 11cd8837f4..fa07873929 100755 --- a/test/functional/p2p_ibd_stalling.py +++ b/test/functional/p2p_ibd_stalling.py @@ -73,6 +73,7 @@ class P2PIBDStallingTest(BitcoinTestFramework): peers = [] self.log.info("Check that a staller does not get disconnected if the 1024 block lookahead buffer is filled") + self.mocktime = int(time.time()) + 1 for id in range(NUM_PEERS): peers.append(node.add_outbound_p2p_connection(P2PStaller(stall_block), p2p_idx=id, connection_type="outbound-full-relay")) peers[-1].block_store = block_dict @@ -85,7 +86,7 @@ class P2PIBDStallingTest(BitcoinTestFramework): self.all_sync_send_with_ping(peers) # If there was a peer marked for stalling, it would get disconnected - self.mocktime = int(time.time()) + 3 + self.mocktime += 3 node.setmocktime(self.mocktime) self.all_sync_send_with_ping(peers) assert_equal(node.num_test_p2p_connections(), NUM_PEERS) diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py index df6e6a2e28..7788be6adb 100755 --- a/test/functional/p2p_node_network_limited.py +++ b/test/functional/p2p_node_network_limited.py @@ -102,10 +102,10 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): tip_height = pruned_node.getblockcount() limit_buffer = 2 # Prevent races by waiting for the tip to arrive first - self.wait_until(lambda: not try_rpc(-1, "Block not found", full_node.getblock, pruned_node.getbestblockhash())) + self.wait_until(lambda: not try_rpc(-1, "Block not available (not fully downloaded)", full_node.getblock, pruned_node.getbestblockhash())) for height in range(start_height_full_node + 1, tip_height + 1): if height <= tip_height - (NODE_NETWORK_LIMITED_MIN_BLOCKS - limit_buffer): - assert_raises_rpc_error(-1, "Block not found on disk", full_node.getblock, pruned_node.getblockhash(height)) + assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", full_node.getblock, pruned_node.getblockhash(height)) else: full_node.getblock(pruned_node.getblockhash(height)) # just assert it does not throw an exception diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py index c881dd6ff4..c37061c307 100755 --- a/test/functional/p2p_permissions.py +++ b/test/functional/p2p_permissions.py @@ -14,8 +14,10 @@ from test_framework.p2p import P2PDataStore from test_framework.test_node import ErrorMatch from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( + append_config, assert_equal, p2p_port, + tor_port, ) from test_framework.wallet import MiniWallet @@ -57,11 +59,14 @@ class P2PPermissionsTests(BitcoinTestFramework): # by modifying the configuration file. ip_port = "127.0.0.1:{}".format(p2p_port(1)) self.nodes[1].replace_in_config([("bind=127.0.0.1", "whitebind=bloomfilter,forcerelay@" + ip_port)]) + # Explicitly bind the tor port to prevent collisions with the default tor port + append_config(self.nodes[1].datadir_path, [f"bind=127.0.0.1:{tor_port(self.nodes[1].index)}=onion"]) self.checkpermission( ["-whitelist=noban@127.0.0.1"], # Check parameter interaction forcerelay should activate relay ["noban", "bloomfilter", "forcerelay", "relay", "download"]) self.nodes[1].replace_in_config([("whitebind=bloomfilter,forcerelay@" + ip_port, "bind=127.0.0.1")]) + self.nodes[1].replace_in_config([(f"bind=127.0.0.1:{tor_port(self.nodes[1].index)}=onion", "")]) self.checkpermission( # legacy whitelistrelay should be ignored diff --git a/test/functional/p2p_seednode.py b/test/functional/p2p_seednode.py new file mode 100755 index 0000000000..6c510a6a0b --- /dev/null +++ b/test/functional/p2p_seednode.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019-2021 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 seednode interaction with the AddrMan +""" +import random +import time + +from test_framework.test_framework import BitcoinTestFramework + +ADD_NEXT_SEEDNODE = 10 + + +class P2PSeedNodes(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.disable_autoconnect = False + + def test_no_seednode(self): + # Check that if no seednode is provided, the node proceeds as usual (without waiting) + with self.nodes[0].assert_debug_log(expected_msgs=[], unexpected_msgs=["Empty addrman, adding seednode", f"Couldn't connect to peers from addrman after {ADD_NEXT_SEEDNODE} seconds. Adding seednode"], timeout=ADD_NEXT_SEEDNODE): + self.restart_node(0) + + def test_seednode_empty_addrman(self): + seed_node = "0.0.0.1" + # Check that the seednode is added to m_addr_fetches on bootstrap on an empty addrman + with self.nodes[0].assert_debug_log(expected_msgs=[f"Empty addrman, adding seednode ({seed_node}) to addrfetch"], timeout=ADD_NEXT_SEEDNODE): + self.restart_node(0, extra_args=[f'-seednode={seed_node}']) + + def test_seednode_addrman_unreachable_peers(self): + seed_node = "0.0.0.2" + node = self.nodes[0] + # Fill the addrman with unreachable nodes + for i in range(10): + ip = f"{random.randrange(128,169)}.{random.randrange(1,255)}.{random.randrange(1,255)}.{random.randrange(1,255)}" + port = 8333 + i + node.addpeeraddress(ip, port) + + # Restart the node so seednode is processed again + with node.assert_debug_log(expected_msgs=[f"Couldn't connect to peers from addrman after {ADD_NEXT_SEEDNODE} seconds. Adding seednode ({seed_node}) to addrfetch"], unexpected_msgs=["Empty addrman, adding seednode"], timeout=ADD_NEXT_SEEDNODE * 1.5): + self.restart_node(0, extra_args=[f'-seednode={seed_node}']) + node.setmocktime(int(time.time()) + ADD_NEXT_SEEDNODE + 1) + + def run_test(self): + self.test_no_seednode() + self.test_seednode_empty_addrman() + self.test_seednode_addrman_unreachable_peers() + + +if __name__ == '__main__': + P2PSeedNodes(__file__).main() + diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py index 11b4d9cc3b..efad4e7c0f 100755 --- a/test/functional/p2p_tx_download.py +++ b/test/functional/p2p_tx_download.py @@ -156,9 +156,9 @@ class TxDownloadTest(BitcoinTestFramework): # One of the peers is asked for the tx peer2.wait_until(lambda: sum(p.tx_getdata_count for p in [peer1, peer2]) == 1) with p2p_lock: - peer_expiry, peer_fallback = (peer1, peer2) if peer1.tx_getdata_count == 1 else (peer2, peer1) + _peer_expiry, peer_fallback = (peer1, peer2) if peer1.tx_getdata_count == 1 else (peer2, peer1) assert_equal(peer_fallback.tx_getdata_count, 0) - self.nodes[0].setmocktime(int(time.time()) + GETDATA_TX_INTERVAL + 1) # Wait for request to peer_expiry to expire + self.nodes[0].setmocktime(int(time.time()) + GETDATA_TX_INTERVAL + 1) # Wait for request to _peer_expiry to expire peer_fallback.wait_until(lambda: peer_fallback.tx_getdata_count >= 1, timeout=1) self.restart_node(0) # reset mocktime diff --git a/test/functional/p2p_unrequested_blocks.py b/test/functional/p2p_unrequested_blocks.py index 835ecbf184..1430131a97 100755 --- a/test/functional/p2p_unrequested_blocks.py +++ b/test/functional/p2p_unrequested_blocks.py @@ -119,7 +119,7 @@ class AcceptBlockTest(BitcoinTestFramework): assert_equal(x['status'], "headers-only") tip_entry_found = True assert tip_entry_found - assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, block_h1f.hash) + assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, block_h1f.hash) # 4. Send another two block that build on the fork. block_h2f = create_block(block_h1f.sha256, create_coinbase(2), block_time) @@ -191,7 +191,7 @@ class AcceptBlockTest(BitcoinTestFramework): # Blocks 1-287 should be accepted, block 288 should be ignored because it's too far ahead for x in all_blocks[:-1]: self.nodes[0].getblock(x.hash) - assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, all_blocks[-1].hash) + assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, all_blocks[-1].hash) # 5. Test handling of unrequested block on the node that didn't process # Should still not be processed (even though it has a child that has more @@ -230,7 +230,7 @@ class AcceptBlockTest(BitcoinTestFramework): assert_equal(self.nodes[0].getblockcount(), 290) self.nodes[0].getblock(all_blocks[286].hash) assert_equal(self.nodes[0].getbestblockhash(), all_blocks[286].hash) - assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, all_blocks[287].hash) + assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, all_blocks[287].hash) self.log.info("Successfully reorged to longer chain") # 8. Create a chain which is invalid at a height longer than the @@ -260,7 +260,7 @@ class AcceptBlockTest(BitcoinTestFramework): assert_equal(x['status'], "headers-only") tip_entry_found = True assert tip_entry_found - assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, block_292.hash) + assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, block_292.hash) test_node.send_message(msg_block(block_289f)) test_node.send_and_ping(msg_block(block_290f)) diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 98147237b1..91e9975ad5 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -32,14 +32,16 @@ from test_framework.blocktools import ( TIME_GENESIS_BLOCK, create_block, create_coinbase, + create_tx_with_script, ) from test_framework.messages import ( CBlockHeader, + COIN, from_hex, msg_block, ) from test_framework.p2p import P2PInterface -from test_framework.script import hash256 +from test_framework.script import hash256, OP_TRUE from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -556,12 +558,12 @@ class BlockchainTest(BitcoinTestFramework): block = node.getblock(blockhash, verbosity) assert_equal(blockhash, hash256(bytes.fromhex(block[:160]))[::-1].hex()) - def assert_fee_not_in_block(verbosity): - block = node.getblock(blockhash, verbosity) + def assert_fee_not_in_block(hash, verbosity): + block = node.getblock(hash, verbosity) assert 'fee' not in block['tx'][1] - def assert_fee_in_block(verbosity): - block = node.getblock(blockhash, verbosity) + def assert_fee_in_block(hash, verbosity): + block = node.getblock(hash, verbosity) tx = block['tx'][1] assert 'fee' in tx assert_equal(tx['fee'], tx['vsize'] * fee_per_byte) @@ -580,8 +582,8 @@ class BlockchainTest(BitcoinTestFramework): total_vout += vout["value"] assert_equal(total_vin, total_vout + tx["fee"]) - def assert_vin_does_not_contain_prevout(verbosity): - block = node.getblock(blockhash, verbosity) + def assert_vin_does_not_contain_prevout(hash, verbosity): + block = node.getblock(hash, verbosity) tx = block["tx"][1] if isinstance(tx, str): # In verbosity level 1, only the transaction hashes are written @@ -595,16 +597,16 @@ class BlockchainTest(BitcoinTestFramework): assert_hexblock_hashes(False) self.log.info("Test that getblock with verbosity 1 doesn't include fee") - assert_fee_not_in_block(1) - assert_fee_not_in_block(True) + assert_fee_not_in_block(blockhash, 1) + assert_fee_not_in_block(blockhash, True) self.log.info('Test that getblock with verbosity 2 and 3 includes expected fee') - assert_fee_in_block(2) - assert_fee_in_block(3) + assert_fee_in_block(blockhash, 2) + assert_fee_in_block(blockhash, 3) self.log.info("Test that getblock with verbosity 1 and 2 does not include prevout") - assert_vin_does_not_contain_prevout(1) - assert_vin_does_not_contain_prevout(2) + assert_vin_does_not_contain_prevout(blockhash, 1) + assert_vin_does_not_contain_prevout(blockhash, 2) self.log.info("Test that getblock with verbosity 3 includes prevout") assert_vin_contains_prevout(3) @@ -612,7 +614,7 @@ class BlockchainTest(BitcoinTestFramework): self.log.info("Test getblock with invalid verbosity type returns proper error message") assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", node.getblock, blockhash, "2") - self.log.info("Test that getblock with verbosity 2 and 3 still works with pruned Undo data") + self.log.info("Test that getblock doesn't work with deleted Undo data") def move_block_file(old, new): old_path = self.nodes[0].blocks_path / old @@ -622,10 +624,8 @@ class BlockchainTest(BitcoinTestFramework): # Move instead of deleting so we can restore chain state afterwards move_block_file('rev00000.dat', 'rev_wrong') - assert_fee_not_in_block(2) - assert_fee_not_in_block(3) - assert_vin_does_not_contain_prevout(2) - assert_vin_does_not_contain_prevout(3) + assert_raises_rpc_error(-32603, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.", lambda: node.getblock(blockhash, 2)) + assert_raises_rpc_error(-32603, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.", lambda: node.getblock(blockhash, 3)) # Restore chain state move_block_file('rev_wrong', 'rev00000.dat') @@ -633,6 +633,31 @@ class BlockchainTest(BitcoinTestFramework): assert 'previousblockhash' not in node.getblock(node.getblockhash(0)) assert 'nextblockhash' not in node.getblock(node.getbestblockhash()) + self.log.info("Test getblock when only header is known") + current_height = node.getblock(node.getbestblockhash())['height'] + block_time = node.getblock(node.getbestblockhash())['time'] + 1 + block = create_block(int(blockhash, 16), create_coinbase(current_height + 1, nValue=100), block_time) + block.solve() + node.submitheader(block.serialize().hex()) + assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", lambda: node.getblock(block.hash)) + + self.log.info("Test getblock when block data is available but undo data isn't") + # Submits a block building on the header-only block, so it can't be connected and has no undo data + tx = create_tx_with_script(block.vtx[0], 0, script_sig=bytes([OP_TRUE]), amount=50 * COIN) + block_noundo = create_block(block.sha256, create_coinbase(current_height + 2, nValue=100), block_time + 1, txlist=[tx]) + block_noundo.solve() + node.submitblock(block_noundo.serialize().hex()) + + assert_fee_not_in_block(block_noundo.hash, 2) + assert_fee_not_in_block(block_noundo.hash, 3) + assert_vin_does_not_contain_prevout(block_noundo.hash, 2) + assert_vin_does_not_contain_prevout(block_noundo.hash, 3) + + self.log.info("Test getblock when block is missing") + move_block_file('blk00000.dat', 'blk00000.dat.bak') + assert_raises_rpc_error(-1, "Block not found on disk", node.getblock, blockhash) + move_block_file('blk00000.dat.bak', 'blk00000.dat') + if __name__ == '__main__': BlockchainTest(__file__).main() diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index 9f4e17a328..d95820bbf8 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -47,7 +47,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): return node.get_wallet_rpc(wallet_name) def run_test(self): - node0, node1, node2 = self.nodes + node0, node1, _node2 = self.nodes self.wallet = MiniWallet(test_node=node0) if self.is_wallet_compiled(): @@ -122,7 +122,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): assert_raises_rpc_error(-4, "Unsupported multisig script size for legacy wallet. Upgrade to descriptors to overcome this limitation for p2sh-segwit or bech32 scripts", wallet_multi.addmultisigaddress, 16, pubkeys, '', 'bech32') def do_multisig(self, nkeys, nsigs, output_type, wallet_multi): - node0, node1, node2 = self.nodes + node0, _node1, node2 = self.nodes pub_keys = self.pub[0: nkeys] priv_keys = self.priv[0: nkeys] diff --git a/test/functional/rpc_deriveaddresses.py b/test/functional/rpc_deriveaddresses.py index c66d91713f..32fdfa350a 100755 --- a/test/functional/rpc_deriveaddresses.py +++ b/test/functional/rpc_deriveaddresses.py @@ -29,6 +29,9 @@ class DeriveaddressesTest(BitcoinTestFramework): assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, [1, 2]), ["bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"]) assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, 2), [address, "bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"]) + ranged_descriptor = descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/<0;1>/*)") + assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, [1, 2]), [["bcrt1q7c8mdmdktrzs8xgpjmqw90tjn65j5a3yj04m3n", "bcrt1qs6n37uzu0v0qfzf0r0csm0dwa7prc0v5uavgy0"], ["bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"]]) + assert_raises_rpc_error(-8, "Range should not be specified for an un-ranged descriptor", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"), [0, 2]) assert_raises_rpc_error(-8, "Range must be specified for a ranged descriptor", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)")) diff --git a/test/functional/rpc_dumptxoutset.py b/test/functional/rpc_dumptxoutset.py index aa12da6ceb..ad05060210 100755 --- a/test/functional/rpc_dumptxoutset.py +++ b/test/functional/rpc_dumptxoutset.py @@ -19,6 +19,17 @@ class DumptxoutsetTest(BitcoinTestFramework): self.setup_clean_chain = True self.num_nodes = 1 + def check_expected_network(self, node, active): + rev_file = node.blocks_path / "rev00000.dat" + bogus_file = node.blocks_path / "bogus.dat" + rev_file.rename(bogus_file) + assert_raises_rpc_error( + -1, 'Could not roll back to requested height.', node.dumptxoutset, 'utxos.dat', rollback=99) + assert_equal(node.getnetworkinfo()['networkactive'], active) + + # Cleanup + bogus_file.rename(rev_file) + def run_test(self): """Test a trivial usage of the dumptxoutset RPC command.""" node = self.nodes[0] @@ -27,8 +38,8 @@ class DumptxoutsetTest(BitcoinTestFramework): self.generate(node, COINBASE_MATURITY) FILENAME = 'txoutset.dat' - out = node.dumptxoutset(FILENAME) - expected_path = node.datadir_path / self.chain / FILENAME + out = node.dumptxoutset(FILENAME, "latest") + expected_path = node.chain_path / FILENAME assert expected_path.is_file() @@ -51,10 +62,22 @@ class DumptxoutsetTest(BitcoinTestFramework): # Specifying a path to an existing or invalid file will fail. assert_raises_rpc_error( - -8, '{} already exists'.format(FILENAME), node.dumptxoutset, FILENAME) + -8, '{} already exists'.format(FILENAME), node.dumptxoutset, FILENAME, "latest") invalid_path = node.datadir_path / "invalid" / "path" assert_raises_rpc_error( - -8, "Couldn't open file {}.incomplete for writing".format(invalid_path), node.dumptxoutset, invalid_path) + -8, "Couldn't open file {}.incomplete for writing".format(invalid_path), node.dumptxoutset, invalid_path, "latest") + + self.log.info(f"Test that dumptxoutset with unknown dump type fails") + assert_raises_rpc_error( + -8, 'Invalid snapshot type "bogus" specified. Please specify "rollback" or "latest"', node.dumptxoutset, 'utxos.dat', "bogus") + + self.log.info(f"Test that dumptxoutset failure does not leave the network activity suspended when it was on previously") + self.check_expected_network(node, True) + + self.log.info(f"Test that dumptxoutset failure leaves the network activity suspended when it was off") + node.setnetworkactive(False) + self.check_expected_network(node, False) + node.setnetworkactive(True) if __name__ == '__main__': diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py index e309018516..62b3d664e0 100755 --- a/test/functional/rpc_getblockfrompeer.py +++ b/test/functional/rpc_getblockfrompeer.py @@ -58,7 +58,7 @@ class GetBlockFromPeerTest(BitcoinTestFramework): self.log.info("Node 0 should only have the header for node 1's block 3") x = next(filter(lambda x: x['hash'] == short_tip, self.nodes[0].getchaintips())) assert_equal(x['status'], "headers-only") - assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, short_tip) + assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].getblock, short_tip) self.log.info("Fetch block from node 1") peers = self.nodes[0].getpeerinfo() diff --git a/test/functional/rpc_getblockstats.py b/test/functional/rpc_getblockstats.py index d1e4895eb6..002763201a 100755 --- a/test/functional/rpc_getblockstats.py +++ b/test/functional/rpc_getblockstats.py @@ -114,7 +114,7 @@ class GetblockstatsTest(BitcoinTestFramework): assert_equal(stats[self.max_stat_pos]['height'], self.start_height + self.max_stat_pos) for i in range(self.max_stat_pos+1): - self.log.info('Checking block %d\n' % (i)) + self.log.info('Checking block %d' % (i)) assert_equal(stats[i], self.expected_stats[i]) # Check selecting block by hash too @@ -182,5 +182,16 @@ class GetblockstatsTest(BitcoinTestFramework): assert_equal(tip_stats["utxo_increase_actual"], 4) assert_equal(tip_stats["utxo_size_inc_actual"], 300) + self.log.info("Test when only header is known") + block = self.generateblock(self.nodes[0], output="raw(55)", transactions=[], submit=False) + self.nodes[0].submitheader(block["hex"]) + assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", lambda: self.nodes[0].getblockstats(block['hash'])) + + self.log.info('Test when block is missing') + (self.nodes[0].blocks_path / 'blk00000.dat').rename(self.nodes[0].blocks_path / 'blk00000.dat.backup') + assert_raises_rpc_error(-1, 'Block not found on disk', self.nodes[0].getblockstats, hash_or_height=1) + (self.nodes[0].blocks_path / 'blk00000.dat.backup').rename(self.nodes[0].blocks_path / 'blk00000.dat') + + if __name__ == '__main__': GetblockstatsTest(__file__).main() diff --git a/test/functional/rpc_getdescriptorinfo.py b/test/functional/rpc_getdescriptorinfo.py index c84928462d..e4e6d6f0f3 100755 --- a/test/functional/rpc_getdescriptorinfo.py +++ b/test/functional/rpc_getdescriptorinfo.py @@ -19,10 +19,15 @@ class DescriptorTest(BitcoinTestFramework): self.extra_args = [["-disablewallet"]] self.wallet_names = [] - def test_desc(self, desc, isrange, issolvable, hasprivatekeys): + def test_desc(self, desc, isrange, issolvable, hasprivatekeys, expanded_descs=None): info = self.nodes[0].getdescriptorinfo(desc) assert_equal(info, self.nodes[0].getdescriptorinfo(descsum_create(desc))) - assert_equal(info['descriptor'], descsum_create(desc)) + if expanded_descs is not None: + assert_equal(info["descriptor"], descsum_create(expanded_descs[0])) + assert_equal(info["multipath_expansion"], [descsum_create(x) for x in expanded_descs]) + else: + assert_equal(info['descriptor'], descsum_create(desc)) + assert "multipath_expansion" not in info assert_equal(info['isrange'], isrange) assert_equal(info['issolvable'], issolvable) assert_equal(info['hasprivatekeys'], hasprivatekeys) @@ -60,6 +65,11 @@ class DescriptorTest(BitcoinTestFramework): self.test_desc("pkh([d34db33f/44h/0h/0h]tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/*)", isrange=True, issolvable=True, hasprivatekeys=False) # A set of *1-of-2* P2WSH multisig outputs where the first multisig key is the *1/0/`i`* child of the first specified xpub and the second multisig key is the *0/0/`i`* child of the second specified xpub, and `i` is any number in a configurable range (`0-1000` by default). self.test_desc("wsh(multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/0/*,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/0/0/*))", isrange=True, issolvable=True, hasprivatekeys=False) + # A multipath descriptor + self.test_desc("wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/<0;1>/*)", isrange=True, issolvable=True, hasprivatekeys=False, + expanded_descs=["wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/0/*)", "wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/*)"]) + self.test_desc("wsh(multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/<1;2>/0/*,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/<2;3>/0/*))", isrange=True, issolvable=True, hasprivatekeys=False, + expanded_descs=["wsh(multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/0/*,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/2/0/*))", "wsh(multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/2/0/*,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/3/0/*))"]) if __name__ == '__main__': diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py index e53e2fa910..a7b560ef57 100755 --- a/test/functional/rpc_scantxoutset.py +++ b/test/functional/rpc_scantxoutset.py @@ -110,6 +110,7 @@ class ScantxoutsetTest(BitcoinTestFramework): assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288")) assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672")) assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": [1500, 1500]}])['total_amount'], Decimal("16.384")) + assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "pkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/<0;1>)"}])["total_amount"], Decimal("12.288")) # Test the reported descriptors for a few matches assert_equal(descriptors(self.nodes[0].scantxoutset("start", [{"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0h/*)", "range": 1499}])), ["pkh([0c5f9a1e/0h/0h/0]026dbd8b2315f296d36e6b6920b1579ca75569464875c7ebe869b536a7d9503c8c)#rthll0rg", "pkh([0c5f9a1e/0h/0h/1]033e6f25d76c00bedb3a8993c7d5739ee806397f0529b1b31dda31ef890f19a60c)#mcjajulr"]) diff --git a/test/functional/rpc_signrawtransactionwithkey.py b/test/functional/rpc_signrawtransactionwithkey.py index be96742aa8..b359a08f39 100755 --- a/test/functional/rpc_signrawtransactionwithkey.py +++ b/test/functional/rpc_signrawtransactionwithkey.py @@ -4,8 +4,8 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test transaction signing using the signrawtransactionwithkey RPC.""" -from test_framework.blocktools import ( - COINBASE_MATURITY, +from test_framework.messages import ( + COIN, ) from test_framework.address import ( address_to_scriptpubkey, @@ -16,7 +16,6 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, - find_vout_for_address, ) from test_framework.script_util import ( key_to_p2pk_script, @@ -26,6 +25,7 @@ from test_framework.script_util import ( ) from test_framework.wallet import ( getnewdestination, + MiniWallet, ) from test_framework.wallet_util import ( generate_keypair, @@ -46,16 +46,12 @@ OUTPUTS = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1} class SignRawTransactionWithKeyTest(BitcoinTestFramework): def set_test_params(self): - self.setup_clean_chain = True - self.num_nodes = 2 + self.num_nodes = 1 def send_to_address(self, addr, amount): - input = {"txid": self.nodes[0].getblock(self.block_hash[self.blk_idx])["tx"][0], "vout": 0} - output = {addr: amount} - self.blk_idx += 1 - rawtx = self.nodes[0].createrawtransaction([input], output) - txid = self.nodes[0].sendrawtransaction(self.nodes[0].signrawtransactionwithkey(rawtx, [self.nodes[0].get_deterministic_priv_key().key])["hex"], 0) - return txid + script_pub_key = address_to_scriptpubkey(addr) + tx = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=script_pub_key, amount=int(amount * COIN)) + return tx["txid"], tx["sent_vout"] def assert_signing_completed_successfully(self, signed_tx): assert 'errors' not in signed_tx @@ -80,14 +76,12 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework): self.log.info("Test signing transaction to P2SH-P2WSH addresses without wallet") # Create a new P2SH-P2WSH 1-of-1 multisig address: embedded_privkey, embedded_pubkey = generate_keypair(wif=True) - p2sh_p2wsh_address = self.nodes[1].createmultisig(1, [embedded_pubkey.hex()], "p2sh-segwit") + p2sh_p2wsh_address = self.nodes[0].createmultisig(1, [embedded_pubkey.hex()], "p2sh-segwit") # send transaction to P2SH-P2WSH 1-of-1 multisig address - self.block_hash = self.generate(self.nodes[0], COINBASE_MATURITY + 1) - self.blk_idx = 0 self.send_to_address(p2sh_p2wsh_address["address"], 49.999) self.generate(self.nodes[0], 1) # Get the UTXO info from scantxoutset - unspent_output = self.nodes[1].scantxoutset('start', [p2sh_p2wsh_address['descriptor']])['unspents'][0] + unspent_output = self.nodes[0].scantxoutset('start', [p2sh_p2wsh_address['descriptor']])['unspents'][0] spk = script_to_p2sh_p2wsh_script(p2sh_p2wsh_address['redeemScript']).hex() unspent_output['witnessScript'] = p2sh_p2wsh_address['redeemScript'] unspent_output['redeemScript'] = script_to_p2wsh_script(unspent_output['witnessScript']).hex() @@ -103,9 +97,9 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework): def keyless_signing_test(self): self.log.info("Test that keyless 'signing' of pay-to-anchor input succeeds") - funding_txid = self.send_to_address(p2a(), 49.999) + [txid, vout] = self.send_to_address(p2a(), 49.999) spending_tx = self.nodes[0].createrawtransaction( - [{"txid": funding_txid, "vout": 0}], + [{"txid": txid, "vout": vout}], [{getnewdestination()[2]: Decimal("49.998")}]) spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [], []) self.assert_signing_completed_successfully(spending_tx_signed) @@ -124,9 +118,7 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework): addr = script_to_p2sh(redeem_script) script_pub_key = address_to_scriptpubkey(addr).hex() # Fund that address - txid = self.send_to_address(addr, 10) - vout = find_vout_for_address(self.nodes[0], txid, addr) - self.generate(self.nodes[0], 1) + [txid, vout] = self.send_to_address(addr, 10) # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys spending_tx = self.nodes[0].createrawtransaction([{'txid': txid, 'vout': vout}], {getnewdestination()[2]: Decimal("9.999")}) spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [{'txid': txid, 'vout': vout, 'scriptPubKey': script_pub_key, 'redeemScript': redeem_script, 'witnessScript': witness_script, 'amount': 10}]) @@ -149,6 +141,7 @@ class SignRawTransactionWithKeyTest(BitcoinTestFramework): assert_raises_rpc_error(-22, "TX decode failed. Make sure the tx has at least one input.", self.nodes[0].signrawtransactionwithkey, tx + "00", privkeys) def run_test(self): + self.wallet = MiniWallet(self.nodes[0]) self.successful_signing_test() self.witness_script_test() self.keyless_signing_test() diff --git a/test/functional/rpc_txoutproof.py b/test/functional/rpc_txoutproof.py index 387132b680..90572245d6 100755 --- a/test/functional/rpc_txoutproof.py +++ b/test/functional/rpc_txoutproof.py @@ -67,6 +67,10 @@ class MerkleBlockTest(BitcoinTestFramework): assert_equal(self.nodes[0].verifytxoutproof(self.nodes[0].gettxoutproof([txid_spent], blockhash)), [txid_spent]) # We can't get the proof if we specify a non-existent block assert_raises_rpc_error(-5, "Block not found", self.nodes[0].gettxoutproof, [txid_spent], "0000000000000000000000000000000000000000000000000000000000000000") + # We can't get the proof if we only have the header of the specified block + block = self.generateblock(self.nodes[0], output="raw(55)", transactions=[], submit=False) + self.nodes[0].submitheader(block["hex"]) + assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", self.nodes[0].gettxoutproof, [txid_spent], block['hash']) # We can get the proof if the transaction is unspent assert_equal(self.nodes[0].verifytxoutproof(self.nodes[0].gettxoutproof([txid_unspent])), [txid_unspent]) # We can get the proof if we provide a list of transactions and one of them is unspent. The ordering of the list should not matter. diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py index 44187ce790..49eb64abad 100755 --- a/test/functional/rpc_users.py +++ b/test/functional/rpc_users.py @@ -139,15 +139,32 @@ class HTTPBasicsTest(BitcoinTestFramework): init_error = 'Error: Unable to start HTTP server. See debug log for details.' self.log.info('Check -rpcauth are validated') - # Empty -rpcauth= are ignored - self.restart_node(0, extra_args=['-rpcauth=']) + self.log.info('Empty -rpcauth are treated as error') self.stop_node(0) + self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth']) + self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=']) + self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=""']) + self.log.info('Check malformed -rpcauth') self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo']) self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo:bar']) self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo:bar:baz']) self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo$bar:baz']) self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo$bar$baz']) + self.log.info('Check interactions between blank and non-blank rpcauth') + # pw = bitcoin + rpcauth_user1 = '-rpcauth=user1:6dd184e5e69271fdd69103464630014f$eb3d7ce67c4d1ff3564270519b03b636c0291012692a5fa3dd1d2075daedd07b' + rpcauth_user2 = '-rpcauth=user2:57b2f77c919eece63cfa46c2f06e46ae$266b63902f99f97eeaab882d4a87f8667ab84435c3799f2ce042ef5a994d620b' + self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=[rpcauth_user1, rpcauth_user2, '-rpcauth=']) + self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=[rpcauth_user1, '-rpcauth=', rpcauth_user2]) + self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=', rpcauth_user1, rpcauth_user2]) + + self.log.info('Check -norpcauth disables previous -rpcauth params') + self.restart_node(0, extra_args=[rpcauth_user1, rpcauth_user2, '-norpcauth']) + assert_equal(401, call_with_auth(self.nodes[0], 'user1', 'bitcoin').status) + assert_equal(401, call_with_auth(self.nodes[0], 'rt', self.rtpassword).status) + self.stop_node(0) + self.log.info('Check that failure to write cookie file will abort the node gracefully') (self.nodes[0].chain_path / ".cookie.tmp").mkdir() self.nodes[0].assert_start_raises_init_error(expected_msg=init_error) diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 5c2fa28a31..705b8e8fe5 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2015-2022 The Bitcoin Core developers +# Copyright (c) 2015-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. """Utilities for manipulating blocks and transactions.""" @@ -74,7 +74,7 @@ def create_block(hashprev=None, coinbase=None, ntime=None, *, version=None, tmpl block.nVersion = version or tmpl.get('version') or VERSIONBITS_LAST_OLD_BLOCK_VERSION block.nTime = ntime or tmpl.get('curtime') or int(time.time() + 600) block.hashPrevBlock = hashprev or int(tmpl['previousblockhash'], 0x10) - if tmpl and not tmpl.get('bits') is None: + if tmpl and tmpl.get('bits') is not None: block.nBits = struct.unpack('>I', bytes.fromhex(tmpl['bits']))[0] else: block.nBits = 0x207fffff # difficulty retargeting is disabled in REGTEST chainparams diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index b73566b0e9..60ca9269a5 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -43,6 +43,11 @@ from .util import ( ) BITCOIND_PROC_WAIT_TIMEOUT = 60 +# The size of the blocks xor key +# from InitBlocksdirXorKey::xor_key.size() +NUM_XOR_BYTES = 8 +# The null blocks key (all 0s) +NULL_BLK_XOR_KEY = bytes([0] * NUM_XOR_BYTES) class FailedToStartError(Exception): @@ -466,6 +471,14 @@ class TestNode(): return self.chain_path / "blocks" @property + def blocks_key_path(self) -> Path: + return self.blocks_path / "xor.dat" + + def read_xor_key(self) -> bytes: + with open(self.blocks_key_path, "rb") as xor_f: + return xor_f.read(NUM_XOR_BYTES) + + @property def wallets_path(self) -> Path: return self.chain_path / "wallets" diff --git a/test/functional/test_framework/test_shell.py b/test/functional/test_framework/test_shell.py index 09ccec28a1..e232430507 100644 --- a/test/functional/test_framework/test_shell.py +++ b/test/functional/test_framework/test_shell.py @@ -2,9 +2,11 @@ # Copyright (c) 2019-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. +import pathlib from test_framework.test_framework import BitcoinTestFramework + class TestShell: """Wrapper Class for BitcoinTestFramework. @@ -67,7 +69,13 @@ class TestShell: # This implementation enforces singleton pattern, and will return the # previously initialized instance if available if not TestShell.instance: - TestShell.instance = TestShell.__TestShell() + # BitcoinTestFramework instances are supposed to be constructed with the path + # of the calling test in order to find shared data like configuration and the + # cache. Since TestShell is meant for interactive use, there is no concrete + # test; passing a dummy name is fine though, as only the containing directory + # is relevant for successful initialization. + tests_directory = pathlib.Path(__file__).resolve().parent.parent + TestShell.instance = TestShell.__TestShell(tests_directory / "testshell_dummy.py") TestShell.instance.running = False return TestShell.instance diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index c3bc861cf6..ce68de7eaa 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -5,7 +5,7 @@ """Helpful routines for regression testing.""" from base64 import b64encode -from decimal import Decimal, ROUND_DOWN +from decimal import Decimal from subprocess import CalledProcessError import hashlib import inspect @@ -21,7 +21,9 @@ import time from . import coverage from .authproxy import AuthServiceProxy, JSONRPCException from collections.abc import Callable -from typing import Optional +from typing import Optional, Union + +SATOSHI_PRECISION = Decimal('0.00000001') logger = logging.getLogger("TestFramework.utils") @@ -261,8 +263,9 @@ def get_fee(tx_size, feerate_btc_kvb): return target_fee_sat / Decimal(1e8) # Return result in BTC -def satoshi_round(amount): - return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN) +def satoshi_round(amount: Union[int, float, str], *, rounding: str) -> Decimal: + """Rounds a Decimal amount to the nearest satoshi using the specified rounding mode.""" + return Decimal(amount).quantize(SATOSHI_PRECISION, rounding=rounding) def wait_until_helper_internal(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, timeout_factor=1.0): @@ -515,12 +518,6 @@ def check_node_connections(*, node, num_in, num_out): assert_equal(info["connections_out"], num_out) -def read_xor_key(*, node): - with open(node.blocks_path / "xor.dat", "rb") as xor_f: - NUM_XOR_BYTES = 8 # From InitBlocksdirXorKey::xor_key.size() - return xor_f.read(NUM_XOR_BYTES) - - # Transaction/Block functions ############################# diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index b85bf1c668..3297a10699 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -406,6 +406,7 @@ BASE_SCRIPTS = [ 'feature_shutdown.py', 'wallet_migration.py', 'p2p_ibd_txrelay.py', + 'p2p_seednode.py', # Don't append tests at the end to avoid merge conflicts # Put them in a random line within the section that fits their approximate run-time ] @@ -521,15 +522,24 @@ def main(): test_list += BASE_SCRIPTS # Remove the test cases that the user has explicitly asked to exclude. + # The user can specify a test case with or without the .py extension. if args.exclude: - exclude_tests = [test.split('.py')[0] for test in args.exclude.split(',')] - for exclude_test in exclude_tests: - # Remove <test_name>.py and <test_name>.py --arg from the test list - exclude_list = [test for test in test_list if test.split('.py')[0] == exclude_test] + def print_warning_missing_test(test_name): + print("{}WARNING!{} Test '{}' not found in current test list.".format(BOLD[1], BOLD[0], test_name)) + def remove_tests(exclude_list): + if not exclude_list: + print_warning_missing_test(exclude_test) for exclude_item in exclude_list: test_list.remove(exclude_item) - if not exclude_list: - print("{}WARNING!{} Test '{}' not found in current test list.".format(BOLD[1], BOLD[0], exclude_test)) + + exclude_tests = [test.strip() for test in args.exclude.split(",")] + for exclude_test in exclude_tests: + # A space in the name indicates it has arguments such as "wallet_basic.py --descriptors" + if ' ' in exclude_test: + remove_tests([test for test in test_list if test.replace('.py', '') == exclude_test.replace('.py', '')]) + else: + # Exclude all variants of a test + remove_tests([test for test in test_list if test.split('.py')[0] == exclude_test.split('.py')[0]]) if args.filter: test_list = list(filter(re.compile(args.filter).search, test_list)) @@ -560,7 +570,6 @@ def main(): run_tests( test_list=test_list, - src_dir=config["environment"]["SRCDIR"], build_dir=config["environment"]["BUILDDIR"], tmpdir=tmpdir, jobs=args.jobs, @@ -572,7 +581,7 @@ def main(): results_filepath=results_filepath, ) -def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control, results_filepath=None): +def run_tests(*, test_list, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control, results_filepath=None): args = args or [] # Warn if bitcoind is already running @@ -595,7 +604,7 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= print(f"{BOLD[1]}WARNING!{BOLD[0]} There may be insufficient free space in {tmpdir} to run the Bitcoin functional test suite. " f"Running the test suite with fewer than {min_space // (1024 * 1024)} MB of free space might cause tests to fail.") - tests_dir = src_dir + '/test/functional/' + tests_dir = f"{build_dir}/test/functional/" # This allows `test_runner.py` to work from an out-of-source build directory using a symlink, # a hard link or a copy on any platform. See https://github.com/bitcoin/bitcoin/pull/27561. sys.path.append(tests_dir) diff --git a/test/functional/tool_signet_miner.py b/test/functional/tool_signet_miner.py index bdefb92ae6..67fb5c9f94 100755 --- a/test/functional/tool_signet_miner.py +++ b/test/functional/tool_signet_miner.py @@ -57,6 +57,7 @@ class SignetMinerTest(BitcoinTestFramework): f'--grind-cmd={self.options.bitcoinutil} grind', '--nbits=1d00ffff', f'--set-block-time={int(time.time())}', + '--poolnum=99', ], check=True, stderr=subprocess.STDOUT) assert_equal(node.getblockcount(), 1) diff --git a/test/functional/wallet_assumeutxo.py b/test/functional/wallet_assumeutxo.py index 0bce2f137c..a5025a64e7 100755 --- a/test/functional/wallet_assumeutxo.py +++ b/test/functional/wallet_assumeutxo.py @@ -93,7 +93,7 @@ class AssumeutxoTest(BitcoinTestFramework): self.log.info( f"Creating a UTXO snapshot at height {SNAPSHOT_BASE_HEIGHT}") - dump_output = n0.dumptxoutset('utxos.dat') + dump_output = n0.dumptxoutset('utxos.dat', "latest") assert_equal( dump_output['txoutset_hash'], diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py index 6a69377c63..84c07b6a28 100755 --- a/test/functional/wallet_importdescriptors.py +++ b/test/functional/wallet_importdescriptors.py @@ -16,6 +16,7 @@ variants. and test the values returned.""" import concurrent.futures +import time from test_framework.authproxy import JSONRPCException from test_framework.blocktools import COINBASE_MATURITY @@ -708,5 +709,56 @@ class ImportDescriptorsTest(BitcoinTestFramework): assert_equal(temp_wallet.getbalance(), encrypted_wallet.getbalance()) + self.log.info("Multipath descriptors") + self.nodes[1].createwallet(wallet_name="multipath", descriptors=True, blank=True) + w_multipath = self.nodes[1].get_wallet_rpc("multipath") + self.nodes[1].createwallet(wallet_name="multipath_split", descriptors=True, blank=True) + w_multisplit = self.nodes[1].get_wallet_rpc("multipath_split") + timestamp = int(time.time()) + + self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"), + "active": True, + "range": 10, + "timestamp": "now", + "label": "some label"}, + success=False, + error_code=-8, + error_message="Multipath descriptors should not have a label", + wallet=w_multipath) + self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"), + "active": True, + "range": 10, + "timestamp": timestamp, + "internal": True}, + success=False, + error_code=-5, + error_message="Cannot have multipath descriptor while also specifying \'internal\'", + wallet=w_multipath) + + self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"), + "active": True, + "range": 10, + "timestamp": timestamp}, + success=True, + wallet=w_multipath) + + self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/10/0/*)"), + "active": True, + "range": 10, + "timestamp": timestamp}, + success=True, + wallet=w_multisplit) + self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/20/0/*)"), + "active": True, + "range": 10, + "internal": True, + "timestamp": timestamp}, + success=True, + wallet=w_multisplit) + for _ in range(0, 10): + assert_equal(w_multipath.getnewaddress(address_type="bech32"), w_multisplit.getnewaddress(address_type="bech32")) + assert_equal(w_multipath.getrawchangeaddress(address_type="bech32"), w_multisplit.getrawchangeaddress(address_type="bech32")) + assert_equal(sorted(w_multipath.listdescriptors()["descriptors"], key=lambda x: x["desc"]), sorted(w_multisplit.listdescriptors()["descriptors"], key=lambda x: x["desc"])) + if __name__ == '__main__': ImportDescriptorsTest(__file__).main() diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py index 42b470bb97..3ce794dc2f 100755 --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -896,6 +896,43 @@ class ImportMultiTest(BitcoinTestFramework): ) assert result[0]['success'] + self.log.info("Multipath descriptors") + self.nodes[1].createwallet(wallet_name="multipath", blank=True, disable_private_keys=True) + w_multipath = self.nodes[1].get_wallet_rpc("multipath") + self.nodes[1].createwallet(wallet_name="multipath_split", blank=True, disable_private_keys=True) + w_multisplit = self.nodes[1].get_wallet_rpc("multipath_split") + + res = w_multipath.importmulti([{"desc": descsum_create(f"wpkh({xpub}/<10;20>/0/*)"), + "keypool": True, + "range": 10, + "timestamp": "now", + "internal": True}]) + assert_equal(res[0]["success"], False) + assert_equal(res[0]["error"]["code"], -5) + assert_equal(res[0]["error"]["message"], "Cannot have multipath descriptor while also specifying 'internal'") + + res = w_multipath.importmulti([{"desc": descsum_create(f"wpkh({xpub}/<10;20>/0/*)"), + "keypool": True, + "range": 10, + "timestamp": "now"}]) + assert_equal(res[0]["success"], True) + + res = w_multisplit.importmulti([{"desc": descsum_create(f"wpkh({xpub}/10/0/*)"), + "keypool": True, + "range": 10, + "timestamp": "now"}]) + assert_equal(res[0]["success"], True) + res = w_multisplit.importmulti([{"desc": descsum_create(f"wpkh({xpub}/20/0/*)"), + "keypool": True, + "range": 10, + "internal": True, + "timestamp": timestamp}]) + assert_equal(res[0]["success"], True) + + for _ in range(0, 9): + assert_equal(w_multipath.getnewaddress(address_type="bech32"), w_multisplit.getnewaddress(address_type="bech32")) + assert_equal(w_multipath.getrawchangeaddress(address_type="bech32"), w_multisplit.getrawchangeaddress(address_type="bech32")) + if __name__ == '__main__': ImportMultiTest(__file__).main() diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py index 7d1d244dff..ef3f925ee8 100755 --- a/test/functional/wallet_upgradewallet.py +++ b/test/functional/wallet_upgradewallet.py @@ -231,7 +231,7 @@ class UpgradeWalletTest(BitcoinTestFramework): assert b'\x07hdchain' in new_kvs hd_chain = new_kvs[b'\x07hdchain'] assert_equal(28, len(hd_chain)) - hd_chain_version, external_counter, seed_id = struct.unpack('<iI20s', hd_chain) + hd_chain_version, _external_counter, seed_id = struct.unpack('<iI20s', hd_chain) assert_equal(1, hd_chain_version) seed_id = bytearray(seed_id) seed_id.reverse() @@ -258,7 +258,7 @@ class UpgradeWalletTest(BitcoinTestFramework): new_kvs = dump_bdb_kv(node_master_wallet) hd_chain = new_kvs[b'\x07hdchain'] assert_equal(32, len(hd_chain)) - hd_chain_version, external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain) + hd_chain_version, _external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain) assert_equal(2, hd_chain_version) assert_equal(0, internal_counter) seed_id = bytearray(seed_id) @@ -284,7 +284,7 @@ class UpgradeWalletTest(BitcoinTestFramework): new_kvs = dump_bdb_kv(node_master_wallet) hd_chain = new_kvs[b'\x07hdchain'] assert_equal(32, len(hd_chain)) - hd_chain_version, external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain) + hd_chain_version, _external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain) assert_equal(2, hd_chain_version) assert_equal(2, internal_counter) # The next addresses are HD and should be on different HD chains (the one remaining key in each pool should have been flushed) @@ -301,8 +301,8 @@ class UpgradeWalletTest(BitcoinTestFramework): new_kvs = dump_bdb_kv(node_master_wallet) for k, old_v in old_kvs.items(): if k.startswith(b'\x07keymeta'): - new_ver, new_create_time, new_kp_str, new_seed_id, new_fpr, new_path_len, new_path, new_has_key_orig = deser_keymeta(BytesIO(new_kvs[k])) - old_ver, old_create_time, old_kp_str, old_seed_id, old_fpr, old_path_len, old_path, old_has_key_orig = deser_keymeta(BytesIO(old_v)) + new_ver, new_create_time, new_kp_str, new_seed_id, _new_fpr, new_path_len, new_path, new_has_key_orig = deser_keymeta(BytesIO(new_kvs[k])) + old_ver, old_create_time, old_kp_str, old_seed_id, _old_fpr, old_path_len, old_path, old_has_key_orig = deser_keymeta(BytesIO(old_v)) assert_equal(10, old_ver) if old_kp_str == b"": # imported things that don't have keymeta (i.e. imported coinbase privkeys) won't be upgraded assert_equal(new_kvs[k], old_v) |