diff options
Diffstat (limited to 'test')
-rwxr-xr-x | test/functional/feature_config_args.py | 6 | ||||
-rwxr-xr-x | test/functional/interface_zmq.py | 106 | ||||
-rwxr-xr-x | test/functional/p2p_timeouts.py | 6 | ||||
-rwxr-xr-x | test/functional/rpc_deprecated.py | 33 | ||||
-rwxr-xr-x | test/functional/rpc_help.py | 64 | ||||
-rwxr-xr-x | test/functional/rpc_invalid_address_message.py | 78 | ||||
-rw-r--r-- | test/functional/test_framework/util.py | 21 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 2 | ||||
-rwxr-xr-x | test/functional/wallet_basic.py | 2 | ||||
-rwxr-xr-x | test/functional/wallet_bumpfee.py | 3 | ||||
-rwxr-xr-x | test/functional/wallet_listdescriptors.py | 67 | ||||
-rwxr-xr-x | test/functional/wallet_multiwallet.py | 4 | ||||
-rwxr-xr-x | test/fuzz/test_runner.py | 38 | ||||
-rwxr-xr-x | test/get_previous_releases.py | 1 | ||||
-rwxr-xr-x | test/lint/check-rpc-mappings.py | 162 | ||||
-rwxr-xr-x | test/lint/lint-includes.sh | 1 | ||||
-rwxr-xr-x | test/lint/lint-whitespace.sh | 2 | ||||
-rw-r--r-- | test/sanitizer_suppressions/ubsan | 60 |
18 files changed, 346 insertions, 310 deletions
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index 3e28dae4b3..2445b6d977 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -7,6 +7,7 @@ import os from test_framework.test_framework import BitcoinTestFramework +from test_framework import util class ConfArgsTest(BitcoinTestFramework): @@ -42,10 +43,11 @@ class ConfArgsTest(BitcoinTestFramework): conf.write("wallet=foo\n") self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Config setting for -wallet only applied on %s network when in [%s] section.' % (self.chain, self.chain)) + main_conf_file_path = os.path.join(self.options.tmpdir, 'node0', 'bitcoin_main.conf') + util.write_config(main_conf_file_path, n=0, chain='', extra_config='includeconf={}\n'.format(inc_conf_file_path)) with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: - conf.write('regtest=0\n') # mainnet conf.write('acceptnonstdtxn=1\n') - self.nodes[0].assert_start_raises_init_error(expected_msg='Error: acceptnonstdtxn is not currently supported for main chain') + self.nodes[0].assert_start_raises_init_error(extra_args=["-conf={}".format(main_conf_file_path)], expected_msg='Error: acceptnonstdtxn is not currently supported for main chain') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('nono\n') diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py index 9b2414cf2d..946bfa51d4 100755 --- a/test/functional/interface_zmq.py +++ b/test/functional/interface_zmq.py @@ -80,34 +80,43 @@ class ZMQTest (BitcoinTestFramework): self.log.debug("Destroying ZMQ context") self.ctx.destroy(linger=None) + # Restart node with the specified zmq notifications enabled, subscribe to + # all of them and return the corresponding ZMQSubscriber objects. + def setup_zmq_test(self, services, recv_timeout=60, connect_nodes=False): + subscribers = [] + for topic, address in services: + socket = self.ctx.socket(zmq.SUB) + socket.set(zmq.RCVTIMEO, recv_timeout*1000) + subscribers.append(ZMQSubscriber(socket, topic.encode())) + + self.restart_node(0, ["-zmqpub%s=%s" % (topic, address) for topic, address in services]) + + if connect_nodes: + self.connect_nodes(0, 1) + + for i, sub in enumerate(subscribers): + sub.socket.connect(services[i][1]) + + # Relax so that the subscribers are ready before publishing zmq messages + sleep(0.2) + + return subscribers + def test_basic(self): # Invalid zmq arguments don't take down the node, see #17185. self.restart_node(0, ["-zmqpubrawtx=foo", "-zmqpubhashtx=bar"]) address = 'tcp://127.0.0.1:28332' - sockets = [] - subs = [] - services = [b"hashblock", b"hashtx", b"rawblock", b"rawtx"] - for service in services: - sockets.append(self.ctx.socket(zmq.SUB)) - sockets[-1].set(zmq.RCVTIMEO, 60000) - subs.append(ZMQSubscriber(sockets[-1], service)) - - # Subscribe to all available topics. + subs = self.setup_zmq_test( + [(topic, address) for topic in ["hashblock", "hashtx", "rawblock", "rawtx"]], + connect_nodes=True) + hashblock = subs[0] hashtx = subs[1] rawblock = subs[2] rawtx = subs[3] - self.restart_node(0, ["-zmqpub%s=%s" % (sub.topic.decode(), address) for sub in [hashblock, hashtx, rawblock, rawtx]]) - self.connect_nodes(0, 1) - for socket in sockets: - socket.connect(address) - - # Relax so that the subscriber is ready before publishing zmq messages - sleep(0.2) - num_blocks = 5 self.log.info("Generate %(n)d blocks (and %(n)d coinbase txes)" % {"n": num_blocks}) genhashes = self.nodes[0].generatetoaddress(num_blocks, ADDRESS_BCRT1_UNSPENDABLE) @@ -174,25 +183,10 @@ class ZMQTest (BitcoinTestFramework): address = 'tcp://127.0.0.1:28333' - services = [b"hashblock", b"hashtx"] - sockets = [] - subs = [] - for service in services: - sockets.append(self.ctx.socket(zmq.SUB)) - # 2 second timeout to check end of notifications - sockets[-1].set(zmq.RCVTIMEO, 2000) - subs.append(ZMQSubscriber(sockets[-1], service)) - - # Subscribe to all available topics. - hashblock = subs[0] - hashtx = subs[1] - # Should only notify the tip if a reorg occurs - self.restart_node(0, ["-zmqpub%s=%s" % (sub.topic.decode(), address) for sub in [hashblock, hashtx]]) - for socket in sockets: - socket.connect(address) - # Relax so that the subscriber is ready before publishing zmq messages - sleep(0.2) + hashblock, hashtx = self.setup_zmq_test( + [(topic, address) for topic in ["hashblock", "hashtx"]], + recv_timeout=2) # 2 second timeout to check end of notifications # Generate 1 block in nodes[0] with 1 mempool tx and receive all notifications payment_txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1.0) @@ -240,15 +234,7 @@ class ZMQTest (BitcoinTestFramework): <32-byte hash>A<8-byte LE uint> : Transactionhash added mempool """ self.log.info("Testing 'sequence' publisher") - address = 'tcp://127.0.0.1:28333' - socket = self.ctx.socket(zmq.SUB) - socket.set(zmq.RCVTIMEO, 60000) - seq = ZMQSubscriber(socket, b'sequence') - - self.restart_node(0, ['-zmqpub%s=%s' % (seq.topic.decode(), address)]) - socket.connect(address) - # Relax so that the subscriber is ready before publishing zmq messages - sleep(0.2) + [seq] = self.setup_zmq_test([("sequence", "tcp://127.0.0.1:28333")]) # Mempool sequence number starts at 1 seq_num = 1 @@ -399,16 +385,7 @@ class ZMQTest (BitcoinTestFramework): return self.log.info("Testing 'mempool sync' usage of sequence notifier") - address = 'tcp://127.0.0.1:28333' - socket = self.ctx.socket(zmq.SUB) - socket.set(zmq.RCVTIMEO, 60000) - seq = ZMQSubscriber(socket, b'sequence') - - self.restart_node(0, ['-zmqpub%s=%s' % (seq.topic.decode(), address)]) - self.connect_nodes(0, 1) - socket.connect(address) - # Relax so that the subscriber is ready before publishing zmq messages - sleep(0.2) + [seq] = self.setup_zmq_test([("sequence", "tcp://127.0.0.1:28333")], connect_nodes=True) # In-memory counter, should always start at 1 next_mempool_seq = self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"] @@ -508,26 +485,17 @@ class ZMQTest (BitcoinTestFramework): def test_multiple_interfaces(self): # Set up two subscribers with different addresses - subscribers = [] - for i in range(2): - address = 'tcp://127.0.0.1:%d' % (28334 + i) - socket = self.ctx.socket(zmq.SUB) - socket.set(zmq.RCVTIMEO, 60000) - hashblock = ZMQSubscriber(socket, b"hashblock") - socket.connect(address) - subscribers.append({'address': address, 'hashblock': hashblock}) - - self.restart_node(0, ['-zmqpub%s=%s' % (subscriber['hashblock'].topic.decode(), subscriber['address']) for subscriber in subscribers]) - - # Relax so that the subscriber is ready before publishing zmq messages - sleep(0.2) + subscribers = self.setup_zmq_test([ + ("hashblock", "tcp://127.0.0.1:28334"), + ("hashblock", "tcp://127.0.0.1:28335"), + ]) # Generate 1 block in nodes[0] and receive all notifications self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE) # Should receive the same block hash on both subscribers - assert_equal(self.nodes[0].getbestblockhash(), subscribers[0]['hashblock'].receive().hex()) - assert_equal(self.nodes[0].getbestblockhash(), subscribers[1]['hashblock'].receive().hex()) + assert_equal(self.nodes[0].getbestblockhash(), subscribers[0].receive().hex()) + assert_equal(self.nodes[0].getbestblockhash(), subscribers[1].receive().hex()) if __name__ == '__main__': ZMQTest().main() diff --git a/test/functional/p2p_timeouts.py b/test/functional/p2p_timeouts.py index 47832b04bf..a7e240dcfa 100755 --- a/test/functional/p2p_timeouts.py +++ b/test/functional/p2p_timeouts.py @@ -74,9 +74,9 @@ class TimeoutsTest(BitcoinTestFramework): no_version_node.send_message(msg_ping()) expected_timeout_logs = [ - "version handshake timeout from 0", - "socket no message in first 3 seconds, 1 0 from 1", - "socket no message in first 3 seconds, 0 0 from 2", + "version handshake timeout peer=0", + "socket no message in first 3 seconds, 1 0 peer=1", + "socket no message in first 3 seconds, 0 0 peer=2", ] with self.nodes[0].assert_debug_log(expected_msgs=expected_timeout_logs): diff --git a/test/functional/rpc_deprecated.py b/test/functional/rpc_deprecated.py index 209d1182d7..1af79b9f7c 100755 --- a/test/functional/rpc_deprecated.py +++ b/test/functional/rpc_deprecated.py @@ -4,7 +4,6 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test deprecation of RPC calls.""" from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_raises_rpc_error, find_vout_for_address class DeprecatedRpcTest(BitcoinTestFramework): def set_test_params(self): @@ -24,37 +23,7 @@ class DeprecatedRpcTest(BitcoinTestFramework): # assert_raises_rpc_error(-32, 'The wallet generate rpc method is deprecated', self.nodes[0].rpc.generate, 1) # self.nodes[1].generate(1) - if self.is_wallet_compiled(): - self.log.info("Test bumpfee RPC") - self.nodes[0].generate(101) - self.nodes[0].createwallet(wallet_name='nopriv', disable_private_keys=True) - noprivs0 = self.nodes[0].get_wallet_rpc('nopriv') - w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name) - self.nodes[1].createwallet(wallet_name='nopriv', disable_private_keys=True) - noprivs1 = self.nodes[1].get_wallet_rpc('nopriv') - - address = w0.getnewaddress() - desc = w0.getaddressinfo(address)['desc'] - change_addr = w0.getrawchangeaddress() - change_desc = w0.getaddressinfo(change_addr)['desc'] - txid = w0.sendtoaddress(address=address, amount=10) - vout = find_vout_for_address(w0, txid, address) - self.nodes[0].generate(1) - rawtx = w0.createrawtransaction([{'txid': txid, 'vout': vout}], {w0.getnewaddress(): 5}, 0, True) - rawtx = w0.fundrawtransaction(rawtx, {'changeAddress': change_addr}) - signed_tx = w0.signrawtransactionwithwallet(rawtx['hex'])['hex'] - - noprivs0.importmulti([{'desc': desc, 'timestamp': 0}, {'desc': change_desc, 'timestamp': 0, 'internal': True}]) - noprivs1.importmulti([{'desc': desc, 'timestamp': 0}, {'desc': change_desc, 'timestamp': 0, 'internal': True}]) - - txid = w0.sendrawtransaction(signed_tx) - self.sync_all() - - assert_raises_rpc_error(-32, 'Using bumpfee with wallets that have private keys disabled is deprecated. Use psbtbumpfee instead or restart bitcoind with -deprecatedrpc=bumpfee. This functionality will be removed in 0.22', noprivs0.bumpfee, txid) - bumped_psbt = noprivs1.bumpfee(txid) - assert 'psbt' in bumped_psbt - else: - self.log.info("No tested deprecated RPC methods") + self.log.info("No tested deprecated RPC methods") if __name__ == '__main__': DeprecatedRpcTest().main() diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py index c83157a843..1eefd109f8 100755 --- a/test/functional/rpc_help.py +++ b/test/functional/rpc_help.py @@ -7,7 +7,39 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error +from collections import defaultdict import os +import re + + +def parse_string(s): + assert s[0] == '"' + assert s[-1] == '"' + return s[1:-1] + + +def process_mapping(fname): + """Find and parse conversion table in implementation file `fname`.""" + cmds = [] + in_rpcs = False + with open(fname, "r", encoding="utf8") as f: + for line in f: + line = line.rstrip() + if not in_rpcs: + if line == 'static const CRPCConvertParam vRPCConvertParams[] =': + in_rpcs = True + else: + if line.startswith('};'): + in_rpcs = False + elif '{' in line and '"' in line: + m = re.search(r'{ *("[^"]*"), *([0-9]+) *, *("[^"]*") *},', line) + assert m, 'No match to table expression: %s' % line + name = parse_string(m.group(1)) + idx = int(m.group(2)) + argname = parse_string(m.group(3)) + cmds.append((name, idx, argname)) + assert not in_rpcs and cmds + return cmds class HelpRpcTest(BitcoinTestFramework): @@ -16,11 +48,43 @@ class HelpRpcTest(BitcoinTestFramework): self.supports_cli = False def run_test(self): + self.test_client_conversion_table() self.test_categories() self.dump_help() if self.is_wallet_compiled(): self.wallet_help() + def test_client_conversion_table(self): + file_conversion_table = os.path.join(self.config["environment"]["SRCDIR"], 'src', 'rpc', 'client.cpp') + mapping_client = process_mapping(file_conversion_table) + # Ignore echojson in client table + mapping_client = [m for m in mapping_client if m[0] != 'echojson'] + + mapping_server = self.nodes[0].help("dump_all_command_conversions") + # Filter all RPCs whether they need conversion + mapping_server_conversion = [tuple(m[:3]) for m in mapping_server if not m[3]] + + # Only check if all RPC methods have been compiled (i.e. wallet is enabled) + if self.is_wallet_compiled() and sorted(mapping_client) != sorted(mapping_server_conversion): + raise AssertionError("RPC client conversion table ({}) and RPC server named arguments mismatch!\n{}".format( + file_conversion_table, + set(mapping_client).symmetric_difference(mapping_server_conversion), + )) + + # Check for conversion difference by argument name. + # It is preferable for API consistency that arguments with the same name + # have the same conversion, so bin by argument name. + all_methods_by_argname = defaultdict(list) + converts_by_argname = defaultdict(list) + for m in mapping_server: + all_methods_by_argname[m[2]].append(m[0]) + converts_by_argname[m[2]].append(m[3]) + + for argname, convert in converts_by_argname.items(): + if all(convert) != any(convert): + # Only allow dummy to fail consistency check + assert argname == 'dummy', ('WARNING: conversion mismatch for argument named %s (%s)' % (argname, list(zip(all_methods_by_argname[argname], converts_by_argname[argname])))) + def test_categories(self): node = self.nodes[0] diff --git a/test/functional/rpc_invalid_address_message.py b/test/functional/rpc_invalid_address_message.py new file mode 100755 index 0000000000..469d6bdb05 --- /dev/null +++ b/test/functional/rpc_invalid_address_message.py @@ -0,0 +1,78 @@ +#!/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 error messages for 'getaddressinfo' and 'validateaddress' RPC commands.""" + +from test_framework.test_framework import BitcoinTestFramework + +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, +) + +BECH32_VALID = 'bcrt1qtmp74ayg7p24uslctssvjm06q5phz4yrxucgnv' +BECH32_INVALID_SIZE = 'bcrt1sqqpl9r5c' +BECH32_INVALID_PREFIX = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4' + +BASE58_VALID = 'mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn' +BASE58_INVALID_PREFIX = '17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem' + +INVALID_ADDRESS = 'asfah14i8fajz0123f' + +class InvalidAddressErrorMessageTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def test_validateaddress(self): + node = self.nodes[0] + + # Bech32 + info = node.validateaddress(BECH32_INVALID_SIZE) + assert not info['isvalid'] + assert_equal(info['error'], 'Invalid Bech32 address data size') + + info = node.validateaddress(BECH32_INVALID_PREFIX) + assert not info['isvalid'] + assert_equal(info['error'], 'Invalid prefix for Bech32 address') + + info = node.validateaddress(BECH32_VALID) + assert info['isvalid'] + assert 'error' not in info + + # Base58 + info = node.validateaddress(BASE58_INVALID_PREFIX) + assert not info['isvalid'] + assert_equal(info['error'], 'Invalid prefix for Base58-encoded address') + + info = node.validateaddress(BASE58_VALID) + assert info['isvalid'] + assert 'error' not in info + + # Invalid address format + info = node.validateaddress(INVALID_ADDRESS) + assert not info['isvalid'] + assert_equal(info['error'], 'Invalid address format') + + def test_getaddressinfo(self): + node = self.nodes[0] + + assert_raises_rpc_error(-5, "Invalid Bech32 address data size", node.getaddressinfo, BECH32_INVALID_SIZE) + + assert_raises_rpc_error(-5, "Invalid prefix for Bech32 address", node.getaddressinfo, BECH32_INVALID_PREFIX) + + assert_raises_rpc_error(-5, "Invalid prefix for Base58-encoded address", node.getaddressinfo, BASE58_INVALID_PREFIX) + + assert_raises_rpc_error(-5, "Invalid address format", node.getaddressinfo, INVALID_ADDRESS) + + def run_test(self): + self.test_validateaddress() + self.test_getaddressinfo() + + +if __name__ == '__main__': + InvalidAddressErrorMessageTest().main() diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index b3eb2d61a7..123c48852c 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -342,16 +342,25 @@ def initialize_datadir(dirname, n, chain): datadir = get_datadir_path(dirname, n) if not os.path.isdir(datadir): os.makedirs(datadir) - # Translate chain name to config name + write_config(os.path.join(datadir, "bitcoin.conf"), n=n, chain=chain) + os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True) + os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True) + return datadir + + +def write_config(config_path, *, n, chain, extra_config=""): + # Translate chain subdirectory name to config name if chain == 'testnet3': chain_name_conf_arg = 'testnet' chain_name_conf_section = 'test' else: chain_name_conf_arg = chain chain_name_conf_section = chain - with open(os.path.join(datadir, "bitcoin.conf"), 'w', encoding='utf8') as f: - f.write("{}=1\n".format(chain_name_conf_arg)) - f.write("[{}]\n".format(chain_name_conf_section)) + with open(config_path, 'w', encoding='utf8') as f: + if chain_name_conf_arg: + f.write("{}=1\n".format(chain_name_conf_arg)) + if chain_name_conf_section: + f.write("[{}]\n".format(chain_name_conf_section)) f.write("port=" + str(p2p_port(n)) + "\n") f.write("rpcport=" + str(rpc_port(n)) + "\n") f.write("fallbackfee=0.0002\n") @@ -364,9 +373,7 @@ def initialize_datadir(dirname, n, chain): f.write("upnp=0\n") f.write("natpmp=0\n") f.write("shrinkdebugfile=0\n") - os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True) - os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True) - return datadir + f.write(extra_config) def get_datadir_path(dirname, n): diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 9bbf862568..c652ac0a06 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -134,6 +134,7 @@ BASE_SCRIPTS = [ 'wallet_keypool_topup.py --descriptors', 'feature_fee_estimation.py', 'interface_zmq.py', + 'rpc_invalid_address_message.py', 'interface_bitcoin_cli.py', 'mempool_resurrect.py', 'wallet_txn_doublespend.py --mineblock', @@ -237,6 +238,7 @@ BASE_SCRIPTS = [ 'rpc_named_arguments.py', 'wallet_listsinceblock.py', 'wallet_listsinceblock.py --descriptors', + 'wallet_listdescriptors.py --descriptors', 'p2p_leak.py', 'wallet_encryption.py', 'wallet_encryption.py --descriptors', diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 6bcb03e8ed..4a589f0393 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -586,7 +586,7 @@ class WalletTest(BitcoinTestFramework): assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999))) # Test getaddressinfo on external address. Note that these addresses are taken from disablewallet.py - assert_raises_rpc_error(-5, "Invalid address", self.nodes[0].getaddressinfo, "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy") + assert_raises_rpc_error(-5, "Invalid prefix for Base58-encoded address", self.nodes[0].getaddressinfo, "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy") address_info = self.nodes[0].getaddressinfo("mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ") assert_equal(address_info['address'], "mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ") assert_equal(address_info["scriptPubKey"], "76a9144e3854046c7bd1594ac904e4793b6a45b36dea0988ac") diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index c8c1f2e374..5fc8438e8f 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -425,6 +425,9 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address): original_txid = watcher.sendrawtransaction(psbt_final["hex"]) assert_equal(len(watcher.decodepsbt(psbt)["tx"]["vin"]), 1) + # bumpfee can't be used on watchonly wallets + assert_raises_rpc_error(-4, "bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead.", watcher.bumpfee, original_txid) + # Bump fee, obnoxiously high to add additional watchonly input bumped_psbt = watcher.psbtbumpfee(original_txid, {"fee_rate": HIGH}) assert_greater_than(len(watcher.decodepsbt(bumped_psbt['psbt'])["tx"]["vin"]), 1) diff --git a/test/functional/wallet_listdescriptors.py b/test/functional/wallet_listdescriptors.py new file mode 100755 index 0000000000..9f8c341bc7 --- /dev/null +++ b/test/functional/wallet_listdescriptors.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test the listdescriptors RPC.""" + +from test_framework.descriptors import ( + descsum_create +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, +) + + +class ListDescriptorsTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + self.skip_if_no_sqlite() + + # do not create any wallet by default + def init_wallet(self, i): + return + + def run_test(self): + node = self.nodes[0] + assert_raises_rpc_error(-18, 'No wallet is loaded.', node.listdescriptors) + + self.log.info('Test that the command is not available for legacy wallets.') + node.createwallet(wallet_name='w1', descriptors=False) + assert_raises_rpc_error(-4, 'listdescriptors is not available for non-descriptor wallets', node.listdescriptors) + + self.log.info('Test the command for empty descriptors wallet.') + node.createwallet(wallet_name='w2', blank=True, descriptors=True) + assert_equal(0, len(node.get_wallet_rpc('w2').listdescriptors())) + + self.log.info('Test the command for a default descriptors wallet.') + node.createwallet(wallet_name='w3', descriptors=True) + result = node.get_wallet_rpc('w3').listdescriptors() + assert_equal(6, len(result)) + assert_equal(6, len([d for d in result if d['active']])) + assert_equal(3, len([d for d in result if d['internal']])) + for item in result: + assert item['desc'] != '' + assert item['next'] == 0 + assert item['range'] == [0, 0] + assert item['timestamp'] is not None + + self.log.info('Test non-active non-range combo descriptor') + node.createwallet(wallet_name='w4', blank=True, descriptors=True) + wallet = node.get_wallet_rpc('w4') + wallet.importdescriptors([{ + 'desc': descsum_create('combo(' + node.get_deterministic_priv_key().key + ')'), + 'timestamp': 1296688602, + }]) + expected = [{'active': False, + 'desc': 'combo(0227d85ba011276cf25b51df6a188b75e604b38770a462b2d0e9fb2fc839ef5d3f)#np574htj', + 'timestamp': 1296688602}] + assert_equal(expected, wallet.listdescriptors()) + + +if __name__ == '__main__': + ListDescriptorsTest().main() diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index bb89e76a9a..bf24b9c7b3 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -304,12 +304,12 @@ class MultiWalletTest(BitcoinTestFramework): if self.options.descriptors: assert_raises_rpc_error(-4, "Wallet file verification failed. SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another bitcoind?", self.nodes[0].loadwallet, wallet_names[0]) else: - assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, wallet_names[0]) + assert_raises_rpc_error(-35, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, wallet_names[0]) # This tests the default wallet that BDB makes, so SQLite wallet doesn't need to test this # Fail to load duplicate wallets by different ways (directory and filepath) path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "wallet.dat") - assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, 'wallet.dat') + assert_raises_rpc_error(-35, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, 'wallet.dat') # Only BDB doesn't open duplicate wallet files. SQLite does not have this limitation. While this may be desired in the future, it is not necessary # Fail to load if one wallet is a copy of another diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py index 3c743603bb..aa0aa11d15 100755 --- a/test/fuzz/test_runner.py +++ b/test/fuzz/test_runner.py @@ -14,6 +14,16 @@ import subprocess import sys +def get_fuzz_env(*, target, source_dir): + return { + 'FUZZ': target, + 'UBSAN_OPTIONS': + f'suppressions={source_dir}/test/sanitizer_suppressions/ubsan:print_stacktrace=1:halt_on_error=1:report_error_type=1', + 'ASAN_OPTIONS': # symbolizer disabled due to https://github.com/google/sanitizers/issues/1364#issuecomment-761072085 + 'symbolize=0:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1', + } + + def main(): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, @@ -129,9 +139,7 @@ def main(): os.path.join(config["environment"]["BUILDDIR"], 'src', 'test', 'fuzz', 'fuzz'), '-help=1', ], - env={ - 'FUZZ': test_list_selection[0] - }, + env=get_fuzz_env(target=test_list_selection[0], source_dir=config['environment']['SRCDIR']), timeout=20, check=True, stderr=subprocess.PIPE, @@ -148,6 +156,7 @@ def main(): if args.generate: return generate_corpus_seeds( fuzz_pool=fuzz_pool, + src_dir=config['environment']['SRCDIR'], build_dir=config["environment"]["BUILDDIR"], seed_dir=args.seed_dir, targets=test_list_selection, @@ -158,6 +167,7 @@ def main(): fuzz_pool=fuzz_pool, corpus=args.seed_dir, test_list=test_list_selection, + src_dir=config['environment']['SRCDIR'], build_dir=config["environment"]["BUILDDIR"], merge_dir=args.m_dir, ) @@ -167,12 +177,13 @@ def main(): fuzz_pool=fuzz_pool, corpus=args.seed_dir, test_list=test_list_selection, + src_dir=config['environment']['SRCDIR'], build_dir=config["environment"]["BUILDDIR"], use_valgrind=args.valgrind, ) -def generate_corpus_seeds(*, fuzz_pool, build_dir, seed_dir, targets): +def generate_corpus_seeds(*, fuzz_pool, src_dir, build_dir, seed_dir, targets): """Generates new corpus seeds. Run {targets} without input, and outputs the generated corpus seeds to @@ -186,9 +197,7 @@ def generate_corpus_seeds(*, fuzz_pool, build_dir, seed_dir, targets): ' '.join(command), subprocess.run( command, - env={ - 'FUZZ': t - }, + env=get_fuzz_env(target=t, source_dir=src_dir), check=True, stderr=subprocess.PIPE, universal_newlines=True, @@ -209,7 +218,7 @@ def generate_corpus_seeds(*, fuzz_pool, build_dir, seed_dir, targets): future.result() -def merge_inputs(*, fuzz_pool, corpus, test_list, build_dir, merge_dir): +def merge_inputs(*, fuzz_pool, corpus, test_list, src_dir, build_dir, merge_dir): logging.info("Merge the inputs from the passed dir into the seed_dir. Passed dir {}".format(merge_dir)) jobs = [] for t in test_list: @@ -227,9 +236,7 @@ def merge_inputs(*, fuzz_pool, corpus, test_list, build_dir, merge_dir): output = 'Run {} with args {}\n'.format(t, " ".join(args)) output += subprocess.run( args, - env={ - 'FUZZ': t - }, + env=get_fuzz_env(target=t, source_dir=src_dir), check=True, stderr=subprocess.PIPE, universal_newlines=True, @@ -242,7 +249,7 @@ def merge_inputs(*, fuzz_pool, corpus, test_list, build_dir, merge_dir): future.result() -def run_once(*, fuzz_pool, corpus, test_list, build_dir, use_valgrind): +def run_once(*, fuzz_pool, corpus, test_list, src_dir, build_dir, use_valgrind): jobs = [] for t in test_list: corpus_path = os.path.join(corpus, t) @@ -257,7 +264,12 @@ def run_once(*, fuzz_pool, corpus, test_list, build_dir, use_valgrind): def job(t, args): output = 'Run {} with args {}'.format(t, args) - result = subprocess.run(args, env={'FUZZ': t}, stderr=subprocess.PIPE, universal_newlines=True) + result = subprocess.run( + args, + env=get_fuzz_env(target=t, source_dir=src_dir), + stderr=subprocess.PIPE, + universal_newlines=True, + ) output += result.stderr return output, result diff --git a/test/get_previous_releases.py b/test/get_previous_releases.py index 1348b8246b..b177fbb4b2 100755 --- a/test/get_previous_releases.py +++ b/test/get_previous_releases.py @@ -175,6 +175,7 @@ def check_host(args) -> int: './depends/config.guess').decode()) if args.download_binary: platforms = { + 'aarch64-*-linux*': 'aarch64-linux-gnu', 'x86_64-*-linux*': 'x86_64-linux-gnu', 'x86_64-apple-darwin*': 'osx64', } diff --git a/test/lint/check-rpc-mappings.py b/test/lint/check-rpc-mappings.py deleted file mode 100755 index 0a4cc875d0..0000000000 --- a/test/lint/check-rpc-mappings.py +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2017-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. -"""Check RPC argument consistency.""" - -from collections import defaultdict -import os -import re -import sys - -# Source files (relative to root) to scan for dispatch tables -SOURCES = [ - "src/rpc/server.cpp", - "src/rpc/blockchain.cpp", - "src/rpc/mining.cpp", - "src/rpc/misc.cpp", - "src/rpc/net.cpp", - "src/rpc/rawtransaction.cpp", - "src/wallet/rpcwallet.cpp", -] -# Source file (relative to root) containing conversion mapping -SOURCE_CLIENT = 'src/rpc/client.cpp' -# Argument names that should be ignored in consistency checks -IGNORE_DUMMY_ARGS = {'dummy', 'arg0', 'arg1', 'arg2', 'arg3', 'arg4', 'arg5', 'arg6', 'arg7', 'arg8', 'arg9'} - -class RPCCommand: - def __init__(self, name, args): - self.name = name - self.args = args - -class RPCArgument: - def __init__(self, names, idx): - self.names = names - self.idx = idx - self.convert = False - -def parse_string(s): - assert s[0] == '"' - assert s[-1] == '"' - return s[1:-1] - -def process_commands(fname): - """Find and parse dispatch table in implementation file `fname`.""" - cmds = [] - in_rpcs = False - with open(fname, "r", encoding="utf8") as f: - for line in f: - line = line.rstrip() - if not in_rpcs: - if re.match(r"static const CRPCCommand .*\[\] =", line): - in_rpcs = True - else: - if line.startswith('};'): - in_rpcs = False - elif '{' in line and '"' in line: - m = re.search(r'{ *("[^"]*"), *("[^"]*"), *&([^,]*), *{([^}]*)} *},', line) - assert m, 'No match to table expression: %s' % line - name = parse_string(m.group(2)) - args_str = m.group(4).strip() - if args_str: - args = [RPCArgument(parse_string(x.strip()).split('|'), idx) for idx, x in enumerate(args_str.split(','))] - else: - args = [] - cmds.append(RPCCommand(name, args)) - assert not in_rpcs and cmds, "Something went wrong with parsing the C++ file: update the regexps" - return cmds - -def process_mapping(fname): - """Find and parse conversion table in implementation file `fname`.""" - cmds = [] - in_rpcs = False - with open(fname, "r", encoding="utf8") as f: - for line in f: - line = line.rstrip() - if not in_rpcs: - if line == 'static const CRPCConvertParam vRPCConvertParams[] =': - in_rpcs = True - else: - if line.startswith('};'): - in_rpcs = False - elif '{' in line and '"' in line: - m = re.search(r'{ *("[^"]*"), *([0-9]+) *, *("[^"]*") *},', line) - assert m, 'No match to table expression: %s' % line - name = parse_string(m.group(1)) - idx = int(m.group(2)) - argname = parse_string(m.group(3)) - cmds.append((name, idx, argname)) - assert not in_rpcs and cmds - return cmds - -def main(): - if len(sys.argv) != 2: - print('Usage: {} ROOT-DIR'.format(sys.argv[0]), file=sys.stderr) - sys.exit(1) - - root = sys.argv[1] - - # Get all commands from dispatch tables - cmds = [] - for fname in SOURCES: - cmds += process_commands(os.path.join(root, fname)) - - cmds_by_name = {} - for cmd in cmds: - cmds_by_name[cmd.name] = cmd - - # Get current convert mapping for client - client = SOURCE_CLIENT - mapping = set(process_mapping(os.path.join(root, client))) - - print('* Checking consistency between dispatch tables and vRPCConvertParams') - - # Check mapping consistency - errors = 0 - for (cmdname, argidx, argname) in mapping: - try: - rargnames = cmds_by_name[cmdname].args[argidx].names - except IndexError: - print('ERROR: %s argument %i (named %s in vRPCConvertParams) is not defined in dispatch table' % (cmdname, argidx, argname)) - errors += 1 - continue - if argname not in rargnames: - print('ERROR: %s argument %i is named %s in vRPCConvertParams but %s in dispatch table' % (cmdname, argidx, argname, rargnames), file=sys.stderr) - errors += 1 - - # Check for conflicts in vRPCConvertParams conversion - # All aliases for an argument must either be present in the - # conversion table, or not. Anything in between means an oversight - # and some aliases won't work. - for cmd in cmds: - for arg in cmd.args: - convert = [((cmd.name, arg.idx, argname) in mapping) for argname in arg.names] - if any(convert) != all(convert): - print('ERROR: %s argument %s has conflicts in vRPCConvertParams conversion specifier %s' % (cmd.name, arg.names, convert)) - errors += 1 - arg.convert = all(convert) - - # Check for conversion difference by argument name. - # It is preferable for API consistency that arguments with the same name - # have the same conversion, so bin by argument name. - all_methods_by_argname = defaultdict(list) - converts_by_argname = defaultdict(list) - for cmd in cmds: - for arg in cmd.args: - for argname in arg.names: - all_methods_by_argname[argname].append(cmd.name) - converts_by_argname[argname].append(arg.convert) - - for argname, convert in converts_by_argname.items(): - if all(convert) != any(convert): - if argname in IGNORE_DUMMY_ARGS: - # these are testing or dummy, don't warn for them - continue - print('WARNING: conversion mismatch for argument named %s (%s)' % - (argname, list(zip(all_methods_by_argname[argname], converts_by_argname[argname])))) - - sys.exit(errors > 0) - - -if __name__ == '__main__': - main() diff --git a/test/lint/lint-includes.sh b/test/lint/lint-includes.sh index dc032665e4..262e9d4c79 100755 --- a/test/lint/lint-includes.sh +++ b/test/lint/lint-includes.sh @@ -68,7 +68,6 @@ EXPECTED_BOOST_INCLUDES=( boost/signals2/signal.hpp boost/test/unit_test.hpp boost/thread/condition_variable.hpp - boost/thread/mutex.hpp boost/thread/shared_mutex.hpp boost/thread/thread.hpp ) diff --git a/test/lint/lint-whitespace.sh b/test/lint/lint-whitespace.sh index c800fd20db..37fcf7804a 100755 --- a/test/lint/lint-whitespace.sh +++ b/test/lint/lint-whitespace.sh @@ -33,7 +33,7 @@ if [ -z "${COMMIT_RANGE}" ]; then fi showdiff() { - if ! git diff -U0 "${COMMIT_RANGE}" -- "." ":(exclude)depends/patches/" ":(exclude)src/leveldb/" ":(exclude)src/crc32c/" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/" ":(exclude)doc/release-notes/" ":(exclude)src/qt/locale/"; then + if ! git diff -U0 "${COMMIT_RANGE}" -- "." ":(exclude)depends/patches/" ":(exclude)contrib/guix/patches/" ":(exclude)src/leveldb/" ":(exclude)src/crc32c/" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/" ":(exclude)doc/release-notes/" ":(exclude)src/qt/locale/"; then echo "Failed to get a diff" exit 1 fi diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index 9a52cd4b57..97f0f45e7f 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -1,3 +1,7 @@ +# -fsanitize=undefined suppressions +# ================================= +# No suppressions at the moment. Hooray! + # -fsanitize=integer suppressions # =============================== # Unsigned integer overflow occurs when the result of an unsigned integer @@ -6,7 +10,8 @@ # contains files in which we expect unsigned integer overflows to occur. The # list is used to suppress -fsanitize=integer warnings when running our CI UBSan # job. -unsigned-integer-overflow:*/include/c++/*/bits/basic_string.tcc +unsigned-integer-overflow:*/include/c++/ +unsigned-integer-overflow:addrman.cpp unsigned-integer-overflow:arith_uint256.h unsigned-integer-overflow:basic_string.h unsigned-integer-overflow:bench/bench.h @@ -15,34 +20,41 @@ unsigned-integer-overflow:bloom.cpp unsigned-integer-overflow:chain.cpp unsigned-integer-overflow:chain.h unsigned-integer-overflow:coded_stream.h +unsigned-integer-overflow:coins.cpp +unsigned-integer-overflow:compressor.cpp unsigned-integer-overflow:core_write.cpp -unsigned-integer-overflow:crypto/* +unsigned-integer-overflow:crypto/ +# unsigned-integer-overflow in FuzzedDataProvider's ConsumeIntegralInRange +unsigned-integer-overflow:FuzzedDataProvider.h unsigned-integer-overflow:hash.cpp -unsigned-integer-overflow:leveldb/db/log_reader.cc -unsigned-integer-overflow:leveldb/util/bloom.cc -unsigned-integer-overflow:leveldb/util/crc32c.h -unsigned-integer-overflow:leveldb/util/hash.cc +unsigned-integer-overflow:leveldb/ unsigned-integer-overflow:policy/fees.cpp unsigned-integer-overflow:prevector.h +unsigned-integer-overflow:pubkey.h unsigned-integer-overflow:script/interpreter.cpp unsigned-integer-overflow:stl_bvector.h unsigned-integer-overflow:txmempool.cpp unsigned-integer-overflow:util/strencodings.cpp unsigned-integer-overflow:validation.cpp - -implicit-integer-sign-change:*/include/c++/*/bits/*.h +implicit-integer-sign-change:*/include/boost/ +implicit-integer-sign-change:*/include/c++/ implicit-integer-sign-change:*/new_allocator.h -implicit-integer-sign-change:/usr/include/boost/date_time/format_date_parser.hpp +implicit-integer-sign-change:addrman.h implicit-integer-sign-change:arith_uint256.cpp implicit-integer-sign-change:bech32.cpp implicit-integer-sign-change:bloom.cpp -implicit-integer-sign-change:chain.* +implicit-integer-sign-change:chain.cpp +implicit-integer-sign-change:chain.h implicit-integer-sign-change:coins.h implicit-integer-sign-change:compat/stdin.cpp implicit-integer-sign-change:compressor.h -implicit-integer-sign-change:crypto/* +implicit-integer-sign-change:crc32c/ +implicit-integer-sign-change:crypto/ +# implicit-integer-sign-change in FuzzedDataProvider's ConsumeIntegralInRange +implicit-integer-sign-change:FuzzedDataProvider.h implicit-integer-sign-change:key.cpp implicit-integer-sign-change:noui.cpp +implicit-integer-sign-change:policy/fees.cpp implicit-integer-sign-change:prevector.h implicit-integer-sign-change:protocol.cpp implicit-integer-sign-change:script/bitcoinconsensus.cpp @@ -53,24 +65,38 @@ implicit-integer-sign-change:test/coins_tests.cpp implicit-integer-sign-change:test/pow_tests.cpp implicit-integer-sign-change:test/prevector_tests.cpp implicit-integer-sign-change:test/sighash_tests.cpp +implicit-integer-sign-change:test/skiplist_tests.cpp implicit-integer-sign-change:test/streams_tests.cpp implicit-integer-sign-change:test/transaction_tests.cpp implicit-integer-sign-change:txmempool.cpp -implicit-integer-sign-change:util/strencodings.* +implicit-integer-sign-change:util/strencodings.cpp +implicit-integer-sign-change:util/strencodings.h implicit-integer-sign-change:validation.cpp implicit-integer-sign-change:zmq/zmqpublishnotifier.cpp implicit-signed-integer-truncation,implicit-integer-sign-change:chain.h implicit-signed-integer-truncation,implicit-integer-sign-change:test/skiplist_tests.cpp +implicit-signed-integer-truncation:addrman.cpp +implicit-signed-integer-truncation:addrman.h implicit-signed-integer-truncation:chain.h -implicit-signed-integer-truncation:crypto/* +implicit-signed-integer-truncation:crypto/ implicit-signed-integer-truncation:cuckoocache.h -implicit-signed-integer-truncation:leveldb/* +implicit-signed-integer-truncation:leveldb/ +implicit-signed-integer-truncation:net.cpp +implicit-signed-integer-truncation:net_processing.cpp implicit-signed-integer-truncation:streams.h implicit-signed-integer-truncation:test/arith_uint256_tests.cpp implicit-signed-integer-truncation:test/skiplist_tests.cpp implicit-signed-integer-truncation:torcontrol.cpp -implicit-unsigned-integer-truncation:crypto/* -implicit-unsigned-integer-truncation:leveldb/* +implicit-unsigned-integer-truncation:*/include/c++/ +implicit-unsigned-integer-truncation:crypto/ +implicit-unsigned-integer-truncation:leveldb/ # std::variant warning fixed in https://github.com/gcc-mirror/gcc/commit/074436cf8cdd2a9ce75cadd36deb8301f00e55b9 implicit-unsigned-integer-truncation:std::__detail::__variant::_Variant_storage -implicit-integer-sign-change:crc32c/* +shift-base:*/include/c++/ +shift-base:arith_uint256.cpp +shift-base:crypto/ +shift-base:hash.cpp +shift-base:leveldb/ +shift-base:net_processing.cpp +shift-base:streams.h +shift-base:util/bip32.cpp |