diff options
Diffstat (limited to 'test')
44 files changed, 706 insertions, 227 deletions
diff --git a/test/README.md b/test/README.md index ecea3213ab..8f08b7afe4 100644 --- a/test/README.md +++ b/test/README.md @@ -49,6 +49,29 @@ You can run any combination (incl. duplicates) of tests by calling: test/functional/test_runner.py <testname1> <testname2> <testname3> ... ``` +Wildcard test names can be passed, if the paths are coherent and the test runner +is called from a `bash` shell or similar that does the globbing. For example, +to run all the wallet tests: + +``` +test/functional/test_runner.py test/functional/wallet* +functional/test_runner.py functional/wallet* (called from the test/ directory) +test_runner.py wallet* (called from the test/functional/ directory) +``` + +but not + +``` +test/functional/test_runner.py wallet* +``` + +Combinations of wildcards can be passed: + +``` +test/functional/test_runner.py ./test/functional/tool* test/functional/mempool* +test_runner.py tool* mempool* +``` + Run the regression test suite with: ``` diff --git a/test/functional/example_test.py b/test/functional/example_test.py index a2726763d0..70dfe81d4e 100755 --- a/test/functional/example_test.py +++ b/test/functional/example_test.py @@ -186,12 +186,15 @@ class ExampleTest(BitcoinTestFramework): self.log.info("Connect node2 and node1") connect_nodes(self.nodes[1], 2) + self.log.info("Wait for node2 to receive all the blocks from node1") + self.sync_all() + self.log.info("Add P2P connection to node2") self.nodes[0].disconnect_p2ps() self.nodes[2].add_p2p_connection(BaseNode()) - self.log.info("Wait for node2 reach current tip. Test that it has propagated all the blocks to us") + self.log.info("Test that node2 propagates all the blocks to us") getdata_request = msg_getdata() for block in blocks: diff --git a/test/functional/feature_abortnode.py b/test/functional/feature_abortnode.py new file mode 100755 index 0000000000..62c3eca07d --- /dev/null +++ b/test/functional/feature_abortnode.py @@ -0,0 +1,48 @@ +#!/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. +"""Test bitcoind aborts if can't disconnect a block. + +- Start a single node and generate 3 blocks. +- Delete the undo data. +- Mine a fork that requires disconnecting the tip. +- Verify that bitcoind AbortNode's. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import wait_until, get_datadir_path, connect_nodes +import os + +class AbortNodeTest(BitcoinTestFramework): + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + + def setup_network(self): + self.setup_nodes() + # We'll connect the nodes later + + def run_test(self): + self.nodes[0].generate(3) + datadir = get_datadir_path(self.options.tmpdir, 0) + + # Deleting the undo file will result in reorg failure + os.unlink(os.path.join(datadir, 'regtest', 'blocks', 'rev00000.dat')) + + # Connecting to a node with a more work chain will trigger a reorg + # attempt. + self.nodes[1].generate(3) + with self.nodes[0].assert_debug_log(["Failed to disconnect block"]): + connect_nodes(self.nodes[0], 1) + self.nodes[1].generate(1) + + # Check that node0 aborted + self.log.info("Waiting for crash") + wait_until(lambda: self.nodes[0].is_node_stopped(), timeout=60) + self.log.info("Node crashed - now verifying restart fails") + self.nodes[0].assert_start_raises_init_error() + +if __name__ == '__main__': + AbortNodeTest().main() diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py index d38eca6cbe..f0bf09e172 100755 --- a/test/functional/feature_bip68_sequence.py +++ b/test/functional/feature_bip68_sequence.py @@ -29,7 +29,10 @@ NOT_FINAL_ERROR = "non-BIP68-final (code 64)" class BIP68Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - self.extra_args = [[], ["-acceptnonstdtxn=0"]] + self.extra_args = [ + ["-acceptnonstdtxn=1"], + ["-acceptnonstdtxn=0"], + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 3ad83cd2b3..fdb608d457 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -78,7 +78,7 @@ class FullBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True - self.extra_args = [[]] + self.extra_args = [['-acceptnonstdtxn=1']] # This is a consensus block test, we don't care about tx policy def run_test(self): node = self.nodes[0] # convenience reference to the node @@ -486,6 +486,14 @@ class FullBlockTest(BitcoinTestFramework): tx_last = tx_new b39_outputs += 1 + # The accounting in the loop above can be off, because it misses the + # compact size encoding of the number of transactions in the block. + # Make sure we didn't accidentally make too big a block. Note that the + # size of the block has non-determinism due to the ECDSA signature in + # the first transaction. + while (len(b39.serialize()) >= MAX_BLOCK_BASE_SIZE): + del b39.vtx[-1] + b39 = self.update_block(39, []) self.send_blocks([b39], True) self.save_spendable_output() diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index b16eafccca..af34f9f0db 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -57,16 +57,34 @@ def cltv_validate(node, tx, height): class BIP65Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - self.extra_args = [['-whitelist=127.0.0.1', '-par=1']] # Use only one script thread to get the exact reject reason for testing + self.extra_args = [[ + '-whitelist=127.0.0.1', + '-par=1', # Use only one script thread to get the exact reject reason for testing + '-acceptnonstdtxn=1', # cltv_invalidate is nonstandard + ]] self.setup_clean_chain = True self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() + def test_cltv_info(self, *, is_active): + assert_equal( + next(s for s in self.nodes[0].getblockchaininfo()['softforks'] if s['id'] == 'bip65'), + { + "id": "bip65", + "version": 4, + "reject": { + "status": is_active + } + }, + ) + def run_test(self): self.nodes[0].add_p2p_connection(P2PInterface()) + self.test_cltv_info(is_active=False) + self.log.info("Mining %d blocks", CLTV_HEIGHT - 2) self.coinbase_txids = [self.nodes[0].getblock(b)['tx'][0] for b in self.nodes[0].generate(CLTV_HEIGHT - 2)] self.nodeaddress = self.nodes[0].getnewaddress() @@ -86,7 +104,9 @@ class BIP65Test(BitcoinTestFramework): block.hashMerkleRoot = block.calc_merkle_root() block.solve() + self.test_cltv_info(is_active=False) self.nodes[0].p2p.send_and_ping(msg_block(block)) + self.test_cltv_info(is_active=False) # Not active as of current tip, but next block must obey rules assert_equal(self.nodes[0].getbestblockhash(), block.hash) self.log.info("Test that blocks must now be at least version 4") @@ -135,7 +155,9 @@ class BIP65Test(BitcoinTestFramework): block.hashMerkleRoot = block.calc_merkle_root() block.solve() + self.test_cltv_info(is_active=False) # Not active as of current tip, but next block must obey rules self.nodes[0].p2p.send_and_ping(msg_block(block)) + self.test_cltv_info(is_active=True) # Active as of current tip assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index f0d6bc21e6..aae262344d 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -22,7 +22,7 @@ class ConfArgsTest(BitcoinTestFramework): conf.write('includeconf={}\n'.format(inc_conf_file_path)) self.nodes[0].assert_start_raises_init_error( - expected_msg='Error parsing command line arguments: Invalid parameter -dash_cli', + expected_msg='Error: Error parsing command line arguments: Invalid parameter -dash_cli', extra_args=['-dash_cli=1'], ) with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: @@ -33,27 +33,32 @@ class ConfArgsTest(BitcoinTestFramework): with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('-dash=1\n') - self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 1: -dash=1, options in configuration file must be specified without leading -') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 1: -dash=1, options in configuration file must be specified without leading -') with open(inc_conf_file_path, 'w', encoding='utf8') as conf: conf.write("wallet=foo\n") self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Config setting for -wallet only applied on regtest network when in [regtest] section.') 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') + + with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('nono\n') - self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 1: nono, if you intended to specify a negated option, use nono=1 instead') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 1: nono, if you intended to specify a negated option, use nono=1 instead') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('server=1\nrpcuser=someuser\nrpcpassword=some#pass') - self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 3, using # in rpcpassword can be ambiguous and should be avoided') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 3, using # in rpcpassword can be ambiguous and should be avoided') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('server=1\nrpcuser=someuser\nmain.rpcpassword=some#pass') - self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 3, using # in rpcpassword can be ambiguous and should be avoided') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 3, using # in rpcpassword can be ambiguous and should be avoided') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('server=1\nrpcuser=someuser\n[main]\nrpcpassword=some#pass') - self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 4, using # in rpcpassword can be ambiguous and should be avoided') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 4, using # in rpcpassword can be ambiguous and should be avoided') inc_conf_file2_path = os.path.join(self.nodes[0].datadir, 'include2.conf') with open(os.path.join(self.nodes[0].datadir, 'bitcoin.conf'), 'a', encoding='utf-8') as conf: diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index 62062926a6..f227a06ebe 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -267,7 +267,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): # Warn if any of the nodes escaped restart. for i in range(3): if self.restart_counts[i] == 0: - self.log.warn("Node %d never crashed during utxo flush!", i) + self.log.warning("Node %d never crashed during utxo flush!", i) if __name__ == "__main__": ChainstateWriteCrashTest().main() diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py index 7480e5c5ba..067e3be1f4 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -51,9 +51,23 @@ class BIP66Test(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() + def test_dersig_info(self, *, is_active): + assert_equal( + next(s for s in self.nodes[0].getblockchaininfo()['softforks'] if s['id'] == 'bip66'), + { + "id": "bip66", + "version": 3, + "reject": { + "status": is_active + } + }, + ) + def run_test(self): self.nodes[0].add_p2p_connection(P2PInterface()) + self.test_dersig_info(is_active=False) + self.log.info("Mining %d blocks", DERSIG_HEIGHT - 2) self.coinbase_txids = [self.nodes[0].getblock(b)['tx'][0] for b in self.nodes[0].generate(DERSIG_HEIGHT - 2)] self.nodeaddress = self.nodes[0].getnewaddress() @@ -74,7 +88,9 @@ class BIP66Test(BitcoinTestFramework): block.rehash() block.solve() + self.test_dersig_info(is_active=False) self.nodes[0].p2p.send_and_ping(msg_block(block)) + self.test_dersig_info(is_active=False) # Not active as of current tip, but next block must obey rules assert_equal(self.nodes[0].getbestblockhash(), block.hash) self.log.info("Test that blocks must now be at least version 3") @@ -128,8 +144,11 @@ class BIP66Test(BitcoinTestFramework): block.rehash() block.solve() + self.test_dersig_info(is_active=False) # Not active as of current tip, but next block must obey rules self.nodes[0].p2p.send_and_ping(msg_block(block)) + self.test_dersig_info(is_active=True) # Active as of current tip assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) + if __name__ == '__main__': BIP66Test().main() diff --git a/test/functional/feature_includeconf.py b/test/functional/feature_includeconf.py index d06f6826f0..2cd6a05d08 100755 --- a/test/functional/feature_includeconf.py +++ b/test/functional/feature_includeconf.py @@ -43,7 +43,7 @@ class IncludeConfTest(BitcoinTestFramework): self.log.info("-includeconf cannot be used as command-line arg") self.stop_node(0) - self.nodes[0].assert_start_raises_init_error(extra_args=["-includeconf=relative2.conf"], expected_msg="Error parsing command line arguments: -includeconf cannot be used from commandline; -includeconf=relative2.conf") + self.nodes[0].assert_start_raises_init_error(extra_args=["-includeconf=relative2.conf"], expected_msg="Error: Error parsing command line arguments: -includeconf cannot be used from commandline; -includeconf=relative2.conf") self.log.info("-includeconf cannot be used recursively. subversion should end with 'main; relative)/'") with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "a", encoding="utf8") as f: @@ -59,11 +59,11 @@ class IncludeConfTest(BitcoinTestFramework): # Commented out as long as we ignore invalid arguments in configuration files #with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: # f.write("foo=bar\n") - #self.nodes[0].assert_start_raises_init_error(expected_msg="Error reading configuration file: Invalid configuration value foo") + #self.nodes[0].assert_start_raises_init_error(expected_msg="Error: Error reading configuration file: Invalid configuration value foo") self.log.info("-includeconf cannot be invalid path") os.remove(os.path.join(self.options.tmpdir, "node0", "relative.conf")) - self.nodes[0].assert_start_raises_init_error(expected_msg="Error reading configuration file: Failed to include configuration file relative.conf") + self.nodes[0].assert_start_raises_init_error(expected_msg="Error: Error reading configuration file: Failed to include configuration file relative.conf") self.log.info("multiple -includeconf args can be used from the base config file. subversion should end with 'main; relative; relative2)/'") with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py index 87c318de9a..180ea0e51d 100755 --- a/test/functional/feature_maxuploadtarget.py +++ b/test/functional/feature_maxuploadtarget.py @@ -35,7 +35,7 @@ class MaxUploadTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 - self.extra_args = [["-maxuploadtarget=800"]] + self.extra_args = [["-maxuploadtarget=800", "-acceptnonstdtxn=1"]] # Cache for utxos, as the listunspent may take a long time later in the test self.utxo_cache = [] diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 1496c5d958..fd79df0b07 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -67,8 +67,8 @@ class ReplaceByFeeTest(BitcoinTestFramework): self.num_nodes = 1 self.extra_args = [ [ + "-acceptnonstdtxn=1", "-maxorphantx=1000", - "-whitelist=127.0.0.1", "-limitancestorcount=50", "-limitancestorsize=101", "-limitdescendantcount=200", diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index 2d4dd96a1d..a71d4071d5 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -53,17 +53,20 @@ class SegWitTest(BitcoinTestFramework): # This test tests SegWit both pre and post-activation, so use the normal BIP9 activation. self.extra_args = [ [ + "-acceptnonstdtxn=1", "-rpcserialversion=0", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", ], [ + "-acceptnonstdtxn=1", "-blockversion=4", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", ], [ + "-acceptnonstdtxn=1", "-blockversion=536870915", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", diff --git a/test/functional/interface_rpc.py b/test/functional/interface_rpc.py index 49ae0fb1a9..e99fa22646 100755 --- a/test/functional/interface_rpc.py +++ b/test/functional/interface_rpc.py @@ -4,6 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Tests some generic aspects of the RPC interface.""" +import os from test_framework.authproxy import JSONRPCException from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_greater_than_or_equal @@ -31,6 +32,7 @@ class RPCInterfaceTest(BitcoinTestFramework): command = info['active_commands'][0] assert_equal(command['method'], 'getrpcinfo') assert_greater_than_or_equal(command['duration'], 0) + assert_equal(info['logpath'], os.path.join(self.nodes[0].datadir, 'regtest', 'debug.log')) def test_batch_request(self): self.log.info("Testing basic JSON-RPC batch request...") diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index 2bb5d8ab7d..209a222004 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -36,7 +36,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.num_nodes = 1 self.extra_args = [[ '-txindex', - '-acceptnonstdtxn=0', # Try to mimic main-net ]] * self.num_nodes def skip_test_if_missing_module(self): diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py index 351b27e94a..edf2069933 100755 --- a/test/functional/mempool_limit.py +++ b/test/functional/mempool_limit.py @@ -13,7 +13,11 @@ class MempoolLimitTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 - self.extra_args = [["-maxmempool=5", "-spendzeroconfchange=0"]] + self.extra_args = [[ + "-acceptnonstdtxn=1", + "-maxmempool=5", + "-spendzeroconfchange=0", + ]] def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py new file mode 100755 index 0000000000..30f851fb8e --- /dev/null +++ b/test/functional/mempool_package_onemore.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-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. +"""Test descendant package tracking carve-out allowing one final transaction in + an otherwise-full package as long as it has only one parent and is <= 10k in + size. +""" + +from decimal import Decimal + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round + +MAX_ANCESTORS = 25 +MAX_DESCENDANTS = 25 + +class MempoolPackagesTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.extra_args = [["-maxorphantx=1000"]] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + # Build a transaction that spends parent_txid:vout + # Return amount sent + def chain_transaction(self, node, parent_txids, vouts, value, fee, num_outputs): + send_value = satoshi_round((value - fee)/num_outputs) + inputs = [] + for (txid, vout) in zip(parent_txids, vouts): + inputs.append({'txid' : txid, 'vout' : vout}) + outputs = {} + for i in range(num_outputs): + outputs[node.getnewaddress()] = send_value + rawtx = node.createrawtransaction(inputs, outputs) + signedtx = node.signrawtransactionwithwallet(rawtx) + txid = node.sendrawtransaction(signedtx['hex']) + fulltx = node.getrawtransaction(txid, 1) + assert len(fulltx['vout']) == num_outputs # make sure we didn't generate a change output + return (txid, send_value) + + def run_test(self): + # Mine some blocks and have them mature. + self.nodes[0].generate(101) + utxo = self.nodes[0].listunspent(10) + txid = utxo[0]['txid'] + vout = utxo[0]['vout'] + value = utxo[0]['amount'] + + fee = Decimal("0.0002") + # MAX_ANCESTORS transactions off a confirmed tx should be fine + chain = [] + for _ in range(4): + (txid, sent_value) = self.chain_transaction(self.nodes[0], [txid], [vout], value, fee, 2) + vout = 0 + value = sent_value + chain.append([txid, value]) + for _ in range(MAX_ANCESTORS - 4): + (txid, sent_value) = self.chain_transaction(self.nodes[0], [txid], [0], value, fee, 1) + value = sent_value + chain.append([txid, value]) + (second_chain, second_chain_value) = self.chain_transaction(self.nodes[0], [utxo[1]['txid']], [utxo[1]['vout']], utxo[1]['amount'], fee, 1) + + # Check mempool has MAX_ANCESTORS + 1 transactions in it + assert_equal(len(self.nodes[0].getrawmempool(True)), MAX_ANCESTORS + 1) + + # Adding one more transaction on to the chain should fail. + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", self.chain_transaction, self.nodes[0], [txid], [0], value, fee, 1) + # ...even if it chains on from some point in the middle of the chain. + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[2][0]], [1], chain[2][1], fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[1][0]], [1], chain[1][1], fee, 1) + # ...even if it chains on to two parent transactions with one in the chain. + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[0][0], second_chain], [1, 0], chain[0][1] + second_chain_value, fee, 1) + # ...especially if its > 40k weight + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 350) + # But not if it chains directly off the first transaction + self.chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1) + # and the second chain should work just fine + self.chain_transaction(self.nodes[0], [second_chain], [0], second_chain_value, fee, 1) + + # Finally, check that we added two transactions + assert_equal(len(self.nodes[0].getrawmempool(True)), MAX_ANCESTORS + 3) + +if __name__ == '__main__': + MempoolPackagesTest().main() diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py index b0a069be81..7e05a8e6c8 100755 --- a/test/functional/mining_prioritisetransaction.py +++ b/test/functional/mining_prioritisetransaction.py @@ -14,7 +14,10 @@ class PrioritiseTransactionTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 - self.extra_args = [["-printpriority=1"], ["-printpriority=1"]] + self.extra_args = [[ + "-printpriority=1", + "-acceptnonstdtxn=1", + ]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index 0994857912..eb3336bd3b 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -95,6 +95,9 @@ class CompactBlocksTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 + self.extra_args = [[ + "-acceptnonstdtxn=1", + ]] self.utxos = [] def skip_test_if_missing_module(self): diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py index 481d697e63..d5c68e622b 100755 --- a/test/functional/p2p_invalid_messages.py +++ b/test/functional/p2p_invalid_messages.py @@ -6,6 +6,7 @@ import asyncio import os import struct +import sys from test_framework import messages from test_framework.mininode import P2PDataStore, NetworkThread @@ -92,18 +93,25 @@ class InvalidMessagesTest(BitcoinTestFramework): # # Send an oversized message, ensure we're disconnected. # - msg_over_size = msg_unrecognized(str_data="b" * (valid_data_limit + 1)) - assert len(msg_over_size.serialize()) == (msg_limit + 1) + # Under macOS this test is skipped due to an unexpected error code + # returned from the closing socket which python/asyncio does not + # yet know how to handle. + # + if sys.platform != 'darwin': + msg_over_size = msg_unrecognized(str_data="b" * (valid_data_limit + 1)) + assert len(msg_over_size.serialize()) == (msg_limit + 1) - with node.assert_debug_log(["Oversized message from peer=4, disconnecting"]): - # An unknown message type (or *any* message type) over - # MAX_PROTOCOL_MESSAGE_LENGTH should result in a disconnect. - node.p2p.send_message(msg_over_size) - node.p2p.wait_for_disconnect(timeout=4) + with node.assert_debug_log(["Oversized message from peer=4, disconnecting"]): + # An unknown message type (or *any* message type) over + # MAX_PROTOCOL_MESSAGE_LENGTH should result in a disconnect. + node.p2p.send_message(msg_over_size) + node.p2p.wait_for_disconnect(timeout=4) - node.disconnect_p2ps() - conn = node.add_p2p_connection(P2PDataStore()) - conn.wait_for_verack() + node.disconnect_p2ps() + conn = node.add_p2p_connection(P2PDataStore()) + conn.wait_for_verack() + else: + self.log.info("Skipping test p2p_invalid_messages/1 (oversized message) under macOS") # # 2. diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py index 1b18dd3e58..3cca2d78db 100755 --- a/test/functional/p2p_invalid_tx.py +++ b/test/functional/p2p_invalid_tx.py @@ -25,6 +25,9 @@ from data import invalid_txs class InvalidTxRequestTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 + self.extra_args = [[ + "-acceptnonstdtxn=1", + ]] self.setup_clean_chain = True def bootstrap_p2p(self, *, num_connections=1): diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py index 573d5f5a5f..a4650df8ee 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_BLOOM, NODE_NETWORK_LIMITED, NODE_WITNESS +from test_framework.messages import CInv, 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 ( @@ -55,7 +55,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): def run_test(self): node = self.nodes[0].add_p2p_connection(P2PIgnoreInv()) - expected_services = NODE_BLOOM | NODE_WITNESS | NODE_NETWORK_LIMITED + expected_services = NODE_WITNESS | NODE_NETWORK_LIMITED self.log.info("Check that node has signalled expected services.") assert_equal(node.nServices, expected_services) @@ -83,7 +83,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): node1.wait_for_addr() #must relay address with NODE_NETWORK_LIMITED - assert_equal(node1.firstAddrnServices, 1036) + assert_equal(node1.firstAddrnServices, expected_services) self.nodes[0].disconnect_p2ps() node1.wait_for_disconnect() diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index b7fa42f593..dca71aec43 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -184,7 +184,11 @@ class SegWitTest(BitcoinTestFramework): self.setup_clean_chain = True self.num_nodes = 3 # This test tests SegWit both pre and post-activation, so use the normal BIP9 activation. - self.extra_args = [["-whitelist=127.0.0.1", "-vbparams=segwit:0:999999999999"], ["-whitelist=127.0.0.1", "-acceptnonstdtxn=0", "-vbparams=segwit:0:999999999999"], ["-whitelist=127.0.0.1", "-vbparams=segwit:0:0"]] + self.extra_args = [ + ["-whitelist=127.0.0.1", "-acceptnonstdtxn=1", "-vbparams=segwit:0:999999999999"], + ["-whitelist=127.0.0.1", "-acceptnonstdtxn=0", "-vbparams=segwit:0:999999999999"], + ["-whitelist=127.0.0.1", "-acceptnonstdtxn=1", "-vbparams=segwit:0:0"], + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index 58010f7c2e..62f3843756 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -129,6 +129,11 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): outval = value - decimal.Decimal("0.00001000") rawtx = node2.createrawtransaction([{"txid": txid, "vout": vout}], [{self.final: outval}]) + prevtx_err = dict(prevtxs[0]) + del prevtx_err["redeemScript"] + + assert_raises_rpc_error(-8, "Missing redeemScript/witnessScript", node2.signrawtransactionwithkey, rawtx, self.priv[0:self.nsigs-1], [prevtx_err]) + rawtx2 = node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs - 1], prevtxs) rawtx3 = node2.signrawtransactionwithkey(rawtx2["hex"], [self.priv[-1]], prevtxs) diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 2fffe96ebe..b3d8696208 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -136,7 +136,7 @@ class PSBTTest(BitcoinTestFramework): assert_greater_than(0.06, res["fee"]) # feeRate of 10 BTC / KB produces a total fee well above -maxtxfee - # previously this was silenty capped at -maxtxfee + # previously this was silently capped at -maxtxfee assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[1].walletcreatefundedpsbt, [{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 10}) # partially sign multisig things with node 1 @@ -346,7 +346,7 @@ class PSBTTest(BitcoinTestFramework): # Try again, now while providing descriptors, making P2SH-segwit work, and causing bip32_derivs and redeem_script to be filled in descs = [self.nodes[1].getaddressinfo(addr)['desc'] for addr in [addr1,addr2,addr3]] - updated = self.nodes[1].utxoupdatepsbt(psbt, descs) + updated = self.nodes[1].utxoupdatepsbt(psbt=psbt, descriptors=descs) decoded = self.nodes[1].decodepsbt(updated) test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'bip32_derivs']) test_psbt_input_keys(decoded['inputs'][1], []) diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py index 102dd22594..8bbb3c04fa 100755 --- a/test/functional/rpc_users.py +++ b/test/functional/rpc_users.py @@ -20,6 +20,17 @@ 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))} + + conn = http.client.HTTPConnection(url.hostname, url.port) + conn.connect() + conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) + resp = conn.getresponse() + conn.close() + return resp + class HTTPBasicsTest(BitcoinTestFramework): def set_test_params(self): @@ -28,15 +39,24 @@ class HTTPBasicsTest(BitcoinTestFramework): def setup_chain(self): super().setup_chain() #Append rpcauth to bitcoin.conf before initialization + self.rtpassword = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM=" rpcauth = "rpcauth=rt:93648e835a54c573682c2eb19f882535$7681e9c5b74bdd85e78166031d2058e1069b3ed7ed967c93fc63abba06f31144" - rpcauth2 = "rpcauth=rt2:f8607b1a88861fac29dfccf9b52ff9f$ff36a0c23c8c62b4846112e50fa888416e94c17bfd4c42f88fd8f55ec6a3137e" - rpcuser = "rpcuser=rpcuser💻" - rpcpassword = "rpcpassword=rpcpassword🔑" - self.user = ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10)) + self.rpcuser = "rpcuser💻" + self.rpcpassword = "rpcpassword🔑" + config = configparser.ConfigParser() config.read_file(open(self.options.configfile)) gen_rpcauth = config['environment']['RPCAUTH'] + + # Generate RPCAUTH with specified password + self.rt2password = "8/F3uMDw4KSEbw96U3CA1C4X05dkHDN2BPFjTgZW4KI=" + p = subprocess.Popen([sys.executable, gen_rpcauth, 'rt2', self.rt2password], stdout=subprocess.PIPE, universal_newlines=True) + lines = p.stdout.read().splitlines() + rpcauth2 = lines[1] + + # Generate RPCAUTH without specifying password + self.user = ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10)) p = subprocess.Popen([sys.executable, gen_rpcauth, self.user], stdout=subprocess.PIPE, universal_newlines=True) lines = p.stdout.read().splitlines() rpcauth3 = lines[1] @@ -47,160 +67,40 @@ class HTTPBasicsTest(BitcoinTestFramework): 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") - f.write(rpcpassword+"\n") - - def run_test(self): - - ################################################## - # Check correctness of the rpcauth config option # - ################################################## - url = urllib.parse.urlparse(self.nodes[0].url) - - #Old authpair - authpair = url.username + ':' + url.password - - #New authpair generated via share/rpcauth tool - password = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM=" - - #Second authpair with different username - password2 = "8/F3uMDw4KSEbw96U3CA1C4X05dkHDN2BPFjTgZW4KI=" - authpairnew = "rt:"+password + f.write("rpcuser={}\n".format(self.rpcuser)) + f.write("rpcpassword={}\n".format(self.rpcpassword)) + def test_auth(self, node, user, password): self.log.info('Correct...') - headers = {"Authorization": "Basic " + str_to_b64str(authpair)} + assert_equal(200, call_with_auth(node, user, password).status) - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() - - #Use new authpair to confirm both work - self.log.info('Correct...') - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() - - #Wrong login name with rt's password self.log.info('Wrong...') - authpairnew = "rtwrong:"+password - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() + assert_equal(401, call_with_auth(node, user, password+'wrong').status) - #Wrong password for rt self.log.info('Wrong...') - authpairnew = "rt:"+password+"wrong" - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} + assert_equal(401, call_with_auth(node, user+'wrong', password).status) - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() - - #Correct for rt2 - self.log.info('Correct...') - authpairnew = "rt2:"+password2 - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() - - #Wrong password for rt2 self.log.info('Wrong...') - authpairnew = "rt2:"+password2+"wrong" - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() + assert_equal(401, call_with_auth(node, user+'wrong', password+'wrong').status) - #Correct for randomly generated user - self.log.info('Correct...') - authpairnew = self.user+":"+self.password - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() + def run_test(self): - #Wrong password for randomly generated user - self.log.info('Wrong...') - authpairnew = self.user+":"+self.password+"Wrong" - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} + ################################################## + # Check correctness of the rpcauth config option # + ################################################## + url = urllib.parse.urlparse(self.nodes[0].url) - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() + self.test_auth(self.nodes[0], url.username, url.password) + self.test_auth(self.nodes[0], 'rt', self.rtpassword) + 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 # ############################################################### url = urllib.parse.urlparse(self.nodes[1].url) - # rpcuser and rpcpassword authpair - self.log.info('Correct...') - rpcuserauthpair = "rpcuser💻:rpcpassword🔑" - - headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() - - #Wrong login name with rpcuser's password - rpcuserauthpair = "rpcuserwrong:rpcpassword" - headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() - - #Wrong password for rpcuser - self.log.info('Wrong...') - rpcuserauthpair = "rpcuser:rpcpasswordwrong" - headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() - + self.test_auth(self.nodes[1], self.rpcuser, self.rpcpassword) if __name__ == '__main__': HTTPBasicsTest ().main () diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index e454ed5987..89a5a65e64 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -44,7 +44,7 @@ BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is BIP 125 opt-in an NODE_NETWORK = (1 << 0) # NODE_GETUTXO = (1 << 1) -NODE_BLOOM = (1 << 2) +# NODE_BLOOM = (1 << 2) NODE_WITNESS = (1 << 3) NODE_NETWORK_LIMITED = (1 << 10) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 26215083fb..efd962ea93 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -19,6 +19,7 @@ import time from . import coverage from .authproxy import AuthServiceProxy, JSONRPCException +from io import BytesIO logger = logging.getLogger("TestFramework.utils") @@ -515,14 +516,13 @@ def gen_return_txouts(): for i in range(512): script_pubkey = script_pubkey + "01" # concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change - txouts = "81" + txouts = [] + from .messages import CTxOut + txout = CTxOut() + txout.nValue = 0 + txout.scriptPubKey = hex_str_to_bytes(script_pubkey) for k in range(128): - # add txout value - txouts = txouts + "0000000000000000" - # add length of script_pubkey - txouts = txouts + "fd0402" - # add script_pubkey - txouts = txouts + script_pubkey + txouts.append(txout) return txouts # Create a spend of each passed-in utxo, splicing in "txouts" to each raw @@ -530,6 +530,7 @@ def gen_return_txouts(): def create_lots_of_big_transactions(node, txouts, utxos, num, fee): addr = node.getnewaddress() txids = [] + from .messages import CTransaction for _ in range(num): t = utxos.pop() inputs = [{"txid": t["txid"], "vout": t["vout"]}] @@ -537,9 +538,11 @@ def create_lots_of_big_transactions(node, txouts, utxos, num, fee): change = t['amount'] - fee outputs[addr] = satoshi_round(change) rawtx = node.createrawtransaction(inputs, outputs) - newtx = rawtx[0:92] - newtx = newtx + txouts - newtx = newtx + rawtx[94:] + tx = CTransaction() + tx.deserialize(BytesIO(hex_str_to_bytes(rawtx))) + for txout in txouts: + tx.vout.append(txout) + newtx = tx.serialize().hex() signresult = node.signrawtransactionwithwallet(newtx, None, "NONE") txid = node.sendrawtransaction(signresult["hex"], 0) txids.append(txid) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 462547f44f..7ad2b78d4e 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -107,6 +107,7 @@ BASE_SCRIPTS = [ 'feature_bip68_sequence.py', 'p2p_feefilter.py', 'feature_reindex.py', + 'feature_abortnode.py', # vv Tests less than 30s vv 'wallet_keypool_topup.py', 'interface_zmq.py', @@ -157,6 +158,7 @@ BASE_SCRIPTS = [ 'rpc_invalidateblock.py', 'feature_rbf.py', 'mempool_packages.py', + 'mempool_package_onemore.py', 'rpc_createmultisig.py', 'feature_versionbits_warning.py', 'rpc_preciousblock.py', @@ -173,6 +175,7 @@ BASE_SCRIPTS = [ 'rpc_bind.py --nonloopback', 'mining_basic.py', 'wallet_bumpfee.py', + 'wallet_bumpfee_totalfee_deprecation.py', 'rpc_named_arguments.py', 'wallet_listsinceblock.py', 'p2p_leak.py', @@ -234,6 +237,7 @@ def main(): parser.add_argument('--quiet', '-q', action='store_true', help='only print dots, results summary and failure logs') parser.add_argument('--tmpdirprefix', '-t', default=tempfile.gettempdir(), help="Root directory for datadirs") parser.add_argument('--failfast', action='store_true', help='stop execution after the first test failure') + parser.add_argument('--filter', help='filter scripts to run by regular expression') args, unknown_args = parser.parse_known_args() # args to be passed on always start with two dashes; tests are the remaining unknown args @@ -269,11 +273,22 @@ def main(): test_list = [] if tests: # Individual tests have been specified. Run specified tests that exist - # in the ALL_SCRIPTS list. Accept the name with or without .py extension. - tests = [test + ".py" if ".py" not in test else test for test in tests] + # in the ALL_SCRIPTS list. Accept names with or without a .py extension. + # Specified tests can contain wildcards, but in that case the supplied + # paths should be coherent, e.g. the same path as that provided to call + # test_runner.py. Examples: + # `test/functional/test_runner.py test/functional/wallet*` + # `test/functional/test_runner.py ./test/functional/wallet*` + # `test_runner.py wallet*` + # but not: + # `test/functional/test_runner.py wallet*` + # Multiple wildcards can be passed: + # `test_runner.py tool* mempool*` for test in tests: - if test in ALL_SCRIPTS: - test_list.append(test) + script = test.split("/")[-1] + script = script + ".py" if ".py" not in script else script + if script in ALL_SCRIPTS: + test_list.append(script) else: print("{}WARNING!{} Test '{}' not found in full test list.".format(BOLD[1], BOLD[0], test)) elif args.extended: @@ -294,6 +309,9 @@ def main(): if not exclude_list: print("{}WARNING!{} Test '{}' not found in current test list.".format(BOLD[1], BOLD[0], exclude_test)) + if args.filter: + test_list = list(filter(re.compile(args.filter).search, test_list)) + if not test_list: print("No valid test scripts specified. Check that your test is in one " "of the test lists in test_runner.py, or run test_runner.py with no arguments to run all tests") diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index fbcf21e729..28a65f7823 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -1,14 +1,20 @@ #!/usr/bin/env python3 -# Copyright (c) 2018 The Bitcoin Core developers +# Copyright (c) 2018-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. """Test bitcoin-wallet.""" + +import hashlib +import os +import stat import subprocess import textwrap from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal +BUFFER_SIZE = 16 * 1024 + class ToolWalletTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 @@ -32,23 +38,54 @@ class ToolWalletTest(BitcoinTestFramework): def assert_tool_output(self, output, *args): p = self.bitcoin_wallet_process(*args) stdout, stderr = p.communicate() - assert_equal(p.poll(), 0) assert_equal(stderr, '') assert_equal(stdout, output) + assert_equal(p.poll(), 0) - def run_test(self): + def wallet_shasum(self): + h = hashlib.sha1() + mv = memoryview(bytearray(BUFFER_SIZE)) + with open(self.wallet_path, 'rb', buffering=0) as f: + for n in iter(lambda : f.readinto(mv), 0): + h.update(mv[:n]) + return h.hexdigest() + def wallet_timestamp(self): + return os.path.getmtime(self.wallet_path) + + def wallet_permissions(self): + return oct(os.lstat(self.wallet_path).st_mode)[-3:] + + def log_wallet_timestamp_comparison(self, old, new): + result = 'unchanged' if new == old else 'increased!' + self.log.debug('Wallet file timestamp {}'.format(result)) + + def test_invalid_tool_commands_and_args(self): + self.log.info('Testing that various invalid commands raise with specific error messages') self.assert_raises_tool_error('Invalid command: foo', 'foo') - # `bitcoin-wallet help` is an error. Use `bitcoin-wallet -help` + # `bitcoin-wallet help` raises an error. Use `bitcoin-wallet -help`. self.assert_raises_tool_error('Invalid command: help', 'help') self.assert_raises_tool_error('Error: two methods provided (info and create). Only one method should be provided.', 'info', 'create') self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo') self.assert_raises_tool_error('Error loading wallet.dat. Is wallet being used by other process?', '-wallet=wallet.dat', 'info') self.assert_raises_tool_error('Error: no wallet file at nonexistent.dat', '-wallet=nonexistent.dat', 'info') - # stop the node to close the wallet to call info command + def test_tool_wallet_info(self): + # Stop the node to close the wallet to call the info command. self.stop_node(0) - + self.log.info('Calling wallet tool info, testing output') + # + # TODO: Wallet tool info should work with wallet file permissions set to + # read-only without raising: + # "Error loading wallet.dat. Is wallet being used by another process?" + # The following lines should be uncommented and the tests still succeed: + # + # self.log.debug('Setting wallet file permissions to 400 (read-only)') + # os.chmod(self.wallet_path, stat.S_IRUSR) + # assert(self.wallet_permissions() in ['400', '666']) # Sanity check. 666 because Appveyor. + # shasum_before = self.wallet_shasum() + timestamp_before = self.wallet_timestamp() + self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before)) out = textwrap.dedent('''\ Wallet info =========== @@ -59,12 +96,35 @@ class ToolWalletTest(BitcoinTestFramework): Address Book: 3 ''') self.assert_tool_output(out, '-wallet=wallet.dat', 'info') - - # mutate the wallet to check the info command output changes accordingly + timestamp_after = self.wallet_timestamp() + self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after)) + self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after) + self.log.debug('Setting wallet file permissions back to 600 (read/write)') + os.chmod(self.wallet_path, stat.S_IRUSR | stat.S_IWUSR) + assert(self.wallet_permissions() in ['600', '666']) # Sanity check. 666 because Appveyor. + # + # TODO: Wallet tool info should not write to the wallet file. + # The following lines should be uncommented and the tests still succeed: + # + # assert_equal(timestamp_before, timestamp_after) + # shasum_after = self.wallet_shasum() + # assert_equal(shasum_before, shasum_after) + # self.log.debug('Wallet file shasum unchanged\n') + + def test_tool_wallet_info_after_transaction(self): + """ + Mutate the wallet with a transaction to verify that the info command + output changes accordingly. + """ self.start_node(0) + self.log.info('Generating transaction to mutate wallet') self.nodes[0].generate(1) self.stop_node(0) + self.log.info('Calling wallet tool info after generating a transaction, testing output') + shasum_before = self.wallet_shasum() + timestamp_before = self.wallet_timestamp() + self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before)) out = textwrap.dedent('''\ Wallet info =========== @@ -75,7 +135,22 @@ class ToolWalletTest(BitcoinTestFramework): Address Book: 3 ''') self.assert_tool_output(out, '-wallet=wallet.dat', 'info') - + shasum_after = self.wallet_shasum() + timestamp_after = self.wallet_timestamp() + self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after)) + self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after) + # + # TODO: Wallet tool info should not write to the wallet file. + # This assertion should be uncommented and succeed: + # assert_equal(timestamp_before, timestamp_after) + assert_equal(shasum_before, shasum_after) + self.log.debug('Wallet file shasum unchanged\n') + + def test_tool_wallet_create_on_existing_wallet(self): + self.log.info('Calling wallet tool create on an existing wallet, testing output') + shasum_before = self.wallet_shasum() + timestamp_before = self.wallet_timestamp() + self.log.debug('Wallet file timestamp before calling create: {}'.format(timestamp_before)) out = textwrap.dedent('''\ Topping up keypool... Wallet info @@ -87,15 +162,48 @@ class ToolWalletTest(BitcoinTestFramework): Address Book: 0 ''') self.assert_tool_output(out, '-wallet=foo', 'create') - + shasum_after = self.wallet_shasum() + timestamp_after = self.wallet_timestamp() + self.log.debug('Wallet file timestamp after calling create: {}'.format(timestamp_after)) + self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after) + assert_equal(timestamp_before, timestamp_after) + assert_equal(shasum_before, shasum_after) + self.log.debug('Wallet file shasum unchanged\n') + + def test_getwalletinfo_on_different_wallet(self): + self.log.info('Starting node with arg -wallet=foo') self.start_node(0, ['-wallet=foo']) + + self.log.info('Calling getwalletinfo on a different wallet ("foo"), testing output') + shasum_before = self.wallet_shasum() + timestamp_before = self.wallet_timestamp() + self.log.debug('Wallet file timestamp before calling getwalletinfo: {}'.format(timestamp_before)) out = self.nodes[0].getwalletinfo() self.stop_node(0) + shasum_after = self.wallet_shasum() + timestamp_after = self.wallet_timestamp() + self.log.debug('Wallet file timestamp after calling getwalletinfo: {}'.format(timestamp_after)) + assert_equal(0, out['txcount']) assert_equal(1000, out['keypoolsize']) assert_equal(1000, out['keypoolsize_hd_internal']) assert_equal(True, 'hdseedid' in out) + self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after) + assert_equal(timestamp_before, timestamp_after) + assert_equal(shasum_after, shasum_before) + self.log.debug('Wallet file shasum unchanged\n') + + def run_test(self): + self.wallet_path = os.path.join(self.nodes[0].datadir, 'regtest', 'wallets', 'wallet.dat') + self.test_invalid_tool_commands_and_args() + # Warning: The following tests are order-dependent. + self.test_tool_wallet_info() + self.test_tool_wallet_info_after_transaction() + self.test_tool_wallet_create_on_existing_wallet() + self.test_getwalletinfo_on_different_wallet() + + if __name__ == '__main__': ToolWalletTest().main() diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index daa834b5b8..34e84fcf55 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -20,6 +20,9 @@ from test_framework.util import ( class WalletTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 + self.extra_args = [[ + "-acceptnonstdtxn=1", + ]] * self.num_nodes self.setup_clean_chain = True def skip_test_if_missing_module(self): diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 1fe029a6fb..bfc01e3f5e 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -37,6 +37,7 @@ class BumpFeeTest(BitcoinTestFramework): self.extra_args = [[ "-walletrbf={}".format(i), "-mintxfee=0.00002", + "-deprecatedrpc=totalFee", ] for i in range(self.num_nodes)] def skip_test_if_missing_module(self): @@ -80,6 +81,7 @@ class BumpFeeTest(BitcoinTestFramework): test_bumpfee_metadata(rbf_node, dest_address) test_locked_wallet_fails(rbf_node, dest_address) test_change_script_match(rbf_node, dest_address) + test_maxtxfee_fails(self, rbf_node, dest_address) # These tests wipe out a number of utxos that are expected in other tests test_small_output_with_feerate_succeeds(rbf_node, dest_address) test_no_more_inputs_fails(rbf_node, dest_address) @@ -248,6 +250,15 @@ def test_settxfee(rbf_node, dest_address): rbf_node.settxfee(Decimal("0.00000000")) # unset paytxfee +def test_maxtxfee_fails(test, rbf_node, dest_address): + test.restart_node(1, ['-maxtxfee=0.00003'] + test.extra_args[1]) + rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT) + rbfid = spend_one_input(rbf_node, dest_address) + assert_raises_rpc_error(-4, "Unable to create transaction: Fee exceeds maximum configured by -maxtxfee", rbf_node.bumpfee, rbfid) + test.restart_node(1, test.extra_args[1]) + rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT) + + def test_rebumping(rbf_node, dest_address): # check that re-bumping the original tx fails, but bumping the bumper succeeds rbfid = spend_one_input(rbf_node, dest_address) diff --git a/test/functional/wallet_bumpfee_totalfee_deprecation.py b/test/functional/wallet_bumpfee_totalfee_deprecation.py new file mode 100755 index 0000000000..b8e097c32e --- /dev/null +++ b/test/functional/wallet_bumpfee_totalfee_deprecation.py @@ -0,0 +1,54 @@ +#!/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. +"""Test deprecation of passing `totalFee` to the bumpfee RPC.""" +from decimal import Decimal + +from test_framework.messages import BIP125_SEQUENCE_NUMBER +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_raises_rpc_error + +class BumpFeeWithTotalFeeArgumentDeprecationTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.extra_args = [[ + "-walletrbf={}".format(i), + "-mintxfee=0.00002", + ] for i in range(self.num_nodes)] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + peer_node, rbf_node = self.nodes + peer_node.generate(110) + self.sync_all() + peer_node.sendtoaddress(rbf_node.getnewaddress(), 0.001) + self.sync_all() + peer_node.generate(1) + self.sync_all() + rbfid = spend_one_input(rbf_node, peer_node.getnewaddress()) + + self.log.info("Testing bumpfee with totalFee argument raises RPC error with deprecation message") + assert_raises_rpc_error( + -8, + "totalFee argument has been deprecated and will be removed in 0.20. " + + "Please use -deprecatedrpc=totalFee to continue using this argument until removal.", + rbf_node.bumpfee, rbfid, {"totalFee": 2000}) + + self.log.info("Testing bumpfee without totalFee argument does not raise") + rbf_node.bumpfee(rbfid) + +def spend_one_input(node, dest_address, change_size=Decimal("0.00049000")): + tx_input = dict(sequence=BIP125_SEQUENCE_NUMBER, + **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000"))) + destinations = {dest_address: Decimal("0.00050000")} + destinations[node.getrawchangeaddress()] = change_size + rawtx = node.createrawtransaction([tx_input], destinations) + signedtx = node.signrawtransactionwithwallet(rawtx) + txid = node.sendrawtransaction(signedtx["hex"]) + return txid + +if __name__ == "__main__": + BumpFeeWithTotalFeeArgumentDeprecationTest().main() diff --git a/test/functional/wallet_create_tx.py b/test/functional/wallet_create_tx.py index 0b584a0bb2..330de8b0fc 100755 --- a/test/functional/wallet_create_tx.py +++ b/test/functional/wallet_create_tx.py @@ -6,6 +6,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + assert_raises_rpc_error, ) from test_framework.blocktools import ( TIME_GENESIS_BLOCK, @@ -26,6 +27,10 @@ class CreateTxWalletTest(BitcoinTestFramework): self.nodes[0].generate(200) self.nodes[0].setmocktime(0) + self.test_anti_fee_sniping() + self.test_tx_size_too_large() + + def test_anti_fee_sniping(self): self.log.info('Check that we have some (old) blocks and that anti-fee-sniping is disabled') assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 200) txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) @@ -38,6 +43,40 @@ class CreateTxWalletTest(BitcoinTestFramework): tx = self.nodes[0].decoderawtransaction(self.nodes[0].gettransaction(txid)['hex']) assert 0 < tx['locktime'] <= 201 + def test_tx_size_too_large(self): + # More than 10kB of outputs, so that we hit -maxtxfee with a high feerate + outputs = {self.nodes[0].getnewaddress(address_type='bech32'): 0.000025 for i in range(400)} + raw_tx = self.nodes[0].createrawtransaction(inputs=[], outputs=outputs) + + for fee_setting in ['-minrelaytxfee=0.01', '-mintxfee=0.01', '-paytxfee=0.01']: + self.log.info('Check maxtxfee in combination with {}'.format(fee_setting)) + self.restart_node(0, extra_args=[fee_setting]) + assert_raises_rpc_error( + -6, + "Fee exceeds maximum configured by -maxtxfee", + lambda: self.nodes[0].sendmany(dummy="", amounts=outputs), + ) + assert_raises_rpc_error( + -4, + "Fee exceeds maximum configured by -maxtxfee", + lambda: self.nodes[0].fundrawtransaction(hexstring=raw_tx), + ) + + self.log.info('Check maxtxfee in combination with settxfee') + self.restart_node(0) + self.nodes[0].settxfee(0.01) + assert_raises_rpc_error( + -6, + "Fee exceeds maximum configured by -maxtxfee", + lambda: self.nodes[0].sendmany(dummy="", amounts=outputs), + ) + assert_raises_rpc_error( + -4, + "Fee exceeds maximum configured by -maxtxfee", + lambda: self.nodes[0].fundrawtransaction(hexstring=raw_tx), + ) + self.nodes[0].settxfee(0) + if __name__ == '__main__': CreateTxWalletTest().main() diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py index c17949a2f6..294f90a0fa 100755 --- a/test/functional/wallet_createwallet.py +++ b/test/functional/wallet_createwallet.py @@ -119,5 +119,8 @@ class CreateWalletTest(BitcoinTestFramework): # Empty passphrase, error assert_raises_rpc_error(-16, 'Cannot encrypt a wallet with a blank password', self.nodes[0].createwallet, 'w7', False, False, '') + self.log.info('Using a passphrase with private keys disabled returns error') + assert_raises_rpc_error(-4, 'Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled.', self.nodes[0].createwallet, wallet_name='w8', disable_private_keys=True, passphrase='thisisapassphrase') + if __name__ == '__main__': CreateWalletTest().main() diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py index c514b7e0b4..fbcb4e75ba 100755 --- a/test/functional/wallet_encryption.py +++ b/test/functional/wallet_encryption.py @@ -49,7 +49,7 @@ class WalletEncryptionTest(BitcoinTestFramework): assert_equal(privkey, self.nodes[0].dumpprivkey(address)) # Check that the timeout is right - time.sleep(2) + time.sleep(3) assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address) # Test wrong passphrase diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py index 5810e94938..91d26e9cb3 100755 --- a/test/functional/wallet_resendwallettransactions.py +++ b/test/functional/wallet_resendwallettransactions.py @@ -57,8 +57,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): # after the last time we tried to broadcast. Use mocktime and give an extra minute to be sure. block_time = int(time.time()) + 6 * 60 node.setmocktime(block_time) - block = create_block(int(node.getbestblockhash(), 16), create_coinbase(node.getblockchaininfo()['blocks']), block_time) - block.nVersion = 3 + block = create_block(int(node.getbestblockhash(), 16), create_coinbase(node.getblockcount() + 1), block_time) block.rehash() block.solve() node.submitblock(ToHex(block)) diff --git a/test/lint/commit-script-check.sh b/test/lint/commit-script-check.sh index 4267f9fa0d..5603456e62 100755 --- a/test/lint/commit-script-check.sh +++ b/test/lint/commit-script-check.sh @@ -18,12 +18,12 @@ if test "x$1" = "x"; then fi RET=0 -PREV_BRANCH=`git name-rev --name-only HEAD` -PREV_HEAD=`git rev-parse HEAD` -for commit in `git rev-list --reverse $1`; do +PREV_BRANCH=$(git name-rev --name-only HEAD) +PREV_HEAD=$(git rev-parse HEAD) +for commit in $(git rev-list --reverse $1); do if git rev-list -n 1 --pretty="%s" $commit | grep -q "^scripted-diff:"; then git checkout --quiet $commit^ || exit - SCRIPT="`git rev-list --format=%b -n1 $commit | sed '/^-BEGIN VERIFY SCRIPT-$/,/^-END VERIFY SCRIPT-$/{//!b};d'`" + SCRIPT="$(git rev-list --format=%b -n1 $commit | sed '/^-BEGIN VERIFY SCRIPT-$/,/^-END VERIFY SCRIPT-$/{//!b};d')" if test "x$SCRIPT" = "x"; then echo "Error: missing script for: $commit" echo "Failed" diff --git a/test/lint/extended-lint-all.sh b/test/lint/extended-lint-all.sh new file mode 100755 index 0000000000..65c51e02f5 --- /dev/null +++ b/test/lint/extended-lint-all.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# +# 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. +# +# This script runs all contrib/devtools/extended-lint-*.sh files, and fails if +# any exit with a non-zero status code. + +# This script is intentionally locale dependent by not setting "export LC_ALL=C" +# in order to allow for the executed lint scripts to opt in or opt out of locale +# dependence themselves. + +set -u + +SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}") +LINTALL=$(basename "${BASH_SOURCE[0]}") + +for f in "${SCRIPTDIR}"/extended-lint-*.sh; do + if [ "$(basename "$f")" != "$LINTALL" ]; then + if ! "$f"; then + echo "^---- failure generated from $f" + exit 1 + fi + fi +done diff --git a/test/lint/extended-lint-cppcheck.sh b/test/lint/extended-lint-cppcheck.sh new file mode 100755 index 0000000000..47df25ba6b --- /dev/null +++ b/test/lint/extended-lint-cppcheck.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# +# 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. +# + +export LC_ALL=C + +ENABLED_CHECKS=( + "Class '.*' has a constructor with 1 argument that is not explicit." + "Struct '.*' has a constructor with 1 argument that is not explicit." +) + +IGNORED_WARNINGS=( + "src/arith_uint256.h:.* Class 'arith_uint256' has a constructor with 1 argument that is not explicit." + "src/arith_uint256.h:.* Class 'base_uint < 256 >' has a constructor with 1 argument that is not explicit." + "src/arith_uint256.h:.* Class 'base_uint' has a constructor with 1 argument that is not explicit." + "src/coins.h:.* Class 'CCoinsViewBacked' has a constructor with 1 argument that is not explicit." + "src/coins.h:.* Class 'CCoinsViewCache' has a constructor with 1 argument that is not explicit." + "src/coins.h:.* Class 'CCoinsViewCursor' has a constructor with 1 argument that is not explicit." + "src/net.h:.* Class 'CNetMessage' has a constructor with 1 argument that is not explicit." + "src/policy/feerate.h:.* Class 'CFeeRate' has a constructor with 1 argument that is not explicit." + "src/prevector.h:.* Class 'const_iterator' has a constructor with 1 argument that is not explicit." + "src/prevector.h:.* Class 'const_reverse_iterator' has a constructor with 1 argument that is not explicit." + "src/prevector.h:.* Class 'iterator' has a constructor with 1 argument that is not explicit." + "src/prevector.h:.* Class 'reverse_iterator' has a constructor with 1 argument that is not explicit." + "src/primitives/block.h:.* Class 'CBlock' has a constructor with 1 argument that is not explicit." + "src/primitives/transaction.h:.* Class 'CTransaction' has a constructor with 1 argument that is not explicit." + "src/protocol.h:.* Class 'CMessageHeader' has a constructor with 1 argument that is not explicit." + "src/qt/guiutil.h:.* Class 'ItemDelegate' has a constructor with 1 argument that is not explicit." + "src/rpc/util.h:.* Struct 'RPCResults' has a constructor with 1 argument that is not explicit." + "src/rpc/util.h:.* style: Struct 'UniValueType' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'AddressDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'ComboDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'ConstPubkeyProvider' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'PKDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'PKHDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'RawDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'SHDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'WPKHDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'WSHDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/script.h:.* Class 'CScript' has a constructor with 1 argument that is not explicit." + "src/script/standard.h:.* Class 'CScriptID' has a constructor with 1 argument that is not explicit." + "src/support/allocators/secure.h:.* Struct 'secure_allocator < char >' has a constructor with 1 argument that is not explicit." + "src/support/allocators/secure.h:.* Struct 'secure_allocator < RNGState >' has a constructor with 1 argument that is not explicit." + "src/support/allocators/secure.h:.* Struct 'secure_allocator < unsigned char >' has a constructor with 1 argument that is not explicit." + "src/support/allocators/zeroafterfree.h:.* Struct 'zero_after_free_allocator < char >' has a constructor with 1 argument that is not explicit." + "src/test/checkqueue_tests.cpp:.* Struct 'FailingCheck' has a constructor with 1 argument that is not explicit." + "src/test/checkqueue_tests.cpp:.* Struct 'MemoryCheck' has a constructor with 1 argument that is not explicit." + "src/test/checkqueue_tests.cpp:.* Struct 'UniqueCheck' has a constructor with 1 argument that is not explicit." + "src/wallet/db.h:.* Class 'BerkeleyEnvironment' has a constructor with 1 argument that is not explicit." +) + +if ! command -v cppcheck > /dev/null; then + echo "Skipping cppcheck linting since cppcheck is not installed. Install by running \"apt install cppcheck\"" + exit 0 +fi + +function join_array { + local IFS="$1" + shift + echo "$*" +} + +ENABLED_CHECKS_REGEXP=$(join_array "|" "${ENABLED_CHECKS[@]}") +IGNORED_WARNINGS_REGEXP=$(join_array "|" "${IGNORED_WARNINGS[@]}") +WARNINGS=$(git ls-files -- "*.cpp" "*.h" ":(exclude)src/leveldb/" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/" | \ + xargs cppcheck --enable=all -j "$(getconf _NPROCESSORS_ONLN)" --language=c++ --std=c++11 --template=gcc -D__cplusplus -DCLIENT_VERSION_BUILD -DCLIENT_VERSION_IS_RELEASE -DCLIENT_VERSION_MAJOR -DCLIENT_VERSION_MINOR -DCLIENT_VERSION_REVISION -DCOPYRIGHT_YEAR -DDEBUG -DHAVE_WORKING_BOOST_SLEEP_FOR -I src/ -q 2>&1 | sort -u | \ + grep -E "${ENABLED_CHECKS_REGEXP}" | \ + grep -vE "${IGNORED_WARNINGS_REGEXP}") +if [[ ${WARNINGS} != "" ]]; then + echo "${WARNINGS}" + echo + echo "Advice not applicable in this specific case? Add an exception by updating" + echo "IGNORED_WARNINGS in $0" + # Uncomment to enforce the developer note policy "By default, declare single-argument constructors `explicit`" + # exit 1 +fi +exit 0 diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index 70cc16337e..8607fc4371 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -39,7 +39,7 @@ CIRCULAR_DEPENDENCIES=() IFS=$'\n' for CIRC in $(cd src && ../contrib/devtools/circular-dependencies.py {*,*/*,*/*/*}.{h,cpp} | sed -e 's/^Circular dependency: //'); do - CIRCULAR_DEPENDENCIES+=($CIRC) + CIRCULAR_DEPENDENCIES+=( "$CIRC" ) IS_EXPECTED_CIRC=0 for EXPECTED_CIRC in "${EXPECTED_CIRCULAR_DEPENDENCIES[@]}"; do if [[ "${CIRC}" == "${EXPECTED_CIRC}" ]]; then diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py index 224e62f04a..47ad896589 100755 --- a/test/lint/lint-format-strings.py +++ b/test/lint/lint-format-strings.py @@ -16,8 +16,7 @@ FALSE_POSITIVES = [ ("src/dbwrapper.cpp", "vsnprintf(p, limit - p, format, backup_ap)"), ("src/index/base.cpp", "FatalError(const char* fmt, const Args&... args)"), ("src/netbase.cpp", "LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args)"), - ("src/util/system.cpp", "strprintf(_(COPYRIGHT_HOLDERS), _(COPYRIGHT_HOLDERS_SUBSTITUTION))"), - ("src/util/system.cpp", "strprintf(COPYRIGHT_HOLDERS, COPYRIGHT_HOLDERS_SUBSTITUTION)"), + ("src/util/system.cpp", "strprintf(_(COPYRIGHT_HOLDERS).translated, COPYRIGHT_HOLDERS_SUBSTITUTION)"), ("src/wallet/wallet.h", "WalletLogPrintf(std::string fmt, Params... parameters)"), ("src/wallet/wallet.h", "LogPrintf((\"%s \" + fmt).c_str(), GetDisplayName(), parameters...)"), ("src/logging.h", "LogPrintf(const char* fmt, const Args&... args)"), diff --git a/test/lint/lint-logs.sh b/test/lint/lint-logs.sh index 1afd4cfc1a..632ed7c812 100755 --- a/test/lint/lint-logs.sh +++ b/test/lint/lint-logs.sh @@ -19,6 +19,7 @@ UNTERMINATED_LOGS=$(git grep --extended-regexp "LogPrintf?\(" -- "*.cpp" | \ grep -v "LogPrint()" | \ grep -v "LogPrintf()") if [[ ${UNTERMINATED_LOGS} != "" ]]; then + # shellcheck disable=SC2028 echo "All calls to LogPrintf() and LogPrint() should be terminated with \\n" echo echo "${UNTERMINATED_LOGS}" diff --git a/test/lint/lint-shell.sh b/test/lint/lint-shell.sh index 6f5e6546c5..69fc3cf368 100755 --- a/test/lint/lint-shell.sh +++ b/test/lint/lint-shell.sh @@ -23,25 +23,9 @@ fi # Disabled warnings: disabled=( - SC1087 # Use braces when expanding arrays, e.g. ${array[idx]} (or ${var}[.. to quiet). - SC2001 # See if you can use ${variable//search/replace} instead. - SC2004 # $/${} is unnecessary on arithmetic variables. - SC2005 # Useless echo? Instead of 'echo $(cmd)', just use 'cmd'. - SC2006 # Use $(..) instead of legacy `..`. - SC2016 # Expressions don't expand in single quotes, use double quotes for that. - SC2028 # echo won't expand escape sequences. Consider printf. SC2046 # Quote this to prevent word splitting. - SC2048 # Use "$@" (with quotes) to prevent whitespace problems. - SC2066 # Since you double quoted this, it will not word split, and the loop will only run once. SC2086 # Double quote to prevent globbing and word splitting. - SC2116 # Useless echo? Instead of 'cmd $(echo foo)', just use 'cmd foo'. SC2162 # read without -r will mangle backslashes. - SC2166 # Prefer [ p ] {&&,||} [ q ] as [ p -{a,o} q ] is not well defined. - SC2181 # Check exit code directly with e.g. 'if mycmd;', not indirectly with $?. - SC2206 # Quote to prevent word splitting, or split robustly with mapfile or read -a. - SC2207 # Prefer mapfile or read -a to split command output (or quote to avoid splitting). - SC2230 # which is non-standard. Use builtin 'command -v' instead. - SC2236 # Don't force -n instead of ! -z. ) shellcheck -e "$(IFS=","; echo "${disabled[*]}")" \ $(git ls-files -- "*.sh" | grep -vE 'src/(secp256k1|univalue)/') |