diff options
Diffstat (limited to 'test')
46 files changed, 1183 insertions, 256 deletions
diff --git a/test/README.md b/test/README.md index a6fb9c9d73..9d4351b1de 100644 --- a/test/README.md +++ b/test/README.md @@ -178,6 +178,26 @@ gdb /home/example/bitcoind <pid> Note: gdb attach step may require ptrace_scope to be modified, or `sudo` preceding the `gdb`. See this link for considerations: https://www.kernel.org/doc/Documentation/security/Yama.txt +##### Profiling + +An easy way to profile node performance during functional tests is provided +for Linux platforms using `perf`. + +Perf will sample the running node and will generate profile data in the node's +datadir. The profile data can then be presented using `perf report` or a graphical +tool like [hotspot](https://github.com/KDAB/hotspot). + +To generate a profile during test suite runs, use the `--perf` flag. + +To see render the output to text, run + +```sh +perf report -i /path/to/datadir/send-big-msgs.perf.data.xxxx --stdio | c++filt | less +``` + +For ways to generate more granular profiles, see the README in +[test/functional](/test/functional). + ### Util tests Util tests can be run locally by running `test/util/bitcoin-util-test.py`. diff --git a/test/config.ini.in b/test/config.ini.in index 28abee2a3d..6b7ef70659 100644 --- a/test/config.ini.in +++ b/test/config.ini.in @@ -16,4 +16,5 @@ RPCAUTH=@abs_top_srcdir@/share/rpcauth/rpcauth.py @ENABLE_WALLET_TRUE@ENABLE_WALLET=true @BUILD_BITCOIN_CLI_TRUE@ENABLE_CLI=true @BUILD_BITCOIND_TRUE@ENABLE_BITCOIND=true +@ENABLE_FUZZ_TRUE@ENABLE_FUZZ=true @ENABLE_ZMQ_TRUE@ENABLE_ZMQ=true diff --git a/test/functional/README.md b/test/functional/README.md index 628c77eb11..5e3009e6af 100644 --- a/test/functional/README.md +++ b/test/functional/README.md @@ -43,6 +43,7 @@ don't have test cases for. - `mining` for tests for mining features, eg `mining_prioritisetransaction.py` - `p2p` for tests that explicitly test the p2p interface, eg `p2p_disconnect_ban.py` - `rpc` for tests for individual RPC methods or features, eg `rpc_listtransactions.py` + - `tool` for tests for tools, eg `tool_wallet.py` - `wallet` for tests for wallet features, eg `wallet_keypool.py` - use an underscore to separate words - exception: for tests for specific RPCs or command line options which don't include underscores, name the test after the exact RPC or argument name, eg `rpc_decodescript.py`, not `rpc_decode_script.py` @@ -122,3 +123,36 @@ Helpers for script.py #### [test_framework/blocktools.py](test_framework/blocktools.py) Helper functions for creating blocks and transactions. + +### Benchmarking with perf + +An easy way to profile node performance during functional tests is provided +for Linux platforms using `perf`. + +Perf will sample the running node and will generate profile data in the node's +datadir. The profile data can then be presented using `perf report` or a graphical +tool like [hotspot](https://github.com/KDAB/hotspot). + +There are two ways of invoking perf: one is to use the `--perf` flag when +running tests, which will profile each node during the entire test run: perf +begins to profile when the node starts and ends when it shuts down. The other +way is the use the `profile_with_perf` context manager, e.g. + +```python +with node.profile_with_perf("send-big-msgs"): + # Perform activity on the node you're interested in profiling, e.g.: + for _ in range(10000): + node.p2p.send_message(some_large_message) +``` + +To see useful textual output, run + +```sh +perf report -i /path/to/datadir/send-big-msgs.perf.data.xxxx --stdio | c++filt | less +``` + +#### See also: + +- [Installing perf](https://askubuntu.com/q/50145) +- [Perf examples](http://www.brendangregg.com/perf.html) +- [Hotspot](https://github.com/KDAB/hotspot): a GUI for perf output analysis diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py index 12a4ce9aff..0eb3dd440b 100755 --- a/test/functional/feature_assumevalid.py +++ b/test/functional/feature_assumevalid.py @@ -180,7 +180,7 @@ class AssumeValidTest(BitcoinTestFramework): for i in range(2202): p2p1.send_message(msg_block(self.blocks[i])) # Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync. - p2p1.sync_with_ping(150) + p2p1.sync_with_ping(200) assert_equal(self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 2202) # Send blocks to node2. Block 102 will be rejected. diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index 302a5ec1cb..96c5e49b0e 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -60,6 +60,7 @@ class BIP65Test(BitcoinTestFramework): self.num_nodes = 1 self.extra_args = [['-whitelist=127.0.0.1', '-par=1']] # Use only one script thread to get the exact reject reason for testing self.setup_clean_chain = True + self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py index 9cbc1b39bd..8ff9498ef3 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -47,6 +47,7 @@ class BIP66Test(BitcoinTestFramework): self.num_nodes = 1 self.extra_args = [['-whitelist=127.0.0.1', '-par=1', '-enablebip61']] # Use only one script thread to get the exact reject reason for testing self.setup_clean_chain = True + self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py index d8083b2840..b98dd6d26c 100755 --- a/test/functional/feature_notifications.py +++ b/test/functional/feature_notifications.py @@ -66,23 +66,7 @@ class NotificationsTest(BitcoinTestFramework): txids_rpc = list(map(lambda t: t['txid'], self.nodes[1].listtransactions("*", block_count))) assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir))) - # Mine another 41 up-version blocks. -alertnotify should trigger on the 51st. - self.log.info("test -alertnotify") - self.nodes[1].generatetoaddress(41, ADDRESS_BCRT1_UNSPENDABLE) - self.sync_all() - - # Give bitcoind 10 seconds to write the alert notification - wait_until(lambda: len(os.listdir(self.alertnotify_dir)), timeout=10) - - for notify_file in os.listdir(self.alertnotify_dir): - os.remove(os.path.join(self.alertnotify_dir, notify_file)) - - # Mine more up-version blocks, should not get more alerts: - self.nodes[1].generatetoaddress(2, ADDRESS_BCRT1_UNSPENDABLE) - self.sync_all() - - self.log.info("-alertnotify should not continue notifying for more unknown version blocks") - assert_equal(len(os.listdir(self.alertnotify_dir)), 0) + # TODO: add test for `-alertnotify` large fork notifications if __name__ == '__main__': NotificationsTest().main() diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index 31d2ee8e13..f042adb7aa 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -44,6 +44,7 @@ RANGE_BEGIN = PORT_MIN + 2 * PORT_RANGE # Start after p2p and rpc ports class ProxyTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 + self.setup_clean_chain = True def setup_nodes(self): self.have_ipv6 = test_ipv6_local() @@ -198,4 +199,3 @@ class ProxyTest(BitcoinTestFramework): if __name__ == '__main__': ProxyTest().main() - diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index 1efc50e71f..658a8cd75e 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -38,31 +38,29 @@ def find_spendable_utxo(node, min_value): raise AssertionError("Unspent output equal or higher than %s not found" % min_value) +txs_mined = {} # txindex from txid to blockhash + class SegWitTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 3 # This test tests SegWit both pre and post-activation, so use the normal BIP9 activation. - # TODO: remove -txindex. Currently required for getrawtransaction call. self.extra_args = [ [ "-rpcserialversion=0", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", - "-txindex" ], [ "-blockversion=4", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", - "-txindex" ], [ "-blockversion=536870915", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", - "-txindex" ], ] @@ -157,10 +155,10 @@ class SegWitTest(BitcoinTestFramework): self.log.info("Verify previous witness txs skipped for mining can now be mined") assert_equal(len(self.nodes[2].getrawmempool()), 4) - block = self.nodes[2].generate(1) # block 432 (first block with new rules; 432 = 144 * 3) + blockhash = self.nodes[2].generate(1)[0] # block 432 (first block with new rules; 432 = 144 * 3) sync_blocks(self.nodes) assert_equal(len(self.nodes[2].getrawmempool()), 0) - segwit_tx_list = self.nodes[2].getblock(block[0])["tx"] + segwit_tx_list = self.nodes[2].getblock(blockhash)["tx"] assert_equal(len(segwit_tx_list), 5) self.log.info("Verify default node can't accept txs with missing witness") @@ -174,15 +172,16 @@ class SegWitTest(BitcoinTestFramework): self.fail_accept(self.nodes[0], "mandatory-script-verify-flag", p2sh_ids[NODE_0][WIT_V1][0], False, witness_script(True, self.pubkey[0])) self.log.info("Verify block and transaction serialization rpcs return differing serializations depending on rpc serialization flag") - assert(self.nodes[2].getblock(block[0], False) != self.nodes[0].getblock(block[0], False)) - assert(self.nodes[1].getblock(block[0], False) == self.nodes[2].getblock(block[0], False)) - for i in range(len(segwit_tx_list)): - tx = FromHex(CTransaction(), self.nodes[2].gettransaction(segwit_tx_list[i])["hex"]) - assert(self.nodes[2].getrawtransaction(segwit_tx_list[i]) != self.nodes[0].getrawtransaction(segwit_tx_list[i])) - assert(self.nodes[1].getrawtransaction(segwit_tx_list[i], 0) == self.nodes[2].getrawtransaction(segwit_tx_list[i])) - assert(self.nodes[0].getrawtransaction(segwit_tx_list[i]) != self.nodes[2].gettransaction(segwit_tx_list[i])["hex"]) - assert(self.nodes[1].getrawtransaction(segwit_tx_list[i]) == self.nodes[2].gettransaction(segwit_tx_list[i])["hex"]) - assert(self.nodes[0].getrawtransaction(segwit_tx_list[i]) == bytes_to_hex_str(tx.serialize_without_witness())) + assert(self.nodes[2].getblock(blockhash, False) != self.nodes[0].getblock(blockhash, False)) + assert(self.nodes[1].getblock(blockhash, False) == self.nodes[2].getblock(blockhash, False)) + + for tx_id in segwit_tx_list: + tx = FromHex(CTransaction(), self.nodes[2].gettransaction(tx_id)["hex"]) + assert(self.nodes[2].getrawtransaction(tx_id, False, blockhash) != self.nodes[0].getrawtransaction(tx_id, False, blockhash)) + assert(self.nodes[1].getrawtransaction(tx_id, False, blockhash) == self.nodes[2].getrawtransaction(tx_id, False, blockhash)) + assert(self.nodes[0].getrawtransaction(tx_id, False, blockhash) != self.nodes[2].gettransaction(tx_id)["hex"]) + assert(self.nodes[1].getrawtransaction(tx_id, False, blockhash) == self.nodes[2].gettransaction(tx_id)["hex"]) + assert(self.nodes[0].getrawtransaction(tx_id, False, blockhash) == bytes_to_hex_str(tx.serialize_without_witness())) self.log.info("Verify witness txs without witness data are invalid after the fork") self.fail_accept(self.nodes[2], 'non-mandatory-script-verify-flag (Witness program hash mismatch) (code 64)', wit_ids[NODE_2][WIT_V0][2], sign=False) @@ -538,7 +537,7 @@ class SegWitTest(BitcoinTestFramework): tx.rehash() signresults = self.nodes[0].signrawtransactionwithwallet(bytes_to_hex_str(tx.serialize_without_witness()))['hex'] txid = self.nodes[0].sendrawtransaction(signresults, True) - self.nodes[0].generate(1) + txs_mined[txid] = self.nodes[0].generate(1)[0] sync_blocks(self.nodes) watchcount = 0 spendcount = 0 @@ -581,7 +580,7 @@ class SegWitTest(BitcoinTestFramework): tx = CTransaction() for i in txids: txtmp = CTransaction() - txraw = self.nodes[0].getrawtransaction(i) + txraw = self.nodes[0].getrawtransaction(i, 0, txs_mined[i]) f = BytesIO(hex_str_to_bytes(txraw)) txtmp.deserialize(f) for j in range(len(txtmp.vout)): diff --git a/test/functional/feature_versionbits_warning.py b/test/functional/feature_versionbits_warning.py index 88df61cabc..1ced618d47 100755 --- a/test/functional/feature_versionbits_warning.py +++ b/test/functional/feature_versionbits_warning.py @@ -22,7 +22,6 @@ VB_TOP_BITS = 0x20000000 VB_UNKNOWN_BIT = 27 # Choose a bit unassigned to any deployment VB_UNKNOWN_VERSION = VB_TOP_BITS | (1 << VB_UNKNOWN_BIT) -WARN_UNKNOWN_RULES_MINED = "Unknown block versions being mined! It's possible unknown rules are in effect" WARN_UNKNOWN_RULES_ACTIVE = "unknown new rules activated (versionbit {})".format(VB_UNKNOWN_BIT) VB_PATTERN = re.compile("Warning: unknown new rules activated.*versionbit") @@ -78,15 +77,10 @@ class VersionBitsWarningTest(BitcoinTestFramework): assert(not VB_PATTERN.match(node.getmininginfo()["warnings"])) assert(not VB_PATTERN.match(node.getnetworkinfo()["warnings"])) - self.log.info("Check that there is a warning if >50 blocks in the last 100 were an unknown version") # Build one period of blocks with VB_THRESHOLD blocks signaling some unknown bit self.send_blocks_with_version(node.p2p, VB_THRESHOLD, VB_UNKNOWN_VERSION) node.generatetoaddress(VB_PERIOD - VB_THRESHOLD, node_deterministic_address) - # Check that get*info() shows the 51/100 unknown block version error. - assert(WARN_UNKNOWN_RULES_MINED in node.getmininginfo()["warnings"]) - assert(WARN_UNKNOWN_RULES_MINED in node.getnetworkinfo()["warnings"]) - self.log.info("Check that there is a warning if previous VB_BLOCKS have >=VB_THRESHOLD blocks with unknown versionbits version.") # Mine a period worth of expected blocks so the generic block-version warning # is cleared. This will move the versionbit state to ACTIVE. diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index f850a54462..f33040242c 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -22,6 +22,8 @@ from test_framework.util import ( hex_str_to_bytes, ) +from test_framework.messages import BLOCK_HEADER_SIZE + class ReqType(Enum): JSON = 1 BIN = 2 @@ -41,8 +43,7 @@ class RESTTest (BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 - # TODO: remove -txindex. Currently required for getrawtransaction call. - self.extra_args = [["-rest", "-txindex"], []] + self.extra_args = [["-rest"], []] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -89,15 +90,17 @@ class RESTTest (BitcoinTestFramework): txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) self.sync_all() - self.nodes[1].generatetoaddress(1, not_related_address) - self.sync_all() - bb_hash = self.nodes[0].getbestblockhash() - assert_equal(self.nodes[1].getbalance(), Decimal("0.1")) - - self.log.info("Load the transaction using the /tx URI") + self.log.info("Test the /tx URI") json_obj = self.test_rest_request("/tx/{}".format(txid)) + assert_equal(json_obj['txid'], txid) + + # Check hex format response + hex_response = self.test_rest_request("/tx/{}".format(txid), req_type=ReqType.HEX, ret_type=RetType.OBJ) + assert_greater_than_or_equal(int(hex_response.getheader('content-length')), + json_obj['size']*2) + spent = (json_obj['vin'][0]['txid'], json_obj['vin'][0]['vout']) # get the vin to later check for utxo (should be spent by then) # get n of 0.1 outpoint n, = filter_output_indices_by_value(json_obj['vout'], Decimal('0.1')) @@ -105,9 +108,14 @@ class RESTTest (BitcoinTestFramework): self.log.info("Query an unspent TXO using the /getutxos URI") - json_obj = self.test_rest_request("/getutxos/{}-{}".format(*spending)) + self.nodes[1].generatetoaddress(1, not_related_address) + self.sync_all() + bb_hash = self.nodes[0].getbestblockhash() + + assert_equal(self.nodes[1].getbalance(), Decimal("0.1")) # Check chainTip response + json_obj = self.test_rest_request("/getutxos/{}-{}".format(*spending)) assert_equal(json_obj['chaintipHash'], bb_hash) # Make sure there is one utxo @@ -214,26 +222,26 @@ class RESTTest (BitcoinTestFramework): # Check binary format response = self.test_rest_request("/block/{}".format(bb_hash), req_type=ReqType.BIN, ret_type=RetType.OBJ) - assert_greater_than(int(response.getheader('content-length')), 80) + assert_greater_than(int(response.getheader('content-length')), BLOCK_HEADER_SIZE) response_bytes = response.read() # Compare with block header response_header = self.test_rest_request("/headers/1/{}".format(bb_hash), req_type=ReqType.BIN, ret_type=RetType.OBJ) - assert_equal(int(response_header.getheader('content-length')), 80) + assert_equal(int(response_header.getheader('content-length')), BLOCK_HEADER_SIZE) response_header_bytes = response_header.read() - assert_equal(response_bytes[:80], response_header_bytes) + assert_equal(response_bytes[:BLOCK_HEADER_SIZE], response_header_bytes) # Check block hex format response_hex = self.test_rest_request("/block/{}".format(bb_hash), req_type=ReqType.HEX, ret_type=RetType.OBJ) - assert_greater_than(int(response_hex.getheader('content-length')), 160) + assert_greater_than(int(response_hex.getheader('content-length')), BLOCK_HEADER_SIZE*2) response_hex_bytes = response_hex.read().strip(b'\n') assert_equal(binascii.hexlify(response_bytes), response_hex_bytes) # Compare with hex block header response_header_hex = self.test_rest_request("/headers/1/{}".format(bb_hash), req_type=ReqType.HEX, ret_type=RetType.OBJ) - assert_greater_than(int(response_header_hex.getheader('content-length')), 160) - response_header_hex_bytes = response_header_hex.read(160) - assert_equal(binascii.hexlify(response_bytes[:80]), response_header_hex_bytes) + assert_greater_than(int(response_header_hex.getheader('content-length')), BLOCK_HEADER_SIZE*2) + response_header_hex_bytes = response_header_hex.read(BLOCK_HEADER_SIZE*2) + assert_equal(binascii.hexlify(response_bytes[:BLOCK_HEADER_SIZE]), response_header_hex_bytes) # Check json format block_json_obj = self.test_rest_request("/block/{}".format(bb_hash)) @@ -272,17 +280,6 @@ class RESTTest (BitcoinTestFramework): json_obj = self.test_rest_request("/headers/5/{}".format(bb_hash)) assert_equal(len(json_obj), 5) # now we should have 5 header objects - self.log.info("Test the /tx URI") - - tx_hash = block_json_obj['tx'][0]['txid'] - json_obj = self.test_rest_request("/tx/{}".format(tx_hash)) - assert_equal(json_obj['txid'], tx_hash) - - # Check hex format response - hex_response = self.test_rest_request("/tx/{}".format(tx_hash), req_type=ReqType.HEX, ret_type=RetType.OBJ) - assert_greater_than_or_equal(int(hex_response.getheader('content-length')), - json_obj['size']*2) - self.log.info("Test tx inclusion in the /mempool and /block URIs") # Make 3 tx and mine them on node 1 diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index e2a219b85a..efe720b9b1 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -29,7 +29,6 @@ from test_framework.util import ( assert_raises_rpc_error, bytes_to_hex_str, hex_str_to_bytes, - wait_until, ) @@ -38,7 +37,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.num_nodes = 1 self.extra_args = [[ '-txindex', - '-reindex', # Need reindex for txindex '-acceptnonstdtxn=0', # Try to mimic main-net ]] * self.num_nodes @@ -56,7 +54,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('Start with empty mempool, and 200 blocks') self.mempool_size = 0 - wait_until(lambda: node.getblockcount() == 200) + assert_equal(node.getblockcount(), 200) assert_equal(node.getmempoolinfo()['size'], self.mempool_size) coins = node.listunspent() diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index 661d9f4c97..d0bef48cb6 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2014-2018 The Bitcoin Core developers +# Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test mining RPCs @@ -11,10 +11,14 @@ import copy from decimal import Decimal -from test_framework.blocktools import create_coinbase +from test_framework.blocktools import ( + create_coinbase, + TIME_GENESIS_BLOCK, +) from test_framework.messages import ( CBlock, CBlockHeader, + BLOCK_HEADER_SIZE ) from test_framework.mininode import ( P2PDataStore, @@ -24,9 +28,11 @@ from test_framework.util import ( assert_equal, assert_raises_rpc_error, bytes_to_hex_str as b2x, + connect_nodes_bi, ) from test_framework.script import CScriptNum + def assert_template(node, block, expect, rehash=True): if rehash: block.hashMerkleRoot = block.calc_merkle_root() @@ -37,9 +43,22 @@ def assert_template(node, block, expect, rehash=True): class MiningTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - self.setup_clean_chain = False + self.setup_clean_chain = True + + def mine_chain(self): + self.log.info('Create some old blocks') + for t in range(TIME_GENESIS_BLOCK, TIME_GENESIS_BLOCK + 200 * 600, 600): + self.nodes[0].setmocktime(t) + self.nodes[0].generate(1) + mining_info = self.nodes[0].getmininginfo() + assert_equal(mining_info['blocks'], 200) + assert_equal(mining_info['currentblocktx'], 0) + assert_equal(mining_info['currentblockweight'], 4000) + self.restart_node(0) + connect_nodes_bi(self.nodes, 0, 1) def run_test(self): + self.mine_chain() node = self.nodes[0] def assert_submitblock(block, result_str_1, result_str_2=None): @@ -52,8 +71,8 @@ class MiningTest(BitcoinTestFramework): mining_info = node.getmininginfo() assert_equal(mining_info['blocks'], 200) assert_equal(mining_info['chain'], 'regtest') - assert_equal(mining_info['currentblocktx'], 0) - assert_equal(mining_info['currentblockweight'], 0) + assert 'currentblocktx' not in mining_info + assert 'currentblockweight' not in mining_info assert_equal(mining_info['difficulty'], Decimal('4.656542373906925E-10')) assert_equal(mining_info['networkhashps'], Decimal('0.003333333333333334')) assert_equal(mining_info['pooledtx'], 0) @@ -131,10 +150,9 @@ class MiningTest(BitcoinTestFramework): self.log.info("getblocktemplate: Test bad tx count") # The tx count is immediately after the block header - TX_COUNT_OFFSET = 80 bad_block_sn = bytearray(block.serialize()) - assert_equal(bad_block_sn[TX_COUNT_OFFSET], 1) - bad_block_sn[TX_COUNT_OFFSET] += 1 + assert_equal(bad_block_sn[BLOCK_HEADER_SIZE], 1) + bad_block_sn[BLOCK_HEADER_SIZE] += 1 assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(bad_block_sn), 'mode': 'proposal', 'rules': ['segwit']}) self.log.info("getblocktemplate: Test bad bits") @@ -164,9 +182,9 @@ class MiningTest(BitcoinTestFramework): assert_submitblock(bad_block, 'prev-blk-not-found', 'prev-blk-not-found') self.log.info('submitheader tests') - assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='xx' * 80)) - assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='ff' * 78)) - assert_raises_rpc_error(-25, 'Must submit previous header', lambda: node.submitheader(hexdata='ff' * 80)) + assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='xx' * BLOCK_HEADER_SIZE)) + assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='ff' * (BLOCK_HEADER_SIZE-2))) + assert_raises_rpc_error(-25, 'Must submit previous header', lambda: node.submitheader(hexdata=b2x(super(CBlock, bad_block).serialize()))) block.nTime += 1 block.solve() diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py index dcc0d1d235..700fdf6e04 100755 --- a/test/functional/p2p_invalid_messages.py +++ b/test/functional/p2p_invalid_messages.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2015-2018 The Bitcoin Core developers +# Copyright (c) 2015-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test node responses to invalid network messages.""" @@ -143,6 +143,7 @@ class InvalidMessagesTest(BitcoinTestFramework): def test_magic_bytes(self): conn = self.nodes[0].add_p2p_connection(P2PDataStore()) + conn._on_data = lambda: None # Need to ignore all incoming messages from now, since they come with "invalid" magic bytes conn.magic_bytes = b'\x00\x11\x22\x32' with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: INVALID MESSAGESTART ping']): conn.send_message(messages.msg_ping(nonce=0xff)) @@ -211,6 +212,5 @@ class InvalidMessagesTest(BitcoinTestFramework): return raw_msg_with_wrong_size - if __name__ == '__main__': InvalidMessagesTest().main() diff --git a/test/functional/p2p_sendheaders.py b/test/functional/p2p_sendheaders.py index c7ae57de86..7d7d251765 100755 --- a/test/functional/p2p_sendheaders.py +++ b/test/functional/p2p_sendheaders.py @@ -490,7 +490,7 @@ class SendHeadersTest(BitcoinTestFramework): # Now announce a header that forks the last two blocks tip = blocks[0].sha256 - height -= 1 + height -= 2 blocks = [] # Create extra blocks for later diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 31e60f1cea..af150a09af 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -35,6 +35,7 @@ from test_framework.util import ( from test_framework.blocktools import ( create_block, create_coinbase, + TIME_GENESIS_BLOCK, ) from test_framework.messages import ( msg_block, @@ -46,9 +47,11 @@ from test_framework.mininode import ( class BlockchainTest(BitcoinTestFramework): def set_test_params(self): + self.setup_clean_chain = True self.num_nodes = 1 def run_test(self): + self.mine_chain() self.restart_node(0, extra_args=['-stopatheight=207', '-prune=1']) # Set extra args with pruning after rescan is complete self._test_getblockchaininfo() @@ -61,6 +64,15 @@ class BlockchainTest(BitcoinTestFramework): self._test_waitforblockheight() assert self.nodes[0].verifychain(4, 0) + def mine_chain(self): + self.log.info('Create some old blocks') + address = self.nodes[0].get_deterministic_priv_key().address + for t in range(TIME_GENESIS_BLOCK, TIME_GENESIS_BLOCK + 200 * 600, 600): + # ten-minute steps from genesis block time + self.nodes[0].setmocktime(t) + self.nodes[0].generatetoaddress(1, address) + assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 200) + def _test_getblockchaininfo(self): self.log.info("Test getblockchaininfo") diff --git a/test/functional/rpc_deriveaddresses.py b/test/functional/rpc_deriveaddresses.py new file mode 100755 index 0000000000..185dffd51c --- /dev/null +++ b/test/functional/rpc_deriveaddresses.py @@ -0,0 +1,55 @@ +#!/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 deriveaddresses rpc call.""" +from test_framework.test_framework import BitcoinTestFramework +from test_framework.descriptors import descsum_create +from test_framework.util import assert_equal, assert_raises_rpc_error + +class DeriveaddressesTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.supports_cli = 1 + + def run_test(self): + assert_raises_rpc_error(-5, "Invalid descriptor", self.nodes[0].deriveaddresses, "a") + + descriptor = "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)#t6wfjs64" + address = "bcrt1qjqmxmkpmxt80xz4y3746zgt0q3u3ferr34acd5" + assert_equal(self.nodes[0].deriveaddresses(descriptor), [address]) + + descriptor = descriptor[:-9] + assert_raises_rpc_error(-5, "Invalid descriptor", self.nodes[0].deriveaddresses, descriptor) + + descriptor_pubkey = "wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)#s9ga3alw" + address = "bcrt1qjqmxmkpmxt80xz4y3746zgt0q3u3ferr34acd5" + assert_equal(self.nodes[0].deriveaddresses(descriptor_pubkey), [address]) + + ranged_descriptor = "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)#kft60nuy" + assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, [1, 2]), ["bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"]) + assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, 2), [address, "bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"]) + + assert_raises_rpc_error(-8, "Range should not be specified for an un-ranged descriptor", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"), [0, 2]) + + assert_raises_rpc_error(-8, "Range must be specified for a ranged descriptor", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)")) + + assert_raises_rpc_error(-8, "End of range is too high", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), 10000000000) + + assert_raises_rpc_error(-8, "Range is too large", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), [1000000000, 2000000000]) + + assert_raises_rpc_error(-8, "Range specified as [begin,end] must not have begin after end", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), [2, 0]) + + assert_raises_rpc_error(-8, "Range should be greater or equal than 0", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), [-1, 0]) + + combo_descriptor = descsum_create("combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)") + assert_equal(self.nodes[0].deriveaddresses(combo_descriptor), ["mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", "mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", address, "2NDvEwGfpEqJWfybzpKPHF2XH3jwoQV3D7x"]) + + hardened_without_privkey_descriptor = descsum_create("wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1'/1/0)") + assert_raises_rpc_error(-5, "Cannot derive script without private keys", self.nodes[0].deriveaddresses, hardened_without_privkey_descriptor) + + bare_multisig_descriptor = descsum_create("multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)") + assert_raises_rpc_error(-5, "Descriptor does not have a corresponding address", self.nodes[0].deriveaddresses, bare_multisig_descriptor) + +if __name__ == '__main__': + DeriveaddressesTest().main() diff --git a/test/functional/rpc_getblockstats.py b/test/functional/rpc_getblockstats.py index ca9e24367a..feba16f1b3 100755 --- a/test/functional/rpc_getblockstats.py +++ b/test/functional/rpc_getblockstats.py @@ -178,5 +178,10 @@ class GetblockstatsTest(BitcoinTestFramework): assert_raises_rpc_error(-5, 'Block not found', self.nodes[0].getblockstats, hash_or_height='000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f') + # Invalid number of args + assert_raises_rpc_error(-1, 'getblockstats hash_or_height ( stats )', self.nodes[0].getblockstats, '00', 1, 2) + assert_raises_rpc_error(-1, 'getblockstats hash_or_height ( stats )', self.nodes[0].getblockstats) + + if __name__ == '__main__': GetblockstatsTest().main() diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 1e10280e60..e9098e4f5a 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -5,8 +5,9 @@ """Test the Partially Signed Transaction RPCs. """ +from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error, find_output, disconnect_nodes, connect_nodes_bi, sync_blocks +from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes_bi, disconnect_nodes, find_output, sync_blocks import json import os @@ -19,8 +20,6 @@ class PSBTTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = False self.num_nodes = 3 - # TODO: remove -txindex. Currently required for getrawtransaction call. - self.extra_args = [[], ["-txindex"], ["-txindex"]] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -160,11 +159,11 @@ class PSBTTest(BitcoinTestFramework): node1_addr = self.nodes[1].getnewaddress() node2_addr = self.nodes[2].getnewaddress() txid1 = self.nodes[0].sendtoaddress(node1_addr, 13) - txid2 =self.nodes[0].sendtoaddress(node2_addr, 13) - self.nodes[0].generate(6) + txid2 = self.nodes[0].sendtoaddress(node2_addr, 13) + blockhash = self.nodes[0].generate(6)[0] self.sync_all() - vout1 = find_output(self.nodes[1], txid1, 13) - vout2 = find_output(self.nodes[2], txid2, 13) + vout1 = find_output(self.nodes[1], txid1, 13, blockhash=blockhash) + vout2 = find_output(self.nodes[2], txid2, 13, blockhash=blockhash) # Create a psbt spending outputs from nodes 1 and 2 psbt_orig = self.nodes[0].createpsbt([{"txid":txid1, "vout":vout1}, {"txid":txid2, "vout":vout2}], {self.nodes[0].getnewaddress():25.999}) @@ -269,6 +268,9 @@ class PSBTTest(BitcoinTestFramework): combined = self.nodes[2].combinepsbt(combiner['combine']) assert_equal(combined, combiner['result']) + # Empty combiner test + assert_raises_rpc_error(-8, "Parameter 'txs' cannot be empty", self.nodes[0].combinepsbt, []) + # Finalizer test for finalizer in finalizers: finalized = self.nodes[2].finalizepsbt(finalizer['finalize'], False)['psbt'] @@ -290,5 +292,75 @@ class PSBTTest(BitcoinTestFramework): psbt = self.nodes[1].walletcreatefundedpsbt([], [{p2pkh : 1}], 0, {"includeWatching" : True}, True) self.nodes[0].decodepsbt(psbt['psbt']) + # Test decoding error: invalid base64 + assert_raises_rpc_error(-22, "TX decode failed invalid base64", self.nodes[0].decodepsbt, ";definitely not base64;") + + # Send to all types of addresses + addr1 = self.nodes[1].getnewaddress("", "bech32") + txid1 = self.nodes[0].sendtoaddress(addr1, 11) + vout1 = find_output(self.nodes[0], txid1, 11) + addr2 = self.nodes[1].getnewaddress("", "legacy") + txid2 = self.nodes[0].sendtoaddress(addr2, 11) + vout2 = find_output(self.nodes[0], txid2, 11) + addr3 = self.nodes[1].getnewaddress("", "p2sh-segwit") + txid3 = self.nodes[0].sendtoaddress(addr3, 11) + vout3 = find_output(self.nodes[0], txid3, 11) + self.sync_all() + + # Update a PSBT with UTXOs from the node + # Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness + psbt = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1},{"txid":txid2, "vout":vout2},{"txid":txid3, "vout":vout3}], {self.nodes[0].getnewaddress():32.999}) + decoded = self.nodes[1].decodepsbt(psbt) + assert "witness_utxo" not in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0] + assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1] + assert "witness_utxo" not in decoded['inputs'][2] and "non_witness_utxo" not in decoded['inputs'][2] + updated = self.nodes[1].utxoupdatepsbt(psbt) + decoded = self.nodes[1].decodepsbt(updated) + assert "witness_utxo" in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0] + assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1] + assert "witness_utxo" not in decoded['inputs'][2] and "non_witness_utxo" not in decoded['inputs'][2] + + # Two PSBTs with a common input should not be joinable + psbt1 = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], {self.nodes[0].getnewaddress():Decimal('10.999')}) + assert_raises_rpc_error(-8, "exists in multiple PSBTs", self.nodes[1].joinpsbts, [psbt1, updated]) + + # Join two distinct PSBTs + addr4 = self.nodes[1].getnewaddress("", "p2sh-segwit") + txid4 = self.nodes[0].sendtoaddress(addr4, 5) + vout4 = find_output(self.nodes[0], txid4, 5) + self.nodes[0].generate(6) + self.sync_all() + psbt2 = self.nodes[1].createpsbt([{"txid":txid4, "vout":vout4}], {self.nodes[0].getnewaddress():Decimal('4.999')}) + psbt2 = self.nodes[1].walletprocesspsbt(psbt2)['psbt'] + psbt2_decoded = self.nodes[0].decodepsbt(psbt2) + assert "final_scriptwitness" in psbt2_decoded['inputs'][0] and "final_scriptSig" in psbt2_decoded['inputs'][0] + joined = self.nodes[0].joinpsbts([psbt, psbt2]) + joined_decoded = self.nodes[0].decodepsbt(joined) + assert len(joined_decoded['inputs']) == 4 and len(joined_decoded['outputs']) == 2 and "final_scriptwitness" not in joined_decoded['inputs'][3] and "final_scriptSig" not in joined_decoded['inputs'][3] + + # Newly created PSBT needs UTXOs and updating + addr = self.nodes[1].getnewaddress("", "p2sh-segwit") + txid = self.nodes[0].sendtoaddress(addr, 7) + addrinfo = self.nodes[1].getaddressinfo(addr) + blockhash = self.nodes[0].generate(6)[0] + self.sync_all() + vout = find_output(self.nodes[0], txid, 7, blockhash=blockhash) + psbt = self.nodes[1].createpsbt([{"txid":txid, "vout":vout}], {self.nodes[0].getnewaddress("", "p2sh-segwit"):Decimal('6.999')}) + analyzed = self.nodes[0].analyzepsbt(psbt) + assert not analyzed['inputs'][0]['has_utxo'] and not analyzed['inputs'][0]['is_final'] and analyzed['inputs'][0]['next'] == 'updater' and analyzed['next'] == 'updater' + + # After update with wallet, only needs signing + updated = self.nodes[1].walletprocesspsbt(psbt, False, 'ALL', True)['psbt'] + analyzed = self.nodes[0].analyzepsbt(updated) + assert analyzed['inputs'][0]['has_utxo'] and not analyzed['inputs'][0]['is_final'] and analyzed['inputs'][0]['next'] == 'signer' and analyzed['next'] == 'signer' and analyzed['inputs'][0]['missing']['signatures'][0] == addrinfo['embedded']['witness_program'] + + # Check fee and size things + assert analyzed['fee'] == Decimal('0.001') and analyzed['estimated_vsize'] == 134 and analyzed['estimated_feerate'] == '0.00746268 BTC/kB' + + # After signing and finalizing, needs extracting + signed = self.nodes[1].walletprocesspsbt(updated)['psbt'] + analyzed = self.nodes[0].analyzepsbt(signed) + assert analyzed['inputs'][0]['has_utxo'] and analyzed['inputs'][0]['is_final'] and analyzed['next'] == 'extractor' + if __name__ == '__main__': PSBTTest().main() diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index a97d753626..c793cf00de 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -42,7 +42,6 @@ class RawTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 3 - # TODO: remove -txindex. Currently required for getrawtransaction call. self.extra_args = [["-addresstype=legacy", "-txindex"], ["-addresstype=legacy", "-txindex"], ["-addresstype=legacy", "-txindex"]] def skip_test_if_missing_module(self): diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py index 11b4db6ec5..18258f5df3 100755 --- a/test/functional/rpc_scantxoutset.py +++ b/test/functional/rpc_scantxoutset.py @@ -95,11 +95,12 @@ class ScantxoutsetTest(BitcoinTestFramework): assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672")) assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288")) assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672")) + assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": [1500,1500]}])['total_amount'], Decimal("16.384")) # Test the reported descriptors for a few matches - assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)", "range": 1499}])), ["pkh([0c5f9a1e/0'/0'/0]026dbd8b2315f296d36e6b6920b1579ca75569464875c7ebe869b536a7d9503c8c)", "pkh([0c5f9a1e/0'/0'/1]033e6f25d76c00bedb3a8993c7d5739ee806397f0529b1b31dda31ef890f19a60c)"]) - assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"])), ["pkh([0c5f9a1e/1/1/0]03e1c5b6e650966971d7e71ef2674f80222752740fc1dfd63bbbd220d2da9bd0fb)"]) - assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])), ['pkh([0c5f9a1e/1/1/0]03e1c5b6e650966971d7e71ef2674f80222752740fc1dfd63bbbd220d2da9bd0fb)', 'pkh([0c5f9a1e/1/1/1500]03832901c250025da2aebae2bfb38d5c703a57ab66ad477f9c578bfbcd78abca6f)', 'pkh([0c5f9a1e/1/1/1]030d820fc9e8211c4169be8530efbc632775d8286167afd178caaf1089b77daba7)']) + assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)", "range": 1499}])), ["pkh([0c5f9a1e/0'/0'/0]026dbd8b2315f296d36e6b6920b1579ca75569464875c7ebe869b536a7d9503c8c)#dzxw429x", "pkh([0c5f9a1e/0'/0'/1]033e6f25d76c00bedb3a8993c7d5739ee806397f0529b1b31dda31ef890f19a60c)#43rvceed"]) + assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"])), ["pkh([0c5f9a1e/1/1/0]03e1c5b6e650966971d7e71ef2674f80222752740fc1dfd63bbbd220d2da9bd0fb)#cxmct4w8"]) + assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])), ['pkh([0c5f9a1e/1/1/0]03e1c5b6e650966971d7e71ef2674f80222752740fc1dfd63bbbd220d2da9bd0fb)#cxmct4w8', 'pkh([0c5f9a1e/1/1/1500]03832901c250025da2aebae2bfb38d5c703a57ab66ad477f9c578bfbcd78abca6f)#vchwd07g', 'pkh([0c5f9a1e/1/1/1]030d820fc9e8211c4169be8530efbc632775d8286167afd178caaf1089b77daba7)#z2t3ypsa']) if __name__ == '__main__': ScantxoutsetTest().main() diff --git a/test/functional/rpc_signrawtransaction.py b/test/functional/rpc_signrawtransaction.py index 291538df64..56e2c73a90 100755 --- a/test/functional/rpc_signrawtransaction.py +++ b/test/functional/rpc_signrawtransaction.py @@ -5,14 +5,17 @@ """Test transaction signing using the signrawtransaction* RPCs.""" from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error +from test_framework.util import assert_equal, assert_raises_rpc_error, bytes_to_hex_str, hex_str_to_bytes +from test_framework.messages import sha256 +from test_framework.script import CScript, OP_0 +from decimal import Decimal class SignRawTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True - self.num_nodes = 1 - self.extra_args = [["-deprecatedrpc=signrawtransaction"]] + self.num_nodes = 2 + self.extra_args = [["-deprecatedrpc=signrawtransaction"], []] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -143,9 +146,33 @@ class SignRawTransactionsTest(BitcoinTestFramework): assert_equal(rawTxSigned['errors'][1]['witness'], ["304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee01", "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"]) assert not rawTxSigned['errors'][0]['witness'] + def witness_script_test(self): + # Now test signing transaction to P2SH-P2WSH addresses without wallet + # Create a new P2SH-P2WSH 1-of-1 multisig address: + embedded_address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress()) + embedded_privkey = self.nodes[1].dumpprivkey(embedded_address["address"]) + p2sh_p2wsh_address = self.nodes[1].addmultisigaddress(1, [embedded_address["pubkey"]], "", "p2sh-segwit") + # send transaction to P2SH-P2WSH 1-of-1 multisig address + self.nodes[0].generate(101) + self.nodes[0].sendtoaddress(p2sh_p2wsh_address["address"], 49.999) + self.nodes[0].generate(1) + self.sync_all() + # Find the UTXO for the transaction node[1] should have received, check witnessScript matches + unspent_output = self.nodes[1].listunspent(0, 999999, [p2sh_p2wsh_address["address"]])[0] + assert_equal(unspent_output["witnessScript"], p2sh_p2wsh_address["redeemScript"]) + p2sh_redeemScript = CScript([OP_0, sha256(hex_str_to_bytes(p2sh_p2wsh_address["redeemScript"]))]) + assert_equal(unspent_output["redeemScript"], bytes_to_hex_str(p2sh_redeemScript)) + # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys + spending_tx = self.nodes[0].createrawtransaction([unspent_output], {self.nodes[1].getnewaddress(): Decimal("49.998")}) + spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [unspent_output]) + # Check the signing completed successfully + assert 'complete' in spending_tx_signed + assert_equal(spending_tx_signed['complete'], True) + def run_test(self): self.successful_signing_test() self.script_verification_error_test() + self.witness_script_test() self.test_with_lock_outputs() diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 6b47cae4c3..15f4502994 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2015-2018 The Bitcoin Core developers +# Copyright (c) 2015-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Utilities for manipulating blocks and transactions.""" @@ -43,9 +43,13 @@ from io import BytesIO MAX_BLOCK_SIGOPS = 20000 +# Genesis block time (regtest) +TIME_GENESIS_BLOCK = 1296688602 + # From BIP141 WITNESS_COMMITMENT_HEADER = b"\xaa\x21\xa9\xed" + def create_block(hashprev, coinbase, ntime=None, *, version=1): """Create a block (with regtest difficulty).""" block = CBlock() diff --git a/test/functional/test_framework/descriptors.py b/test/functional/test_framework/descriptors.py new file mode 100644 index 0000000000..29482ce01e --- /dev/null +++ b/test/functional/test_framework/descriptors.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 Pieter Wuille +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Utility functions related to output descriptors""" + +INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ " +CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" +GENERATOR = [0xf5dee51989, 0xa9fdca3312, 0x1bab10e32d, 0x3706b1677a, 0x644d626ffd] + +def descsum_polymod(symbols): + """Internal function that computes the descriptor checksum.""" + chk = 1 + for value in symbols: + top = chk >> 35 + chk = (chk & 0x7ffffffff) << 5 ^ value + for i in range(5): + chk ^= GENERATOR[i] if ((top >> i) & 1) else 0 + return chk + +def descsum_expand(s): + """Internal function that does the character to symbol expansion""" + groups = [] + symbols = [] + for c in s: + if not c in INPUT_CHARSET: + return None + v = INPUT_CHARSET.find(c) + symbols.append(v & 31) + groups.append(v >> 5) + if len(groups) == 3: + symbols.append(groups[0] * 9 + groups[1] * 3 + groups[2]) + groups = [] + if len(groups) == 1: + symbols.append(groups[0]) + elif len(groups) == 2: + symbols.append(groups[0] * 3 + groups[1]) + return symbols + +def descsum_create(s): + """Add a checksum to a descriptor without""" + symbols = descsum_expand(s) + [0, 0, 0, 0, 0, 0, 0, 0] + checksum = descsum_polymod(symbols) ^ 1 + return s + '#' + ''.join(CHECKSUM_CHARSET[(checksum >> (5 * (7 - i))) & 31] for i in range(8)) + +def descsum_check(s, require=True): + """Verify that the checksum is correct in a descriptor""" + if not '#' in s: + return not require + if s[-9] != '#': + return False + if not all(x in CHECKSUM_CHARSET for x in s[-8:]): + return False + symbols = descsum_expand(s[:-9]) + [CHECKSUM_CHARSET.find(x) for x in s[-8:]] + return descsum_polymod(symbols) == 1 diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 356a45d6d0..4bd58519c5 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -28,7 +28,7 @@ import struct import time from test_framework.siphash import siphash256 -from test_framework.util import hex_str_to_bytes, bytes_to_hex_str +from test_framework.util import hex_str_to_bytes, bytes_to_hex_str, assert_equal MIN_VERSION_SUPPORTED = 60001 MY_VERSION = 70014 # past bip-31 for ping/pong @@ -591,6 +591,8 @@ class CBlockHeader: % (self.nVersion, self.hashPrevBlock, self.hashMerkleRoot, time.ctime(self.nTime), self.nBits, self.nNonce) +BLOCK_HEADER_SIZE = len(CBlockHeader().serialize()) +assert_equal(BLOCK_HEADER_SIZE, 80) class CBlock(CBlockHeader): __slots__ = ("vtx",) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 6dcaff0696..09d7d877a7 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -29,11 +29,11 @@ from .util import ( get_datadir_path, initialize_datadir, p2p_port, - set_node_times, sync_blocks, sync_mempools, ) + class TestStatus(Enum): PASSED = 1 FAILED = 2 @@ -94,7 +94,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.setup_clean_chain = False self.nodes = [] self.network_thread = None - self.mocktime = 0 self.rpc_timeout = 60 # Wait for up to 60 seconds for the RPC server to respond self.supports_cli = False self.bind_to_localhost_only = True @@ -128,6 +127,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): help="Attach a python debugger if test fails") parser.add_argument("--usecli", dest="usecli", default=False, action="store_true", help="use bitcoin-cli instead of RPC for all commands") + parser.add_argument("--perf", dest="perf", default=False, action="store_true", + help="profile running nodes with perf for the duration of the test") self.add_options(parser) self.options = parser.parse_args() @@ -202,11 +203,20 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): node.cleanup_on_exit = False self.log.info("Note: bitcoinds were not stopped and may still be running") - if not self.options.nocleanup and not self.options.noshutdown and success != TestStatus.FAILED: + should_clean_up = ( + not self.options.nocleanup and + not self.options.noshutdown and + success != TestStatus.FAILED and + not self.options.perf + ) + if should_clean_up: self.log.info("Cleaning up {} on exit".format(self.options.tmpdir)) cleanup_tree_on_exit = True + elif self.options.perf: + self.log.warning("Not cleaning up dir {} due to perf data".format(self.options.tmpdir)) + cleanup_tree_on_exit = False else: - self.log.warning("Not cleaning up dir %s" % self.options.tmpdir) + self.log.warning("Not cleaning up dir {}".format(self.options.tmpdir)) cleanup_tree_on_exit = False if success == TestStatus.PASSED: @@ -264,6 +274,19 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.add_nodes(self.num_nodes, extra_args) self.start_nodes() self.import_deterministic_coinbase_privkeys() + if not self.setup_clean_chain: + for n in self.nodes: + assert_equal(n.getblockchaininfo()["blocks"], 199) + # To ensure that all nodes are out of IBD, the most recent block + # must have a timestamp not too old (see IsInitialBlockDownload()). + self.log.debug('Generate a block with current time') + block_hash = self.nodes[0].generate(1)[0] + block = self.nodes[0].getblock(blockhash=block_hash, verbosity=0) + for n in self.nodes: + n.submitblock(block) + chain_info = n.getblockchaininfo() + assert_equal(chain_info["blocks"], 200) + assert_equal(chain_info["initialblockdownload"], False) def import_deterministic_coinbase_privkeys(self): for n in self.nodes: @@ -305,11 +328,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): timewait=self.rpc_timeout, bitcoind=binary[i], bitcoin_cli=self.options.bitcoincli, - mocktime=self.mocktime, coverage_dir=self.options.coveragedir, + cwd=self.options.tmpdir, extra_conf=extra_confs[i], extra_args=extra_args[i], use_cli=self.options.usecli, + start_perf=self.options.perf, )) def start_node(self, i, *args, **kwargs): @@ -422,7 +446,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): def _initialize_chain(self): """Initialize a pre-mined blockchain for use by the test. - Create a cache of a 200-block-long chain (with wallet) for MAX_NODES + Create a cache of a 199-block-long chain (with wallet) for MAX_NODES Afterward, create num_nodes copies from the cache.""" assert self.num_nodes <= MAX_NODES @@ -455,8 +479,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): timewait=self.rpc_timeout, bitcoind=self.options.bitcoind, bitcoin_cli=self.options.bitcoincli, - mocktime=self.mocktime, coverage_dir=None, + cwd=self.options.tmpdir, )) self.nodes[i].args = args self.start_node(i) @@ -465,32 +489,22 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): for node in self.nodes: node.wait_for_rpc_connection() - # For backward compatibility of the python scripts with previous - # versions of the cache, set mocktime to Jan 1, - # 2014 + (201 * 10 * 60)""" - self.mocktime = 1388534400 + (201 * 10 * 60) - - # Create a 200-block-long chain; each of the 4 first nodes + # Create a 199-block-long chain; each of the 4 first nodes # gets 25 mature blocks and 25 immature. - # Note: To preserve compatibility with older versions of - # initialize_chain, only 4 nodes will generate coins. - # - # blocks are created with timestamps 10 minutes apart - # starting from 2010 minutes in the past - block_time = self.mocktime - (201 * 10 * 60) - for i in range(2): - for peer in range(4): - for j in range(25): - set_node_times(self.nodes, block_time) - self.nodes[peer].generatetoaddress(1, self.nodes[peer].get_deterministic_priv_key().address) - block_time += 10 * 60 - # Must sync before next peer starts generating blocks - sync_blocks(self.nodes) + # The 4th node gets only 24 immature blocks so that the very last + # block in the cache does not age too much (have an old tip age). + # This is needed so that we are out of IBD when the test starts, + # see the tip age check in IsInitialBlockDownload(). + for i in range(8): + self.nodes[0].generatetoaddress(25 if i != 7 else 24, self.nodes[i % 4].get_deterministic_priv_key().address) + sync_blocks(self.nodes) + + for n in self.nodes: + assert_equal(n.getblockchaininfo()["blocks"], 199) # Shut them down, and clean up cache directories: self.stop_nodes() self.nodes = [] - self.mocktime = 0 def cache_path(n, *paths): return os.path.join(get_datadir_path(self.options.cachedir, n), "regtest", *paths) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 031a8824b1..37fd2a8744 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -18,6 +18,8 @@ import tempfile import time import urllib.parse import collections +import shlex +import sys from .authproxy import JSONRPCException from .util import ( @@ -59,7 +61,13 @@ class TestNode(): To make things easier for the test writer, any unrecognised messages will be dispatched to the RPC connection.""" - def __init__(self, i, datadir, *, rpchost, timewait, bitcoind, bitcoin_cli, mocktime, coverage_dir, extra_conf=None, extra_args=None, use_cli=False): + def __init__(self, i, datadir, *, rpchost, timewait, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False): + """ + Kwargs: + start_perf (bool): If True, begin profiling the node with `perf` as soon as + the node starts. + """ + self.index = i self.datadir = datadir self.stdout_dir = os.path.join(self.datadir, "stdout") @@ -68,6 +76,7 @@ class TestNode(): self.rpc_timeout = timewait self.binary = bitcoind self.coverage_dir = coverage_dir + self.cwd = cwd if extra_conf is not None: append_config(datadir, extra_conf) # Most callers will just need to add extra args to the standard list below. @@ -81,12 +90,12 @@ class TestNode(): "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", - "-mocktime=" + str(mocktime), - "-uacomment=testnode%d" % i + "-uacomment=testnode%d" % i, ] self.cli = TestNodeCLI(bitcoin_cli, self.datadir) self.use_cli = use_cli + self.start_perf = start_perf self.running = False self.process = None @@ -95,6 +104,8 @@ class TestNode(): self.url = None self.log = logging.getLogger('TestFramework.node%d' % i) self.cleanup_on_exit = True # Whether to kill the node when this object goes away + # Cache perf subprocesses here by their data output filename. + self.perf_subprocesses = {} self.p2ps = [] @@ -160,7 +171,7 @@ class TestNode(): assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection") return getattr(self.rpc, name) - def start(self, extra_args=None, *, stdout=None, stderr=None, **kwargs): + def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, **kwargs): """Start the node.""" if extra_args is None: extra_args = self.extra_args @@ -173,6 +184,9 @@ class TestNode(): self.stderr = stderr self.stdout = stdout + if cwd is None: + cwd = self.cwd + # Delete any existing cookie file -- if such a file exists (eg due to # unclean shutdown), it will get overwritten anyway by bitcoind, and # potentially interfere with our attempt to authenticate @@ -181,11 +195,14 @@ class TestNode(): # add environment variable LIBC_FATAL_STDERR_=1 so that libc errors are written to stderr and not the terminal subp_env = dict(os.environ, LIBC_FATAL_STDERR_="1") - self.process = subprocess.Popen(self.args + extra_args, env=subp_env, stdout=stdout, stderr=stderr, **kwargs) + self.process = subprocess.Popen(self.args + extra_args, env=subp_env, stdout=stdout, stderr=stderr, cwd=cwd, **kwargs) self.running = True self.log.debug("bitcoind started, waiting for RPC to come up") + if self.start_perf: + self._start_perf() + def wait_for_rpc_connection(self): """Sets up an RPC connection to the bitcoind process. Returns False if unable to connect.""" # Poll at a rate of four times per second @@ -195,12 +212,15 @@ class TestNode(): raise FailedToStartError(self._node_msg( 'bitcoind exited with status {} during initialization'.format(self.process.returncode))) try: - self.rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir) - self.rpc.getblockcount() + rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir) + rpc.getblockcount() # If the call to getblockcount() succeeds then the RPC connection is up + self.log.debug("RPC successfully started") + if self.use_cli: + return + self.rpc = rpc self.rpc_connected = True self.url = self.rpc.url - self.log.debug("RPC successfully started") return except IOError as e: if e.errno != errno.ECONNREFUSED: # Port not yet open? @@ -238,6 +258,10 @@ class TestNode(): except http.client.CannotSendRequest: self.log.exception("Unable to stop node.") + # If there are any running perf processes, stop them. + for profile_name in tuple(self.perf_subprocesses.keys()): + self._stop_perf(profile_name) + # Check that stderr is as expected self.stderr.seek(0) stderr = self.stderr.read().decode('utf-8').strip() @@ -317,6 +341,84 @@ class TestNode(): increase_allowed * 100, before_memory_usage, after_memory_usage, perc_increase_memory_usage * 100)) + @contextlib.contextmanager + def profile_with_perf(self, profile_name): + """ + Context manager that allows easy profiling of node activity using `perf`. + + See `test/functional/README.md` for details on perf usage. + + Args: + profile_name (str): This string will be appended to the + profile data filename generated by perf. + """ + subp = self._start_perf(profile_name) + + yield + + if subp: + self._stop_perf(profile_name) + + def _start_perf(self, profile_name=None): + """Start a perf process to profile this node. + + Returns the subprocess running perf.""" + subp = None + + def test_success(cmd): + return subprocess.call( + # shell=True required for pipe use below + cmd, shell=True, + stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) == 0 + + if not sys.platform.startswith('linux'): + self.log.warning("Can't profile with perf; only availabe on Linux platforms") + return None + + if not test_success('which perf'): + self.log.warning("Can't profile with perf; must install perf-tools") + return None + + if not test_success('readelf -S {} | grep .debug_str'.format(shlex.quote(self.binary))): + self.log.warning( + "perf output won't be very useful without debug symbols compiled into bitcoind") + + output_path = tempfile.NamedTemporaryFile( + dir=self.datadir, + prefix="{}.perf.data.".format(profile_name or 'test'), + delete=False, + ).name + + cmd = [ + 'perf', 'record', + '-g', # Record the callgraph. + '--call-graph', 'dwarf', # Compatibility for gcc's --fomit-frame-pointer. + '-F', '101', # Sampling frequency in Hz. + '-p', str(self.process.pid), + '-o', output_path, + ] + subp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + self.perf_subprocesses[profile_name] = subp + + return subp + + def _stop_perf(self, profile_name): + """Stop (and pop) a perf subprocess.""" + subp = self.perf_subprocesses.pop(profile_name) + output_path = subp.args[subp.args.index('-o') + 1] + + subp.terminate() + subp.wait(timeout=10) + + stderr = subp.stderr.read().decode() + if 'Consider tweaking /proc/sys/kernel/perf_event_paranoid' in stderr: + self.log.warning( + "perf couldn't collect data! Try " + "'sudo sysctl -w kernel.perf_event_paranoid=-1'") + else: + report_cmd = "perf report -i {}".format(output_path) + self.log.info("See perf output by running '{}'".format(report_cmd)) + 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. @@ -402,6 +504,14 @@ class TestNodeCLIAttr: def get_request(self, *args, **kwargs): return lambda: self(*args, **kwargs) +def arg_to_cli(arg): + if isinstance(arg, bool): + return str(arg).lower() + elif isinstance(arg, dict) or isinstance(arg, list): + return json.dumps(arg) + else: + return str(arg) + class TestNodeCLI(): """Interface to bitcoin-cli for an individual node""" @@ -433,8 +543,8 @@ class TestNodeCLI(): def send_cli(self, command=None, *args, **kwargs): """Run bitcoin-cli command. Deserializes returned string as python object.""" - pos_args = [str(arg).lower() if type(arg) is bool else str(arg) for arg in args] - named_args = [str(key) + "=" + str(value) for (key, value) in kwargs.items()] + pos_args = [arg_to_cli(arg) for arg in args] + named_args = [str(key) + "=" + arg_to_cli(value) for (key, value) in kwargs.items()] assert not (pos_args and named_args), "Cannot use positional arguments and named arguments in the same bitcoin-cli call" p_args = [self.binary, "-datadir=" + self.datadir] + self.options if named_args: diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index d0a78d8dfd..fef9982412 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -410,12 +410,12 @@ def sync_mempools(rpc_connections, *, wait=1, timeout=60, flush_scheduler=True): # Transaction/Block functions ############################# -def find_output(node, txid, amount): +def find_output(node, txid, amount, *, blockhash=None): """ Return index to output of txid with value amount Raises exception if there is none. """ - txdata = node.getrawtransaction(txid, 1) + txdata = node.getrawtransaction(txid, 1, blockhash) for i in range(len(txdata["vout"])): if txdata["vout"][i]["value"] == amount: return i diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 057fd175b7..bb9ac58e46 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -7,8 +7,6 @@ This module calls down into individual test cases via subprocess. It will forward all unrecognized arguments onto the individual test scripts. -Functional tests are disabled on Windows by default. Use --force to run them anyway. - For a description of arguments recognized by test scripts, see `test/functional/test_framework/test_framework.py:BitcoinTestFramework.main`. @@ -119,8 +117,8 @@ BASE_SCRIPTS = [ 'mempool_persist.py', 'wallet_multiwallet.py', 'wallet_multiwallet.py --usecli', - 'wallet_disableprivatekeys.py', - 'wallet_disableprivatekeys.py --usecli', + 'wallet_createwallet.py', + 'wallet_createwallet.py --usecli', 'interface_http.py', 'interface_rpc.py', 'rpc_psbt.py', @@ -183,6 +181,8 @@ BASE_SCRIPTS = [ 'feature_filelock.py', 'p2p_unrequested_blocks.py', 'feature_includeconf.py', + 'rpc_deriveaddresses.py', + 'rpc_deriveaddresses.py --usecli', 'rpc_scantxoutset.py', 'feature_logging.py', 'p2p_node_network_limited.py', @@ -225,7 +225,6 @@ def main(): 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.') @@ -252,22 +251,12 @@ def main(): # Create base test directory tmpdir = "%s/test_runner_₿_🏃_%s" % (args.tmpdirprefix, datetime.datetime.now().strftime("%Y%m%d_%H%M%S")) - # If we fixed the command-line and filename encoding issue on Windows, these two lines could be removed - if config["environment"]["EXEEXT"] == ".exe": - tmpdir = "%s/test_runner_%s" % (args.tmpdirprefix, datetime.datetime.now().strftime("%Y%m%d_%H%M%S")) - os.makedirs(tmpdir) logging.debug("Temporary test directory at %s" % tmpdir) enable_bitcoind = config["components"].getboolean("ENABLE_BITCOIND") - if config["environment"]["EXEEXT"] == ".exe" and not args.force: - # https://github.com/bitcoin/bitcoin/commit/d52802551752140cf41f0d9a225a43e84404d3e9 - # https://github.com/bitcoin/bitcoin/pull/5677#issuecomment-136646964 - print("Tests currently disabled on Windows by default. Use --force option to enable") - sys.exit(0) - if not enable_bitcoind: print("No functional tests to run.") print("Rerun ./configure with --with-daemon and then make") diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py index 2b684349ce..b0b151d2d6 100755 --- a/test/functional/wallet_abandonconflict.py +++ b/test/functional/wallet_abandonconflict.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2014-2018 The Bitcoin Core developers +# Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the abandontransaction RPC. @@ -13,13 +13,20 @@ from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes, disconnect_nodes, sync_blocks, sync_mempools +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, + connect_nodes, + disconnect_nodes, + sync_blocks, + sync_mempools, +) + class AbandonConflictTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - # TODO: remove -txindex. Currently required for getrawtransaction call. - self.extra_args = [["-minrelaytxfee=0.00001", "-txindex"], []] + self.extra_args = [["-minrelaytxfee=0.00001"], []] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -41,21 +48,21 @@ class AbandonConflictTest(BitcoinTestFramework): sync_blocks(self.nodes) newbalance = self.nodes[0].getbalance() - assert(balance - newbalance < Decimal("0.001")) #no more than fees lost + assert balance - newbalance < Decimal("0.001") #no more than fees lost balance = newbalance # Disconnect nodes so node0's transactions don't get into node1's mempool disconnect_nodes(self.nodes[0], 1) # Identify the 10btc outputs - nA = next(i for i, vout in enumerate(self.nodes[0].getrawtransaction(txA, 1)["vout"]) if vout["value"] == Decimal("10")) - nB = next(i for i, vout in enumerate(self.nodes[0].getrawtransaction(txB, 1)["vout"]) if vout["value"] == Decimal("10")) - nC = next(i for i, vout in enumerate(self.nodes[0].getrawtransaction(txC, 1)["vout"]) if vout["value"] == Decimal("10")) + nA = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txA)["details"] if tx_out["amount"] == Decimal("10")) + nB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txB)["details"] if tx_out["amount"] == Decimal("10")) + nC = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txC)["details"] if tx_out["amount"] == Decimal("10")) - inputs =[] + inputs = [] # spend 10btc outputs from txA and txB - inputs.append({"txid":txA, "vout":nA}) - inputs.append({"txid":txB, "vout":nB}) + inputs.append({"txid": txA, "vout": nA}) + inputs.append({"txid": txB, "vout": nB}) outputs = {} outputs[self.nodes[0].getnewaddress()] = Decimal("14.99998") @@ -64,12 +71,12 @@ class AbandonConflictTest(BitcoinTestFramework): txAB1 = self.nodes[0].sendrawtransaction(signed["hex"]) # Identify the 14.99998btc output - nAB = next(i for i, vout in enumerate(self.nodes[0].getrawtransaction(txAB1, 1)["vout"]) if vout["value"] == Decimal("14.99998")) + nAB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txAB1)["details"] if tx_out["amount"] == Decimal("14.99998")) #Create a child tx spending AB1 and C inputs = [] - inputs.append({"txid":txAB1, "vout":nAB}) - inputs.append({"txid":txC, "vout":nC}) + inputs.append({"txid": txAB1, "vout": nAB}) + inputs.append({"txid": txC, "vout": nC}) outputs = {} outputs[self.nodes[0].getnewaddress()] = Decimal("24.9996") signed2 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs)) @@ -77,8 +84,8 @@ class AbandonConflictTest(BitcoinTestFramework): # Create a child tx spending ABC2 signed3_change = Decimal("24.999") - inputs = [ {"txid":txABC2, "vout":0} ] - outputs = { self.nodes[0].getnewaddress(): signed3_change } + inputs = [{"txid": txABC2, "vout": 0}] + outputs = {self.nodes[0].getnewaddress(): signed3_change} signed3 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs)) # note tx is never directly referenced, only abandoned as a child of the above self.nodes[0].sendrawtransaction(signed3["hex"]) @@ -106,7 +113,7 @@ class AbandonConflictTest(BitcoinTestFramework): unconfbalance = self.nodes[0].getunconfirmedbalance() + self.nodes[0].getbalance() assert_equal(unconfbalance, newbalance) # Also shouldn't show up in listunspent - assert(not txABC2 in [utxo["txid"] for utxo in self.nodes[0].listunspent(0)]) + assert not txABC2 in [utxo["txid"] for utxo in self.nodes[0].listunspent(0)] balance = newbalance # Abandon original transaction and verify inputs are available again @@ -146,8 +153,8 @@ class AbandonConflictTest(BitcoinTestFramework): # Create a double spend of AB1 by spending again from only A's 10 output # Mine double spend from node 1 - inputs =[] - inputs.append({"txid":txA, "vout":nA}) + inputs = [] + inputs.append({"txid": txA, "vout": nA}) outputs = {} outputs[self.nodes[1].getnewaddress()] = Decimal("9.9999") tx = self.nodes[0].createrawtransaction(inputs, outputs) @@ -173,5 +180,6 @@ class AbandonConflictTest(BitcoinTestFramework): self.log.info("conflicted has not resumed causing its inputs to be seen as spent. See Issue #7315") self.log.info(str(balance) + " -> " + str(newbalance) + " ?") + if __name__ == '__main__': AbandonConflictTest().main() diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py index bafa556aad..380dd9c0ad 100755 --- a/test/functional/wallet_address_types.py +++ b/test/functional/wallet_address_types.py @@ -54,6 +54,10 @@ from decimal import Decimal import itertools from test_framework.test_framework import BitcoinTestFramework +from test_framework.descriptors import ( + descsum_create, + descsum_check, +) from test_framework.util import ( assert_equal, assert_greater_than, @@ -167,24 +171,31 @@ class AddressTypeTest(BitcoinTestFramework): assert_equal(deriv['path'][0], 'm') key_descs[deriv['pubkey']] = '[' + deriv['master_fingerprint'] + deriv['path'][1:] + ']' + deriv['pubkey'] + # Verify the descriptor checksum against the Python implementation + assert(descsum_check(info['desc'])) + # Verify that stripping the checksum and recreating it using Python roundtrips + assert(info['desc'] == descsum_create(info['desc'][:-9])) + # Verify that stripping the checksum and feeding it to getdescriptorinfo roundtrips + assert(info['desc'] == self.nodes[0].getdescriptorinfo(info['desc'][:-9])['descriptor']) + if not multisig and typ == 'legacy': # P2PKH - assert_equal(info['desc'], "pkh(%s)" % key_descs[info['pubkey']]) + assert_equal(info['desc'], descsum_create("pkh(%s)" % key_descs[info['pubkey']])) elif not multisig and typ == 'p2sh-segwit': # P2SH-P2WPKH - assert_equal(info['desc'], "sh(wpkh(%s))" % key_descs[info['pubkey']]) + assert_equal(info['desc'], descsum_create("sh(wpkh(%s))" % key_descs[info['pubkey']])) elif not multisig and typ == 'bech32': # P2WPKH - assert_equal(info['desc'], "wpkh(%s)" % key_descs[info['pubkey']]) + assert_equal(info['desc'], descsum_create("wpkh(%s)" % key_descs[info['pubkey']])) elif typ == 'legacy': # P2SH-multisig - assert_equal(info['desc'], "sh(multi(2,%s,%s))" % (key_descs[info['pubkeys'][0]], key_descs[info['pubkeys'][1]])) + assert_equal(info['desc'], descsum_create("sh(multi(2,%s,%s))" % (key_descs[info['pubkeys'][0]], key_descs[info['pubkeys'][1]]))) elif typ == 'p2sh-segwit': # P2SH-P2WSH-multisig - assert_equal(info['desc'], "sh(wsh(multi(2,%s,%s)))" % (key_descs[info['embedded']['pubkeys'][0]], key_descs[info['embedded']['pubkeys'][1]])) + assert_equal(info['desc'], descsum_create("sh(wsh(multi(2,%s,%s)))" % (key_descs[info['embedded']['pubkeys'][0]], key_descs[info['embedded']['pubkeys'][1]]))) elif typ == 'bech32': # P2WSH-multisig - assert_equal(info['desc'], "wsh(multi(2,%s,%s))" % (key_descs[info['pubkeys'][0]], key_descs[info['pubkeys'][1]])) + assert_equal(info['desc'], descsum_create("wsh(multi(2,%s,%s))" % (key_descs[info['pubkeys'][0]], key_descs[info['pubkeys'][1]]))) else: # Unknown type assert(False) diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 10b51ecdac..0477164b6b 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2014-2018 The Bitcoin Core developers +# Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet.""" @@ -18,12 +18,11 @@ from test_framework.util import ( wait_until, ) + class WalletTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = True - # TODO: remove -txindex. Currently required for getrawtransaction call. - self.extra_args = [[], [], ["-txindex"], []] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -190,7 +189,7 @@ class WalletTest(BitcoinTestFramework): txid = self.nodes[2].sendtoaddress(address, 10, "", "", False) self.nodes[2].generate(1) self.sync_all([self.nodes[0:3]]) - node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), Decimal('84'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid))) + node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), Decimal('84'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])) assert_equal(self.nodes[0].getbalance(), Decimal('10')) # Send 10 BTC with subtract fee from amount @@ -199,14 +198,14 @@ class WalletTest(BitcoinTestFramework): self.sync_all([self.nodes[0:3]]) node_2_bal -= Decimal('10') assert_equal(self.nodes[2].getbalance(), node_2_bal) - node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), Decimal('20'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid))) + node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), Decimal('20'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])) # Sendmany 10 BTC txid = self.nodes[2].sendmany('', {address: 10}, 0, "", []) self.nodes[2].generate(1) self.sync_all([self.nodes[0:3]]) node_0_bal += Decimal('10') - node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid))) + node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])) assert_equal(self.nodes[0].getbalance(), node_0_bal) # Sendmany 10 BTC with subtract fee from amount @@ -215,7 +214,7 @@ class WalletTest(BitcoinTestFramework): self.sync_all([self.nodes[0:3]]) node_2_bal -= Decimal('10') assert_equal(self.nodes[2].getbalance(), node_2_bal) - node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid))) + node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])) # Test ResendWalletTransactions: # Create a couple of transactions, then start up a fourth @@ -233,7 +232,7 @@ class WalletTest(BitcoinTestFramework): assert_equal(set(relayed), {txid1, txid2}) sync_mempools(self.nodes) - assert(txid1 in self.nodes[3].getrawmempool()) + assert txid1 in self.nodes[3].getrawmempool() # check if we can list zero value tx as available coins # 1. create raw_tx @@ -260,7 +259,7 @@ class WalletTest(BitcoinTestFramework): if uTx['txid'] == zero_value_txid: found = True assert_equal(uTx['amount'], Decimal('0')) - assert(found) + assert found # do some -walletbroadcast tests self.stop_nodes() @@ -320,12 +319,38 @@ class WalletTest(BitcoinTestFramework): tx_obj = self.nodes[0].gettransaction(txid) assert_equal(tx_obj['amount'], Decimal('-0.0001')) + # General checks for errors from incorrect inputs # This will raise an exception because the amount type is wrong assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4") # This will raise an exception since generate does not accept a string assert_raises_rpc_error(-1, "not an integer", self.nodes[0].generate, "2") + # This will raise an exception for the invalid private key format + assert_raises_rpc_error(-5, "Invalid private key encoding", self.nodes[0].importprivkey, "invalid") + + # This will raise an exception for importing an address with the PS2H flag + temp_address = self.nodes[1].getnewaddress() + assert_raises_rpc_error(-5, "Cannot use the p2sh flag with an address - use a script instead", self.nodes[0].importaddress, temp_address, "label", False, True) + + # This will raise an exception for attempting to dump the private key of an address you do not own + assert_raises_rpc_error(-3, "Address does not refer to a key", self.nodes[0].dumpprivkey, temp_address) + + # This will raise an exception for attempting to get the private key of an invalid Bitcoin address + assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].dumpprivkey, "invalid") + + # This will raise an exception for attempting to set a label for an invalid Bitcoin address + assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].setlabel, "invalid address", "label") + + # This will raise an exception for importing an invalid address + assert_raises_rpc_error(-5, "Invalid Bitcoin address or script", self.nodes[0].importaddress, "invalid") + + # This will raise an exception for attempting to import a pubkey that isn't in hex + assert_raises_rpc_error(-5, "Pubkey must be a hex string", self.nodes[0].importpubkey, "not hex") + + # This will raise an exception for importing an invalid pubkey + assert_raises_rpc_error(-5, "Pubkey is not a valid public key", self.nodes[0].importpubkey, "5361746f736869204e616b616d6f746f") + # Import address and private key to check correct behavior of spendable unspents # 1. Send some coins to generate new UTXO address_to_import = self.nodes[2].getnewaddress() @@ -337,7 +362,7 @@ class WalletTest(BitcoinTestFramework): self.nodes[1].importaddress(address_to_import) # 3. Validate that the imported address is watch-only on node1 - assert(self.nodes[1].getaddressinfo(address_to_import)["iswatchonly"]) + assert self.nodes[1].getaddressinfo(address_to_import)["iswatchonly"] # 4. Check that the unspents after import are not spendable assert_array_result(self.nodes[1].listunspent(), @@ -379,7 +404,7 @@ class WalletTest(BitcoinTestFramework): addr = self.nodes[0].getnewaddress() self.nodes[0].setlabel(addr, label) assert_equal(self.nodes[0].getaddressinfo(addr)['label'], label) - assert(label in self.nodes[0].listlabels()) + assert label in self.nodes[0].listlabels() self.nodes[0].rpc.ensure_ascii = True # restore to default # maintenance tests @@ -438,8 +463,8 @@ class WalletTest(BitcoinTestFramework): # Without walletrejectlongchains, we will still generate a txid # The tx will be stored in the wallet but not accepted to the mempool extra_txid = self.nodes[0].sendtoaddress(sending_addr, Decimal('0.0001')) - assert(extra_txid not in self.nodes[0].getrawmempool()) - assert(extra_txid in [tx["txid"] for tx in self.nodes[0].listtransactions()]) + assert extra_txid not in self.nodes[0].getrawmempool() + assert extra_txid in [tx["txid"] for tx in self.nodes[0].listtransactions()] self.nodes[0].abandontransaction(extra_txid) total_txs = len(self.nodes[0].listtransactions("*", 99999)) @@ -476,7 +501,7 @@ class WalletTest(BitcoinTestFramework): 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)) + tx = self.nodes[0].decoderawtransaction(self.nodes[0].gettransaction(txid)['hex']) output_addresses = [vout['scriptPubKey']['addresses'][0] for vout in tx["vout"]] assert len(output_addresses) > 1 for address in output_addresses: @@ -487,5 +512,6 @@ class WalletTest(BitcoinTestFramework): 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_create_tx.py b/test/functional/wallet_create_tx.py index 27dc0fb279..7b749235e2 100755 --- a/test/functional/wallet_create_tx.py +++ b/test/functional/wallet_create_tx.py @@ -7,17 +7,25 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, ) +from test_framework.blocktools import ( + TIME_GENESIS_BLOCK, +) class CreateTxWalletTest(BitcoinTestFramework): def set_test_params(self): - self.setup_clean_chain = False + self.setup_clean_chain = True self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): + self.log.info('Create some old blocks') + self.nodes[0].setmocktime(TIME_GENESIS_BLOCK) + self.nodes[0].generate(200) + self.nodes[0].setmocktime(0) + self.log.info('Check that we have some (old) blocks and that anti-fee-sniping is disabled') assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 200) txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py new file mode 100755 index 0000000000..7ec72b8649 --- /dev/null +++ b/test/functional/wallet_createwallet.py @@ -0,0 +1,100 @@ +#!/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 createwallet arguments. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, +) + +class CreateWalletTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 1 + self.supports_cli = True + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + node = self.nodes[0] + node.generate(1) # Leave IBD for sethdseed + + self.nodes[0].createwallet(wallet_name='w0') + w0 = node.get_wallet_rpc('w0') + address1 = w0.getnewaddress() + + self.log.info("Test disableprivatekeys creation.") + self.nodes[0].createwallet(wallet_name='w1', disable_private_keys=True) + w1 = node.get_wallet_rpc('w1') + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w1.getnewaddress) + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w1.getrawchangeaddress) + w1.importpubkey(w0.getaddressinfo(address1)['pubkey']) + + self.log.info('Test that private keys cannot be imported') + addr = w0.getnewaddress('', 'legacy') + privkey = w0.dumpprivkey(addr) + assert_raises_rpc_error(-4, 'Cannot import private keys to a wallet with private keys disabled', w1.importprivkey, privkey) + result = w1.importmulti([{'scriptPubKey': {'address': addr}, 'timestamp': 'now', 'keys': [privkey]}]) + assert(not result[0]['success']) + assert('warning' not in result[0]) + assert_equal(result[0]['error']['code'], -4) + assert_equal(result[0]['error']['message'], 'Cannot import private keys to a wallet with private keys disabled') + + self.log.info("Test blank creation with private keys disabled.") + self.nodes[0].createwallet(wallet_name='w2', disable_private_keys=True, blank=True) + w2 = node.get_wallet_rpc('w2') + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w2.getnewaddress) + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w2.getrawchangeaddress) + w2.importpubkey(w0.getaddressinfo(address1)['pubkey']) + + self.log.info("Test blank creation with private keys enabled.") + self.nodes[0].createwallet(wallet_name='w3', disable_private_keys=False, blank=True) + w3 = node.get_wallet_rpc('w3') + assert_equal(w3.getwalletinfo()['keypoolsize'], 0) + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress) + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getrawchangeaddress) + # Import private key + w3.importprivkey(w0.dumpprivkey(address1)) + # Imported private keys are currently ignored by the keypool + assert_equal(w3.getwalletinfo()['keypoolsize'], 0) + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress) + # Set the seed + w3.sethdseed() + assert_equal(w3.getwalletinfo()['keypoolsize'], 1) + w3.getnewaddress() + w3.getrawchangeaddress() + + self.log.info("Test blank creation with privkeys enabled and then encryption") + self.nodes[0].createwallet(wallet_name='w4', disable_private_keys=False, blank=True) + w4 = node.get_wallet_rpc('w4') + assert_equal(w4.getwalletinfo()['keypoolsize'], 0) + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getnewaddress) + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getrawchangeaddress) + # Encrypt the wallet. Nothing should change about the keypool + w4.encryptwallet('pass') + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getnewaddress) + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getrawchangeaddress) + # Now set a seed and it should work. Wallet should also be encrypted + w4.walletpassphrase('pass', 2) + w4.sethdseed() + w4.getnewaddress() + w4.getrawchangeaddress() + + self.log.info("Test blank creation with privkeys disabled and then encryption") + self.nodes[0].createwallet(wallet_name='w5', disable_private_keys=True, blank=True) + w5 = node.get_wallet_rpc('w5') + assert_equal(w5.getwalletinfo()['keypoolsize'], 0) + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getnewaddress) + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getrawchangeaddress) + # Encrypt the wallet + w5.encryptwallet('pass') + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getnewaddress) + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getrawchangeaddress) + +if __name__ == '__main__': + CreateWalletTest().main() diff --git a/test/functional/wallet_disableprivatekeys.py b/test/functional/wallet_disableprivatekeys.py deleted file mode 100755 index 34ff525255..0000000000 --- a/test/functional/wallet_disableprivatekeys.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/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 disable-privatekeys mode. -""" - -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import ( - assert_raises_rpc_error, -) - - -class DisablePrivateKeysTest(BitcoinTestFramework): - def set_test_params(self): - self.setup_clean_chain = False - self.num_nodes = 1 - self.supports_cli = True - - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - - def run_test(self): - node = self.nodes[0] - self.log.info("Test disableprivatekeys creation.") - self.nodes[0].createwallet('w1', True) - self.nodes[0].createwallet('w2') - w1 = node.get_wallet_rpc('w1') - w2 = node.get_wallet_rpc('w2') - assert_raises_rpc_error(-4,"Error: Private keys are disabled for this wallet", w1.getnewaddress) - assert_raises_rpc_error(-4,"Error: Private keys are disabled for this wallet", w1.getrawchangeaddress) - w1.importpubkey(w2.getaddressinfo(w2.getnewaddress())['pubkey']) - -if __name__ == '__main__': - DisablePrivateKeysTest().main() diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index eb42531693..61245e5104 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -27,7 +27,6 @@ class WalletHDTest(BitcoinTestFramework): def run_test(self): # Make sure we use hd, keep masterkeyid masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] - assert_equal(masterkeyid, self.nodes[1].getwalletinfo()['hdmasterkeyid']) assert_equal(len(masterkeyid), 40) # create an internal key @@ -53,7 +52,6 @@ class WalletHDTest(BitcoinTestFramework): hd_info = self.nodes[1].getaddressinfo(hd_add) assert_equal(hd_info["hdkeypath"], "m/0'/0'/"+str(i)+"'") assert_equal(hd_info["hdseedid"], masterkeyid) - assert_equal(hd_info["hdmasterkeyid"], masterkeyid) self.nodes[0].sendtoaddress(hd_add, 1) self.nodes[0].generate(1) self.nodes[0].sendtoaddress(non_hd_add, 1) @@ -83,7 +81,6 @@ class WalletHDTest(BitcoinTestFramework): hd_info_2 = self.nodes[1].getaddressinfo(hd_add_2) assert_equal(hd_info_2["hdkeypath"], "m/0'/0'/"+str(i)+"'") assert_equal(hd_info_2["hdseedid"], masterkeyid) - assert_equal(hd_info_2["hdmasterkeyid"], masterkeyid) assert_equal(hd_add, hd_add_2) connect_nodes_bi(self.nodes, 0, 1) self.sync_all() diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py index f122f19e3a..c6d0425f22 100755 --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -20,6 +20,7 @@ from test_framework.script import ( OP_NOP, ) from test_framework.test_framework import BitcoinTestFramework +from test_framework.descriptors import descsum_create from test_framework.util import ( assert_equal, assert_greater_than, @@ -203,7 +204,7 @@ class ImportMultiTest(BitcoinTestFramework): "keys": [key.privkey]}, success=False, error_code=-4, - error_message='The wallet already contains the private key for this address or script') + error_message='The wallet already contains the private key for this address or script ("' + key.p2pkh_script + '")') # Address + Private key + watchonly self.log.info("Should import an address with private key and with watchonly") @@ -543,5 +544,275 @@ class ImportMultiTest(BitcoinTestFramework): solvable=True, ismine=False) + # Test importing of a P2SH-P2WPKH address via descriptor + private key + key = get_key(self.nodes[0]) + self.log.info("Should not import a p2sh-p2wpkh address from descriptor without checksum and private key") + self.test_importmulti({"desc": "sh(wpkh(" + key.pubkey + "))", + "timestamp": "now", + "label": "Descriptor import test", + "keys": [key.privkey]}, + success=False, + error_code=-5, + error_message="Descriptor is invalid") + + # Test importing of a P2SH-P2WPKH address via descriptor + private key + key = get_key(self.nodes[0]) + self.log.info("Should import a p2sh-p2wpkh address from descriptor and private key") + self.test_importmulti({"desc": descsum_create("sh(wpkh(" + key.pubkey + "))"), + "timestamp": "now", + "label": "Descriptor import test", + "keys": [key.privkey]}, + success=True) + test_address(self.nodes[1], + key.p2sh_p2wpkh_addr, + solvable=True, + ismine=True, + label="Descriptor import test") + + # Test ranged descriptor fails if range is not specified + xpriv = "tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg" + addresses = ["2N7yv4p8G8yEaPddJxY41kPihnWvs39qCMf", "2MsHxyb2JS3pAySeNUsJ7mNnurtpeenDzLA"] # hdkeypath=m/0'/0'/0' and 1' + desc = "sh(wpkh(" + xpriv + "/0'/0'/*'" + "))" + self.log.info("Ranged descriptor import should fail without a specified range") + self.test_importmulti({"desc": descsum_create(desc), + "timestamp": "now"}, + success=False, + error_code=-8, + error_message='Descriptor is ranged, please specify the range') + + # Test importing of a ranged descriptor without keys + self.log.info("Should import the ranged descriptor with specified range as solvable") + self.test_importmulti({"desc": descsum_create(desc), + "timestamp": "now", + "range": 1}, + success=True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + for address in addresses: + test_address(self.nodes[1], + key.p2sh_p2wpkh_addr, + solvable=True) + + # Test importing of a P2PKH address via descriptor + key = get_key(self.nodes[0]) + self.log.info("Should import a p2pkh address from descriptor") + self.test_importmulti({"desc": descsum_create("pkh(" + key.pubkey + ")"), + "timestamp": "now", + "label": "Descriptor import test"}, + True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + test_address(self.nodes[1], + key.p2pkh_addr, + solvable=True, + ismine=False, + label="Descriptor import test") + + # Test import fails if both desc and scriptPubKey are provided + key = get_key(self.nodes[0]) + self.log.info("Import should fail if both scriptPubKey and desc are provided") + self.test_importmulti({"desc": descsum_create("pkh(" + key.pubkey + ")"), + "scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now"}, + success=False, + error_code=-8, + error_message='Both a descriptor and a scriptPubKey should not be provided.') + + # Test import fails if neither desc nor scriptPubKey are present + key = get_key(self.nodes[0]) + self.log.info("Import should fail if neither a descriptor nor a scriptPubKey are provided") + self.test_importmulti({"timestamp": "now"}, + success=False, + error_code=-8, + error_message='Either a descriptor or scriptPubKey must be provided.') + + # Test importing of a multisig via descriptor + key1 = get_key(self.nodes[0]) + key2 = get_key(self.nodes[0]) + self.log.info("Should import a 1-of-2 bare multisig from descriptor") + self.test_importmulti({"desc": descsum_create("multi(1," + key1.pubkey + "," + key2.pubkey + ")"), + "timestamp": "now"}, + success=True) + self.log.info("Should not treat individual keys from the imported bare multisig as watchonly") + test_address(self.nodes[1], + key1.p2pkh_addr, + ismine=False, + iswatchonly=False) + + # Import pubkeys with key origin info + self.log.info("Addresses should have hd keypath and master key id after import with key origin") + pub_addr = self.nodes[1].getnewaddress() + pub_addr = self.nodes[1].getnewaddress() + info = self.nodes[1].getaddressinfo(pub_addr) + pub = info['pubkey'] + pub_keypath = info['hdkeypath'] + pub_fpr = info['hdmasterfingerprint'] + result = self.nodes[0].importmulti( + [{ + 'desc' : descsum_create("wpkh([" + pub_fpr + pub_keypath[1:] +"]" + pub + ")"), + "timestamp": "now", + }] + ) + assert result[0]['success'] + pub_import_info = self.nodes[0].getaddressinfo(pub_addr) + assert_equal(pub_import_info['hdmasterfingerprint'], pub_fpr) + assert_equal(pub_import_info['pubkey'], pub) + assert_equal(pub_import_info['hdkeypath'], pub_keypath) + + # Import privkeys with key origin info + priv_addr = self.nodes[1].getnewaddress() + info = self.nodes[1].getaddressinfo(priv_addr) + priv = self.nodes[1].dumpprivkey(priv_addr) + priv_keypath = info['hdkeypath'] + priv_fpr = info['hdmasterfingerprint'] + result = self.nodes[0].importmulti( + [{ + 'desc' : descsum_create("wpkh([" + priv_fpr + priv_keypath[1:] + "]" + priv + ")"), + "timestamp": "now", + }] + ) + assert result[0]['success'] + priv_import_info = self.nodes[0].getaddressinfo(priv_addr) + assert_equal(priv_import_info['hdmasterfingerprint'], priv_fpr) + assert_equal(priv_import_info['hdkeypath'], priv_keypath) + + # Make sure the key origin info are still there after a restart + self.stop_nodes() + self.start_nodes() + import_info = self.nodes[0].getaddressinfo(pub_addr) + assert_equal(import_info['hdmasterfingerprint'], pub_fpr) + assert_equal(import_info['hdkeypath'], pub_keypath) + import_info = self.nodes[0].getaddressinfo(priv_addr) + assert_equal(import_info['hdmasterfingerprint'], priv_fpr) + assert_equal(import_info['hdkeypath'], priv_keypath) + + # Check legacy import does not import key origin info + self.log.info("Legacy imports don't have key origin info") + pub_addr = self.nodes[1].getnewaddress() + info = self.nodes[1].getaddressinfo(pub_addr) + pub = info['pubkey'] + result = self.nodes[0].importmulti( + [{ + 'scriptPubKey': {'address': pub_addr}, + 'pubkeys': [pub], + "timestamp": "now", + }] + ) + assert result[0]['success'] + pub_import_info = self.nodes[0].getaddressinfo(pub_addr) + assert_equal(pub_import_info['pubkey'], pub) + assert 'hdmasterfingerprint' not in pub_import_info + assert 'hdkeypath' not in pub_import_info + + # Import some public keys to the keypool of a no privkey wallet + self.log.info("Adding pubkey to keypool of disableprivkey wallet") + self.nodes[1].createwallet(wallet_name="noprivkeys", disable_private_keys=True) + wrpc = self.nodes[1].get_wallet_rpc("noprivkeys") + + addr1 = self.nodes[0].getnewaddress() + addr2 = self.nodes[0].getnewaddress() + pub1 = self.nodes[0].getaddressinfo(addr1)['pubkey'] + pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey'] + result = wrpc.importmulti( + [{ + 'desc': descsum_create('wpkh(' + pub1 + ')'), + 'keypool': True, + "timestamp": "now", + }, + { + 'desc': descsum_create('wpkh(' + pub2 + ')'), + 'keypool': True, + "timestamp": "now", + }] + ) + assert result[0]['success'] + assert result[1]['success'] + assert_equal(wrpc.getwalletinfo()["keypoolsize"], 2) + newaddr1 = wrpc.getnewaddress() + assert_equal(addr1, newaddr1) + newaddr2 = wrpc.getnewaddress() + assert_equal(addr2, newaddr2) + + # Import some public keys to the internal keypool of a no privkey wallet + self.log.info("Adding pubkey to internal keypool of disableprivkey wallet") + addr1 = self.nodes[0].getnewaddress() + addr2 = self.nodes[0].getnewaddress() + pub1 = self.nodes[0].getaddressinfo(addr1)['pubkey'] + pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey'] + result = wrpc.importmulti( + [{ + 'desc': descsum_create('wpkh(' + pub1 + ')'), + 'keypool': True, + 'internal': True, + "timestamp": "now", + }, + { + 'desc': descsum_create('wpkh(' + pub2 + ')'), + 'keypool': True, + 'internal': True, + "timestamp": "now", + }] + ) + assert result[0]['success'] + assert result[1]['success'] + assert_equal(wrpc.getwalletinfo()["keypoolsize_hd_internal"], 2) + newaddr1 = wrpc.getrawchangeaddress() + assert_equal(addr1, newaddr1) + newaddr2 = wrpc.getrawchangeaddress() + assert_equal(addr2, newaddr2) + + # Import a multisig and make sure the keys don't go into the keypool + self.log.info('Imported scripts with pubkeys shoud not have their pubkeys go into the keypool') + addr1 = self.nodes[0].getnewaddress() + addr2 = self.nodes[0].getnewaddress() + pub1 = self.nodes[0].getaddressinfo(addr1)['pubkey'] + pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey'] + result = wrpc.importmulti( + [{ + 'desc': descsum_create('wsh(multi(2,' + pub1 + ',' + pub2 + '))'), + 'keypool': True, + "timestamp": "now", + }] + ) + assert result[0]['success'] + assert_equal(wrpc.getwalletinfo()["keypoolsize"], 0) + + # Cannot import those pubkeys to keypool of wallet with privkeys + self.log.info("Pubkeys cannot be added to the keypool of a wallet with private keys") + wrpc = self.nodes[1].get_wallet_rpc("") + assert wrpc.getwalletinfo()['private_keys_enabled'] + result = wrpc.importmulti( + [{ + 'desc': descsum_create('wpkh(' + pub1 + ')'), + 'keypool': True, + "timestamp": "now", + }] + ) + assert_equal(result[0]['error']['code'], -8) + assert_equal(result[0]['error']['message'], "Keys can only be imported to the keypool when private keys are disabled") + + # Make sure ranged imports import keys in order + self.log.info('Key ranges should be imported in order') + wrpc = self.nodes[1].get_wallet_rpc("noprivkeys") + assert_equal(wrpc.getwalletinfo()["keypoolsize"], 0) + assert_equal(wrpc.getwalletinfo()["private_keys_enabled"], False) + xpub = "tpubDAXcJ7s7ZwicqjprRaEWdPoHKrCS215qxGYxpusRLLmJuT69ZSicuGdSfyvyKpvUNYBW1s2U3NSrT6vrCYB9e6nZUEvrqnwXPF8ArTCRXMY" + addresses = [ + 'bcrt1qtmp74ayg7p24uslctssvjm06q5phz4yrxucgnv', # m/0'/0'/0 + 'bcrt1q8vprchan07gzagd5e6v9wd7azyucksq2xc76k8', # m/0'/0'/1 + 'bcrt1qtuqdtha7zmqgcrr26n2rqxztv5y8rafjp9lulu', # m/0'/0'/2 + 'bcrt1qau64272ymawq26t90md6an0ps99qkrse58m640', # m/0'/0'/3 + 'bcrt1qsg97266hrh6cpmutqen8s4s962aryy77jp0fg0', # m/0'/0'/4 + ] + result = wrpc.importmulti( + [{ + 'desc': descsum_create('wpkh([80002067/0h/0h]' + xpub + '/*)'), + 'keypool': True, + 'timestamp': 'now', + 'range' : [0, 4], + }] + ) + for i in range(0, 5): + addr = wrpc.getnewaddress('', 'bech32') + assert_equal(addr, addresses[i]) + if __name__ == '__main__': ImportMultiTest().main() diff --git a/test/functional/wallet_keypool.py b/test/functional/wallet_keypool.py index ceb9709712..1116196268 100755 --- a/test/functional/wallet_keypool.py +++ b/test/functional/wallet_keypool.py @@ -21,7 +21,6 @@ class KeyPoolTest(BitcoinTestFramework): addr_before_encrypting = nodes[0].getnewaddress() addr_before_encrypting_data = nodes[0].getaddressinfo(addr_before_encrypting) wallet_info_old = nodes[0].getwalletinfo() - assert_equal(wallet_info_old['hdseedid'], wallet_info_old['hdmasterkeyid']) assert(addr_before_encrypting_data['hdseedid'] == wallet_info_old['hdseedid']) # Encrypt wallet and wait to terminate @@ -30,7 +29,6 @@ class KeyPoolTest(BitcoinTestFramework): addr = nodes[0].getnewaddress() addr_data = nodes[0].getaddressinfo(addr) wallet_info = nodes[0].getwalletinfo() - assert_equal(wallet_info['hdseedid'], wallet_info['hdmasterkeyid']) assert(addr_before_encrypting_data['hdseedid'] != wallet_info['hdseedid']) assert(addr_data['hdseedid'] == wallet_info['hdseedid']) assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress) diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 8ab569a3c3..df778f57df 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -315,6 +315,14 @@ class MultiWalletTest(BitcoinTestFramework): self.nodes[0].loadwallet(wallet_name) assert_equal(rpc.getaddressinfo(addr)['ismine'], True) + # Test .walletlock file is closed + self.start_node(1) + wallet = os.path.join(self.options.tmpdir, 'my_wallet') + self.nodes[0].createwallet(wallet) + assert_raises_rpc_error(-4, "Error initializing wallet database environment", self.nodes[1].loadwallet, wallet) + self.nodes[0].unloadwallet(wallet) + self.nodes[1].loadwallet(wallet) + if __name__ == '__main__': MultiWalletTest().main() diff --git a/test/functional/wallet_txn_clone.py b/test/functional/wallet_txn_clone.py index 1c2e0a9cb7..a0a35bf43d 100755 --- a/test/functional/wallet_txn_clone.py +++ b/test/functional/wallet_txn_clone.py @@ -4,13 +4,16 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet accounts properly when there are cloned transactions with malleated scriptsigs.""" +import io from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + bytes_to_hex_str as b2x, connect_nodes, disconnect_nodes, sync_blocks, ) +from test_framework.messages import CTransaction, COIN class TxnMallTest(BitcoinTestFramework): def set_test_params(self): @@ -72,19 +75,14 @@ class TxnMallTest(BitcoinTestFramework): clone_raw = self.nodes[0].createrawtransaction(clone_inputs, clone_outputs, clone_locktime) # createrawtransaction randomizes the order of its outputs, so swap them if necessary. - # output 0 is at version+#inputs+input+sigstub+sequence+#outputs - # 40 BTC serialized is 00286bee00000000 - pos0 = 2 * (4 + 1 + 36 + 1 + 4 + 1) - hex40 = "00286bee00000000" - output_len = 16 + 2 + 2 * int("0x" + clone_raw[pos0 + 16:pos0 + 16 + 2], 0) - if (rawtx1["vout"][0]["value"] == 40 and clone_raw[pos0:pos0 + 16] != hex40 or rawtx1["vout"][0]["value"] != 40 and clone_raw[pos0:pos0 + 16] == hex40): - output0 = clone_raw[pos0:pos0 + output_len] - output1 = clone_raw[pos0 + output_len:pos0 + 2 * output_len] - clone_raw = clone_raw[:pos0] + output1 + output0 + clone_raw[pos0 + 2 * output_len:] + clone_tx = CTransaction() + clone_tx.deserialize(io.BytesIO(bytes.fromhex(clone_raw))) + if (rawtx1["vout"][0]["value"] == 40 and clone_tx.vout[0].nValue != 40*COIN or rawtx1["vout"][0]["value"] != 40 and clone_tx.vout[0].nValue == 40*COIN): + (clone_tx.vout[0], clone_tx.vout[1]) = (clone_tx.vout[1], clone_tx.vout[0]) # Use a different signature hash type to sign. This creates an equivalent but malleated clone. # Don't send the clone anywhere yet - tx1_clone = self.nodes[0].signrawtransactionwithwallet(clone_raw, None, "ALL|ANYONECANPAY") + tx1_clone = self.nodes[0].signrawtransactionwithwallet(b2x(clone_tx.serialize()), None, "ALL|ANYONECANPAY") assert_equal(tx1_clone["complete"], True) # Have node0 mine a block, if requested: diff --git a/test/functional/wallet_txn_doublespend.py b/test/functional/wallet_txn_doublespend.py index f114d5ab68..9050e21247 100755 --- a/test/functional/wallet_txn_doublespend.py +++ b/test/functional/wallet_txn_doublespend.py @@ -34,6 +34,14 @@ class TxnMallTest(BitcoinTestFramework): def run_test(self): # All nodes should start with 1,250 BTC: starting_balance = 1250 + + # All nodes should be out of IBD. + # If the nodes are not all out of IBD, that can interfere with + # blockchain sync later in the test when nodes are connected, due to + # timing issues. + for n in self.nodes: + assert n.getblockchaininfo()["initialblockdownload"] == False + for i in range(4): assert_equal(self.nodes[i].getbalance(), starting_balance) self.nodes[i].getnewaddress("") # bug workaround, coins generated assigned to first getnewaddress! diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py new file mode 100755 index 0000000000..1869f71753 --- /dev/null +++ b/test/fuzz/test_runner.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Run fuzz test targets. +""" + +import argparse +import configparser +import os +import sys +import subprocess +import logging + + +def main(): + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument( + "-l", + "--loglevel", + dest="loglevel", + default="INFO", + help="log events at this level and higher to the console. Can be set to DEBUG, INFO, WARNING, ERROR or CRITICAL. Passing --loglevel DEBUG will output all logs to console.", + ) + parser.add_argument( + '--export_coverage', + action='store_true', + help='If true, export coverage information to files in the seed corpus', + ) + parser.add_argument( + 'seed_dir', + help='The seed corpus to run on (must contain subfolders for each fuzz target).', + ) + parser.add_argument( + 'target', + nargs='*', + help='The target(s) to run. Default is to run all targets.', + ) + + args = parser.parse_args() + + # Set up logging + logging.basicConfig( + format='%(message)s', + level=int(args.loglevel) if args.loglevel.isdigit() else args.loglevel.upper(), + ) + + # Read config generated by configure. + config = configparser.ConfigParser() + configfile = os.path.abspath(os.path.dirname(__file__)) + "/../config.ini" + config.read_file(open(configfile, encoding="utf8")) + + if not config["components"].getboolean("ENABLE_FUZZ"): + logging.error("Must have fuzz targets built") + sys.exit(1) + + # Build list of tests + test_list_all = parse_test_list(makefile=os.path.join(config["environment"]["SRCDIR"], 'src', 'Makefile.test.include')) + + if not test_list_all: + logging.error("No fuzz targets found") + sys.exit(1) + + logging.info("Fuzz targets found: {}".format(test_list_all)) + + args.target = args.target or test_list_all # By default run all + test_list_error = list(set(args.target).difference(set(test_list_all))) + if test_list_error: + logging.error("Unknown fuzz targets selected: {}".format(test_list_error)) + test_list_selection = list(set(test_list_all).intersection(set(args.target))) + if not test_list_selection: + logging.error("No fuzz targets selected") + logging.info("Fuzz targets selected: {}".format(test_list_selection)) + + try: + help_output = subprocess.run( + args=[ + os.path.join(config["environment"]["BUILDDIR"], 'src', 'test', 'fuzz', test_list_selection[0]), + '-help=1', + ], + timeout=1, + check=True, + stderr=subprocess.PIPE, + universal_newlines=True, + ).stderr + if "libFuzzer" not in help_output: + logging.error("Must be built with libFuzzer") + sys.exit(1) + except subprocess.TimeoutExpired: + logging.error("subprocess timed out: Currently only libFuzzer is supported") + sys.exit(1) + + run_once( + corpus=args.seed_dir, + test_list=test_list_selection, + build_dir=config["environment"]["BUILDDIR"], + export_coverage=args.export_coverage, + ) + + +def run_once(*, corpus, test_list, build_dir, export_coverage): + for t in test_list: + args = [ + os.path.join(build_dir, 'src', 'test', 'fuzz', t), + '-runs=1', + os.path.join(corpus, t), + ] + logging.debug('Run {} with args {}'.format(t, args)) + output = subprocess.run(args, check=True, stderr=subprocess.PIPE, universal_newlines=True).stderr + logging.debug('Output: {}'.format(output)) + if not export_coverage: + continue + for l in output.splitlines(): + if 'INITED' in l: + with open(os.path.join(corpus, t + '_coverage'), 'w', encoding='utf-8') as cov_file: + cov_file.write(l) + break + + +def parse_test_list(makefile): + with open(makefile, encoding='utf-8') as makefile_test: + test_list_all = [] + read_targets = False + for line in makefile_test.readlines(): + line = line.strip().replace('test/fuzz/', '').replace(' \\', '') + if read_targets: + if not line: + break + test_list_all.append(line) + continue + + if line == 'FUZZ_TARGETS =': + read_targets = True + return test_list_all + + +if __name__ == '__main__': + main() diff --git a/test/lint/lint-locale-dependence.sh b/test/lint/lint-locale-dependence.sh index 1534d5ef68..2b6c78c2c8 100755 --- a/test/lint/lint-locale-dependence.sh +++ b/test/lint/lint-locale-dependence.sh @@ -8,6 +8,7 @@ KNOWN_VIOLATIONS=( "src/dbwrapper.cpp:.*vsnprintf" "src/httprpc.cpp.*trim" "src/init.cpp:.*atoi" + "src/init.cpp:.*fprintf" "src/qt/rpcconsole.cpp:.*atoi" "src/rest.cpp:.*strtol" "src/test/dbwrapper_tests.cpp:.*snprintf" @@ -18,7 +19,6 @@ KNOWN_VIOLATIONS=( "src/util/strencodings.cpp:.*strtoul" "src/util/strencodings.h:.*atoi" "src/util/system.cpp:.*atoi" - "src/util/system.cpp:.*fprintf" ) 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 index 1b897cd131..863caa9d5c 100755 --- a/test/lint/lint-python-dead-code.sh +++ b/test/lint/lint-python-dead-code.sh @@ -15,5 +15,5 @@ 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" \ + --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,profile_with_perf" \ $(git ls-files -- "*.py" ":(exclude)contrib/" ":(exclude)test/functional/data/invalid_txs.py") diff --git a/test/lint/lint-whitespace.sh b/test/lint/lint-whitespace.sh index f318e19071..d5c1dee42d 100755 --- a/test/lint/lint-whitespace.sh +++ b/test/lint/lint-whitespace.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Copyright (c) 2017 The Bitcoin Core developers +# Copyright (c) 2017-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. # @@ -12,11 +12,11 @@ export LC_ALL=C while getopts "?" opt; do case $opt in ?) - echo "Usage: .lint-whitespace.sh [N]" - echo " TRAVIS_COMMIT_RANGE='<commit range>' .lint-whitespace.sh" - echo " .lint-whitespace.sh -?" + echo "Usage: $0 [N]" + echo " TRAVIS_COMMIT_RANGE='<commit range>' $0" + echo " $0 -?" echo "Checks unstaged changes, the previous N commits, or a commit range." - echo "TRAVIS_COMMIT_RANGE='47ba2c3...ee50c9e' .lint-whitespace.sh" + echo "TRAVIS_COMMIT_RANGE='47ba2c3...ee50c9e' $0" exit 0 ;; esac diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index f0107f1361..d55119b266 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -4,7 +4,6 @@ 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 |