diff options
Diffstat (limited to 'test')
46 files changed, 1054 insertions, 174 deletions
diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json index dd40a096de..57f2608ee9 100644 --- a/test/functional/data/rpc_psbt.json +++ b/test/functional/data/rpc_psbt.json @@ -17,7 +17,8 @@ "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIQIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1PtnuylhxDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wCAwABAAAAAAEAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A", "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAgAAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A", - "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A" + "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A", + "cHNidP8BAHMCAAAAAbiWoY6pOQepFsEGhUPXaulX9rvye2NH+NrdlAHg+WgpAQAAAAD/////AkBLTAAAAAAAF6kUqWwXCcLM5BN2zoNqMNT5qMlIi7+HQEtMAAAAAAAXqRSVF/in2XNxAlN1OSxkyp0z+Wtg2YcAAAAAAAEBIBNssgAAAAAAF6kUamsvautR8hRlMRY6OKNTx03DK96HAQcXFgAUo8u1LWpHprjt/uENAwBpGZD0UH0BCGsCRzBEAiAONfH3DYiw67ZbylrsxCF/XXpVwyWBRgofyRbPslzvwgIgIKCsWw5sHSIPh1icNvcVLZLHWj6NA7Dk+4Os2pOnMbQBIQPGStfYHPtyhpV7zIWtn0Q4GXv5gK1zy/tnJ+cBXu4iiwABABYAFMwmJQEz+HDpBEEabxJ5PogPsqZRAAEAFgAUyCrGc3h3FYCmiIspbv2pSTKZ5jU" ], "valid" : [ "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA", diff --git a/test/functional/example_test.py b/test/functional/example_test.py index 3f15367a75..be3544ee74 100755 --- a/test/functional/example_test.py +++ b/test/functional/example_test.py @@ -164,13 +164,13 @@ class ExampleTest(BitcoinTestFramework): self.tip = int(self.nodes[0].getbestblockhash(), 16) self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1 - height = 1 + height = self.nodes[0].getblockcount() for i in range(10): # Use the mininode and blocktools functionality to manually build a block # Calling the generate() rpc is easier, but this allows us to exactly # control the blocks and transactions. - block = create_block(self.tip, create_coinbase(height), self.block_time) + block = create_block(self.tip, create_coinbase(height+1), self.block_time) block.solve() block_message = msg_block(block) # Send message is used to send a P2P message to the node over our P2PInterface diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 628cefb76d..e50f67a345 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -579,14 +579,14 @@ class FullBlockTest(BitcoinTestFramework): while b47.sha256 < target: b47.nNonce += 1 b47.rehash() - self.sync_blocks([b47], False, request_block=False) + self.sync_blocks([b47], False, force_send=True, reject_reason='high-hash') self.log.info("Reject a block with a timestamp >2 hours in the future") self.move_tip(44) b48 = self.next_block(48, solve=False) b48.nTime = int(time.time()) + 60 * 60 * 3 b48.solve() - self.sync_blocks([b48], False, request_block=False) + self.sync_blocks([b48], False, force_send=True, reject_reason='time-too-new') self.log.info("Reject a block with invalid merkle hash") self.move_tip(44) @@ -600,7 +600,7 @@ class FullBlockTest(BitcoinTestFramework): b50 = self.next_block(50) b50.nBits = b50.nBits - 1 b50.solve() - self.sync_blocks([b50], False, request_block=False, reconnect=True) + self.sync_blocks([b50], False, force_send=True, reject_reason='bad-diffbits', reconnect=True) self.log.info("Reject a block with two coinbase transactions") self.move_tip(44) @@ -630,7 +630,7 @@ class FullBlockTest(BitcoinTestFramework): b54 = self.next_block(54, spend=out[15]) b54.nTime = b35.nTime - 1 b54.solve() - self.sync_blocks([b54], False, request_block=False) + self.sync_blocks([b54], False, force_send=True, reject_reason='time-too-old') # valid timestamp self.move_tip(53) @@ -824,7 +824,7 @@ class FullBlockTest(BitcoinTestFramework): tx.vin.append(CTxIn(COutPoint(b64a.vtx[1].sha256, 0))) b64a = self.update_block("64a", [tx]) assert_equal(len(b64a.serialize()), MAX_BLOCK_BASE_SIZE + 8) - self.sync_blocks([b64a], success=False, reject_reason='non-canonical ReadCompactSize():') + self.sync_blocks([b64a], success=False, reject_reason='non-canonical ReadCompactSize()') # bitcoind doesn't disconnect us for sending a bloated block, but if we subsequently # resend the header message, it won't send us the getdata message again. Just @@ -1078,11 +1078,11 @@ class FullBlockTest(BitcoinTestFramework): self.move_tip(77) b80 = self.next_block(80, spend=out[25]) - self.sync_blocks([b80], False, request_block=False) + self.sync_blocks([b80], False, force_send=True) self.save_spendable_output() b81 = self.next_block(81, spend=out[26]) - self.sync_blocks([b81], False, request_block=False) # other chain is same length + self.sync_blocks([b81], False, force_send=True) # other chain is same length self.save_spendable_output() b82 = self.next_block(82, spend=out[27]) @@ -1189,7 +1189,7 @@ class FullBlockTest(BitcoinTestFramework): blocks2 = [] for i in range(89, LARGE_REORG_SIZE + 89): blocks2.append(self.next_block("alt" + str(i))) - self.sync_blocks(blocks2, False, request_block=False) + self.sync_blocks(blocks2, False, force_send=True) # extend alt chain to trigger re-org block = self.next_block("alt" + str(chain1_tip + 1)) @@ -1198,7 +1198,7 @@ class FullBlockTest(BitcoinTestFramework): # ... and re-org back to the first chain self.move_tip(chain1_tip) block = self.next_block(chain1_tip + 1) - self.sync_blocks([block], False, request_block=False) + self.sync_blocks([block], False, force_send=True) block = self.next_block(chain1_tip + 2) self.sync_blocks([block], True, timeout=180) @@ -1309,14 +1309,15 @@ class FullBlockTest(BitcoinTestFramework): self.nodes[0].disconnect_p2ps() self.bootstrap_p2p() - def sync_blocks(self, blocks, success=True, reject_reason=None, request_block=True, reconnect=False, timeout=60): + def sync_blocks(self, blocks, success=True, reject_reason=None, force_send=False, reconnect=False, timeout=60): """Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block. Call with success = False if the tip shouldn't advance to the most recent block.""" - self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason, request_block=request_block, timeout=timeout, expect_disconnect=reconnect) + self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason, force_send=force_send, timeout=timeout, expect_disconnect=reconnect) if reconnect: self.reconnect_p2p() + if __name__ == '__main__': FullBlockTest().main() diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index f84b08a199..302a5ec1cb 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -25,7 +25,6 @@ CLTV_HEIGHT = 1351 # Reject codes that we might receive in this test REJECT_INVALID = 16 -REJECT_OBSOLETE = 17 REJECT_NONSTANDARD = 64 def cltv_invalidate(tx): diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index 492772d5e3..d87eabaa6d 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -30,6 +30,15 @@ class ConfArgsTest(BitcoinTestFramework): 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') 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') + + with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: + conf.write('testnot.datadir=1\n[testnet]\n') + self.restart_node(0) + self.nodes[0].stop_node(expected_stderr='Warning: Section [testnet] is not recognized.' + os.linesep + 'Warning: Section [testnot] is not recognized.') + + with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('') # clear def run_test(self): diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index ae1eacf2d7..70d67aa53a 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -69,6 +69,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): def setup_network(self): self.add_nodes(self.num_nodes, extra_args=self.extra_args) self.start_nodes() + self.import_deterministic_coinbase_privkeys() # Leave them unconnected, we'll use submitblock directly in this test def restart_node(self, node_index, expected_tip): diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py index 16272877e7..9cbc1b39bd 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -22,7 +22,6 @@ DERSIG_HEIGHT = 1251 # Reject codes that we might receive in this test REJECT_INVALID = 16 -REJECT_OBSOLETE = 17 REJECT_NONSTANDARD = 64 # A canonical signature consists of: diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index aaab4279b5..b68e46adbc 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -144,6 +144,9 @@ class EstimateFeeTest(BitcoinTestFramework): # (68k weight is room enough for 120 or so transactions) # Node2 is a stingy miner, that # produces too small blocks (room for only 55 or so transactions) + self.start_nodes() + self.import_deterministic_coinbase_privkeys() + self.stop_nodes() def transact_and_mine(self, numblocks, mining_node): min_fee = Decimal("0.00001") @@ -171,11 +174,6 @@ class EstimateFeeTest(BitcoinTestFramework): newmem.append(utx) self.memutxo = newmem - def import_deterministic_coinbase_privkeys(self): - self.start_nodes() - super().import_deterministic_coinbase_privkeys() - self.stop_nodes() - def run_test(self): self.log.info("This test is time consuming, please be patient") self.log.info("Splitting inputs so we can generate tx's") diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index 772151dc4b..c162f46d63 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -63,6 +63,8 @@ class PruneTest(BitcoinTestFramework): def setup_nodes(self): self.add_nodes(self.num_nodes, self.extra_args) self.start_nodes() + for n in self.nodes: + n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase', rescan=False) def create_big_chain(self): # Start by creating some coinbases we can spend later @@ -247,7 +249,7 @@ class PruneTest(BitcoinTestFramework): return index def prune(index, expected_ret=None): - ret = node.pruneblockchain(height(index)) + ret = node.pruneblockchain(height=height(index)) # Check the return value. When use_timestamp is True, just check # that the return value is less than or equal to the expected # value, because when more than one block is generated per second, diff --git a/test/functional/interface_rpc.py b/test/functional/interface_rpc.py new file mode 100755 index 0000000000..e3d7b0655d --- /dev/null +++ b/test/functional/interface_rpc.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Tests some generic aspects of the RPC interface.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + +class RPCInterfaceTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + + def test_batch_request(self): + self.log.info("Testing basic JSON-RPC batch request...") + + results = self.nodes[0].batch([ + # A basic request that will work fine. + {"method": "getblockcount", "id": 1}, + # Request that will fail. The whole batch request should still + # work fine. + {"method": "invalidmethod", "id": 2}, + # Another call that should succeed. + {"method": "getbestblockhash", "id": 3}, + ]) + + result_by_id = {} + for res in results: + result_by_id[res["id"]] = res + + assert_equal(result_by_id[1]['error'], None) + assert_equal(result_by_id[1]['result'], 0) + + assert_equal(result_by_id[2]['error']['code'], -32601) + assert_equal(result_by_id[2]['result'], None) + + assert_equal(result_by_id[3]['error'], None) + assert result_by_id[3]['result'] is not None + + def run_test(self): + self.test_batch_request() + + +if __name__ == '__main__': + RPCInterfaceTest().main() diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py index 48136a0108..94fea37090 100755 --- a/test/functional/interface_zmq.py +++ b/test/functional/interface_zmq.py @@ -69,6 +69,7 @@ class ZMQTest (BitcoinTestFramework): ] self.add_nodes(self.num_nodes, self.extra_args) self.start_nodes() + self.import_deterministic_coinbase_privkeys() def run_test(self): try: @@ -121,10 +122,10 @@ class ZMQTest (BitcoinTestFramework): self.log.info("Test the getzmqnotifications RPC") assert_equal(self.nodes[0].getzmqnotifications(), [ - {"type": "pubhashblock", "address": ADDRESS}, - {"type": "pubhashtx", "address": ADDRESS}, - {"type": "pubrawblock", "address": ADDRESS}, - {"type": "pubrawtx", "address": ADDRESS}, + {"type": "pubhashblock", "address": ADDRESS, "hwm": 1000}, + {"type": "pubhashtx", "address": ADDRESS, "hwm": 1000}, + {"type": "pubrawblock", "address": ADDRESS, "hwm": 1000}, + {"type": "pubrawtx", "address": ADDRESS, "hwm": 1000}, ]) assert_equal(self.nodes[1].getzmqnotifications(), []) diff --git a/test/functional/mempool_resurrect.py b/test/functional/mempool_resurrect.py index d035ca907a..845beb551e 100755 --- a/test/functional/mempool_resurrect.py +++ b/test/functional/mempool_resurrect.py @@ -47,12 +47,11 @@ class MempoolCoinbaseTest(BitcoinTestFramework): tx = self.nodes[0].gettransaction(txid) assert(tx["confirmations"] > 0) - # Use invalidateblock to re-org back; all transactions should - # end up unconfirmed and back in the mempool + # Use invalidateblock to re-org back for node in self.nodes: node.invalidateblock(blocks[0]) - # mempool should be empty, all txns confirmed + # All txns should be back in mempool with 0 confirmations assert_equal(set(self.nodes[0].getrawmempool()), set(spends1_id+spends2_id)) for txid in spends1_id+spends2_id: tx = self.nodes[0].gettransaction(txid) diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index ff55ea5528..9f01be0646 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -30,9 +30,10 @@ from test_framework.util import ( def assert_template(node, block, expect, rehash=True): if rehash: block.hashMerkleRoot = block.calc_merkle_root() - rsp = node.getblocktemplate({'data': b2x(block.serialize()), 'mode': 'proposal'}) + rsp = node.getblocktemplate(template_request={'data': b2x(block.serialize()), 'mode': 'proposal'}) assert_equal(rsp, expect) + class MiningTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 diff --git a/test/functional/p2p_disconnect_ban.py b/test/functional/p2p_disconnect_ban.py index 67f24d6bff..1b11a2a294 100755 --- a/test/functional/p2p_disconnect_ban.py +++ b/test/functional/p2p_disconnect_ban.py @@ -22,7 +22,7 @@ class DisconnectBanTest(BitcoinTestFramework): self.log.info("setban: successfully ban single IP address") assert_equal(len(self.nodes[1].getpeerinfo()), 2) # node1 should have 2 connections to node0 at this point - self.nodes[1].setban("127.0.0.1", "add") + self.nodes[1].setban(subnet="127.0.0.1", command="add") wait_until(lambda: len(self.nodes[1].getpeerinfo()) == 0, timeout=10) assert_equal(len(self.nodes[1].getpeerinfo()), 0) # all nodes must be disconnected at this point assert_equal(len(self.nodes[1].listbanned()), 1) diff --git a/test/functional/p2p_invalid_block.py b/test/functional/p2p_invalid_block.py index 0678b1a651..1e0b876593 100755 --- a/test/functional/p2p_invalid_block.py +++ b/test/functional/p2p_invalid_block.py @@ -77,9 +77,9 @@ class InvalidBlockRequestTest(BitcoinTestFramework): block2.vtx.append(tx2) assert_equal(block2.hashMerkleRoot, block2.calc_merkle_root()) assert_equal(orig_hash, block2.rehash()) - assert(block2_orig.vtx != block2.vtx) + assert block2_orig.vtx != block2.vtx - node.p2p.send_blocks_and_test([block2], node, success=False, request_block=False, reject_reason='bad-txns-duplicate') + node.p2p.send_blocks_and_test([block2], node, success=False, reject_reason='bad-txns-duplicate') # Check transactions for duplicate inputs self.log.info("Test duplicate input block.") @@ -89,7 +89,7 @@ class InvalidBlockRequestTest(BitcoinTestFramework): block2_orig.hashMerkleRoot = block2_orig.calc_merkle_root() block2_orig.rehash() block2_orig.solve() - node.p2p.send_blocks_and_test([block2_orig], node, success=False, request_block=False, reject_reason='bad-txns-inputs-duplicate') + node.p2p.send_blocks_and_test([block2_orig], node, success=False, reject_reason='bad-txns-inputs-duplicate') self.log.info("Test very broken block.") @@ -102,7 +102,8 @@ class InvalidBlockRequestTest(BitcoinTestFramework): block3.rehash() block3.solve() - node.p2p.send_blocks_and_test([block3], node, success=False, request_block=False, reject_reason='bad-cb-amount') + node.p2p.send_blocks_and_test([block3], node, success=False, reject_reason='bad-cb-amount') + if __name__ == '__main__': InvalidBlockRequestTest().main() diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py new file mode 100755 index 0000000000..3ee67980a4 --- /dev/null +++ b/test/functional/p2p_invalid_messages.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-2018 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 node responses to invalid network messages.""" +import struct + +from test_framework import messages +from test_framework.mininode import P2PDataStore +from test_framework.test_framework import BitcoinTestFramework + + +class msg_unrecognized: + """Nonsensical message. Modeled after similar types in test_framework.messages.""" + + command = b'badmsg' + + def __init__(self, str_data): + self.str_data = str_data.encode() if not isinstance(str_data, bytes) else str_data + + def serialize(self): + return messages.ser_string(self.str_data) + + def __repr__(self): + return "{}(data={})".format(self.command, self.str_data) + + +class msg_nametoolong(msg_unrecognized): + + command = b'thisnameiswayyyyyyyyytoolong' + + +class InvalidMessagesTest(BitcoinTestFramework): + + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + + def run_test(self): + """ + 0. Send a bunch of large (4MB) messages of an unrecognized type. Check to see + that it isn't an effective DoS against the node. + + 1. Send an oversized (4MB+) message and check that we're disconnected. + + 2. Send a few messages with an incorrect data size in the header, ensure the + messages are ignored. + + 3. Send an unrecognized message with a command name longer than 12 characters. + + """ + node = self.nodes[0] + self.node = node + node.add_p2p_connection(P2PDataStore()) + conn2 = node.add_p2p_connection(P2PDataStore()) + + msg_limit = 4 * 1000 * 1000 # 4MB, per MAX_PROTOCOL_MESSAGE_LENGTH + valid_data_limit = msg_limit - 5 # Account for the 4-byte length prefix + + # + # 0. + # + # Send as large a message as is valid, ensure we aren't disconnected but + # also can't exhaust resources. + # + msg_at_size = msg_unrecognized("b" * valid_data_limit) + assert len(msg_at_size.serialize()) == msg_limit + + with node.assert_memory_usage_stable(increase_allowed=0.5): + self.log.info( + "Sending a bunch of large, junk messages to test " + "memory exhaustion. May take a bit...") + + # Run a bunch of times to test for memory exhaustion. + for _ in range(80): + node.p2p.send_message(msg_at_size) + + # Check that, even though the node is being hammered by nonsense from one + # connection, it can still service other peers in a timely way. + for _ in range(20): + conn2.sync_with_ping(timeout=2) + + # Peer 1, despite serving up a bunch of nonsense, should still be connected. + self.log.info("Waiting for node to drop junk messages.") + node.p2p.sync_with_ping(timeout=30) + assert node.p2p.is_connected + + # + # 1. + # + # Send an oversized message, ensure we're disconnected. + # + msg_over_size = msg_unrecognized("b" * (valid_data_limit + 1)) + assert len(msg_over_size.serialize()) == (msg_limit + 1) + + with node.assert_debug_log(["Oversized message from peer=0, 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() + + # + # 2. + # + # Send messages with an incorrect data size in the header. + # + actual_size = 100 + msg = msg_unrecognized("b" * actual_size) + + # TODO: handle larger-than cases. I haven't been able to pin down what behavior to expect. + for wrong_size in (2, 77, 78, 79): + self.log.info("Sending a message with incorrect size of {}".format(wrong_size)) + + # Unmodified message should submit okay. + node.p2p.send_and_ping(msg) + + # A message lying about its data size results in a disconnect when the incorrect + # data size is less than the actual size. + # + # TODO: why does behavior change at 78 bytes? + # + node.p2p.send_raw_message(self._tweak_msg_data_size(msg, wrong_size)) + + # For some reason unknown to me, we sometimes have to push additional data to the + # peer in order for it to realize a disconnect. + try: + node.p2p.send_message(messages.msg_ping(nonce=123123)) + except IOError: + pass + + node.p2p.wait_for_disconnect(timeout=10) + node.disconnect_p2ps() + node.add_p2p_connection(P2PDataStore()) + + # + # 3. + # + # Send a message with a too-long command name. + # + node.p2p.send_message(msg_nametoolong("foobar")) + node.p2p.wait_for_disconnect(timeout=4) + + # Node is still up. + conn = node.add_p2p_connection(P2PDataStore()) + conn.sync_with_ping() + + + def _tweak_msg_data_size(self, message, wrong_size): + """ + Return a raw message based on another message but with an incorrect data size in + the message header. + """ + raw_msg = self.node.p2p.build_message(message) + + bad_size_bytes = struct.pack("<I", wrong_size) + num_header_bytes_before_size = 4 + 12 + + # Replace the correct data size in the message with an incorrect one. + raw_msg_with_wrong_size = ( + raw_msg[:num_header_bytes_before_size] + + bad_size_bytes + + raw_msg[(num_header_bytes_before_size + len(bad_size_bytes)):] + ) + assert len(raw_msg) == len(raw_msg_with_wrong_size) + + return raw_msg_with_wrong_size + + + +if __name__ == '__main__': + InvalidMessagesTest().main() diff --git a/test/functional/p2p_leak_tx.py b/test/functional/p2p_leak_tx.py new file mode 100755 index 0000000000..dc4d475b2d --- /dev/null +++ b/test/functional/p2p_leak_tx.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017-2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test that we don't leak txs to inbound peers that we haven't yet announced to""" + +from test_framework.messages import msg_getdata, CInv +from test_framework.mininode import P2PDataStore +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, +) + + +class P2PNode(P2PDataStore): + def on_inv(self, msg): + pass + + +class P2PLeakTxTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + gen_node = self.nodes[0] # The block and tx generating node + gen_node.generate(1) + + inbound_peer = self.nodes[0].add_p2p_connection(P2PNode()) # An "attacking" inbound peer + + MAX_REPEATS = 100 + self.log.info("Running test up to {} times.".format(MAX_REPEATS)) + for i in range(MAX_REPEATS): + self.log.info('Run repeat {}'.format(i + 1)) + txid = gen_node.sendtoaddress(gen_node.getnewaddress(), 0.01) + + want_tx = msg_getdata() + want_tx.inv.append(CInv(t=1, h=int(txid, 16))) + inbound_peer.last_message.pop('notfound', None) + inbound_peer.send_message(want_tx) + inbound_peer.sync_with_ping() + + if inbound_peer.last_message.get('notfound'): + self.log.debug('tx {} was not yet announced to us.'.format(txid)) + self.log.debug("node has responded with a notfound message. End test.") + assert_equal(inbound_peer.last_message['notfound'].vec[0].hash, int(txid, 16)) + inbound_peer.last_message.pop('notfound') + break + else: + self.log.debug('tx {} was already announced to us. Try test again.'.format(txid)) + assert int(txid, 16) in [inv.hash for inv in inbound_peer.last_message['inv'].inv] + + +if __name__ == '__main__': + P2PLeakTxTest().main() diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py index ec3d336dc1..359880506e 100755 --- a/test/functional/p2p_node_network_limited.py +++ b/test/functional/p2p_node_network_limited.py @@ -43,8 +43,8 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): disconnect_nodes(self.nodes[1], 2) def setup_network(self): - super(NodeNetworkLimitedTest, self).setup_network() - self.disconnect_all() + self.add_nodes(self.num_nodes, self.extra_args) + self.start_nodes() def run_test(self): node = self.nodes[0].add_p2p_connection(P2PIgnoreInv()) diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 92b690176d..31e60f1cea 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -133,7 +133,7 @@ class BlockchainTest(BitcoinTestFramework): assert_raises_rpc_error(-8, "Block is not in main chain", self.nodes[0].getchaintxstats, blockhash=blockhash) self.nodes[0].reconsiderblock(blockhash) - chaintxstats = self.nodes[0].getchaintxstats(1) + chaintxstats = self.nodes[0].getchaintxstats(nblocks=1) # 200 txs plus genesis tx assert_equal(chaintxstats['txcount'], 201) # tx rate should be 1 per 10 minutes, or 1/600 @@ -211,7 +211,7 @@ class BlockchainTest(BitcoinTestFramework): besthash = node.getbestblockhash() secondbesthash = node.getblockhash(199) - header = node.getblockheader(besthash) + header = node.getblockheader(blockhash=besthash) assert_equal(header['hash'], besthash) assert_equal(header['height'], 200) @@ -287,7 +287,7 @@ class BlockchainTest(BitcoinTestFramework): def assert_waitforheight(height, timeout=2): assert_equal( - node.waitforblockheight(height, timeout)['height'], + node.waitforblockheight(height=height, timeout=timeout)['height'], current_height) assert_waitforheight(0) diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py index be096af892..78d6e78aed 100755 --- a/test/functional/rpc_help.py +++ b/test/functional/rpc_help.py @@ -7,12 +7,18 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error +import os + class HelpRpcTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def run_test(self): + self.test_categories() + self.dump_help() + + def test_categories(self): node = self.nodes[0] # wrong argument count @@ -37,6 +43,15 @@ class HelpRpcTest(BitcoinTestFramework): assert_equal(titles, components) + def dump_help(self): + dump_dir = os.path.join(self.options.tmpdir, 'rpc_help_dump') + os.mkdir(dump_dir) + calls = [line.split(' ', 1)[0] for line in self.nodes[0].help().splitlines() if line and not line.startswith('==')] + for call in calls: + with open(os.path.join(dump_dir, call), 'w', encoding='utf-8') as f: + # Make sure the node can generate the help at runtime without crashing + f.write(self.nodes[0].help(call)) + if __name__ == '__main__': HelpRpcTest().main() diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index 1e525214fa..0affddcf05 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -74,12 +74,12 @@ class NetTest(BitcoinTestFramework): assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True) assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2) - self.nodes[0].setnetworkactive(False) + self.nodes[0].setnetworkactive(state=False) assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], False) # Wait a bit for all sockets to close wait_until(lambda: self.nodes[0].getnetworkinfo()['connections'] == 0, timeout=3) - self.nodes[0].setnetworkactive(True) + self.nodes[0].setnetworkactive(state=True) connect_nodes_bi(self.nodes, 0, 1) assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True) assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2) @@ -88,7 +88,7 @@ class NetTest(BitcoinTestFramework): assert_equal(self.nodes[0].getaddednodeinfo(), []) # add a node (node2) to node0 ip_port = "127.0.0.1:{}".format(p2p_port(2)) - self.nodes[0].addnode(ip_port, 'add') + self.nodes[0].addnode(node=ip_port, command='add') # check that the node has indeed been added added_nodes = self.nodes[0].getaddednodeinfo(ip_port) assert_equal(len(added_nodes), 1) diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 54dc871448..04d9bb65a6 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -6,7 +6,7 @@ """ from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error, find_output +from test_framework.util import assert_equal, assert_raises_rpc_error, find_output, disconnect_nodes, connect_nodes_bi, sync_blocks import json import os @@ -23,6 +23,45 @@ class PSBTTest(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() + def test_utxo_conversion(self): + mining_node = self.nodes[2] + offline_node = self.nodes[0] + online_node = self.nodes[1] + + # Disconnect offline node from others + disconnect_nodes(offline_node, 1) + disconnect_nodes(online_node, 0) + disconnect_nodes(offline_node, 2) + disconnect_nodes(mining_node, 0) + + # Mine a transaction that credits the offline address + offline_addr = offline_node.getnewaddress(address_type="p2sh-segwit") + online_addr = online_node.getnewaddress(address_type="p2sh-segwit") + online_node.importaddress(offline_addr, "", False) + mining_node.sendtoaddress(address=offline_addr, amount=1.0) + mining_node.generate(nblocks=1) + sync_blocks([mining_node, online_node]) + + # Construct an unsigned PSBT on the online node (who doesn't know the output is Segwit, so will include a non-witness UTXO) + utxos = online_node.listunspent(addresses=[offline_addr]) + raw = online_node.createrawtransaction([{"txid":utxos[0]["txid"], "vout":utxos[0]["vout"]}],[{online_addr:0.9999}]) + psbt = online_node.walletprocesspsbt(online_node.converttopsbt(raw))["psbt"] + assert("non_witness_utxo" in mining_node.decodepsbt(psbt)["inputs"][0]) + + # Have the offline node sign the PSBT (which will update the UTXO to segwit) + signed_psbt = offline_node.walletprocesspsbt(psbt)["psbt"] + assert("witness_utxo" in mining_node.decodepsbt(signed_psbt)["inputs"][0]) + + # Make sure we can mine the resulting transaction + txid = mining_node.sendrawtransaction(mining_node.finalizepsbt(signed_psbt)["hex"]) + mining_node.generate(1) + sync_blocks([mining_node, online_node]) + assert_equal(online_node.gettxout(txid,0)["confirmations"], 1) + + # Reconnect + connect_nodes_bi(self.nodes, 0, 1) + connect_nodes_bi(self.nodes, 0, 2) + def run_test(self): # Create and fund a raw tx for sending 10 BTC psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.nodes[2].getnewaddress():10})['psbt'] @@ -107,6 +146,9 @@ class PSBTTest(BitcoinTestFramework): # Make sure that a psbt with signatures cannot be converted signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex']) assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex']) + assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex'], False) + # Unless we allow it to convert and strip signatures + self.nodes[0].converttopsbt(signedtx['hex'], True) # Explicitly allow converting non-empty txs new_psbt = self.nodes[0].converttopsbt(rawtx['hex']) @@ -168,6 +210,13 @@ class PSBTTest(BitcoinTestFramework): assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE assert_equal(decoded_psbt["tx"]["locktime"], 0) + # Regression test for 14473 (mishandling of already-signed witness transaction): + psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}]) + complete_psbt = self.nodes[0].walletprocesspsbt(psbtx_info["psbt"]) + double_processed_psbt = self.nodes[0].walletprocesspsbt(complete_psbt["psbt"]) + assert_equal(complete_psbt, double_processed_psbt) + # We don't care about the decode result, but decoding must succeed. + self.nodes[0].decodepsbt(double_processed_psbt["psbt"]) # BIP 174 Test Vectors @@ -224,6 +273,16 @@ class PSBTTest(BitcoinTestFramework): extracted = self.nodes[2].finalizepsbt(extractor['extract'], True)['hex'] assert_equal(extracted, extractor['result']) + # Unload extra wallets + for i, signer in enumerate(signers): + self.nodes[2].unloadwallet("wallet{}".format(i)) + + self.test_utxo_conversion() + + # Test that psbts with p2pkh outputs are created properly + p2pkh = self.nodes[0].getnewaddress(address_type='legacy') + psbt = self.nodes[1].walletcreatefundedpsbt([], [{p2pkh : 1}], 0, {"includeWatching" : True}, True) + self.nodes[0].decodepsbt(psbt['psbt']) if __name__ == '__main__': PSBTTest().main() diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 35004fb588..81cce1167b 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -169,7 +169,7 @@ def get_legacy_sigopcount_tx(tx, accurate=True): return count def witness_script(use_p2wsh, pubkey): - """Create a scriptPubKey for a pay-to-wtiness TxOut. + """Create a scriptPubKey for a pay-to-witness TxOut. This is either a P2WPKH output for the given pubkey, or a P2WSH output of a 1-of-1 multisig for the given pubkey. Returns the hex encoding of the diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 8e9372767d..c72cb8835c 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -35,7 +35,6 @@ MY_VERSION = 70014 # past bip-31 for ping/pong MY_SUBVERSION = b"/python-mininode-tester:0.0.3/" MY_RELAY = 1 # from version 70001 onwards, fRelay should be appended to version messages (BIP37) -MAX_INV_SZ = 50000 MAX_LOCATOR_SZ = 101 MAX_BLOCK_BASE_SIZE = 1000000 @@ -58,9 +57,6 @@ MSG_TYPE_MASK = 0xffffffff >> 2 def sha256(s): return hashlib.new('sha256', s).digest() -def ripemd160(s): - return hashlib.new('ripemd160', s).digest() - def hash256(s): return sha256(sha256(s)) @@ -887,13 +883,12 @@ class BlockTransactions: class CPartialMerkleTree: - __slots__ = ("fBad", "nTransactions", "vBits", "vHash") + __slots__ = ("nTransactions", "vBits", "vHash") def __init__(self): self.nTransactions = 0 self.vHash = [] self.vBits = [] - self.fBad = False def deserialize(self, f): self.nTransactions = struct.unpack("<i", f.read(4))[0] @@ -1232,6 +1227,23 @@ class msg_mempool: return "msg_mempool()" +class msg_notfound: + __slots__ = ("vec", ) + command = b"notfound" + + def __init__(self, vec=None): + self.vec = vec or [] + + def deserialize(self, f): + self.vec = deser_vector(f, CInv) + + def serialize(self): + return ser_vector(self.vec) + + def __repr__(self): + return "msg_notfound(vec=%s)" % (repr(self.vec)) + + class msg_sendheaders: __slots__ = () command = b"sendheaders" diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index 6864e8a838..ca5734d67d 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -21,7 +21,38 @@ import struct import sys import threading -from test_framework.messages import CBlockHeader, MIN_VERSION_SUPPORTED, msg_addr, msg_block, MSG_BLOCK, msg_blocktxn, msg_cmpctblock, msg_feefilter, msg_getaddr, msg_getblocks, msg_getblocktxn, msg_getdata, msg_getheaders, msg_headers, msg_inv, msg_mempool, msg_ping, msg_pong, msg_reject, msg_sendcmpct, msg_sendheaders, msg_tx, MSG_TX, MSG_TYPE_MASK, msg_verack, msg_version, NODE_NETWORK, NODE_WITNESS, sha256 +from test_framework.messages import ( + CBlockHeader, + MIN_VERSION_SUPPORTED, + msg_addr, + msg_block, + MSG_BLOCK, + msg_blocktxn, + msg_cmpctblock, + msg_feefilter, + msg_getaddr, + msg_getblocks, + msg_getblocktxn, + msg_getdata, + msg_getheaders, + msg_headers, + msg_inv, + msg_mempool, + msg_notfound, + msg_ping, + msg_pong, + msg_reject, + msg_sendcmpct, + msg_sendheaders, + msg_tx, + MSG_TX, + MSG_TYPE_MASK, + msg_verack, + msg_version, + NODE_NETWORK, + NODE_WITNESS, + sha256, +) from test_framework.util import wait_until logger = logging.getLogger("TestFramework.mininode") @@ -40,6 +71,7 @@ MESSAGEMAP = { b"headers": msg_headers, b"inv": msg_inv, b"mempool": msg_mempool, + b"notfound": msg_notfound, b"ping": msg_ping, b"pong": msg_pong, b"reject": msg_reject, @@ -175,10 +207,13 @@ class P2PConnection(asyncio.Protocol): This method takes a P2P payload, builds the P2P header and adds the message to the send buffer to be sent over the socket.""" + tmsg = self.build_message(message) + self._log_message("send", message) + return self.send_raw_message(tmsg) + + def send_raw_message(self, raw_message_bytes): if not self.is_connected: raise IOError('Not connected') - self._log_message("send", message) - tmsg = self._build_message(message) def maybe_write(): if not self._transport: @@ -188,12 +223,12 @@ class P2PConnection(asyncio.Protocol): # Python 3.4 versions. if hasattr(self._transport, 'is_closing') and self._transport.is_closing(): return - self._transport.write(tmsg) + self._transport.write(raw_message_bytes) NetworkThread.network_event_loop.call_soon_threadsafe(maybe_write) # Class utility methods - def _build_message(self, message): + def build_message(self, message): """Build a serialized P2P message""" command = message.command data = message.serialize() @@ -295,6 +330,7 @@ class P2PInterface(P2PConnection): def on_getheaders(self, message): pass def on_headers(self, message): pass def on_mempool(self, message): pass + def on_notfound(self, message): pass def on_pong(self, message): pass def on_reject(self, message): pass def on_sendcmpct(self, message): pass @@ -313,7 +349,7 @@ class P2PInterface(P2PConnection): self.send_message(msg_pong(message.nonce)) def on_verack(self, message): - self.verack_received = True + pass def on_version(self, message): assert message.nVersion >= MIN_VERSION_SUPPORTED, "Version {} received. Test framework only supports versions greater than {}".format(message.nVersion, MIN_VERSION_SUPPORTED) @@ -376,9 +412,9 @@ class P2PInterface(P2PConnection): # Message sending helper functions - def send_and_ping(self, message): + def send_and_ping(self, message, timeout=60): self.send_message(message) - self.sync_with_ping() + self.sync_with_ping(timeout=timeout) # Sync up with the node def sync_with_ping(self, timeout=60): @@ -475,14 +511,14 @@ class P2PDataStore(P2PInterface): if response is not None: self.send_message(response) - def send_blocks_and_test(self, blocks, node, *, success=True, request_block=True, reject_reason=None, expect_disconnect=False, timeout=60): + def send_blocks_and_test(self, blocks, node, *, success=True, force_send=False, reject_reason=None, expect_disconnect=False, timeout=60): """Send blocks to test node and test whether the tip advances. - add all blocks to our block_store - send a headers message for the final block - the on_getheaders handler will ensure that any getheaders are responded to - - if request_block is True: wait for getdata for each of the blocks. The on_getdata handler will - ensure that any getdata messages are responded to + - if force_send is False: wait for getdata for each of the blocks. The on_getdata handler will + ensure that any getdata messages are responded to. Otherwise send the full block unsolicited. - if success is True: assert that the node's tip advances to the most recent block - if success is False: assert that the node's tip doesn't advance - if reject_reason is set: assert that the correct reject message is logged""" @@ -494,9 +530,11 @@ class P2PDataStore(P2PInterface): reject_reason = [reject_reason] if reject_reason else [] with node.assert_debug_log(expected_msgs=reject_reason): - self.send_message(msg_headers([CBlockHeader(blocks[-1])])) - - if request_block: + if force_send: + for b in blocks: + self.send_message(msg_block(block=b)) + else: + self.send_message(msg_headers([CBlockHeader(blocks[-1])])) wait_until(lambda: blocks[-1].sha256 in self.getdata_requests, timeout=timeout, lock=mininode_lock) if expect_disconnect: diff --git a/test/functional/test_framework/socks5.py b/test/functional/test_framework/socks5.py index dd0f209268..a21c864e75 100644 --- a/test/functional/test_framework/socks5.py +++ b/test/functional/test_framework/socks5.py @@ -54,10 +54,9 @@ class Socks5Command(): return 'Socks5Command(%s,%s,%s,%s,%s,%s)' % (self.cmd, self.atyp, self.addr, self.port, self.username, self.password) class Socks5Connection(): - def __init__(self, serv, conn, peer): + def __init__(self, serv, conn): self.serv = serv self.conn = conn - self.peer = peer def handle(self): """Handle socks5 request according to RFC192.""" @@ -137,9 +136,9 @@ class Socks5Server(): def run(self): while self.running: - (sockconn, peer) = self.s.accept() + (sockconn, _) = self.s.accept() if self.running: - conn = Socks5Connection(self, sockconn, peer) + conn = Socks5Connection(self, sockconn) thread = threading.Thread(None, conn.handle) thread.daemon = True thread.start() diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 7e2ec673df..44fc185e6d 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -168,7 +168,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.skip_test_if_missing_module() self.setup_chain() self.setup_network() - self.import_deterministic_coinbase_privkeys() self.run_test() success = TestStatus.PASSED except JSONRPCException as e: @@ -261,11 +260,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): extra_args = self.extra_args self.add_nodes(self.num_nodes, extra_args) self.start_nodes() + self.import_deterministic_coinbase_privkeys() def import_deterministic_coinbase_privkeys(self): - if self.setup_clean_chain: - return - for n in self.nodes: try: n.getwalletinfo() @@ -273,7 +270,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): assert str(e).startswith('Method not found') continue - n.importprivkey(n.get_deterministic_priv_key().key) + n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase') def run_test(self): """Tests must override this method to define test logic""" diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index cc1bfabbfa..27f99c259c 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -115,6 +115,25 @@ class TestNode(): ] return PRIV_KEYS[self.index] + def get_mem_rss_kilobytes(self): + """Get the memory usage (RSS) per `ps`. + + Returns None if `ps` is unavailable. + """ + assert self.running + + try: + return int(subprocess.check_output( + ["ps", "h", "-o", "rss", "{}".format(self.process.pid)], + stderr=subprocess.DEVNULL).split()[-1]) + + # Avoid failing on platforms where ps isn't installed. + # + # We could later use something like `psutils` to work across platforms. + except (FileNotFoundError, subprocess.SubprocessError): + self.log.exception("Unable to get memory usage") + return None + def _node_msg(self, msg: str) -> str: """Return a modified msg that identifies this node by its index as a debugging aid.""" return "[node %d] %s" % (self.index, msg) @@ -199,21 +218,6 @@ class TestNode(): def generate(self, nblocks, maxtries=1000000): self.log.debug("TestNode.generate() dispatches `generate` call to `generatetoaddress`") - # Try to import the node's deterministic private key. This is a no-op if the private key - # has already been imported. - try: - self.rpc.importprivkey(privkey=self.get_deterministic_priv_key().key, label='coinbase', rescan=False) - except JSONRPCException as e: - # This may fail if: - # - wallet is disabled ('Method not found') - # - there are multiple wallets to import to ('Wallet file not specified') - # - wallet is locked ('Error: Please enter the wallet passphrase with walletpassphrase first') - # Just ignore those errors. We can make this tidier by importing the privkey during TestFramework.setup_nodes - # TODO: tidy up deterministic privkey import. - assert str(e).startswith('Method not found') or \ - str(e).startswith('Wallet file not specified') or \ - str(e).startswith('Error: Please enter the wallet passphrase with walletpassphrase first') - return self.generatetoaddress(nblocks=nblocks, address=self.get_deterministic_priv_key().address, maxtries=maxtries) def get_wallet_rpc(self, wallet_name): @@ -286,6 +290,33 @@ class TestNode(): if re.search(re.escape(expected_msg), log, flags=re.MULTILINE) is None: self._raise_assertion_error('Expected message "{}" does not partially match log:\n\n{}\n\n'.format(expected_msg, print_log)) + @contextlib.contextmanager + def assert_memory_usage_stable(self, *, increase_allowed=0.03): + """Context manager that allows the user to assert that a node's memory usage (RSS) + hasn't increased beyond some threshold percentage. + + Args: + increase_allowed (float): the fractional increase in memory allowed until failure; + e.g. `0.12` for up to 12% increase allowed. + """ + before_memory_usage = self.get_mem_rss_kilobytes() + + yield + + after_memory_usage = self.get_mem_rss_kilobytes() + + if not (before_memory_usage and after_memory_usage): + self.log.warning("Unable to detect memory usage (RSS) - skipping memory check.") + return + + perc_increase_memory_usage = (after_memory_usage / before_memory_usage) - 1 + + if perc_increase_memory_usage > increase_allowed: + self._raise_assertion_error( + "Memory usage increased over threshold of {:.3f}% from {} to {} ({:.3f}%)".format( + increase_allowed * 100, before_memory_usage, after_memory_usage, + perc_increase_memory_usage * 100)) + def assert_start_raises_init_error(self, extra_args=None, expected_msg=None, match=ErrorMatch.FULL_TEXT, *args, **kwargs): """Attempt to start the node and expect it to raise an error. diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index c1dcc46e23..da55a3a156 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -68,9 +68,6 @@ if os.name != 'nt' or sys.getwindowsversion() >= (10, 0, 14393): TEST_EXIT_PASSED = 0 TEST_EXIT_SKIPPED = 77 -# 20 minutes represented in seconds -TRAVIS_TIMEOUT_DURATION = 20 * 60 - BASE_SCRIPTS = [ # Scripts that are run by the travis build process. # Longest test should go first, to favor running tests in parallel @@ -123,6 +120,7 @@ BASE_SCRIPTS = [ 'wallet_disableprivatekeys.py', 'wallet_disableprivatekeys.py --usecli', 'interface_http.py', + 'interface_rpc.py', 'rpc_psbt.py', 'rpc_users.py', 'feature_proxy.py', @@ -139,6 +137,7 @@ BASE_SCRIPTS = [ 'mining_prioritisetransaction.py', 'p2p_invalid_locator.py', 'p2p_invalid_block.py', + 'p2p_invalid_messages.py', 'p2p_invalid_tx.py', 'feature_assumevalid.py', 'example_test.py', @@ -152,10 +151,12 @@ BASE_SCRIPTS = [ 'feature_versionbits_warning.py', 'rpc_preciousblock.py', 'wallet_importprunedfunds.py', + 'p2p_leak_tx.py', 'rpc_signmessage.py', 'feature_nulldummy.py', 'mempool_accept.py', 'wallet_import_rescan.py', + 'wallet_import_with_label.py', 'rpc_bind.py --ipv4', 'rpc_bind.py --ipv6', 'rpc_bind.py --nonloopback', @@ -213,15 +214,16 @@ def main(): epilog=''' Help text and arguments for individual test script:''', formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument('--combinedlogslen', '-c', type=int, default=0, help='print a combined log (of length n lines) from all test nodes and test framework to the console on failure.') + parser.add_argument('--combinedlogslen', '-c', type=int, default=0, metavar='n', help='On failure, print a log (of length n lines) to the console, combined from the test framework and all test nodes.') parser.add_argument('--coverage', action='store_true', help='generate a basic coverage report for the RPC interface') + parser.add_argument('--ci', action='store_true', help='Run checks and code that are usually only enabled in a continuous integration environment') parser.add_argument('--exclude', '-x', help='specify a comma-separated-list of scripts to exclude.') parser.add_argument('--extended', action='store_true', help='run the extended test suite in addition to the basic tests') parser.add_argument('--force', '-f', action='store_true', help='run tests even on platforms where they are disabled by default (e.g. windows).') parser.add_argument('--help', '-h', '-?', action='store_true', help='print help text and exit') parser.add_argument('--jobs', '-j', type=int, default=4, help='how many test scripts to run in parallel. Default=4.') parser.add_argument('--keepcache', '-k', action='store_true', help='the default behavior is to flush the cache directory on startup. --keepcache retains the cache from the previous testrun.') - parser.add_argument('--quiet', '-q', action='store_true', help='only print results summary and failure logs') + 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') args, unknown_args = parser.parse_known_args() @@ -305,26 +307,26 @@ def main(): subprocess.check_call([sys.executable, os.path.join(config["environment"]["SRCDIR"], 'test', 'functional', test_list[0].split()[0]), '-h']) sys.exit(0) - check_script_list(config["environment"]["SRCDIR"]) + check_script_list(src_dir=config["environment"]["SRCDIR"], fail_on_warn=args.ci) check_script_prefixes() if not args.keepcache: shutil.rmtree("%s/test/cache" % config["environment"]["BUILDDIR"], ignore_errors=True) run_tests( - test_list, - config["environment"]["SRCDIR"], - config["environment"]["BUILDDIR"], - tmpdir, + test_list=test_list, + src_dir=config["environment"]["SRCDIR"], + build_dir=config["environment"]["BUILDDIR"], + tmpdir=tmpdir, jobs=args.jobs, enable_coverage=args.coverage, args=passon_args, combined_logs_len=args.combinedlogslen, failfast=args.failfast, - level=logging_level + runs_ci=args.ci, ) -def run_tests(test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, level=logging.DEBUG): +def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, runs_ci): args = args or [] # Warn if bitcoind is already running (unix only) @@ -359,7 +361,14 @@ def run_tests(test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=Fal raise #Run Tests - job_queue = TestHandler(jobs, tests_dir, tmpdir, test_list, flags, level) + job_queue = TestHandler( + num_tests_parallel=jobs, + tests_dir=tests_dir, + tmpdir=tmpdir, + test_list=test_list, + flags=flags, + timeout_duration=40 * 60 if runs_ci else float('inf'), # in seconds + ) start_time = time.time() test_results = [] @@ -440,14 +449,14 @@ class TestHandler: Trigger the test scripts passed in via the list. """ - def __init__(self, num_tests_parallel, tests_dir, tmpdir, test_list=None, flags=None, logging_level=logging.DEBUG): - assert(num_tests_parallel >= 1) + def __init__(self, *, num_tests_parallel, tests_dir, tmpdir, test_list, flags, timeout_duration): + assert num_tests_parallel >= 1 self.num_jobs = num_tests_parallel self.tests_dir = tests_dir self.tmpdir = tmpdir + self.timeout_duration = timeout_duration self.test_list = test_list self.flags = flags - self.logging_level = logging_level self.num_running = 0 self.jobs = [] @@ -480,7 +489,7 @@ class TestHandler: time.sleep(.5) for job in self.jobs: (name, start_time, proc, testdir, log_out, log_err) = job - if os.getenv('TRAVIS') == 'true' and int(time.time() - start_time) > TRAVIS_TIMEOUT_DURATION: + if int(time.time() - start_time) > self.timeout_duration: # In travis, timeout individual tests (to stop tests hanging and not providing useful output). proc.send_signal(signal.SIGINT) if proc.poll() is not None: @@ -495,14 +504,12 @@ class TestHandler: status = "Failed" self.num_running -= 1 self.jobs.remove(job) - if self.logging_level == logging.DEBUG: - clearline = '\r' + (' ' * dot_count) + '\r' - print(clearline, end='', flush=True) - dot_count = 0 + clearline = '\r' + (' ' * dot_count) + '\r' + print(clearline, end='', flush=True) + dot_count = 0 return TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr - if self.logging_level == logging.DEBUG: - print('.', end='', flush=True) - dot_count += 1 + print('.', end='', flush=True) + dot_count += 1 def kill_and_join(self): """Send SIGKILL to all jobs and block until all have ended.""" @@ -560,7 +567,7 @@ def check_script_prefixes(): raise AssertionError("Some tests are not following naming convention!") -def check_script_list(src_dir): +def check_script_list(*, src_dir, fail_on_warn): """Check scripts directory. Check that there are no scripts in the functional tests directory which are @@ -570,10 +577,11 @@ def check_script_list(src_dir): missed_tests = list(python_files - set(map(lambda x: x.split()[0], ALL_SCRIPTS + NON_SCRIPTS))) if len(missed_tests) != 0: print("%sWARNING!%s The following scripts are not being run: %s. Check the test lists in test_runner.py." % (BOLD[1], BOLD[0], str(missed_tests))) - if os.getenv('TRAVIS') == 'true': + if fail_on_warn: # On travis this warning is an error to prevent merging incomplete commits into master sys.exit(1) + class RPCCoverage(): """ Coverage reporting utilities for test_runner. @@ -629,7 +637,7 @@ class RPCCoverage(): with open(coverage_ref_filename, 'r', encoding="utf8") as coverage_ref_file: all_cmds.update([line.strip() for line in coverage_ref_file.readlines()]) - for root, dirs, files in os.walk(self.dir): + for root, _, files in os.walk(self.dir): for filename in files: if filename.startswith(coverage_file_prefix): coverage_filenames.add(os.path.join(root, filename)) diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 4de3356d79..c9b40905f0 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -28,10 +28,9 @@ class WalletTest(BitcoinTestFramework): self.skip_if_no_wallet() def setup_network(self): - self.add_nodes(4) - self.start_node(0) - self.start_node(1) - self.start_node(2) + self.setup_nodes() + # Only need nodes 0-2 running at start of test + self.stop_node(3) connect_nodes_bi(self.nodes, 0, 1) connect_nodes_bi(self.nodes, 1, 2) connect_nodes_bi(self.nodes, 0, 2) @@ -480,7 +479,7 @@ class WalletTest(BitcoinTestFramework): # Verify nothing new in wallet assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999))) - # Test getaddressinfo. Note that these addresses are taken from disablewallet.py + # 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") address_info = self.nodes[0].getaddressinfo("mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ") assert_equal(address_info['address'], "mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ") @@ -488,6 +487,22 @@ class WalletTest(BitcoinTestFramework): assert not address_info["ismine"] assert not address_info["iswatchonly"] assert not address_info["isscript"] + assert not address_info["ischange"] + + # Test getaddressinfo 'ischange' field on change address. + self.nodes[0].generate(1) + destination = self.nodes[1].getnewaddress() + txid = self.nodes[0].sendtoaddress(destination, 0.123) + tx = self.nodes[0].decoderawtransaction(self.nodes[0].getrawtransaction(txid)) + output_addresses = [vout['scriptPubKey']['addresses'][0] for vout in tx["vout"]] + assert len(output_addresses) > 1 + for address in output_addresses: + ischange = self.nodes[0].getaddressinfo(address)['ischange'] + assert_equal(ischange, address != destination) + if ischange: + change = address + self.nodes[0].setlabel(change, 'foobar') + assert_equal(self.nodes[0].getaddressinfo(change)['ischange'], False) if __name__ == '__main__': WalletTest().main() diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py index ab9ebed8d4..c514b7e0b4 100755 --- a/test/functional/wallet_encryption.py +++ b/test/functional/wallet_encryption.py @@ -31,12 +31,18 @@ class WalletEncryptionTest(BitcoinTestFramework): privkey = self.nodes[0].dumpprivkey(address) assert_equal(privkey[:1], "c") assert_equal(len(privkey), 52) + assert_raises_rpc_error(-15, "Error: running with an unencrypted wallet, but walletpassphrase was called", self.nodes[0].walletpassphrase, 'ff', 1) + assert_raises_rpc_error(-15, "Error: running with an unencrypted wallet, but walletpassphrasechange was called.", self.nodes[0].walletpassphrasechange, 'ff', 'ff') # Encrypt the wallet + assert_raises_rpc_error(-8, "passphrase can not be empty", self.nodes[0].encryptwallet, '') self.nodes[0].encryptwallet(passphrase) # Test that the wallet is encrypted assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address) + assert_raises_rpc_error(-15, "Error: running with an encrypted wallet, but encryptwallet was called.", self.nodes[0].encryptwallet, 'ff') + assert_raises_rpc_error(-8, "passphrase can not be empty", self.nodes[0].walletpassphrase, '', 1) + assert_raises_rpc_error(-8, "passphrase can not be empty", self.nodes[0].walletpassphrasechange, '', 'ff') # Check that walletpassphrase works self.nodes[0].walletpassphrase(passphrase, 2) diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py index f043a544fc..46462a16f3 100755 --- a/test/functional/wallet_import_rescan.py +++ b/test/functional/wallet_import_rescan.py @@ -46,11 +46,11 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")): if self.call == Call.single: if self.data == Data.address: - response = self.try_rpc(self.node.importaddress, address=self.address["address"], rescan=rescan) + response = self.try_rpc(self.node.importaddress, address=self.address["address"], label=self.label, rescan=rescan) elif self.data == Data.pub: - response = self.try_rpc(self.node.importpubkey, pubkey=self.address["pubkey"], rescan=rescan) + response = self.try_rpc(self.node.importpubkey, pubkey=self.address["pubkey"], label=self.label, rescan=rescan) elif self.data == Data.priv: - response = self.try_rpc(self.node.importprivkey, privkey=self.key, rescan=rescan) + response = self.try_rpc(self.node.importprivkey, privkey=self.key, label=self.label, rescan=rescan) assert_equal(response, None) elif self.call in (Call.multiaddress, Call.multiscript): @@ -61,18 +61,32 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")): "timestamp": timestamp + TIMESTAMP_WINDOW + (1 if self.rescan == Rescan.late_timestamp else 0), "pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [], "keys": [self.key] if self.data == Data.priv else [], + "label": self.label, "watchonly": self.data != Data.priv }], {"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)}) assert_equal(response, [{"success": True}]) def check(self, txid=None, amount=None, confirmations=None): - """Verify that listreceivedbyaddress returns expected values.""" + """Verify that listtransactions/listreceivedbyaddress return expected values.""" + + txs = self.node.listtransactions(label=self.label, count=10000, include_watchonly=True) + assert_equal(len(txs), self.expected_txs) addresses = self.node.listreceivedbyaddress(minconf=0, include_watchonly=True, address_filter=self.address['address']) if self.expected_txs: assert_equal(len(addresses[0]["txids"]), self.expected_txs) if txid is not None: + tx, = [tx for tx in txs if tx["txid"] == txid] + assert_equal(tx["label"], self.label) + assert_equal(tx["address"], self.address["address"]) + assert_equal(tx["amount"], amount) + assert_equal(tx["category"], "receive") + assert_equal(tx["label"], self.label) + assert_equal(tx["txid"], txid) + assert_equal(tx["confirmations"], confirmations) + assert_equal("trusted" not in tx, True) + address, = [ad for ad in addresses if txid in ad["txids"]] assert_equal(address["address"], self.address["address"]) assert_equal(address["amount"], self.expected_balance) @@ -122,21 +136,20 @@ class ImportRescanTest(BitcoinTestFramework): # Import keys with pruning disabled self.start_nodes(extra_args=[[]] * self.num_nodes) - super().import_deterministic_coinbase_privkeys() + for n in self.nodes: + n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase') self.stop_nodes() self.start_nodes() for i in range(1, self.num_nodes): connect_nodes(self.nodes[i], 0) - def import_deterministic_coinbase_privkeys(self): - pass - def run_test(self): # Create one transaction on node 0 with a unique amount for # each possible type of wallet import RPC. for i, variant in enumerate(IMPORT_VARIANTS): - variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress()) + variant.label = "label {} {}".format(i, variant) + variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress(variant.label)) variant.key = self.nodes[1].dumpprivkey(variant.address["address"]) variant.initial_amount = 1 - (i + 1) / 64 variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount) diff --git a/test/functional/wallet_import_with_label.py b/test/functional/wallet_import_with_label.py new file mode 100755 index 0000000000..95acaa752e --- /dev/null +++ b/test/functional/wallet_import_with_label.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 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 behavior of RPC importprivkey on set and unset labels of +addresses. + +It tests different cases in which an address is imported with importaddress +with or without a label and then its private key is imported with importprivkey +with and without a label. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +class ImportWithLabel(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.setup_clean_chain = True + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + """Main test logic""" + + self.log.info( + "Test importaddress with label and importprivkey without label." + ) + self.log.info("Import a watch-only address with a label.") + address = self.nodes[0].getnewaddress() + label = "Test Label" + self.nodes[1].importaddress(address, label) + address_assert = self.nodes[1].getaddressinfo(address) + + assert_equal(address_assert["iswatchonly"], True) + assert_equal(address_assert["ismine"], False) + assert_equal(address_assert["label"], label) + + self.log.info( + "Import the watch-only address's private key without a " + "label and the address should keep its label." + ) + priv_key = self.nodes[0].dumpprivkey(address) + self.nodes[1].importprivkey(priv_key) + + assert_equal(label, self.nodes[1].getaddressinfo(address)["label"]) + + self.log.info( + "Test importaddress without label and importprivkey with label." + ) + self.log.info("Import a watch-only address without a label.") + address2 = self.nodes[0].getnewaddress() + self.nodes[1].importaddress(address2) + address_assert2 = self.nodes[1].getaddressinfo(address2) + + assert_equal(address_assert2["iswatchonly"], True) + assert_equal(address_assert2["ismine"], False) + assert_equal(address_assert2["label"], "") + + self.log.info( + "Import the watch-only address's private key with a " + "label and the address should have its label updated." + ) + priv_key2 = self.nodes[0].dumpprivkey(address2) + label2 = "Test Label 2" + self.nodes[1].importprivkey(priv_key2, label2) + + assert_equal(label2, self.nodes[1].getaddressinfo(address2)["label"]) + + self.log.info("Test importaddress with label and importprivkey with label.") + self.log.info("Import a watch-only address with a label.") + address3 = self.nodes[0].getnewaddress() + label3_addr = "Test Label 3 for importaddress" + self.nodes[1].importaddress(address3, label3_addr) + address_assert3 = self.nodes[1].getaddressinfo(address3) + + assert_equal(address_assert3["iswatchonly"], True) + assert_equal(address_assert3["ismine"], False) + assert_equal(address_assert3["label"], label3_addr) + + self.log.info( + "Import the watch-only address's private key with a " + "label and the address should have its label updated." + ) + priv_key3 = self.nodes[0].dumpprivkey(address3) + label3_priv = "Test Label 3 for importprivkey" + self.nodes[1].importprivkey(priv_key3, label3_priv) + + assert_equal(label3_priv, self.nodes[1].getaddressinfo(address3)["label"]) + + self.log.info( + "Test importprivkey won't label new dests with the same " + "label as others labeled dests for the same key." + ) + self.log.info("Import a watch-only legacy address with a label.") + address4 = self.nodes[0].getnewaddress() + label4_addr = "Test Label 4 for importaddress" + self.nodes[1].importaddress(address4, label4_addr) + address_assert4 = self.nodes[1].getaddressinfo(address4) + + assert_equal(address_assert4["iswatchonly"], True) + assert_equal(address_assert4["ismine"], False) + assert_equal(address_assert4["label"], label4_addr) + + self.log.info("Asserts address has no embedded field with dests.") + + assert_equal(address_assert4.get("embedded"), None) + + self.log.info( + "Import the watch-only address's private key without a " + "label and new destinations for the key should have an " + "empty label while the 'old' destination should keep " + "its label." + ) + priv_key4 = self.nodes[0].dumpprivkey(address4) + self.nodes[1].importprivkey(priv_key4) + address_assert4 = self.nodes[1].getaddressinfo(address4) + + assert address_assert4.get("embedded") + + bcaddress_assert = self.nodes[1].getaddressinfo( + address_assert4["embedded"]["address"] + ) + + assert_equal(address_assert4["label"], label4_addr) + assert_equal(bcaddress_assert["label"], "") + + self.stop_nodes() + + +if __name__ == "__main__": + ImportWithLabel().main() diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py index f78ff19791..5c789b1c03 100755 --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -11,8 +11,14 @@ from test_framework.util import ( assert_greater_than, assert_raises_rpc_error, bytes_to_hex_str, + hex_str_to_bytes ) - +from test_framework.script import ( + CScript, + OP_0, + hash160 +) +from test_framework.messages import sha256 class ImportMultiTest(BitcoinTestFramework): def set_test_params(self): @@ -48,7 +54,7 @@ class ImportMultiTest(BitcoinTestFramework): # RPC importmulti ----------------------------------------------- - # Bitcoin Address + # Bitcoin Address (implicit non-internal) self.log.info("Should import an address") address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) result = self.nodes[1].importmulti([{ @@ -62,6 +68,7 @@ class ImportMultiTest(BitcoinTestFramework): assert_equal(address_assert['iswatchonly'], True) assert_equal(address_assert['ismine'], False) assert_equal(address_assert['timestamp'], timestamp) + assert_equal(address_assert['ischange'], False) watchonly_address = address['address'] watchonly_timestamp = timestamp @@ -89,6 +96,20 @@ class ImportMultiTest(BitcoinTestFramework): assert_equal(address_assert['iswatchonly'], True) assert_equal(address_assert['ismine'], False) assert_equal(address_assert['timestamp'], timestamp) + assert_equal(address_assert['ischange'], True) + + # ScriptPubKey + internal + label + self.log.info("Should not allow a label to be specified when internal is true") + address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) + result = self.nodes[1].importmulti([{ + "scriptPubKey": address['scriptPubKey'], + "timestamp": "now", + "internal": True, + "label": "Example label" + }]) + assert_equal(result[0]['success'], False) + assert_equal(result[0]['error']['code'], -8) + assert_equal(result[0]['error']['message'], 'Internal addresses should not have a label') # Nonstandard scriptPubKey + !internal self.log.info("Should not import a nonstandard scriptPubKey without internal flag") @@ -107,7 +128,7 @@ class ImportMultiTest(BitcoinTestFramework): assert_equal('timestamp' in address_assert, False) - # Address + Public key + !Internal + # Address + Public key + !Internal(explicit) self.log.info("Should import an address with public key") address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) result = self.nodes[1].importmulti([{ @@ -115,7 +136,8 @@ class ImportMultiTest(BitcoinTestFramework): "address": address['address'] }, "timestamp": "now", - "pubkeys": [ address['pubkey'] ] + "pubkeys": [ address['pubkey'] ], + "internal": False }]) assert_equal(result[0]['success'], True) address_assert = self.nodes[1].getaddressinfo(address['address']) @@ -133,7 +155,7 @@ class ImportMultiTest(BitcoinTestFramework): "pubkeys": [ address['pubkey'] ], "internal": True }] - result = self.nodes[1].importmulti(request) + result = self.nodes[1].importmulti(requests=request) assert_equal(result[0]['success'], True) address_assert = self.nodes[1].getaddressinfo(address['address']) assert_equal(address_assert['iswatchonly'], True) @@ -148,7 +170,7 @@ class ImportMultiTest(BitcoinTestFramework): "timestamp": "now", "pubkeys": [ address['pubkey'] ] }] - result = self.nodes[1].importmulti(request) + result = self.nodes[1].importmulti(requests=request) assert_equal(result[0]['success'], False) assert_equal(result[0]['error']['code'], -8) assert_equal(result[0]['error']['message'], 'Internal must be set to true for nonstandard scriptPubKey imports.') @@ -198,7 +220,7 @@ class ImportMultiTest(BitcoinTestFramework): }]) assert_equal(result[0]['success'], False) assert_equal(result[0]['error']['code'], -8) - assert_equal(result[0]['error']['message'], 'Incompatibility found between watchonly and keys') + assert_equal(result[0]['error']['message'], 'Watch-only addresses should not include private keys') address_assert = self.nodes[1].getaddressinfo(address['address']) assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['ismine'], False) @@ -339,7 +361,7 @@ class ImportMultiTest(BitcoinTestFramework): }]) assert_equal(result[0]['success'], False) assert_equal(result[0]['error']['code'], -8) - assert_equal(result[0]['error']['message'], 'Incompatibility found between watchonly and keys') + assert_equal(result[0]['error']['message'], 'Watch-only addresses should not include private keys') # Address + Public key + !Internal + Wrong pubkey @@ -355,7 +377,7 @@ class ImportMultiTest(BitcoinTestFramework): }]) assert_equal(result[0]['success'], False) assert_equal(result[0]['error']['code'], -5) - assert_equal(result[0]['error']['message'], 'Consistency check failed') + assert_equal(result[0]['error']['message'], 'Key does not match address destination') address_assert = self.nodes[1].getaddressinfo(address['address']) assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['ismine'], False) @@ -375,7 +397,7 @@ class ImportMultiTest(BitcoinTestFramework): result = self.nodes[1].importmulti(request) assert_equal(result[0]['success'], False) assert_equal(result[0]['error']['code'], -5) - assert_equal(result[0]['error']['message'], 'Consistency check failed') + assert_equal(result[0]['error']['message'], 'Key does not match address destination') address_assert = self.nodes[1].getaddressinfo(address['address']) assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['ismine'], False) @@ -395,7 +417,7 @@ class ImportMultiTest(BitcoinTestFramework): }]) assert_equal(result[0]['success'], False) assert_equal(result[0]['error']['code'], -5) - assert_equal(result[0]['error']['message'], 'Consistency check failed') + assert_equal(result[0]['error']['message'], 'Key does not match address destination') address_assert = self.nodes[1].getaddressinfo(address['address']) assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['ismine'], False) @@ -414,7 +436,7 @@ class ImportMultiTest(BitcoinTestFramework): }]) assert_equal(result[0]['success'], False) assert_equal(result[0]['error']['code'], -5) - assert_equal(result[0]['error']['message'], 'Consistency check failed') + assert_equal(result[0]['error']['message'], 'Key does not match address destination') address_assert = self.nodes[1].getaddressinfo(address['address']) assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['ismine'], False) @@ -458,6 +480,147 @@ class ImportMultiTest(BitcoinTestFramework): "timestamp": "", }]) + # Import P2WPKH address as watch only + self.log.info("Should import a P2WPKH address as watch only") + address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="bech32")) + result = self.nodes[1].importmulti([{ + "scriptPubKey": { + "address": address['address'] + }, + "timestamp": "now", + }]) + assert_equal(result[0]['success'], True) + address_assert = self.nodes[1].getaddressinfo(address['address']) + assert_equal(address_assert['iswatchonly'], True) + assert_equal(address_assert['solvable'], False) + + # Import P2WPKH address with public key but no private key + self.log.info("Should import a P2WPKH address and public key as solvable but not spendable") + address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="bech32")) + result = self.nodes[1].importmulti([{ + "scriptPubKey": { + "address": address['address'] + }, + "timestamp": "now", + "pubkeys": [ address['pubkey'] ] + }]) + assert_equal(result[0]['success'], True) + address_assert = self.nodes[1].getaddressinfo(address['address']) + assert_equal(address_assert['ismine'], False) + assert_equal(address_assert['solvable'], True) + + # Import P2WPKH address with key and check it is spendable + self.log.info("Should import a P2WPKH address with key") + address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="bech32")) + result = self.nodes[1].importmulti([{ + "scriptPubKey": { + "address": address['address'] + }, + "timestamp": "now", + "keys": [self.nodes[0].dumpprivkey(address['address'])] + }]) + assert_equal(result[0]['success'], True) + address_assert = self.nodes[1].getaddressinfo(address['address']) + assert_equal(address_assert['iswatchonly'], False) + assert_equal(address_assert['ismine'], True) + + # P2WSH multisig address without scripts or keys + sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) + sig_address_2 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) + multi_sig_script = self.nodes[0].addmultisigaddress(2, [sig_address_1['pubkey'], sig_address_2['pubkey']], "", "bech32") + self.log.info("Should import a p2wsh multisig as watch only without respective redeem script and private keys") + result = self.nodes[1].importmulti([{ + "scriptPubKey": { + "address": multi_sig_script['address'] + }, + "timestamp": "now" + }]) + assert_equal(result[0]['success'], True) + address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address']) + assert_equal(address_assert['solvable'], False) + + # Same P2WSH multisig address as above, but now with witnessscript + private keys + self.log.info("Should import a p2wsh with respective redeem script and private keys") + result = self.nodes[1].importmulti([{ + "scriptPubKey": { + "address": multi_sig_script['address'] + }, + "timestamp": "now", + "witnessscript": multi_sig_script['redeemScript'], + "keys": [ self.nodes[0].dumpprivkey(sig_address_1['address']), self.nodes[0].dumpprivkey(sig_address_2['address']) ] + }]) + assert_equal(result[0]['success'], True) + address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address']) + assert_equal(address_assert['solvable'], True) + assert_equal(address_assert['ismine'], True) + assert_equal(address_assert['sigsrequired'], 2) + + # P2SH-P2WPKH address with no redeemscript or public or private key + sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="p2sh-segwit")) + pubkeyhash = hash160(hex_str_to_bytes(sig_address_1['pubkey'])) + pkscript = CScript([OP_0, pubkeyhash]) + self.log.info("Should import a p2sh-p2wpkh without redeem script or keys") + result = self.nodes[1].importmulti([{ + "scriptPubKey": { + "address": sig_address_1['address'] + }, + "timestamp": "now" + }]) + assert_equal(result[0]['success'], True) + address_assert = self.nodes[1].getaddressinfo(sig_address_1['address']) + assert_equal(address_assert['solvable'], False) + assert_equal(address_assert['ismine'], False) + + # P2SH-P2WPKH address + redeemscript + public key with no private key + self.log.info("Should import a p2sh-p2wpkh with respective redeem script and pubkey as solvable") + result = self.nodes[1].importmulti([{ + "scriptPubKey": { + "address": sig_address_1['address'] + }, + "timestamp": "now", + "redeemscript": bytes_to_hex_str(pkscript), + "pubkeys": [ sig_address_1['pubkey'] ] + }]) + assert_equal(result[0]['success'], True) + address_assert = self.nodes[1].getaddressinfo(sig_address_1['address']) + assert_equal(address_assert['solvable'], True) + assert_equal(address_assert['ismine'], False) + + # P2SH-P2WPKH address + redeemscript + private key + sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress(address_type="p2sh-segwit")) + pubkeyhash = hash160(hex_str_to_bytes(sig_address_1['pubkey'])) + pkscript = CScript([OP_0, pubkeyhash]) + self.log.info("Should import a p2sh-p2wpkh with respective redeem script and private keys") + result = self.nodes[1].importmulti([{ + "scriptPubKey": { + "address": sig_address_1['address'] + }, + "timestamp": "now", + "redeemscript": bytes_to_hex_str(pkscript), + "keys": [ self.nodes[0].dumpprivkey(sig_address_1['address'])] + }]) + assert_equal(result[0]['success'], True) + address_assert = self.nodes[1].getaddressinfo(sig_address_1['address']) + assert_equal(address_assert['solvable'], True) + assert_equal(address_assert['ismine'], True) + + # P2SH-P2WSH 1-of-1 multisig + redeemscript with no private key + sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) + multi_sig_script = self.nodes[0].addmultisigaddress(1, [sig_address_1['pubkey']], "", "p2sh-segwit") + scripthash = sha256(hex_str_to_bytes(multi_sig_script['redeemScript'])) + redeem_script = CScript([OP_0, scripthash]) + self.log.info("Should import a p2sh-p2wsh with respective redeem script but no private key") + result = self.nodes[1].importmulti([{ + "scriptPubKey": { + "address": multi_sig_script['address'] + }, + "timestamp": "now", + "redeemscript": bytes_to_hex_str(redeem_script), + "witnessscript": multi_sig_script['redeemScript'] + }]) + assert_equal(result[0]['success'], True) + address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address']) + assert_equal(address_assert['solvable'], True) if __name__ == '__main__': ImportMultiTest ().main () diff --git a/test/functional/wallet_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py index 26b181db33..78426018ef 100755 --- a/test/functional/wallet_importprunedfunds.py +++ b/test/functional/wallet_importprunedfunds.py @@ -81,7 +81,7 @@ class ImportPrunedFundsTest(BitcoinTestFramework): # Import with affiliated address with no rescan self.nodes[1].importaddress(address=address2, rescan=False) - self.nodes[1].importprunedfunds(rawtxn2, proof2) + self.nodes[1].importprunedfunds(rawtransaction=rawtxn2, txoutproof=proof2) assert [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid2] # Import with private key with no rescan diff --git a/test/functional/wallet_listreceivedby.py b/test/functional/wallet_listreceivedby.py index 9e8667c600..011975e371 100755 --- a/test/functional/wallet_listreceivedby.py +++ b/test/functional/wallet_listreceivedby.py @@ -18,11 +18,6 @@ class ReceivedByTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - def import_deterministic_coinbase_privkeys(self): - assert_equal(0, len(self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True))) - super().import_deterministic_coinbase_privkeys() - self.num_cb_reward_addresses = len(self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True)) - def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -31,6 +26,9 @@ class ReceivedByTest(BitcoinTestFramework): self.nodes[0].generate(1) sync_blocks(self.nodes) + # save the number of coinbase reward addresses so far + num_cb_reward_addresses = len(self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True)) + self.log.info("listreceivedbyaddress Test") # Send from node 0 to 1 @@ -76,7 +74,7 @@ class ReceivedByTest(BitcoinTestFramework): assert_raises_rpc_error(-4, "address_filter parameter was invalid", self.nodes[1].listreceivedbyaddress, minconf=0, include_empty=True, include_watchonly=True, address_filter="bamboozling") # Another address receive money res = self.nodes[1].listreceivedbyaddress(0, True, True) - assert_equal(len(res), 2 + self.num_cb_reward_addresses) # Right now 2 entries + assert_equal(len(res), 2 + num_cb_reward_addresses) # Right now 2 entries other_addr = self.nodes[1].getnewaddress() txid2 = self.nodes[0].sendtoaddress(other_addr, 0.1) self.nodes[0].generate(1) @@ -93,7 +91,7 @@ class ReceivedByTest(BitcoinTestFramework): assert_equal(len(res), 1) # Should be two entries though without filter res = self.nodes[1].listreceivedbyaddress(0, True, True) - assert_equal(len(res), 3 + self.num_cb_reward_addresses) # Became 3 entries + assert_equal(len(res), 3 + num_cb_reward_addresses) # Became 3 entries # Not on random addr other_addr = self.nodes[0].getnewaddress() # note on node[0]! just a random addr diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py index 5a17395abd..8ca0387268 100755 --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -97,9 +97,10 @@ class ListTransactionsTest(BitcoinTestFramework): txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1) self.nodes[1].generate(1) self.sync_all() - assert not [tx for tx in self.nodes[0].listtransactions(dummy="*", count=100, skip=0, include_watchonly=False) if "label" in tx and tx["label"] == "watchonly"] - txs = [tx for tx in self.nodes[0].listtransactions(dummy="*", count=100, skip=0, include_watchonly=True) if "label" in tx and tx['label'] == 'watchonly'] - assert_array_result(txs, {"category": "receive", "amount": Decimal("0.1")}, {"txid": txid}) + assert len(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=False)) == 0 + assert_array_result(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=True), + {"category": "receive", "amount": Decimal("0.1")}, + {"txid": txid, "label": "watchonly"}) self.run_rbf_opt_in_test() diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 4f663c82c7..8ab569a3c3 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -223,6 +223,9 @@ class MultiWalletTest(BitcoinTestFramework): # Fail to load duplicate wallets assert_raises_rpc_error(-4, 'Wallet file verification failed: Error loading wallet w1. Duplicate -wallet filename specified.', self.nodes[0].loadwallet, wallet_names[0]) + # Fail to load duplicate wallets by different ways (directory and filepath) + assert_raises_rpc_error(-4, "Wallet file verification failed: Error loading wallet wallet.dat. Duplicate -wallet filename specified.", self.nodes[0].loadwallet, 'wallet.dat') + # Fail to load if one wallet is a copy of another assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy') diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index 3972baed1d..be67cbed31 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -9,7 +9,7 @@ export LC_ALL=C EXPECTED_CIRCULAR_DEPENDENCIES=( - "chainparamsbase -> util -> chainparamsbase" + "chainparamsbase -> util/system -> chainparamsbase" "checkpoints -> validation -> checkpoints" "index/txindex -> validation -> index/txindex" "policy/fees -> txmempool -> policy/fees" @@ -29,7 +29,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "validation -> validationinterface -> validation" "wallet/coincontrol -> wallet/wallet -> wallet/coincontrol" "wallet/fees -> wallet/wallet -> wallet/fees" - "wallet/rpcwallet -> wallet/wallet -> wallet/rpcwallet" "wallet/wallet -> wallet/walletdb -> wallet/wallet" "policy/fees -> policy/policy -> validation -> policy/fees" "policy/rbf -> txmempool -> validation -> policy/rbf" diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py index 2fb35fd8ca..5caebf3739 100755 --- a/test/lint/lint-format-strings.py +++ b/test/lint/lint-format-strings.py @@ -16,8 +16,8 @@ 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.cpp", "strprintf(_(COPYRIGHT_HOLDERS), _(COPYRIGHT_HOLDERS_SUBSTITUTION))"), - ("src/util.cpp", "strprintf(COPYRIGHT_HOLDERS, COPYRIGHT_HOLDERS_SUBSTITUTION)"), + ("src/util/system.cpp", "strprintf(_(COPYRIGHT_HOLDERS), _(COPYRIGHT_HOLDERS_SUBSTITUTION))"), + ("src/util/system.cpp", "strprintf(COPYRIGHT_HOLDERS, 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-locale-dependence.sh b/test/lint/lint-locale-dependence.sh index cbee437c91..44170a6b5a 100755 --- a/test/lint/lint-locale-dependence.sh +++ b/test/lint/lint-locale-dependence.sh @@ -2,7 +2,6 @@ export LC_ALL=C KNOWN_VIOLATIONS=( - "src/base58.cpp:.*isspace" "src/bitcoin-tx.cpp.*stoul" "src/bitcoin-tx.cpp.*trim_right" "src/bitcoin-tx.cpp:.*atoi" @@ -18,20 +17,17 @@ KNOWN_VIOLATIONS=( "src/test/getarg_tests.cpp.*split" "src/torcontrol.cpp:.*atoi" "src/torcontrol.cpp:.*strtol" - "src/uint256.cpp:.*isspace" "src/uint256.cpp:.*tolower" - "src/util.cpp:.*atoi" - "src/util.cpp:.*fprintf" - "src/util.cpp:.*tolower" - "src/utilmoneystr.cpp:.*isdigit" - "src/utilmoneystr.cpp:.*isspace" - "src/utilstrencodings.cpp:.*atoi" - "src/utilstrencodings.cpp:.*isspace" - "src/utilstrencodings.cpp:.*strtol" - "src/utilstrencodings.cpp:.*strtoll" - "src/utilstrencodings.cpp:.*strtoul" - "src/utilstrencodings.cpp:.*strtoull" - "src/utilstrencodings.h:.*atoi" + "src/util/system.cpp:.*atoi" + "src/util/system.cpp:.*fprintf" + "src/util/system.cpp:.*tolower" + "src/util/moneystr.cpp:.*isdigit" + "src/util/strencodings.cpp:.*atoi" + "src/util/strencodings.cpp:.*strtol" + "src/util/strencodings.cpp:.*strtoll" + "src/util/strencodings.cpp:.*strtoul" + "src/util/strencodings.cpp:.*strtoull" + "src/util/strencodings.h:.*atoi" ) REGEXP_IGNORE_EXTERNAL_DEPENDENCIES="^src/(crypto/ctaes/|leveldb/|secp256k1/|tinyformat.h|univalue/)" diff --git a/test/lint/lint-python-dead-code.sh b/test/lint/lint-python-dead-code.sh new file mode 100755 index 0000000000..3341f794f9 --- /dev/null +++ b/test/lint/lint-python-dead-code.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# Find dead Python code. + +export LC_ALL=C + +if ! command -v vulture > /dev/null; then + echo "Skipping Python dead code linting since vulture is not installed. Install by running \"pip3 install vulture\"" + exit 0 +fi + +vulture \ + --min-confidence 60 \ + --ignore-names "argtypes,connection_lost,connection_made,converter,data_received,daemon,errcheck,get_ecdh_key,get_privkey,is_compressed,is_fullyvalid,msg_generic,on_*,optionxform,restype,set_privkey" \ + $(git ls-files -- "*.py" ":(exclude)contrib/") diff --git a/test/lint/lint-rpc-help.sh b/test/lint/lint-rpc-help.sh new file mode 100755 index 0000000000..faac5d43e2 --- /dev/null +++ b/test/lint/lint-rpc-help.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2018 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 that all RPC help texts are generated by RPCHelpMan. + +export LC_ALL=C + +EXIT_CODE=0 + +# Assume that all multiline strings passed into a runtime_error are help texts. +# This is potentially fragile, but the linter is only temporary and can safely +# be removed early 2019. + +non_autogenerated_help=$(grep --perl-regexp --null-data --only-matching 'runtime_error\(\n\s*".*\\n"\n' $(git ls-files -- "*.cpp")) +if [[ ${non_autogenerated_help} != "" ]]; then + echo "Must use RPCHelpMan to generate the help for the following RPC methods:" + echo "${non_autogenerated_help}" + echo + EXIT_CODE=1 +fi +exit ${EXIT_CODE} diff --git a/test/sanitizer_suppressions/tsan b/test/sanitizer_suppressions/tsan new file mode 100644 index 0000000000..209c46f096 --- /dev/null +++ b/test/sanitizer_suppressions/tsan @@ -0,0 +1,24 @@ +# ThreadSanitizer suppressions +# ============================ + +# fChecked is theoretically racy, practically only in unit tests +race:CheckBlock + +# WalletBatch (unidentified deadlock) +deadlock:WalletBatch + +# Intentional deadlock in tests +deadlock:TestPotentialDeadLockDetected + +# Wildcard for all gui tests, should be replaced with non-wildcard suppressions +race:src/qt/test/* +deadlock:src/qt/test/* + +# WIP: Unidentified suppressions to run the functional tests +#race:zmqpublishnotifier.cpp +# +#deadlock:CreateWalletFromFile +#deadlock:importprivkey +#deadlock:walletdb.h +#deadlock:walletdb.cpp +#deadlock:wallet/db.cpp diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan new file mode 100644 index 0000000000..e90d5c2ac0 --- /dev/null +++ b/test/sanitizer_suppressions/ubsan @@ -0,0 +1,36 @@ +alignment:move.h +alignment:prevector.h +bool:wallet/wallet.cpp +float-divide-by-zero:policy/fees.cpp +float-divide-by-zero:validation.cpp +float-divide-by-zero:wallet/wallet.cpp +nonnull-attribute:support/cleanse.cpp +unsigned-integer-overflow:arith_uint256.h +unsigned-integer-overflow:basic_string.h +unsigned-integer-overflow:bench/bench.h +unsigned-integer-overflow:bitcoin-tx.cpp +unsigned-integer-overflow:bloom.cpp +unsigned-integer-overflow:chain.cpp +unsigned-integer-overflow:chain.h +unsigned-integer-overflow:coded_stream.h +unsigned-integer-overflow:core_write.cpp +unsigned-integer-overflow:crypto/chacha20.cpp +unsigned-integer-overflow:crypto/ctaes/ctaes.c +unsigned-integer-overflow:crypto/ripemd160.cpp +unsigned-integer-overflow:crypto/sha1.cpp +unsigned-integer-overflow:crypto/sha256.cpp +unsigned-integer-overflow:crypto/sha512.cpp +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:policy/fees.cpp +unsigned-integer-overflow:prevector.h +unsigned-integer-overflow:script/interpreter.cpp +unsigned-integer-overflow:stl_bvector.h +unsigned-integer-overflow:streams.h +unsigned-integer-overflow:txmempool.cpp +unsigned-integer-overflow:util/strencodings.cpp +unsigned-integer-overflow:validation.cpp +vptr:fs.cpp diff --git a/test/util/rpcauth-test.py b/test/util/rpcauth-test.py index 46e9fbc739..53058dc394 100755 --- a/test/util/rpcauth-test.py +++ b/test/util/rpcauth-test.py @@ -24,8 +24,8 @@ class TestRPCAuth(unittest.TestCase): self.rpcauth = importlib.import_module('rpcauth') def test_generate_salt(self): - self.assertLessEqual(len(self.rpcauth.generate_salt()), 32) - self.assertGreaterEqual(len(self.rpcauth.generate_salt()), 16) + for i in range(16, 32 + 1): + self.assertEqual(len(self.rpcauth.generate_salt(i)), i * 2) def test_generate_password(self): password = self.rpcauth.generate_password() @@ -34,7 +34,7 @@ class TestRPCAuth(unittest.TestCase): self.assertEqual(expected_password, password) def test_check_password_hmac(self): - salt = self.rpcauth.generate_salt() + salt = self.rpcauth.generate_salt(16) password = self.rpcauth.generate_password() password_hmac = self.rpcauth.password_to_hmac(salt, password) |