diff options
Diffstat (limited to 'test')
52 files changed, 1153 insertions, 245 deletions
diff --git a/test/README.md b/test/README.md index e1dab92a06..b036a66f67 100644 --- a/test/README.md +++ b/test/README.md @@ -225,6 +225,10 @@ gdb /home/example/bitcoind <pid> Note: gdb attach step may require ptrace_scope to be modified, or `sudo` preceding the `gdb`. See this link for considerations: https://www.kernel.org/doc/Documentation/security/Yama.txt +Often while debugging rpc calls from functional tests, the test might reach timeout before +process can return a response. Use `--timeout-factor 0` to disable all rpc timeouts for that partcular +functional test. Ex: `test/functional/wallet_hd.py --timeout-factor 0`. + ##### Profiling An easy way to profile node performance during functional tests is provided @@ -257,6 +261,7 @@ Use the `-v` option for verbose output. | Lint test | Dependency | Version [used by CI](../ci/lint/04_install.sh) | Installation |-----------|:----------:|:-------------------------------------------:|-------------- | [`lint-python.sh`](lint/lint-python.sh) | [flake8](https://gitlab.com/pycqa/flake8) | [3.7.8](https://github.com/bitcoin/bitcoin/pull/15257) | `pip3 install flake8==3.7.8` +| [`lint-python.sh`](lint/lint-python.sh) | [mypy](https://github.com/python/mypy) | [0.700](https://github.com/bitcoin/bitcoin/pull/18210) | `pip3 install mypy==0.700` | [`lint-shell.sh`](lint/lint-shell.sh) | [ShellCheck](https://github.com/koalaman/shellcheck) | [0.6.0](https://github.com/bitcoin/bitcoin/pull/15166) | [details...](https://github.com/koalaman/shellcheck#installing) | [`lint-shell.sh`](lint/lint-shell.sh) | [yq](https://github.com/kislyuk/yq) | default | `pip3 install yq` | [`lint-spelling.sh`](lint/lint-spelling.sh) | [codespell](https://github.com/codespell-project/codespell) | [1.15.0](https://github.com/bitcoin/bitcoin/pull/16186) | `pip3 install codespell==1.15.0` diff --git a/test/config.ini.in b/test/config.ini.in index 9687206ee1..be1bfe8752 100644 --- a/test/config.ini.in +++ b/test/config.ini.in @@ -7,6 +7,7 @@ [environment] PACKAGE_NAME=@PACKAGE_NAME@ +PACKAGE_BUGREPORT=@PACKAGE_BUGREPORT@ SRCDIR=@abs_top_srcdir@ BUILDDIR=@abs_top_builddir@ EXEEXT=@EXEEXT@ diff --git a/test/functional/README.md b/test/functional/README.md index 004e0afb1d..aff5f714f2 100644 --- a/test/functional/README.md +++ b/test/functional/README.md @@ -26,10 +26,12 @@ don't have test cases for. The Travis linter also checks this, but [possibly not in all cases](https://github.com/bitcoin/bitcoin/pull/14884#discussion_r239585126). - See [the python lint script](/test/lint/lint-python.sh) that checks for violations that could lead to bugs and issues in the test code. +- Use [type hints](https://docs.python.org/3/library/typing.html) in your code to improve code readability + and to detect possible bugs earlier. - Avoid wildcard imports - Use a module-level docstring to describe what the test is testing, and how it is testing it. -- When subclassing the BitcoinTestFramwork, place overrides for the +- When subclassing the BitcoinTestFramework, place overrides for the `set_test_params()`, `add_options()` and `setup_xxxx()` methods at the top of the subclass, then locally-defined helper methods, then the `run_test()` method. - Use `'{}'.format(x)` for string formatting, not `'%s' % x`. @@ -45,7 +47,7 @@ don't have test cases for. - `rpc` for tests for individual RPC methods or features, eg `rpc_listtransactions.py` - `tool` for tests for tools, eg `tool_wallet.py` - `wallet` for tests for wallet features, eg `wallet_keypool.py` -- use an underscore to separate words +- Use an underscore to separate words - exception: for tests for specific RPCs or command line options which don't include underscores, name the test after the exact RPC or argument name, eg `rpc_decodescript.py`, not `rpc_decode_script.py` - Don't use the redundant word `test` in the name, eg `interface_zmq.py`, not `interface_zmq_test.py` diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py index ae5721bec2..6e72db1d96 100644 --- a/test/functional/data/invalid_txs.py +++ b/test/functional/data/invalid_txs.py @@ -21,6 +21,7 @@ Invalid tx cases not covered here can be found by running: """ import abc +from typing import Optional from test_framework.messages import ( COutPoint, CTransaction, @@ -56,7 +57,7 @@ class BadTxTemplate: __metaclass__ = abc.ABCMeta # The expected error code given by bitcoind upon submission of the tx. - reject_reason = "" + reject_reason = "" # type: Optional[str] # Only specified if it differs from mempool acceptance error. block_reject_reason = "" diff --git a/test/functional/example_test.py b/test/functional/example_test.py index 70dfe81d4e..5d782026dc 100755 --- a/test/functional/example_test.py +++ b/test/functional/example_test.py @@ -15,7 +15,7 @@ from collections import defaultdict # Avoid wildcard * imports from test_framework.blocktools import (create_block, create_coinbase) -from test_framework.messages import CInv +from test_framework.messages import CInv, MSG_BLOCK from test_framework.mininode import ( P2PInterface, mininode_lock, @@ -198,7 +198,7 @@ class ExampleTest(BitcoinTestFramework): getdata_request = msg_getdata() for block in blocks: - getdata_request.inv.append(CInv(2, block)) + getdata_request.inv.append(CInv(MSG_BLOCK, block)) self.nodes[2].p2p.send_message(getdata_request) # wait_until() will loop until a predicate condition is met. Use it to test properties of the diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py index 9cff79a42c..596ff206f2 100755 --- a/test/functional/feature_backwards_compatibility.py +++ b/test/functional/feature_backwards_compatibility.py @@ -6,7 +6,11 @@ Test various backwards compatibility scenarios. Download the previous node binaries: -contrib/devtools/previous_release.sh -b v0.19.0.1 v0.18.1 v0.17.1 +contrib/devtools/previous_release.sh -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2 + +v0.15.2 is not required by this test, but it is used in wallet_upgradewallet.py. +Due to a hardfork in regtest, it can't be used to sync nodes. + Due to RPC changes introduced in various versions the below tests won't work for older versions without some patches or workarounds. @@ -22,6 +26,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.descriptors import descsum_create from test_framework.util import ( + adjust_bitcoin_conf_for_pre_17, assert_equal, sync_blocks, sync_mempools, @@ -31,14 +36,15 @@ from test_framework.util import ( class BackwardsCompatibilityTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True - self.num_nodes = 5 + self.num_nodes = 6 # Add new version after each release: self.extra_args = [ ["-addresstype=bech32"], # Pre-release: use to mine blocks ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # Pre-release: use to receive coins, swap wallets, etc - ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.19.0.1 + ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.19.1 ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.18.1 - ["-nowallet", "-walletrbf=1", "-addresstype=bech32"] # v0.17.1 + ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.17.1 + ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.16.3 ] def skip_test_if_missing_module(self): @@ -49,10 +55,13 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): self.add_nodes(self.num_nodes, extra_args=self.extra_args, versions=[ None, None, - 190001, + 190100, 180100, 170100, + 160300, ]) + # adapt bitcoin.conf, because older bitcoind's don't recognize config sections + adjust_bitcoin_conf_for_pre_17(self.nodes[5].bitcoinconf) self.start_nodes() @@ -65,10 +74,11 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): res = self.nodes[self.num_nodes - 1].getblockchaininfo() assert_equal(res['blocks'], 101) - node_master = self.nodes[self.num_nodes - 4] - node_v19 = self.nodes[self.num_nodes - 3] - node_v18 = self.nodes[self.num_nodes - 2] - node_v17 = self.nodes[self.num_nodes - 1] + node_master = self.nodes[self.num_nodes - 5] + node_v19 = self.nodes[self.num_nodes - 4] + node_v18 = self.nodes[self.num_nodes - 3] + node_v17 = self.nodes[self.num_nodes - 2] + node_v16 = self.nodes[self.num_nodes - 1] self.log.info("Test wallet backwards compatibility...") # Create a number of wallets and open them in older versions: @@ -167,6 +177,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): node_v19_wallets_dir = os.path.join(node_v19.datadir, "regtest/wallets") node_v18_wallets_dir = os.path.join(node_v18.datadir, "regtest/wallets") node_v17_wallets_dir = os.path.join(node_v17.datadir, "regtest/wallets") + node_v16_wallets_dir = os.path.join(node_v16.datadir, "regtest") node_master.unloadwallet("w1") node_master.unloadwallet("w2") node_v19.unloadwallet("w1_v19") @@ -174,6 +185,13 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): node_v18.unloadwallet("w1_v18") node_v18.unloadwallet("w2_v18") + # Copy wallets to v0.16 + for wallet in os.listdir(node_master_wallets_dir): + shutil.copytree( + os.path.join(node_master_wallets_dir, wallet), + os.path.join(node_v16_wallets_dir, wallet) + ) + # Copy wallets to v0.17 for wallet in os.listdir(node_master_wallets_dir): shutil.copytree( @@ -292,10 +310,17 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): # assert_raises_rpc_error(-4, "Wallet loading failed.", node_v17.loadwallet, 'w3_v18') # Instead, we stop node and try to launch it with the wallet: - self.stop_node(self.num_nodes - 1) + self.stop_node(4) node_v17.assert_start_raises_init_error(["-wallet=w3_v18"], "Error: Error loading w3_v18: Wallet requires newer version of Bitcoin Core") node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: Error loading w3: Wallet requires newer version of Bitcoin Core") - self.start_node(self.num_nodes - 1) + self.start_node(4) + + # Open most recent wallet in v0.16 (no loadwallet RPC) + self.stop_node(5) + self.start_node(5, extra_args=["-wallet=w2"]) + wallet = node_v16.get_wallet_rpc("w2") + info = wallet.getwalletinfo() + assert info['keypoolsize'] == 1 self.log.info("Test wallet upgrade path...") # u1: regular wallet, created with v0.17 diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index 1a7c656274..a4dc455d57 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -71,7 +71,7 @@ class ConfArgsTest(BitcoinTestFramework): with open(inc_conf_file2_path, 'w', encoding='utf-8') as conf: conf.write('[testnet]\n') self.restart_node(0) - self.nodes[0].stop_node(expected_stderr='Warning: ' + inc_conf_file_path + ':1 Section [testnot] is not recognized.' + os.linesep + 'Warning: ' + inc_conf_file2_path + ':1 Section [testnet] is not recognized.') + self.nodes[0].stop_node(expected_stderr='Warning: ' + inc_conf_file_path + ':1 Section [testnot] is not recognized.' + os.linesep + inc_conf_file2_path + ':1 Section [testnet] is not recognized.') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('') # clear diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index 5bbdb8cda1..7b38e09bf9 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -256,7 +256,11 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): self.log.debug("Mining longer tip") block_hashes = [] while current_height + 1 > self.nodes[3].getblockcount(): - block_hashes.extend(self.nodes[3].generate(min(10, current_height + 1 - self.nodes[3].getblockcount()))) + block_hashes.extend(self.nodes[3].generatetoaddress( + nblocks=min(10, current_height + 1 - self.nodes[3].getblockcount()), + # new address to avoid mining a block that has just been invalidated + address=self.nodes[3].getnewaddress(), + )) self.log.debug("Syncing %d new blocks...", len(block_hashes)) self.sync_node3blocks(block_hashes) utxo_list = self.nodes[3].listunspent() @@ -281,5 +285,6 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): if self.restart_counts[i] == 0: self.log.warning("Node %d never crashed during utxo flush!", i) + if __name__ == "__main__": ChainstateWriteCrashTest().main() diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py index d4a8f8a715..7eabf86cad 100755 --- a/test/functional/feature_maxuploadtarget.py +++ b/test/functional/feature_maxuploadtarget.py @@ -13,7 +13,7 @@ if uploadtarget has been reached. from collections import defaultdict import time -from test_framework.messages import CInv, msg_getdata +from test_framework.messages import CInv, MSG_BLOCK, msg_getdata from test_framework.mininode import P2PInterface from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, mine_large_block @@ -84,7 +84,7 @@ class MaxUploadTest(BitcoinTestFramework): # the same big old block too many times (expect: disconnect) getdata_request = msg_getdata() - getdata_request.inv.append(CInv(2, big_old_block)) + getdata_request.inv.append(CInv(MSG_BLOCK, big_old_block)) max_bytes_per_day = 800*1024*1024 daily_buffer = 144 * 4000000 @@ -109,7 +109,7 @@ class MaxUploadTest(BitcoinTestFramework): # Requesting the current block on p2p_conns[1] should succeed indefinitely, # even when over the max upload target. # We'll try 800 times - getdata_request.inv = [CInv(2, big_new_block)] + getdata_request.inv = [CInv(MSG_BLOCK, big_new_block)] for i in range(800): p2p_conns[1].send_and_ping(getdata_request) assert_equal(p2p_conns[1].block_receive_map[big_new_block], i+1) @@ -117,7 +117,7 @@ class MaxUploadTest(BitcoinTestFramework): self.log.info("Peer 1 able to repeatedly download new block") # But if p2p_conns[1] tries for an old block, it gets disconnected too. - getdata_request.inv = [CInv(2, big_old_block)] + getdata_request.inv = [CInv(MSG_BLOCK, big_old_block)] p2p_conns[1].send_message(getdata_request) p2p_conns[1].wait_for_disconnect() assert_equal(len(self.nodes[0].getpeerinfo()), 1) @@ -138,23 +138,22 @@ class MaxUploadTest(BitcoinTestFramework): self.nodes[0].disconnect_p2ps() self.log.info("Restarting node 0 with noban permission and 1MB maxuploadtarget") - self.stop_node(0) - self.start_node(0, ["-whitelist=noban@127.0.0.1", "-maxuploadtarget=1"]) + self.restart_node(0, ["-whitelist=noban@127.0.0.1", "-maxuploadtarget=1"]) # Reconnect to self.nodes[0] self.nodes[0].add_p2p_connection(TestP2PConn()) #retrieve 20 blocks which should be enough to break the 1MB limit - getdata_request.inv = [CInv(2, big_new_block)] + getdata_request.inv = [CInv(MSG_BLOCK, big_new_block)] for i in range(20): self.nodes[0].p2p.send_and_ping(getdata_request) assert_equal(self.nodes[0].p2p.block_receive_map[big_new_block], i+1) - getdata_request.inv = [CInv(2, big_old_block)] + getdata_request.inv = [CInv(MSG_BLOCK, big_old_block)] self.nodes[0].p2p.send_and_ping(getdata_request) - assert_equal(len(self.nodes[0].getpeerinfo()), 1) #node is still connected because of the whitelist + assert_equal(len(self.nodes[0].getpeerinfo()), 1) #node is still connected because of the noban permission - self.log.info("Peer still connected after trying to download old block (whitelisted)") + self.log.info("Peer still connected after trying to download old block (noban permission)") if __name__ == '__main__': MaxUploadTest().main() diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py index b110a559c0..fb0c7ceed4 100755 --- a/test/functional/feature_notifications.py +++ b/test/functional/feature_notifications.py @@ -5,12 +5,14 @@ """Test the -alertnotify, -blocknotify and -walletnotify options.""" import os -from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE +from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE, keyhash_to_p2pkh from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, wait_until, connect_nodes, + disconnect_nodes, + hex_str_to_bytes, ) # Linux allow all characters other than \x00 @@ -81,8 +83,67 @@ class NotificationsTest(BitcoinTestFramework): # directory content should equal the generated transaction hashes txids_rpc = list(map(lambda t: notify_outputname(self.wallet, t['txid']), self.nodes[1].listtransactions("*", block_count))) assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir))) + for tx_file in os.listdir(self.walletnotify_dir): + os.remove(os.path.join(self.walletnotify_dir, tx_file)) + + # Conflicting transactions tests. Give node 0 same wallet seed as + # node 1, generate spends from node 0, and check notifications + # triggered by node 1 + self.log.info("test -walletnotify with conflicting transactions") + self.nodes[0].sethdseed(seed=self.nodes[1].dumpprivkey(keyhash_to_p2pkh(hex_str_to_bytes(self.nodes[1].getwalletinfo()['hdseedid'])[::-1]))) + self.nodes[0].rescanblockchain() + self.nodes[0].generatetoaddress(100, ADDRESS_BCRT1_UNSPENDABLE) + + # Generate transaction on node 0, sync mempools, and check for + # notification on node 1. + tx1 = self.nodes[0].sendtoaddress(address=ADDRESS_BCRT1_UNSPENDABLE, amount=1, replaceable=True) + assert_equal(tx1 in self.nodes[0].getrawmempool(), True) + self.sync_mempools() + self.expect_wallet_notify([tx1]) + + # Generate bump transaction, sync mempools, and check for bump1 + # notification. In the future, per + # https://github.com/bitcoin/bitcoin/pull/9371, it might be better + # to have notifications for both tx1 and bump1. + bump1 = self.nodes[0].bumpfee(tx1)["txid"] + assert_equal(bump1 in self.nodes[0].getrawmempool(), True) + self.sync_mempools() + self.expect_wallet_notify([bump1]) + + # Add bump1 transaction to new block, checking for a notification + # and the correct number of confirmations. + self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE) + self.sync_blocks() + self.expect_wallet_notify([bump1]) + assert_equal(self.nodes[1].gettransaction(bump1)["confirmations"], 1) + + # Generate a second transaction to be bumped. + tx2 = self.nodes[0].sendtoaddress(address=ADDRESS_BCRT1_UNSPENDABLE, amount=1, replaceable=True) + assert_equal(tx2 in self.nodes[0].getrawmempool(), True) + self.sync_mempools() + self.expect_wallet_notify([tx2]) + + # Bump tx2 as bump2 and generate a block on node 0 while + # disconnected, then reconnect and check for notifications on node 1 + # about newly confirmed bump2 and newly conflicted tx2. + disconnect_nodes(self.nodes[0], 1) + bump2 = self.nodes[0].bumpfee(tx2)["txid"] + self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE) + assert_equal(self.nodes[0].gettransaction(bump2)["confirmations"], 1) + assert_equal(tx2 in self.nodes[1].getrawmempool(), True) + connect_nodes(self.nodes[0], 1) + self.sync_blocks() + self.expect_wallet_notify([bump2, tx2]) + assert_equal(self.nodes[1].gettransaction(bump2)["confirmations"], 1) # TODO: add test for `-alertnotify` large fork notifications + def expect_wallet_notify(self, tx_ids): + wait_until(lambda: len(os.listdir(self.walletnotify_dir)) >= len(tx_ids), timeout=10) + assert_equal(sorted(notify_outputname(self.wallet, tx_id) for tx_id in tx_ids), sorted(os.listdir(self.walletnotify_dir))) + for tx_file in os.listdir(self.walletnotify_dir): + os.remove(os.path.join(self.walletnotify_dir, tx_file)) + + if __name__ == '__main__': NotificationsTest().main() diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index fdd86310c0..24c357091f 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -108,12 +108,7 @@ class SegWitTest(BitcoinTestFramework): assert tmpl['sigoplimit'] == 20000 assert tmpl['transactions'][0]['hash'] == txid assert tmpl['transactions'][0]['sigops'] == 2 - tmpl = self.nodes[0].getblocktemplate({'rules': ['segwit']}) - assert tmpl['sizelimit'] == 1000000 - assert 'weightlimit' not in tmpl - assert tmpl['sigoplimit'] == 20000 - assert tmpl['transactions'][0]['hash'] == txid - assert tmpl['transactions'][0]['sigops'] == 2 + assert '!segwit' not in tmpl['rules'] self.nodes[0].generate(1) # block 162 balance_presetup = self.nodes[0].getbalance() @@ -213,6 +208,7 @@ class SegWitTest(BitcoinTestFramework): assert tmpl['sigoplimit'] == 80000 assert tmpl['transactions'][0]['txid'] == txid assert tmpl['transactions'][0]['sigops'] == 8 + assert '!segwit' in tmpl['rules'] self.nodes[0].generate(1) # Mine a block to clear the gbt cache diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index 1c94305220..7530e7daf6 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -67,6 +67,7 @@ class TestBitcoinCli(BitcoinTestFramework): if self.is_wallet_compiled(): self.log.info("Test -getinfo and bitcoin-cli getwalletinfo return expected wallet info") assert_equal(cli_get_info['balance'], BALANCE) + assert 'balances' not in cli_get_info.keys() wallet_info = self.nodes[0].getwalletinfo() assert_equal(cli_get_info['keypoolsize'], wallet_info['keypoolsize']) assert_equal(cli_get_info['unlocked_until'], wallet_info['unlocked_until']) @@ -76,42 +77,60 @@ class TestBitcoinCli(BitcoinTestFramework): # Setup to test -getinfo and -rpcwallet= with multiple wallets. wallets = ['', 'Encrypted', 'secret'] - amounts = [Decimal('59.999928'), Decimal(9), Decimal(31)] + amounts = [BALANCE + Decimal('9.999928'), Decimal(9), Decimal(31)] self.nodes[0].createwallet(wallet_name=wallets[1]) self.nodes[0].createwallet(wallet_name=wallets[2]) w1 = self.nodes[0].get_wallet_rpc(wallets[0]) w2 = self.nodes[0].get_wallet_rpc(wallets[1]) w3 = self.nodes[0].get_wallet_rpc(wallets[2]) w1.walletpassphrase(password, self.rpc_timeout) + w2.encryptwallet(password) w1.sendtoaddress(w2.getnewaddress(), amounts[1]) w1.sendtoaddress(w3.getnewaddress(), amounts[2]) # Mine a block to confirm; adds a block reward (50 BTC) to the default wallet. self.nodes[0].generate(1) - self.log.info("Test -getinfo with multiple wallets loaded returns no balance") - assert_equal(set(self.nodes[0].listwallets()), set(wallets)) - assert 'balance' not in self.nodes[0].cli('-getinfo').send_cli().keys() - self.log.info("Test -getinfo with multiple wallets and -rpcwallet returns specified wallet balance") for i in range(len(wallets)): - cli_get_info = self.nodes[0].cli('-getinfo').send_cli('-rpcwallet={}'.format(wallets[i])) + cli_get_info = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[i])).send_cli() + assert 'balances' not in cli_get_info.keys() assert_equal(cli_get_info['balance'], amounts[i]) - self.log.info("Test -getinfo with multiple wallets and -rpcwallet=non-existing-wallet returns no balance") - assert 'balance' not in self.nodes[0].cli('-getinfo').send_cli('-rpcwallet=does-not-exist').keys() + self.log.info("Test -getinfo with multiple wallets and -rpcwallet=non-existing-wallet returns no balances") + cli_get_info_keys = self.nodes[0].cli('-getinfo', '-rpcwallet=does-not-exist').send_cli().keys() + assert 'balance' not in cli_get_info_keys + assert 'balances' not in cli_get_info_keys - self.log.info("Test -getinfo after unloading all wallets except a non-default one returns its balance") + self.log.info("Test -getinfo with multiple wallets returns all loaded wallet names and balances") + assert_equal(set(self.nodes[0].listwallets()), set(wallets)) + cli_get_info = self.nodes[0].cli('-getinfo').send_cli() + assert 'balance' not in cli_get_info.keys() + assert_equal(cli_get_info['balances'], {k: v for k, v in zip(wallets, amounts)}) + + # Unload the default wallet and re-verify. self.nodes[0].unloadwallet(wallets[0]) + assert wallets[0] not in self.nodes[0].listwallets() + cli_get_info = self.nodes[0].cli('-getinfo').send_cli() + assert 'balance' not in cli_get_info.keys() + assert_equal(cli_get_info['balances'], {k: v for k, v in zip(wallets[1:], amounts[1:])}) + + self.log.info("Test -getinfo after unloading all wallets except a non-default one returns its balance") self.nodes[0].unloadwallet(wallets[2]) assert_equal(self.nodes[0].listwallets(), [wallets[1]]) - assert_equal(self.nodes[0].cli('-getinfo').send_cli()['balance'], amounts[1]) - - self.log.info("Test -getinfo -rpcwallet=remaining-non-default-wallet returns its balance") - assert_equal(self.nodes[0].cli('-getinfo').send_cli('-rpcwallet={}'.format(wallets[1]))['balance'], amounts[1]) - - self.log.info("Test -getinfo with -rpcwallet=unloaded wallet returns no balance") - assert 'balance' not in self.nodes[0].cli('-getinfo').send_cli('-rpcwallet={}'.format(wallets[2])).keys() + cli_get_info = self.nodes[0].cli('-getinfo').send_cli() + assert 'balances' not in cli_get_info.keys() + assert_equal(cli_get_info['balance'], amounts[1]) + + self.log.info("Test -getinfo with -rpcwallet=remaining-non-default-wallet returns only its balance") + cli_get_info = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[1])).send_cli() + assert 'balances' not in cli_get_info.keys() + assert_equal(cli_get_info['balance'], amounts[1]) + + self.log.info("Test -getinfo with -rpcwallet=unloaded wallet returns no balances") + cli_get_info = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[2])).send_cli() + assert 'balance' not in cli_get_info_keys + assert 'balances' not in cli_get_info_keys else: self.log.info("*** Wallet not compiled; cli getwalletinfo and -getinfo wallet tests skipped") self.nodes[0].generate(1) # maintain block parity with the wallet_compiled conditional branch diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py index a07dad18d6..5b7216b253 100755 --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -7,6 +7,7 @@ from decimal import Decimal from test_framework.messages import COIN +from test_framework.mininode import P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -58,6 +59,7 @@ class MempoolPackagesTest(BitcoinTestFramework): def run_test(self): # Mine some blocks and have them mature. + self.nodes[0].add_p2p_connection(P2PTxInvStore()) # keep track of invs self.nodes[0].generate(101) utxo = self.nodes[0].listunspent(10) txid = utxo[0]['txid'] @@ -72,6 +74,10 @@ class MempoolPackagesTest(BitcoinTestFramework): value = sent_value chain.append(txid) + # 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 + self.nodes[0].p2p.wait_for_broadcast(chain) + # Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor # count and fees should look correct mempool = self.nodes[0].getrawmempool(True) @@ -212,6 +218,10 @@ class MempoolPackagesTest(BitcoinTestFramework): for tx in chain[:MAX_ANCESTORS_CUSTOM]: assert tx in mempool1 # TODO: more detailed check of node1's mempool (fees etc.) + # check transaction unbroadcast info (should be false if in both mempools) + mempool = self.nodes[0].getrawmempool(True) + for tx in mempool: + assert_equal(mempool[tx]['unbroadcast'], False) # TODO: test ancestor size limits diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py index 3969da2eb0..5d00648aed 100755 --- a/test/functional/mempool_persist.py +++ b/test/functional/mempool_persist.py @@ -84,7 +84,9 @@ class MempoolPersistTest(BitcoinTestFramework): assert_greater_than_or_equal(tx_creation_time_higher, tx_creation_time) # disconnect nodes & make a txn that remains in the unbroadcast set. - disconnect_nodes(self.nodes[0], 2) + disconnect_nodes(self.nodes[0], 1) + assert(len(self.nodes[0].getpeerinfo()) == 0) + assert(len(self.nodes[0].p2ps) == 0) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), Decimal("12")) connect_nodes(self.nodes[0], 2) @@ -157,8 +159,10 @@ class MempoolPersistTest(BitcoinTestFramework): # clear out mempool node0.generate(1) - # disconnect nodes to make a txn that remains in the unbroadcast set. - disconnect_nodes(node0, 1) + # ensure node0 doesn't have any connections + # make a transaction that will remain in the unbroadcast set + assert(len(node0.getpeerinfo()) == 0) + assert(len(node0.p2ps) == 0) node0.sendtoaddress(self.nodes[1].getnewaddress(), Decimal("12")) # shutdown, then startup with wallet disabled diff --git a/test/functional/mempool_unbroadcast.py b/test/functional/mempool_unbroadcast.py index a561f28b91..365d011157 100755 --- a/test/functional/mempool_unbroadcast.py +++ b/test/functional/mempool_unbroadcast.py @@ -16,6 +16,7 @@ from test_framework.util import ( disconnect_nodes, ) +MAX_INITIAL_BROADCAST_DELAY = 15 * 60 # 15 minutes in seconds class MempoolUnbroadcastTest(BitcoinTestFramework): def set_test_params(self): @@ -53,6 +54,13 @@ class MempoolUnbroadcastTest(BitcoinTestFramework): txFS = node.signrawtransactionwithwallet(txF["hex"]) rpc_tx_hsh = node.sendrawtransaction(txFS["hex"]) + # check transactions are in unbroadcast using rpc + mempoolinfo = self.nodes[0].getmempoolinfo() + assert_equal(mempoolinfo['unbroadcastcount'], 2) + mempool = self.nodes[0].getrawmempool(True) + for tx in mempool: + assert_equal(mempool[tx]['unbroadcast'], True) + # check that second node doesn't have these two txns mempool = self.nodes[1].getrawmempool() assert rpc_tx_hsh not in mempool @@ -65,24 +73,30 @@ class MempoolUnbroadcastTest(BitcoinTestFramework): connect_nodes(node, 1) # fast forward into the future & ensure that the second node has the txns - node.mockscheduler(15 * 60) # 15 min in seconds + node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY) self.sync_mempools(timeout=30) mempool = self.nodes[1].getrawmempool() assert rpc_tx_hsh in mempool assert wallet_tx_hsh in mempool + # check that transactions are no longer in first node's unbroadcast set + mempool = self.nodes[0].getrawmempool(True) + for tx in mempool: + assert_equal(mempool[tx]['unbroadcast'], False) + self.log.info("Add another connection & ensure transactions aren't broadcast again") conn = node.add_p2p_connection(P2PTxInvStore()) - node.mockscheduler(15 * 60) - time.sleep(5) + node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY) + time.sleep(2) # allow sufficient time for possibility of broadcast assert_equal(len(conn.get_invs()), 0) + disconnect_nodes(node, 1) + node.disconnect_p2ps() + def test_txn_removal(self): self.log.info("Test that transactions removed from mempool are removed from unbroadcast set") node = self.nodes[0] - disconnect_nodes(node, 1) - node.disconnect_p2ps # since the node doesn't have any connections, it will not receive # any GETDATAs & thus the transaction will remain in the unbroadcast set. diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index 1bda167c87..86d7c78d63 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -29,8 +29,6 @@ from test_framework.util import ( assert_raises_rpc_error, connect_nodes, ) -from test_framework.script import CScriptNum - def assert_template(node, block, expect, rehash=True): if rehash: @@ -91,12 +89,6 @@ class MiningTest(BitcoinTestFramework): coinbase_tx.rehash() # round-trip the encoded bip34 block height commitment - assert_equal(CScriptNum.decode(coinbase_tx.vin[0].scriptSig), next_height) - # round-trip negative and multi-byte CScriptNums to catch python regression - assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(1500))), 1500) - assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(-1500))), -1500) - assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(-1))), -1) - block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) diff --git a/test/functional/p2p_blockfilters.py b/test/functional/p2p_blockfilters.py new file mode 100755 index 0000000000..6d947ac660 --- /dev/null +++ b/test/functional/p2p_blockfilters.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Tests NODE_COMPACT_FILTERS (BIP 157/158). + +Tests that a node configured with -blockfilterindex and -peerblockfilters can serve +cfheaders and cfcheckpts. +""" + +from test_framework.messages import ( + FILTER_TYPE_BASIC, + hash256, + msg_getcfcheckpt, + msg_getcfheaders, + msg_getcfilters, + ser_uint256, + uint256_from_str, +) +from test_framework.mininode import P2PInterface +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + connect_nodes, + disconnect_nodes, + wait_until, +) + +class CFiltersClient(P2PInterface): + def __init__(self): + super().__init__() + # Store the cfilters received. + self.cfilters = [] + + def pop_cfilters(self): + cfilters = self.cfilters + self.cfilters = [] + return cfilters + + def on_cfilter(self, message): + """Store cfilters received in a list.""" + self.cfilters.append(message) + +class CompactFiltersTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.rpc_timeout = 480 + self.num_nodes = 2 + self.extra_args = [ + ["-blockfilterindex", "-peerblockfilters"], + ["-blockfilterindex"], + ] + + def run_test(self): + # Node 0 supports COMPACT_FILTERS, node 1 does not. + node0 = self.nodes[0].add_p2p_connection(CFiltersClient()) + node1 = self.nodes[1].add_p2p_connection(CFiltersClient()) + + # Nodes 0 & 1 share the same first 999 blocks in the chain. + self.nodes[0].generate(999) + self.sync_blocks(timeout=600) + + # Stale blocks by disconnecting nodes 0 & 1, mining, then reconnecting + disconnect_nodes(self.nodes[0], 1) + + self.nodes[0].generate(1) + wait_until(lambda: self.nodes[0].getblockcount() == 1000) + stale_block_hash = self.nodes[0].getblockhash(1000) + + self.nodes[1].generate(1001) + wait_until(lambda: self.nodes[1].getblockcount() == 2000) + + self.log.info("get cfcheckpt on chain to be re-orged out.") + request = msg_getcfcheckpt( + filter_type=FILTER_TYPE_BASIC, + stop_hash=int(stale_block_hash, 16) + ) + node0.send_and_ping(message=request) + response = node0.last_message['cfcheckpt'] + assert_equal(response.filter_type, request.filter_type) + assert_equal(response.stop_hash, request.stop_hash) + assert_equal(len(response.headers), 1) + + self.log.info("Reorg node 0 to a new chain.") + connect_nodes(self.nodes[0], 1) + self.sync_blocks(timeout=600) + + main_block_hash = self.nodes[0].getblockhash(1000) + assert main_block_hash != stale_block_hash, "node 0 chain did not reorganize" + + self.log.info("Check that peers can fetch cfcheckpt on active chain.") + tip_hash = self.nodes[0].getbestblockhash() + request = msg_getcfcheckpt( + filter_type=FILTER_TYPE_BASIC, + stop_hash=int(tip_hash, 16) + ) + node0.send_and_ping(request) + response = node0.last_message['cfcheckpt'] + assert_equal(response.filter_type, request.filter_type) + assert_equal(response.stop_hash, request.stop_hash) + + main_cfcheckpt = self.nodes[0].getblockfilter(main_block_hash, 'basic')['header'] + tip_cfcheckpt = self.nodes[0].getblockfilter(tip_hash, 'basic')['header'] + assert_equal( + response.headers, + [int(header, 16) for header in (main_cfcheckpt, tip_cfcheckpt)] + ) + + self.log.info("Check that peers can fetch cfcheckpt on stale chain.") + request = msg_getcfcheckpt( + filter_type=FILTER_TYPE_BASIC, + stop_hash=int(stale_block_hash, 16) + ) + node0.send_and_ping(request) + response = node0.last_message['cfcheckpt'] + + stale_cfcheckpt = self.nodes[0].getblockfilter(stale_block_hash, 'basic')['header'] + assert_equal( + response.headers, + [int(header, 16) for header in (stale_cfcheckpt,)] + ) + + self.log.info("Check that peers can fetch cfheaders on active chain.") + request = msg_getcfheaders( + filter_type=FILTER_TYPE_BASIC, + start_height=1, + stop_hash=int(main_block_hash, 16) + ) + node0.send_and_ping(request) + response = node0.last_message['cfheaders'] + main_cfhashes = response.hashes + assert_equal(len(main_cfhashes), 1000) + assert_equal( + compute_last_header(response.prev_header, response.hashes), + int(main_cfcheckpt, 16) + ) + + self.log.info("Check that peers can fetch cfheaders on stale chain.") + request = msg_getcfheaders( + filter_type=FILTER_TYPE_BASIC, + start_height=1, + stop_hash=int(stale_block_hash, 16) + ) + node0.send_and_ping(request) + response = node0.last_message['cfheaders'] + stale_cfhashes = response.hashes + assert_equal(len(stale_cfhashes), 1000) + assert_equal( + compute_last_header(response.prev_header, response.hashes), + int(stale_cfcheckpt, 16) + ) + + self.log.info("Check that peers can fetch cfilters.") + stop_hash = self.nodes[0].getblockhash(10) + request = msg_getcfilters( + filter_type=FILTER_TYPE_BASIC, + start_height=1, + stop_hash=int(stop_hash, 16) + ) + node0.send_message(request) + node0.sync_with_ping() + response = node0.pop_cfilters() + assert_equal(len(response), 10) + + self.log.info("Check that cfilter responses are correct.") + for cfilter, cfhash, height in zip(response, main_cfhashes, range(1, 11)): + block_hash = self.nodes[0].getblockhash(height) + assert_equal(cfilter.filter_type, FILTER_TYPE_BASIC) + assert_equal(cfilter.block_hash, int(block_hash, 16)) + computed_cfhash = uint256_from_str(hash256(cfilter.filter_data)) + assert_equal(computed_cfhash, cfhash) + + self.log.info("Check that peers can fetch cfilters for stale blocks.") + request = msg_getcfilters( + filter_type=FILTER_TYPE_BASIC, + start_height=1000, + stop_hash=int(stale_block_hash, 16) + ) + node0.send_message(request) + node0.sync_with_ping() + response = node0.pop_cfilters() + assert_equal(len(response), 1) + + cfilter = response[0] + assert_equal(cfilter.filter_type, FILTER_TYPE_BASIC) + assert_equal(cfilter.block_hash, int(stale_block_hash, 16)) + computed_cfhash = uint256_from_str(hash256(cfilter.filter_data)) + assert_equal(computed_cfhash, stale_cfhashes[999]) + + self.log.info("Requests to node 1 without NODE_COMPACT_FILTERS results in disconnection.") + requests = [ + msg_getcfcheckpt( + filter_type=FILTER_TYPE_BASIC, + stop_hash=int(main_block_hash, 16) + ), + msg_getcfheaders( + filter_type=FILTER_TYPE_BASIC, + start_height=1000, + stop_hash=int(main_block_hash, 16) + ), + msg_getcfilters( + filter_type=FILTER_TYPE_BASIC, + start_height=1000, + stop_hash=int(main_block_hash, 16) + ), + ] + for request in requests: + node1 = self.nodes[1].add_p2p_connection(P2PInterface()) + node1.send_message(request) + node1.wait_for_disconnect() + + self.log.info("Check that invalid requests result in disconnection.") + requests = [ + # Requesting too many filters results in disconnection. + msg_getcfilters( + filter_type=FILTER_TYPE_BASIC, + start_height=0, + stop_hash=int(main_block_hash, 16) + ), + # Requesting too many filter headers results in disconnection. + msg_getcfheaders( + filter_type=FILTER_TYPE_BASIC, + start_height=0, + stop_hash=int(tip_hash, 16) + ), + # Requesting unknown filter type results in disconnection. + msg_getcfcheckpt( + filter_type=255, + stop_hash=int(main_block_hash, 16) + ), + # Requesting unknown hash results in disconnection. + msg_getcfcheckpt( + filter_type=FILTER_TYPE_BASIC, + stop_hash=123456789, + ), + ] + for request in requests: + node0 = self.nodes[0].add_p2p_connection(P2PInterface()) + node0.send_message(request) + node0.wait_for_disconnect() + +def compute_last_header(prev_header, hashes): + """Compute the last filter header from a starting header and a sequence of filter hashes.""" + header = ser_uint256(prev_header) + for filter_hash in hashes: + header = hash256(ser_uint256(filter_hash) + header) + return uint256_from_str(header) + +if __name__ == '__main__': + CompactFiltersTest().main() diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py index 3258a38e3c..c155dda664 100755 --- a/test/functional/p2p_blocksonly.py +++ b/test/functional/p2p_blocksonly.py @@ -57,6 +57,29 @@ class P2PBlocksOnly(BitcoinTestFramework): self.nodes[0].p2p.wait_for_tx(txid) assert_equal(self.nodes[0].getmempoolinfo()['size'], 1) + self.log.info('Check that txs from whitelisted peers are not rejected and relayed to others') + self.log.info("Restarting node 0 with whitelist permission and blocksonly") + self.restart_node(0, ["-persistmempool=0", "-whitelist=127.0.0.1", "-whitelistforcerelay", "-blocksonly"]) + assert_equal(self.nodes[0].getrawmempool(),[]) + first_peer = self.nodes[0].add_p2p_connection(P2PInterface()) + second_peer = self.nodes[0].add_p2p_connection(P2PInterface()) + peer_1_info = self.nodes[0].getpeerinfo()[0] + assert_equal(peer_1_info['whitelisted'], True) + assert_equal(peer_1_info['permissions'], ['noban', 'forcerelay', 'relay', 'mempool']) + peer_2_info = self.nodes[0].getpeerinfo()[1] + assert_equal(peer_2_info['whitelisted'], True) + assert_equal(peer_2_info['permissions'], ['noban', 'forcerelay', 'relay', 'mempool']) + assert_equal(self.nodes[0].testmempoolaccept([sigtx])[0]['allowed'], True) + txid = self.nodes[0].testmempoolaccept([sigtx])[0]['txid'] + + self.log.info('Check that the tx from whitelisted first_peer is relayed to others (ie.second_peer)') + with self.nodes[0].assert_debug_log(["received getdata"]): + first_peer.send_message(msg_tx(FromHex(CTransaction(), sigtx))) + self.log.info('Check that the whitelisted peer is still connected after sending the transaction') + assert_equal(first_peer.is_connected, True) + second_peer.wait_for_tx(txid) + assert_equal(self.nodes[0].getmempoolinfo()['size'], 1) + self.log.info("Whitelisted peer's transaction is accepted and relayed") if __name__ == '__main__': P2PBlocksOnly().main() diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index 66e6f8c424..d77a744758 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -10,7 +10,7 @@ Version 2 compact blocks are post-segwit (wtxids) import random from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment -from test_framework.messages import BlockTransactions, BlockTransactionsRequest, calculate_shortid, CBlock, CBlockHeader, CInv, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, FromHex, HeaderAndShortIDs, msg_no_witness_block, msg_no_witness_blocktxn, msg_cmpctblock, msg_getblocktxn, msg_getdata, msg_getheaders, msg_headers, msg_inv, msg_sendcmpct, msg_sendheaders, msg_tx, msg_block, msg_blocktxn, MSG_WITNESS_FLAG, NODE_NETWORK, P2PHeaderAndShortIDs, PrefilledTransaction, ser_uint256, ToHex +from test_framework.messages import BlockTransactions, BlockTransactionsRequest, calculate_shortid, CBlock, CBlockHeader, CInv, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, FromHex, HeaderAndShortIDs, msg_no_witness_block, msg_no_witness_blocktxn, msg_cmpctblock, msg_getblocktxn, msg_getdata, msg_getheaders, msg_headers, msg_inv, msg_sendcmpct, msg_sendheaders, msg_tx, msg_block, msg_blocktxn, MSG_BLOCK, MSG_CMPCT_BLOCK, MSG_WITNESS_FLAG, NODE_NETWORK, P2PHeaderAndShortIDs, PrefilledTransaction, ser_uint256, ToHex from test_framework.mininode import mininode_lock, P2PInterface from test_framework.script import CScript, OP_TRUE, OP_DROP from test_framework.test_framework import BitcoinTestFramework @@ -44,7 +44,7 @@ class TestP2PConn(P2PInterface): def on_inv(self, message): for x in self.last_message["inv"].inv: - if x.type == 2: + if x.type == MSG_BLOCK: self.block_announced = True self.announced_blockhashes.add(x.hash) @@ -307,7 +307,7 @@ class CompactBlocksTest(BitcoinTestFramework): # Now fetch the compact block using a normal non-announce getdata with mininode_lock: test_node.clear_block_announcement() - inv = CInv(4, block_hash) # 4 == "CompactBlock" + inv = CInv(MSG_CMPCT_BLOCK, block_hash) test_node.send_message(msg_getdata([inv])) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) @@ -380,7 +380,7 @@ class CompactBlocksTest(BitcoinTestFramework): block = self.build_block_on_tip(node, segwit=segwit) if announce == "inv": - test_node.send_message(msg_inv([CInv(2, block.sha256)])) + test_node.send_message(msg_inv([CInv(MSG_BLOCK, block.sha256)])) wait_until(lambda: "getheaders" in test_node.last_message, timeout=30, lock=mininode_lock) test_node.send_header_for_blocks([block]) else: @@ -564,7 +564,8 @@ class CompactBlocksTest(BitcoinTestFramework): # We should receive a getdata request test_node.wait_for_getdata([block.sha256], timeout=10) - assert test_node.last_message["getdata"].inv[0].type == 2 or test_node.last_message["getdata"].inv[0].type == 2 | MSG_WITNESS_FLAG + assert test_node.last_message["getdata"].inv[0].type == MSG_BLOCK or \ + test_node.last_message["getdata"].inv[0].type == MSG_BLOCK | MSG_WITNESS_FLAG # Deliver the block if version == 2: @@ -633,7 +634,7 @@ class CompactBlocksTest(BitcoinTestFramework): wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) test_node.clear_block_announcement() - test_node.send_message(msg_getdata([CInv(4, int(new_blocks[0], 16))])) + test_node.send_message(msg_getdata([CInv(MSG_CMPCT_BLOCK, int(new_blocks[0], 16))])) wait_until(lambda: "cmpctblock" in test_node.last_message, timeout=30, lock=mininode_lock) test_node.clear_block_announcement() @@ -642,7 +643,7 @@ class CompactBlocksTest(BitcoinTestFramework): test_node.clear_block_announcement() with mininode_lock: test_node.last_message.pop("block", None) - test_node.send_message(msg_getdata([CInv(4, int(new_blocks[0], 16))])) + test_node.send_message(msg_getdata([CInv(MSG_CMPCT_BLOCK, int(new_blocks[0], 16))])) wait_until(lambda: "block" in test_node.last_message, timeout=30, lock=mininode_lock) with mininode_lock: test_node.last_message["block"].block.calc_sha256() diff --git a/test/functional/p2p_feefilter.py b/test/functional/p2p_feefilter.py index 4f242bd94a..805cb1e84f 100755 --- a/test/functional/p2p_feefilter.py +++ b/test/functional/p2p_feefilter.py @@ -7,7 +7,7 @@ from decimal import Decimal import time -from test_framework.messages import msg_feefilter +from test_framework.messages import MSG_TX, msg_feefilter from test_framework.mininode import mininode_lock, P2PInterface from test_framework.test_framework import BitcoinTestFramework @@ -31,7 +31,7 @@ class TestP2PConn(P2PInterface): def on_inv(self, message): for i in message.inv: - if (i.type == 1): + if (i.type == MSG_TX): self.txinvs.append(hashToHex(i.hash)) def clear_invs(self): diff --git a/test/functional/p2p_fingerprint.py b/test/functional/p2p_fingerprint.py index c9fbb830c8..d743abe681 100755 --- a/test/functional/p2p_fingerprint.py +++ b/test/functional/p2p_fingerprint.py @@ -11,7 +11,7 @@ the node should pretend that it does not have it to avoid fingerprinting. import time from test_framework.blocktools import (create_block, create_coinbase) -from test_framework.messages import CInv +from test_framework.messages import CInv, MSG_BLOCK from test_framework.mininode import ( P2PInterface, msg_headers, @@ -48,7 +48,7 @@ class P2PFingerprintTest(BitcoinTestFramework): # Send a getdata request for a given block hash def send_block_request(self, block_hash, node): msg = msg_getdata() - msg.inv.append(CInv(2, block_hash)) # 2 == "Block" + msg.inv.append(CInv(MSG_BLOCK, block_hash)) node.send_message(msg) # Send a getheaders request for a given single block hash diff --git a/test/functional/p2p_getdata.py b/test/functional/p2p_getdata.py new file mode 100755 index 0000000000..d1b11c2c61 --- /dev/null +++ b/test/functional/p2p_getdata.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 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 GETDATA processing behavior""" +from collections import defaultdict + +from test_framework.messages import ( + CInv, + msg_getdata, +) +from test_framework.mininode import P2PInterface +from test_framework.test_framework import BitcoinTestFramework + + +class P2PStoreBlock(P2PInterface): + def __init__(self): + super().__init__() + self.blocks = defaultdict(int) + + def on_block(self, message): + message.block.calc_sha256() + self.blocks[message.block.sha256] += 1 + + +class GetdataTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def run_test(self): + p2p_block_store = self.nodes[0].add_p2p_connection(P2PStoreBlock()) + + self.log.info("test that an invalid GETDATA doesn't prevent processing of future messages") + + # Send invalid message and verify that node responds to later ping + invalid_getdata = msg_getdata() + invalid_getdata.inv.append(CInv(t=0, h=0)) # INV type 0 is invalid. + p2p_block_store.send_and_ping(invalid_getdata) + + # Check getdata still works by fetching tip block + best_block = int(self.nodes[0].getbestblockhash(), 16) + good_getdata = msg_getdata() + good_getdata.inv.append(CInv(t=2, h=best_block)) + p2p_block_store.send_and_ping(good_getdata) + p2p_block_store.wait_until(lambda: self.nodes[0].p2ps[0].blocks[best_block] == 1) + + +if __name__ == '__main__': + GetdataTest().main() diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py index 4bd832e8f7..81302374c9 100755 --- a/test/functional/p2p_invalid_messages.py +++ b/test/functional/p2p_invalid_messages.py @@ -7,7 +7,16 @@ import asyncio import struct import sys -from test_framework import messages +from test_framework.messages import ( + CBlockHeader, + CInv, + msg_getdata, + msg_headers, + msg_inv, + msg_ping, + MSG_TX, + ser_string, +) from test_framework.mininode import ( NetworkThread, P2PDataStore, @@ -25,7 +34,7 @@ class msg_unrecognized: self.str_data = str_data.encode() if not isinstance(str_data, bytes) else str_data def serialize(self): - return messages.ser_string(self.str_data) + return ser_string(self.str_data) def __repr__(self): return "{}(data={})".format(self.msgtype, self.str_data) @@ -135,7 +144,7 @@ class InvalidMessagesTest(BitcoinTestFramework): # For some reason unknown to me, we sometimes have to push additional data to the # peer in order for it to realize a disconnect. try: - node.p2p.send_message(messages.msg_ping(nonce=123123)) + node.p2p.send_message(msg_ping(nonce=123123)) except IOError: pass @@ -158,7 +167,7 @@ class InvalidMessagesTest(BitcoinTestFramework): asyncio.run_coroutine_threadsafe(swap_magic_bytes(), NetworkThread.network_event_loop).result() with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: INVALID MESSAGESTART ping']): - conn.send_message(messages.msg_ping(nonce=0xff)) + conn.send_message(msg_ping(nonce=0xff)) conn.wait_for_disconnect(timeout=1) self.nodes[0].disconnect_p2ps() @@ -206,13 +215,13 @@ class InvalidMessagesTest(BitcoinTestFramework): def test_large_inv(self): conn = self.nodes[0].add_p2p_connection(P2PInterface()) with self.nodes[0].assert_debug_log(['Misbehaving', 'peer=4 (0 -> 20): message inv size() = 50001']): - msg = messages.msg_inv([messages.CInv(1, 1)] * 50001) + msg = msg_inv([CInv(MSG_TX, 1)] * 50001) conn.send_and_ping(msg) with self.nodes[0].assert_debug_log(['Misbehaving', 'peer=4 (20 -> 40): message getdata size() = 50001']): - msg = messages.msg_getdata([messages.CInv(1, 1)] * 50001) + msg = msg_getdata([CInv(MSG_TX, 1)] * 50001) conn.send_and_ping(msg) with self.nodes[0].assert_debug_log(['Misbehaving', 'peer=4 (40 -> 60): headers message size = 2001']): - msg = messages.msg_headers([messages.CBlockHeader()] * 2001) + msg = msg_headers([CBlockHeader()] * 2001) conn.send_and_ping(msg) self.nodes[0].disconnect_p2ps() diff --git a/test/functional/p2p_leak_tx.py b/test/functional/p2p_leak_tx.py index 6b3436fa5f..da30ad5977 100755 --- a/test/functional/p2p_leak_tx.py +++ b/test/functional/p2p_leak_tx.py @@ -4,7 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test that we don't leak txs to inbound peers that we haven't yet announced to""" -from test_framework.messages import msg_getdata, CInv +from test_framework.messages import msg_getdata, CInv, MSG_TX from test_framework.mininode import P2PDataStore from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -37,7 +37,7 @@ class P2PLeakTxTest(BitcoinTestFramework): txid = gen_node.sendtoaddress(gen_node.getnewaddress(), 0.01) want_tx = msg_getdata() - want_tx.inv.append(CInv(t=1, h=int(txid, 16))) + want_tx.inv.append(CInv(t=MSG_TX, h=int(txid, 16))) inbound_peer.last_message.pop('notfound', None) inbound_peer.send_and_ping(want_tx) diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py index 4f4dd6c975..ed3429a037 100755 --- a/test/functional/p2p_node_network_limited.py +++ b/test/functional/p2p_node_network_limited.py @@ -8,7 +8,7 @@ Tests that a node configured with -prune=550 signals NODE_NETWORK_LIMITED correc and that it responds to getdata requests for blocks correctly: - send a block within 288 + 2 of the tip - disconnect peers who request blocks older than that.""" -from test_framework.messages import CInv, msg_getdata, msg_verack, NODE_NETWORK_LIMITED, NODE_WITNESS +from test_framework.messages import CInv, MSG_BLOCK, msg_getdata, msg_verack, NODE_NETWORK_LIMITED, NODE_WITNESS from test_framework.mininode import P2PInterface, mininode_lock from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -31,7 +31,7 @@ class P2PIgnoreInv(P2PInterface): wait_until(test_function, timeout=timeout, lock=mininode_lock) def send_getdata_for_block(self, blockhash): getdata_request = msg_getdata() - getdata_request.inv.append(CInv(2, int(blockhash, 16))) + getdata_request.inv.append(CInv(MSG_BLOCK, int(blockhash, 16))) self.send_message(getdata_request) class NodeNetworkLimitedTest(BitcoinTestFramework): diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index 6fb0fec32b..8803086213 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -22,6 +22,8 @@ from test_framework.messages import ( CTxOut, CTxWitness, MAX_BLOCK_BASE_SIZE, + MSG_BLOCK, + MSG_TX, MSG_WITNESS_FLAG, NODE_NETWORK, NODE_WITNESS, @@ -157,7 +159,7 @@ class TestP2PConn(P2PInterface): def announce_tx_and_wait_for_getdata(self, tx, timeout=60, success=True): with mininode_lock: self.last_message.pop("getdata", None) - self.send_message(msg_inv(inv=[CInv(1, tx.sha256)])) + self.send_message(msg_inv(inv=[CInv(MSG_TX, tx.sha256)])) if success: self.wait_for_getdata([tx.sha256], timeout) else: @@ -173,7 +175,7 @@ class TestP2PConn(P2PInterface): if use_header: self.send_message(msg) else: - self.send_message(msg_inv(inv=[CInv(2, block.sha256)])) + self.send_message(msg_inv(inv=[CInv(MSG_BLOCK, block.sha256)])) self.wait_for_getheaders() self.send_message(msg) self.wait_for_getdata([block.sha256]) @@ -293,7 +295,7 @@ class SegWitTest(BitcoinTestFramework): return func_wrapper - @subtest + @subtest # type: ignore def test_non_witness_transaction(self): """See if sending a regular transaction works, and create a utxo to use in later tests.""" # Mine a block with an anyone-can-spend coinbase, @@ -322,7 +324,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.append(UTXO(tx.sha256, 0, 49 * 100000000)) self.nodes[0].generate(1) - @subtest + @subtest # type: ignore def test_unnecessary_witness_before_segwit_activation(self): """Verify that blocks with witnesses are rejected before activation.""" @@ -353,7 +355,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop(0) self.utxo.append(UTXO(tx.sha256, 0, tx.vout[0].nValue)) - @subtest + @subtest # type: ignore def test_block_relay(self): """Test that block requests to NODE_WITNESS peer are with MSG_WITNESS_FLAG. @@ -449,7 +451,7 @@ class SegWitTest(BitcoinTestFramework): self.old_node.announce_tx_and_wait_for_getdata(block4.vtx[0]) assert block4.sha256 not in self.old_node.getdataset - @subtest + @subtest # type: ignore def test_v0_outputs_arent_spendable(self): """Test that v0 outputs aren't spendable before segwit activation. @@ -531,7 +533,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop(0) self.utxo.append(UTXO(txid, 2, value)) - @subtest + @subtest # type: ignore def test_getblocktemplate_before_lockin(self): txid = int(self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1), 16) @@ -557,7 +559,7 @@ class SegWitTest(BitcoinTestFramework): self.nodes[0].generate(1) self.sync_blocks() - @subtest + @subtest # type: ignore def test_witness_tx_relay_before_segwit_activation(self): # Generate a transaction that doesn't require a witness, but send it @@ -576,7 +578,7 @@ class SegWitTest(BitcoinTestFramework): # Verify that if a peer doesn't set nServices to include NODE_WITNESS, # the getdata is just for the non-witness portion. self.old_node.announce_tx_and_wait_for_getdata(tx) - assert self.old_node.last_message["getdata"].inv[0].type == 1 + assert self.old_node.last_message["getdata"].inv[0].type == MSG_TX # Since we haven't delivered the tx yet, inv'ing the same tx from # a witness transaction ought not result in a getdata. @@ -599,7 +601,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop(0) self.utxo.append(UTXO(tx_hash, 0, tx_value)) - @subtest + @subtest # type: ignore def test_standardness_v0(self): """Test V0 txout standardness. @@ -677,7 +679,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue)) assert_equal(len(self.nodes[1].getrawmempool()), 0) - @subtest + @subtest # type: ignore def advance_to_segwit_active(self): """Mine enough blocks to activate segwit.""" assert not softfork_active(self.nodes[0], 'segwit') @@ -688,7 +690,7 @@ class SegWitTest(BitcoinTestFramework): assert softfork_active(self.nodes[0], 'segwit') self.segwit_active = True - @subtest + @subtest # type: ignore def test_p2sh_witness(self): """Test P2SH wrapped witness programs.""" @@ -757,7 +759,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop(0) self.utxo.append(UTXO(spend_tx.sha256, 0, spend_tx.vout[0].nValue)) - @subtest + @subtest # type: ignore def test_witness_commitments(self): """Test witness commitments. @@ -847,7 +849,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop(0) self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue)) - @subtest + @subtest # type: ignore def test_block_malleability(self): # Make sure that a block that has too big a virtual size @@ -887,7 +889,7 @@ class SegWitTest(BitcoinTestFramework): block.vtx[0].wit.vtxinwit[0].scriptWitness.stack = [ser_uint256(0)] test_witness_block(self.nodes[0], self.test_node, block, accepted=True) - @subtest + @subtest # type: ignore def test_witness_block_size(self): # TODO: Test that non-witness carrying blocks can't exceed 1MB # Skipping this test for now; this is covered in p2p-fullblocktest.py @@ -965,7 +967,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop(0) self.utxo.append(UTXO(block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue)) - @subtest + @subtest # type: ignore def test_submit_block(self): """Test that submitblock adds the nonce automatically when possible.""" block = self.build_next_block() @@ -1001,7 +1003,7 @@ class SegWitTest(BitcoinTestFramework): # Tip should not advance! assert self.nodes[0].getbestblockhash() != block_2.hash - @subtest + @subtest # type: ignore def test_extra_witness_data(self): """Test extra witness data in a transaction.""" @@ -1074,7 +1076,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop(0) self.utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue)) - @subtest + @subtest # type: ignore def test_max_witness_push_length(self): """Test that witness stack can only allow up to 520 byte pushes.""" @@ -1111,7 +1113,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop() self.utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue)) - @subtest + @subtest # type: ignore def test_max_witness_program_length(self): """Test that witness outputs greater than 10kB can't be spent.""" @@ -1159,7 +1161,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop() self.utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue)) - @subtest + @subtest # type: ignore def test_witness_input_length(self): """Test that vin length must match vtxinwit length.""" @@ -1241,7 +1243,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop() self.utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue)) - @subtest + @subtest # type: ignore def test_tx_relay_after_segwit_activation(self): """Test transaction relay after segwit activation. @@ -1310,9 +1312,9 @@ class SegWitTest(BitcoinTestFramework): tx3.wit.vtxinwit[0].scriptWitness.stack = [witness_program] # Also check that old_node gets a tx announcement, even though this is # a witness transaction. - self.old_node.wait_for_inv([CInv(1, tx2.sha256)]) # wait until tx2 was inv'ed + self.old_node.wait_for_inv([CInv(MSG_TX, tx2.sha256)]) # wait until tx2 was inv'ed test_transaction_acceptance(self.nodes[0], self.test_node, tx3, with_witness=True, accepted=True) - self.old_node.wait_for_inv([CInv(1, tx3.sha256)]) + self.old_node.wait_for_inv([CInv(MSG_TX, tx3.sha256)]) # Test that getrawtransaction returns correct witness information # hash, size, vsize @@ -1334,7 +1336,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop(0) self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue)) - @subtest + @subtest # type: ignore def test_segwit_versions(self): """Test validity of future segwit version transactions. @@ -1416,7 +1418,7 @@ class SegWitTest(BitcoinTestFramework): # Add utxo to our list self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue)) - @subtest + @subtest # type: ignore def test_premature_coinbase_witness_spend(self): block = self.build_next_block() @@ -1451,7 +1453,7 @@ class SegWitTest(BitcoinTestFramework): test_witness_block(self.nodes[0], self.test_node, block2, accepted=True) self.sync_blocks() - @subtest + @subtest # type: ignore def test_uncompressed_pubkey(self): """Test uncompressed pubkey validity in segwit transactions. @@ -1556,7 +1558,7 @@ class SegWitTest(BitcoinTestFramework): test_witness_block(self.nodes[0], self.test_node, block, accepted=True) self.utxo.append(UTXO(tx5.sha256, 0, tx5.vout[0].nValue)) - @subtest + @subtest # type: ignore def test_signature_version_1(self): key = ECKey() @@ -1738,7 +1740,7 @@ class SegWitTest(BitcoinTestFramework): for i in range(len(tx.vout)): self.utxo.append(UTXO(tx.sha256, i, tx.vout[i].nValue)) - @subtest + @subtest # type: ignore def test_non_standard_witness_blinding(self): """Test behavior of unnecessary witnesses in transactions does not blind the node for the transaction""" @@ -1792,7 +1794,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop(0) self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue)) - @subtest + @subtest # type: ignore def test_non_standard_witness(self): """Test detection of non-standard P2WSH witness""" pad = chr(1).encode('latin-1') @@ -1892,7 +1894,7 @@ class SegWitTest(BitcoinTestFramework): self.utxo.pop(0) - @subtest + @subtest # type: ignore def test_upgrade_after_activation(self): """Test the behavior of starting up a segwit-aware node after the softfork has activated.""" @@ -1914,7 +1916,7 @@ class SegWitTest(BitcoinTestFramework): assert_equal(self.nodes[0].getblock(block_hash), self.nodes[2].getblock(block_hash)) height -= 1 - @subtest + @subtest # type: ignore def test_witness_sigops(self): """Test sigop counting is correct inside witnesses.""" diff --git a/test/functional/p2p_sendheaders.py b/test/functional/p2p_sendheaders.py index a8fba306a7..481b1c1841 100755 --- a/test/functional/p2p_sendheaders.py +++ b/test/functional/p2p_sendheaders.py @@ -92,6 +92,7 @@ from test_framework.mininode import ( NODE_WITNESS, P2PInterface, mininode_lock, + MSG_BLOCK, msg_block, msg_getblocks, msg_getdata, @@ -120,7 +121,7 @@ class BaseNode(P2PInterface): """Request data for a list of block hashes.""" msg = msg_getdata() for x in block_hashes: - msg.inv.append(CInv(2, x)) + msg.inv.append(CInv(MSG_BLOCK, x)) self.send_message(msg) def send_get_headers(self, locator, hashstop): @@ -131,7 +132,7 @@ class BaseNode(P2PInterface): def send_block_inv(self, blockhash): msg = msg_inv() - msg.inv = [CInv(2, blockhash)] + msg.inv = [CInv(MSG_BLOCK, blockhash)] self.send_message(msg) def send_header_for_blocks(self, new_blocks): diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py index a999fba818..10f5eea0e5 100755 --- a/test/functional/p2p_tx_download.py +++ b/test/functional/p2p_tx_download.py @@ -63,7 +63,7 @@ class TxDownloadTest(BitcoinTestFramework): txid = 0xdeadbeef self.log.info("Announce the txid from each incoming peer to node 0") - msg = msg_inv([CInv(t=1, h=txid)]) + msg = msg_inv([CInv(t=MSG_TX, h=txid)]) for p in self.nodes[0].p2ps: p.send_and_ping(msg) @@ -104,7 +104,7 @@ class TxDownloadTest(BitcoinTestFramework): self.log.info( "Announce the transaction to all nodes from all {} incoming peers, but never send it".format(NUM_INBOUND)) - msg = msg_inv([CInv(t=1, h=txid)]) + msg = msg_inv([CInv(t=MSG_TX, h=txid)]) for p in self.peers: p.send_and_ping(msg) @@ -135,13 +135,13 @@ class TxDownloadTest(BitcoinTestFramework): with mininode_lock: p.tx_getdata_count = 0 - p.send_message(msg_inv([CInv(t=1, h=i) for i in txids])) + p.send_message(msg_inv([CInv(t=MSG_TX, h=i) for i in txids])) wait_until(lambda: p.tx_getdata_count >= MAX_GETDATA_IN_FLIGHT, lock=mininode_lock) with mininode_lock: assert_equal(p.tx_getdata_count, MAX_GETDATA_IN_FLIGHT) self.log.info("Now check that if we send a NOTFOUND for a transaction, we'll get one more request") - p.send_message(msg_notfound(vec=[CInv(t=1, h=txids[0])])) + p.send_message(msg_notfound(vec=[CInv(t=MSG_TX, h=txids[0])])) wait_until(lambda: p.tx_getdata_count >= MAX_GETDATA_IN_FLIGHT + 1, timeout=10, lock=mininode_lock) with mininode_lock: assert_equal(p.tx_getdata_count, MAX_GETDATA_IN_FLIGHT + 1) @@ -154,7 +154,7 @@ class TxDownloadTest(BitcoinTestFramework): def test_spurious_notfound(self): self.log.info('Check that spurious notfound is ignored') - self.nodes[0].p2ps[0].send_message(msg_notfound(vec=[CInv(1, 1)])) + self.nodes[0].p2ps[0].send_message(msg_notfound(vec=[CInv(MSG_TX, 1)])) def run_test(self): # Setup the p2p connections diff --git a/test/functional/p2p_unrequested_blocks.py b/test/functional/p2p_unrequested_blocks.py index 3aaf4b9977..c323168848 100755 --- a/test/functional/p2p_unrequested_blocks.py +++ b/test/functional/p2p_unrequested_blocks.py @@ -54,7 +54,7 @@ Node1 is unused in tests 3-7: import time from test_framework.blocktools import create_block, create_coinbase, create_tx_with_script -from test_framework.messages import CBlockHeader, CInv, msg_block, msg_headers, msg_inv +from test_framework.messages import CBlockHeader, CInv, MSG_BLOCK, msg_block, msg_headers, msg_inv from test_framework.mininode import mininode_lock, P2PInterface from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -210,7 +210,7 @@ class AcceptBlockTest(BitcoinTestFramework): with mininode_lock: # Clear state so we can check the getdata request test_node.last_message.pop("getdata", None) - test_node.send_message(msg_inv([CInv(2, block_h3.sha256)])) + test_node.send_message(msg_inv([CInv(MSG_BLOCK, block_h3.sha256)])) test_node.sync_with_ping() with mininode_lock: diff --git a/test/functional/rpc_getaddressinfo_label_deprecation.py b/test/functional/rpc_getaddressinfo_label_deprecation.py index 4c6b2fe5cf..09545ebce7 100755 --- a/test/functional/rpc_getaddressinfo_label_deprecation.py +++ b/test/functional/rpc_getaddressinfo_label_deprecation.py @@ -4,7 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ Test deprecation of the RPC getaddressinfo `label` field. It has been -superceded by the `labels` field. +superseded by the `labels` field. """ from test_framework.test_framework import BitcoinTestFramework diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index 376bb35f07..58d8c4abe1 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -28,6 +28,7 @@ from test_framework.messages import ( NODE_WITNESS, ) + def assert_net_servicesnames(servicesflag, servicenames): """Utility that checks if all flags are correctly decoded in `getpeerinfo` and `getnetworkinfo`. @@ -40,6 +41,7 @@ def assert_net_servicesnames(servicesflag, servicenames): servicesflag_generated |= getattr(test_framework.messages, 'NODE_' + servicename) assert servicesflag_generated == servicesflag + class NetTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -57,6 +59,7 @@ class NetTest(BitcoinTestFramework): self._test_getnetworkinfo() self._test_getaddednodeinfo() self._test_getpeerinfo() + self.test_service_flags() self._test_getnodeaddresses() def _test_connection_count(self): @@ -139,6 +142,11 @@ class NetTest(BitcoinTestFramework): for info in peer_info: assert_net_servicesnames(int(info[0]["services"], 0x10), info[0]["servicesnames"]) + def test_service_flags(self): + self.nodes[0].add_p2p_connection(P2PInterface(), services=(1 << 4) | (1 << 63)) + assert_equal(['UNKNOWN[2^4]', 'UNKNOWN[2^63]'], self.nodes[0].getpeerinfo()[-1]['servicesnames']) + self.nodes[0].disconnect_p2ps() + def _test_getnodeaddresses(self): self.nodes[0].add_p2p_connection(P2PInterface()) @@ -174,5 +182,6 @@ class NetTest(BitcoinTestFramework): node_addresses = self.nodes[0].getnodeaddresses(LARGE_REQUEST_COUNT) assert_greater_than(LARGE_REQUEST_COUNT, len(node_addresses)) + if __name__ == '__main__': NetTest().main() diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py index b75ce15f2e..daf02fc4f3 100755 --- a/test/functional/rpc_users.py +++ b/test/functional/rpc_users.py @@ -20,6 +20,7 @@ import string import configparser import sys + def call_with_auth(node, user, password): url = urllib.parse.urlparse(node.url) headers = {"Authorization": "Basic " + str_to_b64str('{}:{}'.format(user, password))} @@ -64,9 +65,9 @@ class HTTPBasicsTest(BitcoinTestFramework): self.password = lines[3] with open(os.path.join(get_datadir_path(self.options.tmpdir, 0), "bitcoin.conf"), 'a', encoding='utf8') as f: - f.write(rpcauth+"\n") - f.write(rpcauth2+"\n") - f.write(rpcauth3+"\n") + f.write(rpcauth + "\n") + f.write(rpcauth2 + "\n") + f.write(rpcauth3 + "\n") with open(os.path.join(get_datadir_path(self.options.tmpdir, 1), "bitcoin.conf"), 'a', encoding='utf8') as f: f.write("rpcuser={}\n".format(self.rpcuser)) f.write("rpcpassword={}\n".format(self.rpcpassword)) @@ -76,19 +77,16 @@ class HTTPBasicsTest(BitcoinTestFramework): assert_equal(200, call_with_auth(node, user, password).status) self.log.info('Wrong...') - assert_equal(401, call_with_auth(node, user, password+'wrong').status) + assert_equal(401, call_with_auth(node, user, password + 'wrong').status) self.log.info('Wrong...') - assert_equal(401, call_with_auth(node, user+'wrong', password).status) + assert_equal(401, call_with_auth(node, user + 'wrong', password).status) self.log.info('Wrong...') - assert_equal(401, call_with_auth(node, user+'wrong', password+'wrong').status) + assert_equal(401, call_with_auth(node, user + 'wrong', password + 'wrong').status) def run_test(self): - - ################################################## - # Check correctness of the rpcauth config option # - ################################################## + self.log.info('Check correctness of the rpcauth config option') url = urllib.parse.urlparse(self.nodes[0].url) self.test_auth(self.nodes[0], url.username, url.password) @@ -96,12 +94,18 @@ class HTTPBasicsTest(BitcoinTestFramework): self.test_auth(self.nodes[0], 'rt2', self.rt2password) self.test_auth(self.nodes[0], self.user, self.password) - ############################################################### - # Check correctness of the rpcuser/rpcpassword config options # - ############################################################### + self.log.info('Check correctness of the rpcuser/rpcpassword config options') url = urllib.parse.urlparse(self.nodes[1].url) self.test_auth(self.nodes[1], self.rpcuser, self.rpcpassword) + self.log.info('Check that failure to write cookie file will abort the node gracefully') + self.stop_node(0) + cookie_file = os.path.join(get_datadir_path(self.options.tmpdir, 0), self.chain, '.cookie.tmp') + os.mkdir(cookie_file) + init_error = 'Error: Unable to start HTTP server. See debug log for details.' + self.nodes[0].assert_start_raises_init_error(expected_msg=init_error) + + if __name__ == '__main__': - HTTPBasicsTest ().main () + HTTPBasicsTest().main() diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py index 8f410f233e..9506b63f82 100644 --- a/test/functional/test_framework/address.py +++ b/test/functional/test_framework/address.py @@ -5,12 +5,15 @@ """Encode and decode BASE58, P2PKH and P2SH addresses.""" import enum +import unittest from .script import hash256, hash160, sha256, CScript, OP_0 from .util import hex_str_to_bytes from . import segwit_addr +from test_framework.util import assert_equal + ADDRESS_BCRT1_UNSPENDABLE = 'bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj' ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR = 'addr(bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj)#juyq9d97' # Coins sent to this address can be spent with a witness stack of just OP_TRUE @@ -41,7 +44,32 @@ def byte_to_base58(b, version): str = str[2:] return result -# TODO: def base58_decode + +def base58_to_byte(s, verify_checksum=True): + if not s: + return b'' + n = 0 + for c in s: + n *= 58 + assert c in chars + digit = chars.index(c) + n += digit + h = '%x' % n + if len(h) % 2: + h = '0' + h + res = n.to_bytes((n.bit_length() + 7) // 8, 'big') + pad = 0 + for c in s: + if c == chars[0]: + pad += 1 + else: + break + res = b'\x00' * pad + res + if verify_checksum: + assert_equal(hash256(res[:-4])[:4], res[-4:]) + + return res[1:-4], int(res[0]) + def keyhash_to_p2pkh(hash, main = False): assert len(hash) == 20 @@ -100,3 +128,22 @@ def check_script(script): if (type(script) is bytes or type(script) is CScript): return script assert False + + +class TestFrameworkScript(unittest.TestCase): + def test_base58encodedecode(self): + def check_base58(data, version): + self.assertEqual(base58_to_byte(byte_to_base58(data, version)), (data, version)) + + check_base58(b'\x1f\x8e\xa1p*{\xd4\x94\x1b\xca\tA\xb8R\xc4\xbb\xfe\xdb.\x05', 111) + check_base58(b':\x0b\x05\xf4\xd7\xf6l;\xa7\x00\x9fE50)l\x84\\\xc9\xcf', 111) + check_base58(b'A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111) + check_base58(b'\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111) + check_base58(b'\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111) + check_base58(b'\0\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111) + check_base58(b'\x1f\x8e\xa1p*{\xd4\x94\x1b\xca\tA\xb8R\xc4\xbb\xfe\xdb.\x05', 0) + check_base58(b':\x0b\x05\xf4\xd7\xf6l;\xa7\x00\x9fE50)l\x84\\\xc9\xcf', 0) + check_base58(b'A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0) + check_base58(b'\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0) + check_base58(b'\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0) + check_base58(b'\0\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0) diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index d741b00ba0..afc1995009 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -4,6 +4,8 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Utilities for manipulating blocks and transactions.""" +import unittest + from .address import ( key_to_p2sh_p2wpkh, key_to_p2wpkh, @@ -217,3 +219,9 @@ def send_to_witness(use_p2wsh, node, utxo, pubkey, encode_p2sh, amount, sign=Tru tx_to_witness = ToHex(tx) return node.sendrawtransaction(tx_to_witness) + +class TestFrameworkBlockTools(unittest.TestCase): + def test_create_coinbase(self): + height = 20 + coinbase_tx = create_coinbase(height=height) + assert_equal(CScriptNum.decode(coinbase_tx.vin[0].scriptSig), height) diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 4855f62a8f..4d1dd4422e 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -54,9 +54,12 @@ NODE_NETWORK_LIMITED = (1 << 10) MSG_TX = 1 MSG_BLOCK = 2 MSG_FILTERED_BLOCK = 3 +MSG_CMPCT_BLOCK = 4 MSG_WITNESS_FLAG = 1 << 30 MSG_TYPE_MASK = 0xffffffff >> 2 +FILTER_TYPE_BASIC = 0 + # Serialization/deserialization tools def sha256(s): return hashlib.new('sha256', s).digest() @@ -1512,3 +1515,154 @@ class msg_no_witness_blocktxn(msg_blocktxn): def serialize(self): return self.block_transactions.serialize(with_witness=False) + + +class msg_getcfilters: + __slots__ = ("filter_type", "start_height", "stop_hash") + msgtype = b"getcfilters" + + def __init__(self, filter_type, start_height, stop_hash): + self.filter_type = filter_type + self.start_height = start_height + self.stop_hash = stop_hash + + def deserialize(self, f): + self.filter_type = struct.unpack("<B", f.read(1))[0] + self.start_height = struct.unpack("<I", f.read(4))[0] + self.stop_hash = deser_uint256(f) + + def serialize(self): + r = b"" + r += struct.pack("<B", self.filter_type) + r += struct.pack("<I", self.start_height) + r += ser_uint256(self.stop_hash) + return r + + def __repr__(self): + return "msg_getcfilters(filter_type={:#x}, start_height={}, stop_hash={:x})".format( + self.filter_type, self.start_height, self.stop_hash) + +class msg_cfilter: + __slots__ = ("filter_type", "block_hash", "filter_data") + msgtype = b"cfilter" + + def __init__(self, filter_type=None, block_hash=None, filter_data=None): + self.filter_type = filter_type + self.block_hash = block_hash + self.filter_data = filter_data + + def deserialize(self, f): + self.filter_type = struct.unpack("<B", f.read(1))[0] + self.block_hash = deser_uint256(f) + self.filter_data = deser_string(f) + + def serialize(self): + r = b"" + r += struct.pack("<B", self.filter_type) + r += ser_uint256(self.block_hash) + r += ser_string(self.filter_data) + return r + + def __repr__(self): + return "msg_cfilter(filter_type={:#x}, block_hash={:x})".format( + self.filter_type, self.block_hash) + +class msg_getcfheaders: + __slots__ = ("filter_type", "start_height", "stop_hash") + msgtype = b"getcfheaders" + + def __init__(self, filter_type, start_height, stop_hash): + self.filter_type = filter_type + self.start_height = start_height + self.stop_hash = stop_hash + + def deserialize(self, f): + self.filter_type = struct.unpack("<B", f.read(1))[0] + self.start_height = struct.unpack("<I", f.read(4))[0] + self.stop_hash = deser_uint256(f) + + def serialize(self): + r = b"" + r += struct.pack("<B", self.filter_type) + r += struct.pack("<I", self.start_height) + r += ser_uint256(self.stop_hash) + return r + + def __repr__(self): + return "msg_getcfheaders(filter_type={:#x}, start_height={}, stop_hash={:x})".format( + self.filter_type, self.start_height, self.stop_hash) + +class msg_cfheaders: + __slots__ = ("filter_type", "stop_hash", "prev_header", "hashes") + msgtype = b"cfheaders" + + def __init__(self, filter_type=None, stop_hash=None, prev_header=None, hashes=None): + self.filter_type = filter_type + self.stop_hash = stop_hash + self.prev_header = prev_header + self.hashes = hashes + + def deserialize(self, f): + self.filter_type = struct.unpack("<B", f.read(1))[0] + self.stop_hash = deser_uint256(f) + self.prev_header = deser_uint256(f) + self.hashes = deser_uint256_vector(f) + + def serialize(self): + r = b"" + r += struct.pack("<B", self.filter_type) + r += ser_uint256(self.stop_hash) + r += ser_uint256(self.prev_header) + r += ser_uint256_vector(self.hashes) + return r + + def __repr__(self): + return "msg_cfheaders(filter_type={:#x}, stop_hash={:x})".format( + self.filter_type, self.stop_hash) + +class msg_getcfcheckpt: + __slots__ = ("filter_type", "stop_hash") + msgtype = b"getcfcheckpt" + + def __init__(self, filter_type, stop_hash): + self.filter_type = filter_type + self.stop_hash = stop_hash + + def deserialize(self, f): + self.filter_type = struct.unpack("<B", f.read(1))[0] + self.stop_hash = deser_uint256(f) + + def serialize(self): + r = b"" + r += struct.pack("<B", self.filter_type) + r += ser_uint256(self.stop_hash) + return r + + def __repr__(self): + return "msg_getcfcheckpt(filter_type={:#x}, stop_hash={:x})".format( + self.filter_type, self.stop_hash) + +class msg_cfcheckpt: + __slots__ = ("filter_type", "stop_hash", "headers") + msgtype = b"cfcheckpt" + + def __init__(self, filter_type=None, stop_hash=None, headers=None): + self.filter_type = filter_type + self.stop_hash = stop_hash + self.headers = headers + + def deserialize(self, f): + self.filter_type = struct.unpack("<B", f.read(1))[0] + self.stop_hash = deser_uint256(f) + self.headers = deser_uint256_vector(f) + + def serialize(self): + r = b"" + r += struct.pack("<B", self.filter_type) + r += ser_uint256(self.stop_hash) + r += ser_uint256_vector(self.headers) + return r + + def __repr__(self): + return "msg_cfcheckpt(filter_type={:#x}, stop_hash={:x})".format( + self.filter_type, self.stop_hash) diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index 31cec66ee7..45063aaff2 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -31,6 +31,9 @@ from test_framework.messages import ( msg_block, MSG_BLOCK, msg_blocktxn, + msg_cfcheckpt, + msg_cfheaders, + msg_cfilter, msg_cmpctblock, msg_feefilter, msg_filteradd, @@ -67,6 +70,9 @@ MESSAGEMAP = { b"addr": msg_addr, b"block": msg_block, b"blocktxn": msg_blocktxn, + b"cfcheckpt": msg_cfcheckpt, + b"cfheaders": msg_cfheaders, + b"cfilter": msg_cfilter, b"cmpctblock": msg_cmpctblock, b"feefilter": msg_feefilter, b"filteradd": msg_filteradd, @@ -120,9 +126,9 @@ class P2PConnection(asyncio.Protocol): def is_connected(self): return self._transport is not None - def peer_connect(self, dstaddr, dstport, *, net, factor): + def peer_connect(self, dstaddr, dstport, *, net, timeout_factor): assert not self.is_connected - self.factor = factor + self.timeout_factor = timeout_factor self.dstaddr = dstaddr self.dstport = dstport # The initial message to send after the connection was made: @@ -328,6 +334,9 @@ class P2PInterface(P2PConnection): def on_addr(self, message): pass def on_block(self, message): pass def on_blocktxn(self, message): pass + def on_cfcheckpt(self, message): pass + def on_cfheaders(self, message): pass + def on_cfilter(self, message): pass def on_cmpctblock(self, message): pass def on_feefilter(self, message): pass def on_filteradd(self, message): pass @@ -368,8 +377,8 @@ class P2PInterface(P2PConnection): # Connection helper methods - def wait_until(self, test_function, timeout): - wait_until(test_function, timeout=timeout, lock=mininode_lock, factor=self.factor) + def wait_until(self, test_function, timeout=60): + wait_until(test_function, timeout=timeout, lock=mininode_lock, timeout_factor=self.timeout_factor) def wait_for_disconnect(self, timeout=60): test_function = lambda: not self.is_connected @@ -642,12 +651,24 @@ class P2PTxInvStore(P2PInterface): self.tx_invs_received = defaultdict(int) def on_inv(self, message): + super().on_inv(message) # Send getdata in response. # Store how many times invs have been received for each tx. for i in message.inv: if i.type == MSG_TX: # save txid self.tx_invs_received[i.hash] += 1 + super().on_inv(message) + def get_invs(self): with mininode_lock: return list(self.tx_invs_received.keys()) + + def wait_for_broadcast(self, txns, timeout=60): + """Waits for the txns (list of txids) to complete initial broadcast. + The mempool should mark unbroadcast=False for these transactions. + """ + # Wait until invs have been received (and getdatas sent) for each txid. + self.wait_until(lambda: set(self.get_invs()) == set([int(tx, 16) for tx in txns]), timeout) + # Flush messages and wait for the getdatas to be processed + self.sync_with_ping() diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 9102266456..cc5f8307d3 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -9,6 +9,7 @@ This file is modified from python-bitcoinlib. import hashlib import struct import unittest +from typing import List, Dict from .messages import ( CTransaction, @@ -21,7 +22,7 @@ from .messages import ( ) MAX_SCRIPT_ELEMENT_SIZE = 520 -OPCODE_NAMES = {} +OPCODE_NAMES = {} # type: Dict[CScriptOp, str] def hash160(s): return hashlib.new('ripemd160', sha256(s)).digest() @@ -37,7 +38,7 @@ def bn2vch(v): # Serialize to bytes return encoded_v.to_bytes(n_bytes, 'little') -_opcode_instances = [] +_opcode_instances = [] # type: List[CScriptOp] class CScriptOp(int): """A single script opcode""" __slots__ = () @@ -731,3 +732,9 @@ class TestFrameworkScript(unittest.TestCase): self.assertEqual(bn2vch(0xFFFFFFFF), bytes([0xFF, 0xFF, 0xFF, 0xFF, 0x00])) self.assertEqual(bn2vch(123456789), bytes([0x15, 0xCD, 0x5B, 0x07])) self.assertEqual(bn2vch(-54321), bytes([0x31, 0xD4, 0x80])) + + def test_cscriptnum_encoding(self): + # round-trip negative and multi-byte CScriptNums + values = [0, 1, -1, -2, 127, 128, -255, 256, (1 << 15) - 1, -(1 << 16), (1 << 24) - 1, (1 << 31), 1 - (1 << 32), 1 << 40, 1500, -1500] + for value in values: + self.assertEqual(CScriptNum.decode(CScriptNum.encode(CScriptNum(value))), value) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 17acd4dcd3..9f5e9e5f0d 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -91,6 +91,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): This class also contains various public and private helper methods.""" + chain = None # type: str + setup_clean_chain = None # type: bool + def __init__(self): """Sets test framework defaults. Do not override this method. Instead, override the set_test_params() method""" self.chain = 'regtest' @@ -102,7 +105,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.bind_to_localhost_only = True self.set_test_params() self.parse_args() - self.rpc_timeout = int(self.rpc_timeout * self.options.factor) # optionally, increase timeout by a factor + if self.options.timeout_factor == 0 : + self.options.timeout_factor = 99999 + self.rpc_timeout = int(self.rpc_timeout * self.options.timeout_factor) # optionally, increase timeout by a factor def main(self): """Main function. This should not be overridden by the subclass test scripts.""" @@ -138,6 +143,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): sys.exit(exit_code) def parse_args(self): + previous_releases_path = os.getenv("PREVIOUS_RELEASES_DIR") or os.getcwd() + "/releases" parser = argparse.ArgumentParser(usage="%(prog)s [options]") parser.add_argument("--nocleanup", dest="nocleanup", default=False, action="store_true", help="Leave bitcoinds and test.* datadir on exit or error") @@ -152,6 +158,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): help="Print out all RPC calls as they are made") parser.add_argument("--portseed", dest="port_seed", default=os.getpid(), type=int, help="The seed to use for assigning port numbers (default: current process id)") + parser.add_argument("--previous-releases", dest="prev_releases", action="store_true", + default=os.path.isdir(previous_releases_path) and bool(os.listdir(previous_releases_path)), + help="Force test of previous releases (default: %(default)s)") parser.add_argument("--coveragedir", dest="coveragedir", help="Write tested RPC commands into this directory") parser.add_argument("--configfile", dest="configfile", @@ -169,9 +178,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): help="set a random seed for deterministically reproducing a previous test run") parser.add_argument("--descriptors", default=False, action="store_true", help="Run test using a descriptor wallet") - parser.add_argument('--factor', type=float, default=1.0, help='adjust test timeouts by a factor') + parser.add_argument('--timeout-factor', dest="timeout_factor", type=float, default=1.0, help='adjust test timeouts by a factor. Setting it to 0 disables all timeouts') self.add_options(parser) self.options = parser.parse_args() + self.options.previous_releases_path = previous_releases_path def setup(self): """Call this method to start up the test framework object with options set.""" @@ -185,10 +195,18 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): config = configparser.ConfigParser() config.read_file(open(self.options.configfile)) self.config = config - self.options.bitcoind = os.getenv("BITCOIND", default=config["environment"]["BUILDDIR"] + '/src/bitcoind' + config["environment"]["EXEEXT"]) - self.options.bitcoincli = os.getenv("BITCOINCLI", default=config["environment"]["BUILDDIR"] + '/src/bitcoin-cli' + config["environment"]["EXEEXT"]) - - self.options.previous_releases_path = os.getenv("PREVIOUS_RELEASES_DIR") or os.getcwd() + "/releases" + fname_bitcoind = os.path.join( + config["environment"]["BUILDDIR"], + "src", + "bitcoind" + config["environment"]["EXEEXT"], + ) + fname_bitcoincli = os.path.join( + config["environment"]["BUILDDIR"], + "src", + "bitcoin-cli" + config["environment"]["EXEEXT"], + ) + self.options.bitcoind = os.getenv("BITCOIND", default=fname_bitcoind) + self.options.bitcoincli = os.getenv("BITCOINCLI", default=fname_bitcoincli) os.environ['PATH'] = os.pathsep.join([ os.path.join(config['environment']['BUILDDIR'], 'src'), @@ -275,7 +293,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): exit_code = TEST_EXIT_SKIPPED else: self.log.error("Test failed. Test logging available at %s/test_framework.log", self.options.tmpdir) + self.log.error("") self.log.error("Hint: Call {} '{}' to consolidate all logs".format(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../combine_logs.py"), self.options.tmpdir)) + self.log.error("") + self.log.error("If this failure happened unexpectedly or intermittently, please file a bug and provide a link or upload of the combined log.") + self.log.error(self.config['environment']['PACKAGE_BUGREPORT']) + self.log.error("") exit_code = TEST_EXIT_FAILED # Logging.shutdown will not remove stream- and filehandlers, so we must # do it explicitly. Handlers are removed so the next test run can apply @@ -387,7 +410,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): # Public helper methods. These can be accessed by the subclass test scripts. - def add_nodes(self, num_nodes, extra_args=None, *, rpchost=None, binary=None, binary_cli=None, versions=None): + def add_nodes(self, num_nodes: int, extra_args=None, *, rpchost=None, binary=None, binary_cli=None, versions=None): """Instantiate TestNode objects. Should only be called once after the nodes have been specified in @@ -435,7 +458,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): chain=self.chain, rpchost=rpchost, timewait=self.rpc_timeout, - factor=self.options.factor, + timeout_factor=self.options.timeout_factor, bitcoind=binary[i], bitcoin_cli=binary_cli[i], version=versions[i], @@ -581,7 +604,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): extra_args=['-disablewallet'], rpchost=None, timewait=self.rpc_timeout, - factor=self.options.factor, + timeout_factor=self.options.timeout_factor, bitcoind=self.options.bitcoind, bitcoin_cli=self.options.bitcoincli, coverage_dir=None, @@ -671,17 +694,11 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): def has_previous_releases(self): """Checks whether previous releases are present and enabled.""" - if os.getenv("TEST_PREVIOUS_RELEASES") == "false": - # disabled - return False - if not os.path.isdir(self.options.previous_releases_path): - if os.getenv("TEST_PREVIOUS_RELEASES") == "true": - raise AssertionError("TEST_PREVIOUS_RELEASES=true but releases missing: {}".format( + if self.options.prev_releases: + raise AssertionError("Force test of previous releases but releases missing: {}".format( self.options.previous_releases_path)) - # missing - return False - return True + return self.options.prev_releases def is_cli_compiled(self): """Checks whether bitcoin-cli was compiled.""" diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 404c1b207b..ebc0501e11 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -62,7 +62,7 @@ class TestNode(): To make things easier for the test writer, any unrecognised messages will be dispatched to the RPC connection.""" - def __init__(self, i, datadir, *, chain, rpchost, timewait, factor, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False): + def __init__(self, i, datadir, *, chain, rpchost, timewait, timeout_factor, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False): """ Kwargs: start_perf (bool): If True, begin profiling the node with `perf` as soon as @@ -128,7 +128,7 @@ class TestNode(): self.perf_subprocesses = {} self.p2ps = [] - self.factor = factor + self.timeout_factor = timeout_factor AddressKeyPair = collections.namedtuple('AddressKeyPair', ['address', 'key']) PRIV_KEYS = [ @@ -219,7 +219,12 @@ class TestNode(): raise FailedToStartError(self._node_msg( 'bitcoind exited with status {} during initialization'.format(self.process.returncode))) try: - rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.chain, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir) + rpc = get_rpc_proxy( + rpc_url(self.datadir, self.index, self.chain, self.rpchost), + self.index, + timeout=self.rpc_timeout // 2, # Shorter timeout to allow for one retry in case of ETIMEDOUT + coveragedir=self.coverage_dir, + ) rpc.getblockcount() # If the call to getblockcount() succeeds then the RPC connection is up if self.version_is_at_least(190000): @@ -241,7 +246,7 @@ class TestNode(): # The wait is done here to make tests as robust as possible # and prevent racy tests and intermittent failures as much # as possible. Some tests might not need this, but the - # overhead is trivial, and the added gurantees are worth + # overhead is trivial, and the added guarantees are worth # the minimal performance cost. self.log.debug("RPC successfully started") if self.use_cli: @@ -260,7 +265,11 @@ class TestNode(): # succeeds. Try again to properly raise the FailedToStartError pass except OSError as e: - if e.errno != errno.ECONNREFUSED: # Port not yet open? + if e.errno == errno.ETIMEDOUT: + pass # Treat identical to ConnectionResetError + elif e.errno == errno.ECONNREFUSED: + pass # Port not yet open? + else: raise # unknown OS error except ValueError as e: # cookie file not found and no rpcuser or rpcpassword; bitcoind is still starting if "No RPC credentials" not in str(e): @@ -349,13 +358,13 @@ class TestNode(): return True def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT): - wait_until(self.is_node_stopped, timeout=timeout, factor=self.factor) + wait_until(self.is_node_stopped, timeout=timeout, timeout_factor=self.timeout_factor) @contextlib.contextmanager def assert_debug_log(self, expected_msgs, unexpected_msgs=None, timeout=2): if unexpected_msgs is None: unexpected_msgs = [] - time_end = time.time() + timeout * self.factor + time_end = time.time() + timeout * self.timeout_factor debug_log = os.path.join(self.datadir, self.chain, 'debug.log') with open(debug_log, encoding='utf-8') as dl: dl.seek(0, 2) @@ -512,7 +521,7 @@ class TestNode(): if 'dstaddr' not in kwargs: kwargs['dstaddr'] = '127.0.0.1' - p2p_conn.peer_connect(**kwargs, net=self.chain, factor=self.factor)() + p2p_conn.peer_connect(**kwargs, net=self.chain, timeout_factor=self.timeout_factor)() self.p2ps.append(p2p_conn) if wait_for_verack: # Wait for the node to send us the version and verack @@ -526,7 +535,7 @@ class TestNode(): # transaction that will be added to the mempool as soon as we return here. # # So syncing here is redundant when we only want to send a message, but the cost is low (a few milliseconds) - # in comparision to the upside of making tests less fragile and unexpected intermittent errors less likely. + # in comparison to the upside of making tests less fragile and unexpected intermittent errors less likely. p2p_conn.sync_with_ping() return p2p_conn @@ -562,6 +571,8 @@ class TestNodeCLIAttr: def arg_to_cli(arg): if isinstance(arg, bool): return str(arg).lower() + elif arg is None: + return 'null' elif isinstance(arg, dict) or isinstance(arg, list): return json.dumps(arg, default=EncodeDecimal) else: @@ -632,27 +643,13 @@ class RPCOverloadWrapper(): def __getattr__(self, name): return getattr(self.rpc, name) - def createwallet(self, wallet_name, disable_private_keys=None, blank=None, passphrase=None, avoid_reuse=None, descriptors=None): - if self.is_cli: - if disable_private_keys is None: - disable_private_keys = 'null' - if blank is None: - blank = 'null' - if passphrase is None: - passphrase = '' - if avoid_reuse is None: - avoid_reuse = 'null' + def createwallet(self, wallet_name, disable_private_keys=None, blank=None, passphrase='', avoid_reuse=None, descriptors=None): if descriptors is None: descriptors = self.descriptors return self.__getattr__('createwallet')(wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors) def importprivkey(self, privkey, label=None, rescan=None): wallet_info = self.getwalletinfo() - if self.is_cli: - if label is None: - label = 'null' - if rescan is None: - rescan = 'null' if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']): return self.__getattr__('importprivkey')(privkey, label, rescan) desc = descsum_create('combo(' + privkey + ')') @@ -667,11 +664,6 @@ class RPCOverloadWrapper(): def addmultisigaddress(self, nrequired, keys, label=None, address_type=None): wallet_info = self.getwalletinfo() - if self.is_cli: - if label is None: - label = 'null' - if address_type is None: - address_type = 'null' if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']): return self.__getattr__('addmultisigaddress')(nrequired, keys, label, address_type) cms = self.createmultisig(nrequired, keys, address_type) @@ -687,11 +679,6 @@ class RPCOverloadWrapper(): def importpubkey(self, pubkey, label=None, rescan=None): wallet_info = self.getwalletinfo() - if self.is_cli: - if label is None: - label = 'null' - if rescan is None: - rescan = 'null' if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']): return self.__getattr__('importpubkey')(pubkey, label, rescan) desc = descsum_create('combo(' + pubkey + ')') @@ -706,13 +693,6 @@ class RPCOverloadWrapper(): def importaddress(self, address, label=None, rescan=None, p2sh=None): wallet_info = self.getwalletinfo() - if self.is_cli: - if label is None: - label = 'null' - if rescan is None: - rescan = 'null' - if p2sh is None: - p2sh = 'null' if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']): return self.__getattr__('importaddress')(address, label, rescan, p2sh) is_hex = False diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 6cfb22befe..52306c8c3d 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -208,10 +208,10 @@ def str_to_b64str(string): def satoshi_round(amount): return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN) -def wait_until(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, factor=1.0): +def wait_until(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, timeout_factor=1.0): if attempts == float('inf') and timeout == float('inf'): timeout = 60 - timeout = timeout * factor + timeout = timeout * timeout_factor attempt = 0 time_end = time.time() + timeout @@ -413,7 +413,11 @@ def connect_nodes(from_connection, node_num): from_connection.addnode(ip_port, "onetry") # poll until version handshake complete to avoid race conditions # with transaction relaying - wait_until(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo())) + # See comments in net_processing: + # * Must have a version message before anything else + # * Must have a verack message before anything else + wait_until(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo())) + wait_until(lambda: all(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo())) def sync_blocks(rpc_connections, *, wait=1, timeout=60): diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 45f591ba4a..0ea65c68b8 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -42,7 +42,7 @@ except UnicodeDecodeError: if os.name != 'nt' or sys.getwindowsversion() >= (10, 0, 14393): if os.name == 'nt': import ctypes - kernel32 = ctypes.windll.kernel32 + kernel32 = ctypes.windll.kernel32 # type: ignore ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 STD_OUTPUT_HANDLE = -11 STD_ERROR_HANDLE = -12 @@ -67,6 +67,8 @@ TEST_EXIT_PASSED = 0 TEST_EXIT_SKIPPED = 77 TEST_FRAMEWORK_MODULES = [ + "address", + "blocktools", "script", ] @@ -157,6 +159,7 @@ BASE_SCRIPTS = [ 'rpc_deprecated.py', 'wallet_disable.py', 'p2p_addr_relay.py', + 'p2p_getdata.py', 'rpc_net.py', 'wallet_keypool.py', 'wallet_keypool.py --descriptors', @@ -225,6 +228,7 @@ BASE_SCRIPTS = [ 'feature_loadblock.py', 'p2p_dos_header_tree.py', 'p2p_unrequested_blocks.py', + 'p2p_blockfilters.py', 'feature_includeconf.py', 'feature_asmap.py', 'mempool_unbroadcast.py', diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index 039ce7daee..524e1593ba 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -203,6 +203,14 @@ class ToolWalletTest(BitcoinTestFramework): assert_equal(shasum_after, shasum_before) self.log.debug('Wallet file shasum unchanged\n') + def test_salvage(self): + # TODO: Check salvage actually salvages and doesn't break things. https://github.com/bitcoin/bitcoin/issues/7463 + self.log.info('Check salvage') + self.start_node(0, ['-wallet=salvage']) + self.stop_node(0) + + self.assert_tool_output('', '-wallet=salvage', 'salvage') + def run_test(self): self.wallet_path = os.path.join(self.nodes[0].datadir, self.chain, 'wallets', 'wallet.dat') self.test_invalid_tool_commands_and_args() @@ -211,7 +219,7 @@ class ToolWalletTest(BitcoinTestFramework): self.test_tool_wallet_info_after_transaction() self.test_tool_wallet_create_on_existing_wallet() self.test_getwalletinfo_on_different_wallet() - + self.test_salvage() if __name__ == '__main__': ToolWalletTest().main() diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 9e295af330..797c903dd3 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -404,8 +404,6 @@ class WalletTest(BitcoinTestFramework): '-reindex', '-zapwallettxes=1', '-zapwallettxes=2', - # disabled until issue is fixed: https://github.com/bitcoin/bitcoin/issues/7463 - # '-salvagewallet', ] chainlimit = 6 for m in maintenance: diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index 09f89eb59d..c441b75652 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -11,7 +11,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, connect_nodes, - assert_raises_rpc_error + assert_raises_rpc_error, ) @@ -32,11 +32,11 @@ class WalletHDTest(BitcoinTestFramework): # create an internal key change_addr = self.nodes[1].getrawchangeaddress() - change_addrV= self.nodes[1].getaddressinfo(change_addr) + change_addrV = self.nodes[1].getaddressinfo(change_addr) if self.options.descriptors: assert_equal(change_addrV["hdkeypath"], "m/84'/1'/0'/1/0") else: - assert_equal(change_addrV["hdkeypath"], "m/0'/1'/0'") #first internal child key + assert_equal(change_addrV["hdkeypath"], "m/0'/1'/0'") #first internal child key # Import a non-HD private key in the HD wallet non_hd_add = 'bcrt1qmevj8zfx0wdvp05cqwkmr6mxkfx60yezwjksmt' @@ -58,7 +58,7 @@ class WalletHDTest(BitcoinTestFramework): if self.options.descriptors: assert_equal(hd_info["hdkeypath"], "m/84'/1'/0'/0/" + str(i)) else: - assert_equal(hd_info["hdkeypath"], "m/0'/0'/"+str(i)+"'") + assert_equal(hd_info["hdkeypath"], "m/0'/0'/" + str(i) + "'") assert_equal(hd_info["hdmasterfingerprint"], hd_fingerprint) self.nodes[0].sendtoaddress(hd_add, 1) self.nodes[0].generate(1) @@ -67,11 +67,11 @@ class WalletHDTest(BitcoinTestFramework): # create an internal key (again) change_addr = self.nodes[1].getrawchangeaddress() - change_addrV= self.nodes[1].getaddressinfo(change_addr) + change_addrV = self.nodes[1].getaddressinfo(change_addr) if self.options.descriptors: assert_equal(change_addrV["hdkeypath"], "m/84'/1'/0'/1/1") else: - assert_equal(change_addrV["hdkeypath"], "m/0'/1'/1'") #second internal child key + assert_equal(change_addrV["hdkeypath"], "m/0'/1'/1'") #second internal child key self.sync_all() assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1) @@ -82,7 +82,10 @@ class WalletHDTest(BitcoinTestFramework): # otherwise node1 would auto-recover all funds in flag the keypool keys as used shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "blocks")) shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "chainstate")) - shutil.copyfile(os.path.join(self.nodes[1].datadir, "hd.bak"), os.path.join(self.nodes[1].datadir, self.chain, 'wallets', "wallet.dat")) + shutil.copyfile( + os.path.join(self.nodes[1].datadir, "hd.bak"), + os.path.join(self.nodes[1].datadir, self.chain, 'wallets', "wallet.dat"), + ) self.start_node(1) # Assert that derivation is deterministic @@ -93,7 +96,7 @@ class WalletHDTest(BitcoinTestFramework): if self.options.descriptors: assert_equal(hd_info_2["hdkeypath"], "m/84'/1'/0'/0/" + str(i)) else: - assert_equal(hd_info_2["hdkeypath"], "m/0'/0'/"+str(i)+"'") + assert_equal(hd_info_2["hdkeypath"], "m/0'/0'/" + str(i) + "'") assert_equal(hd_info_2["hdmasterfingerprint"], hd_fingerprint) assert_equal(hd_add, hd_add_2) connect_nodes(self.nodes[0], 1) @@ -108,7 +111,10 @@ class WalletHDTest(BitcoinTestFramework): self.stop_node(1) shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "blocks")) shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "chainstate")) - shutil.copyfile(os.path.join(self.nodes[1].datadir, "hd.bak"), os.path.join(self.nodes[1].datadir, self.chain, "wallets", "wallet.dat")) + shutil.copyfile( + os.path.join(self.nodes[1].datadir, "hd.bak"), + os.path.join(self.nodes[1].datadir, self.chain, "wallets", "wallet.dat"), + ) self.start_node(1, extra_args=self.extra_args[1]) connect_nodes(self.nodes[0], 1) self.sync_all() @@ -142,8 +148,9 @@ class WalletHDTest(BitcoinTestFramework): new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] assert orig_masterkeyid != new_masterkeyid addr = self.nodes[1].getnewaddress() - assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], 'm/0\'/0\'/0\'') # Make sure the new address is the first from the keypool - self.nodes[1].keypoolrefill(1) # Fill keypool with 1 key + # Make sure the new address is the first from the keypool + assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], 'm/0\'/0\'/0\'') + self.nodes[1].keypoolrefill(1) # Fill keypool with 1 key # Set a new HD seed on node 1 without flushing the keypool new_seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress()) @@ -153,13 +160,15 @@ class WalletHDTest(BitcoinTestFramework): assert orig_masterkeyid != new_masterkeyid addr = self.nodes[1].getnewaddress() assert_equal(orig_masterkeyid, self.nodes[1].getaddressinfo(addr)['hdseedid']) - assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], 'm/0\'/0\'/1\'') # Make sure the new address continues previous keypool + # Make sure the new address continues previous keypool + assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], 'm/0\'/0\'/1\'') # Check that the next address is from the new seed self.nodes[1].keypoolrefill(1) next_addr = self.nodes[1].getnewaddress() assert_equal(new_masterkeyid, self.nodes[1].getaddressinfo(next_addr)['hdseedid']) - assert_equal(self.nodes[1].getaddressinfo(next_addr)['hdkeypath'], 'm/0\'/0\'/0\'') # Make sure the new address is not from previous keypool + # Make sure the new address is not from previous keypool + assert_equal(self.nodes[1].getaddressinfo(next_addr)['hdkeypath'], 'm/0\'/0\'/0\'') assert next_addr != addr # Sethdseed parameter validity @@ -170,5 +179,104 @@ class WalletHDTest(BitcoinTestFramework): assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, new_seed) assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, self.nodes[1].dumpprivkey(self.nodes[1].getnewaddress())) + self.log.info('Test sethdseed restoring with keys outside of the initial keypool') + self.nodes[0].generate(10) + # Restart node 1 with keypool of 3 and a different wallet + self.nodes[1].createwallet(wallet_name='origin', blank=True) + self.stop_node(1) + self.start_node(1, extra_args=['-keypool=3', '-wallet=origin']) + connect_nodes(self.nodes[0], 1) + + # sethdseed restoring and seeing txs to addresses out of the keypool + origin_rpc = self.nodes[1].get_wallet_rpc('origin') + seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress()) + origin_rpc.sethdseed(True, seed) + + self.nodes[1].createwallet(wallet_name='restore', blank=True) + restore_rpc = self.nodes[1].get_wallet_rpc('restore') + restore_rpc.sethdseed(True, seed) # Set to be the same seed as origin_rpc + restore_rpc.sethdseed(True) # Rotate to a new seed, making original `seed` inactive + + self.nodes[1].createwallet(wallet_name='restore2', blank=True) + restore2_rpc = self.nodes[1].get_wallet_rpc('restore2') + restore2_rpc.sethdseed(True, seed) # Set to be the same seed as origin_rpc + restore2_rpc.sethdseed(True) # Rotate to a new seed, making original `seed` inactive + + # Check persistence of inactive seed by reloading restore. restore2 is still loaded to test the case where the wallet is not reloaded + restore_rpc.unloadwallet() + self.nodes[1].loadwallet('restore') + restore_rpc = self.nodes[1].get_wallet_rpc('restore') + + # Empty origin keypool and get an address that is beyond the initial keypool + origin_rpc.getnewaddress() + origin_rpc.getnewaddress() + last_addr = origin_rpc.getnewaddress() # Last address of initial keypool + addr = origin_rpc.getnewaddress() # First address beyond initial keypool + + # Check that the restored seed has last_addr but does not have addr + info = restore_rpc.getaddressinfo(last_addr) + assert_equal(info['ismine'], True) + info = restore_rpc.getaddressinfo(addr) + assert_equal(info['ismine'], False) + info = restore2_rpc.getaddressinfo(last_addr) + assert_equal(info['ismine'], True) + info = restore2_rpc.getaddressinfo(addr) + assert_equal(info['ismine'], False) + # Check that the origin seed has addr + info = origin_rpc.getaddressinfo(addr) + assert_equal(info['ismine'], True) + + # Send a transaction to addr, which is out of the initial keypool. + # The wallet that has set a new seed (restore_rpc) should not detect this transaction. + txid = self.nodes[0].sendtoaddress(addr, 1) + origin_rpc.sendrawtransaction(self.nodes[0].gettransaction(txid)['hex']) + self.nodes[0].generate(1) + self.sync_blocks() + origin_rpc.gettransaction(txid) + assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', restore_rpc.gettransaction, txid) + out_of_kp_txid = txid + + # Send a transaction to last_addr, which is in the initial keypool. + # The wallet that has set a new seed (restore_rpc) should detect this transaction and generate 3 new keys from the initial seed. + # The previous transaction (out_of_kp_txid) should still not be detected as a rescan is required. + txid = self.nodes[0].sendtoaddress(last_addr, 1) + origin_rpc.sendrawtransaction(self.nodes[0].gettransaction(txid)['hex']) + self.nodes[0].generate(1) + self.sync_blocks() + origin_rpc.gettransaction(txid) + restore_rpc.gettransaction(txid) + assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', restore_rpc.gettransaction, out_of_kp_txid) + restore2_rpc.gettransaction(txid) + assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', restore2_rpc.gettransaction, out_of_kp_txid) + + # After rescanning, restore_rpc should now see out_of_kp_txid and generate an additional key. + # addr should now be part of restore_rpc and be ismine + restore_rpc.rescanblockchain() + restore_rpc.gettransaction(out_of_kp_txid) + info = restore_rpc.getaddressinfo(addr) + assert_equal(info['ismine'], True) + restore2_rpc.rescanblockchain() + restore2_rpc.gettransaction(out_of_kp_txid) + info = restore2_rpc.getaddressinfo(addr) + assert_equal(info['ismine'], True) + + # Check again that 3 keys were derived. + # Empty keypool and get an address that is beyond the initial keypool + origin_rpc.getnewaddress() + origin_rpc.getnewaddress() + last_addr = origin_rpc.getnewaddress() + addr = origin_rpc.getnewaddress() + + # Check that the restored seed has last_addr but does not have addr + info = restore_rpc.getaddressinfo(last_addr) + assert_equal(info['ismine'], True) + info = restore_rpc.getaddressinfo(addr) + assert_equal(info['ismine'], False) + info = restore2_rpc.getaddressinfo(last_addr) + assert_equal(info['ismine'], True) + info = restore2_rpc.getaddressinfo(addr) + assert_equal(info['ismine'], False) + + if __name__ == '__main__': - WalletHDTest().main () + WalletHDTest().main() diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py index f8d1720469..fb4a1f9792 100755 --- a/test/functional/wallet_labels.py +++ b/test/functional/wallet_labels.py @@ -134,6 +134,33 @@ class WalletLabelsTest(BitcoinTestFramework): # in the label. This is a no-op. change_label(node, labels[2].addresses[0], labels[2], labels[2]) + self.log.info('Check watchonly labels') + node.createwallet(wallet_name='watch_only', disable_private_keys=True, descriptors=False) + wallet_watch_only = node.get_wallet_rpc('watch_only') + BECH32_VALID = { + '✔️_VER15_PROG40': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqn2cjv3', + '✔️_VER16_PROG03': 'bcrt1sqqqqqjq8pdp', + '✔️_VER16_PROB02': 'bcrt1sqqqqqjq8pv', + } + BECH32_INVALID = { + '❌_VER15_PROG41': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzc7xyq', + '❌_VER16_PROB01': 'bcrt1sqqpl9r5c', + } + for l in BECH32_VALID: + ad = BECH32_VALID[l] + wallet_watch_only.importaddress(label=l, rescan=False, address=ad) + node.generatetoaddress(1, ad) + assert_equal(wallet_watch_only.getaddressesbylabel(label=l), {ad: {'purpose': 'receive'}}) + assert_equal(wallet_watch_only.getreceivedbylabel(label=l), 0) + for l in BECH32_INVALID: + ad = BECH32_INVALID[l] + assert_raises_rpc_error( + -5, + "Invalid Bitcoin address or script", + lambda: wallet_watch_only.importaddress(label=l, rescan=False, address=ad), + ) + + class Label: def __init__(self, name): # Label name diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 580a61f9f3..ff9ff34185 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -122,10 +122,6 @@ class MultiWalletTest(BitcoinTestFramework): self.nodes[0].assert_start_raises_init_error(['-zapwallettxes=1', '-wallet=w1', '-wallet=w2'], "Error: -zapwallettxes is only allowed with a single wallet file") self.nodes[0].assert_start_raises_init_error(['-zapwallettxes=2', '-wallet=w1', '-wallet=w2'], "Error: -zapwallettxes is only allowed with a single wallet file") - self.log.info("Do not allow -salvagewallet with multiwallet") - self.nodes[0].assert_start_raises_init_error(['-salvagewallet', '-wallet=w1', '-wallet=w2'], "Error: -salvagewallet is only allowed with a single wallet file") - self.nodes[0].assert_start_raises_init_error(['-salvagewallet=1', '-wallet=w1', '-wallet=w2'], "Error: -salvagewallet is only allowed with a single wallet file") - # if wallets/ doesn't exist, datadir should be the default wallet dir wallet_dir2 = data_dir('walletdir') os.rename(wallet_dir(), wallet_dir2) diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py index b384998d56..3417616d77 100755 --- a/test/functional/wallet_resendwallettransactions.py +++ b/test/functional/wallet_resendwallettransactions.py @@ -49,16 +49,21 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): block.solve() node.submitblock(ToHex(block)) - # Transaction should not be rebroadcast node.syncwithvalidationinterfacequeue() - node.p2ps[1].sync_with_ping() - assert_equal(node.p2ps[1].tx_invs_received[txid], 0) + now = int(time.time()) + + # Transaction should not be rebroadcast within first 12 hours + # Leave 2 mins for buffer + twelve_hrs = 12 * 60 * 60 + two_min = 2 * 60 + node.setmocktime(now + twelve_hrs - two_min) + time.sleep(2) # ensure enough time has passed for rebroadcast attempt to occur + assert_equal(txid in node.p2ps[1].get_invs(), False) self.log.info("Bump time & check that transaction is rebroadcast") # Transaction should be rebroadcast approximately 24 hours in the future, # but can range from 12-36. So bump 36 hours to be sure. - rebroadcast_time = int(time.time()) + 36 * 60 * 60 - node.setmocktime(rebroadcast_time) + node.setmocktime(now + 36 * 60 * 60) wait_until(lambda: node.p2ps[1].tx_invs_received[txid] >= 1, lock=mininode_lock) diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py index e7e71bf3f6..bb81746715 100755 --- a/test/functional/wallet_upgradewallet.py +++ b/test/functional/wallet_upgradewallet.py @@ -4,9 +4,11 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """upgradewallet RPC functional test -Test upgradewallet RPC. Download v0.15.2 v0.16.3 node binaries: +Test upgradewallet RPC. Download node binaries: -contrib/devtools/previous_release.sh -b v0.15.2 v0.16.3 +contrib/devtools/previous_release.sh -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2 + +Only v0.15.2 and v0.16.3 are required by this test. The others are used in feature_backwards_compatibility.py """ import os diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py index e2454c4237..56b18752ec 100755 --- a/test/fuzz/test_runner.py +++ b/test/fuzz/test_runner.py @@ -38,6 +38,7 @@ def main(): ) parser.add_argument( '--par', + '-j', type=int, default=4, help='How many targets to merge or execute in parallel.', diff --git a/test/lint/lint-includes.sh b/test/lint/lint-includes.sh index bd9c8337ac..5404565b94 100755 --- a/test/lint/lint-includes.sh +++ b/test/lint/lint-includes.sh @@ -67,9 +67,9 @@ EXPECTED_BOOST_INCLUDES=( boost/signals2/last_value.hpp boost/signals2/signal.hpp boost/test/unit_test.hpp - boost/thread.hpp boost/thread/condition_variable.hpp boost/thread/mutex.hpp + boost/thread/shared_mutex.hpp boost/thread/thread.hpp boost/variant.hpp boost/variant/apply_visitor.hpp diff --git a/test/lint/lint-python.sh b/test/lint/lint-python.sh index 86ac5a930f..decea38c4f 100755 --- a/test/lint/lint-python.sh +++ b/test/lint/lint-python.sh @@ -7,6 +7,7 @@ # Check for specified flake8 warnings in python files. export LC_ALL=C +export MYPY_CACHE_DIR="${BASE_ROOT_DIR}/test/.mypy_cache" enabled=( E101 # indentation contains mixed spaces and tabs @@ -89,10 +90,20 @@ elif PYTHONWARNINGS="ignore" flake8 --version | grep -q "Python 2"; then exit 0 fi -PYTHONWARNINGS="ignore" flake8 --ignore=B,C,E,F,I,N,W --select=$(IFS=","; echo "${enabled[*]}") $( +EXIT_CODE=0 + +if ! PYTHONWARNINGS="ignore" flake8 --ignore=B,C,E,F,I,N,W --select=$(IFS=","; echo "${enabled[*]}") $( if [[ $# == 0 ]]; then git ls-files "*.py" else echo "$@" fi -) +); then + EXIT_CODE=1 +fi + +if ! mypy --ignore-missing-imports $(git ls-files "test/functional/*.py"); then + EXIT_CODE=1 +fi + +exit $EXIT_CODE diff --git a/test/sanitizer_suppressions/tsan b/test/sanitizer_suppressions/tsan index 70eea34363..fd2e1a329c 100644 --- a/test/sanitizer_suppressions/tsan +++ b/test/sanitizer_suppressions/tsan @@ -1,6 +1,34 @@ # ThreadSanitizer suppressions # ============================ +# double locks (TODO fix) +mutex:g_genesis_wait_mutex +mutex:Interrupt +mutex:CThreadInterrupt +mutex:CConnman::Interrupt +mutex:CConnman::WakeMessageHandler +mutex:CConnman::ThreadOpenConnections +mutex:CConnman::ThreadOpenAddedConnections +mutex:CConnman::SocketHandler +mutex:UpdateTip +mutex:PeerLogicValidation::UpdatedBlockTip +mutex:g_best_block_mutex +# race (TODO fix) +race:CConnman::WakeMessageHandler +race:CConnman::ThreadMessageHandler +race:fHaveGenesis +race:ProcessNewBlock +race:ThreadImport +race:WalletBatch::WriteHDChain +race:zmq::* +race:bitcoin-qt +# deadlock (TODO fix) +deadlock:CConnman::ForNode +deadlock:CConnman::GetNodeStats +deadlock:CChainState::ConnectTip +deadlock:UpdateTip +deadlock:wallet_tests::CreateWalletFromFile + # WalletBatch (unidentified deadlock) deadlock:WalletBatch |