diff options
Diffstat (limited to 'test')
43 files changed, 935 insertions, 465 deletions
diff --git a/test/README.md b/test/README.md index c3e4ae9ad2..e1dab92a06 100644 --- a/test/README.md +++ b/test/README.md @@ -145,7 +145,7 @@ levels using the logger included in the test_framework, e.g. `test_framework.log` and no logs are output to the console. - when run directly, *all* logs are written to `test_framework.log` and INFO level and above are output to the console. -- when run on Travis, no logs are output to the console. However, if a test +- when run by [our CI (Continuous Integration)](/ci/README.md), no logs are output to the console. However, if a test fails, the `test_framework.log` and bitcoind `debug.log`s will all be dumped to the console to help troubleshooting. diff --git a/test/functional/README.md b/test/functional/README.md index 77a9ce9acb..004e0afb1d 100644 --- a/test/functional/README.md +++ b/test/functional/README.md @@ -51,10 +51,13 @@ don't have test cases for. #### General test-writing advice +- Instead of inline comments or no test documentation at all, log the comments to the test log, e.g. + `self.log.info('Create enough transactions to fill a block')`. Logs make the test code easier to read and the test + logic easier [to debug](/test/README.md#test-logging). - Set `self.num_nodes` to the minimum number of nodes necessary for the test. Having additional unrequired nodes adds to the execution time of the test as well as memory/CPU/disk requirements (which is important when running tests in - parallel or on Travis). + parallel). - Avoid stop-starting the nodes multiple times during the test if possible. A stop-start takes several seconds, so doing it several times blows up the runtime of the test. @@ -131,9 +134,6 @@ Utilities for manipulating transaction scripts (originally from python-bitcoinli #### [key.py](test_framework/key.py) Test-only secp256k1 elliptic curve implementation -#### [bignum.py](test_framework/bignum.py) -Helpers for script.py - #### [blocktools.py](test_framework/blocktools.py) Helper functions for creating blocks and transactions. diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py index 99c88bbcc0..ce14998fd1 100644 --- a/test/functional/data/invalid_txs.py +++ b/test/functional/data/invalid_txs.py @@ -21,7 +21,13 @@ Invalid tx cases not covered here can be found by running: """ import abc -from test_framework.messages import CTransaction, CTxIn, CTxOut, COutPoint +from test_framework.messages import ( + COutPoint, + CTransaction, + CTxIn, + CTxOut, + MAX_MONEY, +) from test_framework import script as sc from test_framework.blocktools import create_tx_with_script, MAX_BLOCK_SIGOPS from test_framework.script import ( @@ -166,7 +172,7 @@ class SpendTooMuch(BadTxTemplate): self.spend_tx, 0, script_pub_key=basic_p2sh, amount=(self.spend_avail + 1)) -class SpendNegative(BadTxTemplate): +class CreateNegative(BadTxTemplate): reject_reason = 'bad-txns-vout-negative' expect_disconnect = True @@ -174,6 +180,25 @@ class SpendNegative(BadTxTemplate): return create_tx_with_script(self.spend_tx, 0, amount=-1) +class CreateTooLarge(BadTxTemplate): + reject_reason = 'bad-txns-vout-toolarge' + expect_disconnect = True + + def get_tx(self): + return create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY + 1) + + +class CreateSumTooLarge(BadTxTemplate): + reject_reason = 'bad-txns-txouttotal-toolarge' + expect_disconnect = True + + def get_tx(self): + tx = create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY) + tx.vout = [tx.vout[0]] * 2 + tx.calc_sha256() + return tx + + class InvalidOPIFConstruction(BadTxTemplate): reject_reason = "mandatory-script-verify-flag-failed (Invalid OP_IF construction)" expect_disconnect = True @@ -237,4 +262,3 @@ DisabledOpcodeTemplates = [getDisabledOpcodeTemplate(opcode) for opcode in [ def iter_all_templates(): """Iterate through all bad transaction template types.""" return BadTxTemplate.__subclasses__() - diff --git a/test/functional/feature_abortnode.py b/test/functional/feature_abortnode.py index 9b878e8bf8..e47e709431 100755 --- a/test/functional/feature_abortnode.py +++ b/test/functional/feature_abortnode.py @@ -14,11 +14,12 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import wait_until, get_datadir_path, connect_nodes import os -class AbortNodeTest(BitcoinTestFramework): +class AbortNodeTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 + self.rpc_timeout = 240 def setup_network(self): self.setup_nodes() @@ -44,5 +45,6 @@ class AbortNodeTest(BitcoinTestFramework): self.log.info("Node crashed - now verifying restart fails") self.nodes[0].assert_start_raises_init_error() + if __name__ == '__main__': AbortNodeTest().main() diff --git a/test/functional/feature_asmap.py b/test/functional/feature_asmap.py new file mode 100755 index 0000000000..2c6553fbe2 --- /dev/null +++ b/test/functional/feature_asmap.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test asmap config argument for ASN-based IP bucketing. + +Verify node behaviour and debug log when launching bitcoind in these cases: + +1. `bitcoind` with no -asmap arg, using /16 prefix for IP bucketing + +2. `bitcoind -asmap=<absolute path>`, using the unit test skeleton asmap + +3. `bitcoind -asmap=<relative path>`, using the unit test skeleton asmap + +4. `bitcoind -asmap/-asmap=` with no file specified, using the default asmap + +5. `bitcoind -asmap` with no file specified and a missing default asmap file + +6. `bitcoind -asmap` with an empty (unparsable) default asmap file + +The tests are order-independent. + +""" +import os +import shutil + +from test_framework.test_framework import BitcoinTestFramework + +DEFAULT_ASMAP_FILENAME = 'ip_asn.map' # defined in src/init.cpp +ASMAP = '../../src/test/data/asmap.raw' # path to unit test skeleton asmap +VERSION = 'fec61fa21a9f46f3b17bdcd660d7f4cd90b966aad3aec593c99b35f0aca15853' + +def expected_messages(filename): + return ['Opened asmap file "{}" (59 bytes) from disk'.format(filename), + 'Using asmap version {} for IP bucketing'.format(VERSION)] + +class AsmapTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 1 + + def test_without_asmap_arg(self): + self.log.info('Test bitcoind with no -asmap arg passed') + self.stop_node(0) + with self.node.assert_debug_log(['Using /16 prefix for IP bucketing']): + self.start_node(0) + + def test_asmap_with_absolute_path(self): + self.log.info('Test bitcoind -asmap=<absolute path>') + self.stop_node(0) + filename = os.path.join(self.datadir, 'my-map-file.map') + shutil.copyfile(self.asmap_raw, filename) + with self.node.assert_debug_log(expected_messages(filename)): + self.start_node(0, ['-asmap={}'.format(filename)]) + os.remove(filename) + + def test_asmap_with_relative_path(self): + self.log.info('Test bitcoind -asmap=<relative path>') + self.stop_node(0) + name = 'ASN_map' + filename = os.path.join(self.datadir, name) + shutil.copyfile(self.asmap_raw, filename) + with self.node.assert_debug_log(expected_messages(filename)): + self.start_node(0, ['-asmap={}'.format(name)]) + os.remove(filename) + + def test_default_asmap(self): + shutil.copyfile(self.asmap_raw, self.default_asmap) + for arg in ['-asmap', '-asmap=']: + self.log.info('Test bitcoind {} (using default map file)'.format(arg)) + self.stop_node(0) + with self.node.assert_debug_log(expected_messages(self.default_asmap)): + self.start_node(0, [arg]) + os.remove(self.default_asmap) + + def test_default_asmap_with_missing_file(self): + self.log.info('Test bitcoind -asmap with missing default map file') + self.stop_node(0) + msg = "Error: Could not find asmap file \"{}\"".format(self.default_asmap) + self.node.assert_start_raises_init_error(extra_args=['-asmap'], expected_msg=msg) + + def test_empty_asmap(self): + self.log.info('Test bitcoind -asmap with empty map file') + self.stop_node(0) + with open(self.default_asmap, "w", encoding="utf-8") as f: + f.write("") + msg = "Error: Could not parse asmap file \"{}\"".format(self.default_asmap) + self.node.assert_start_raises_init_error(extra_args=['-asmap'], expected_msg=msg) + os.remove(self.default_asmap) + + def run_test(self): + self.node = self.nodes[0] + self.datadir = os.path.join(self.node.datadir, self.chain) + self.default_asmap = os.path.join(self.datadir, DEFAULT_ASMAP_FILENAME) + self.asmap_raw = os.path.join(os.path.dirname(os.path.realpath(__file__)), ASMAP) + + self.test_without_asmap_arg() + self.test_asmap_with_absolute_path() + self.test_asmap_with_relative_path() + self.test_default_asmap() + self.test_default_asmap_with_missing_file() + self.test_empty_asmap() + + +if __name__ == '__main__': + AsmapTest().main() diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py index 1b434c4485..ef4d9411c5 100755 --- a/test/functional/feature_assumevalid.py +++ b/test/functional/feature_assumevalid.py @@ -47,16 +47,19 @@ from test_framework.script import (CScript, OP_TRUE) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal + class BaseNode(P2PInterface): def send_header_for_blocks(self, new_blocks): headers_message = msg_headers() headers_message.headers = [CBlockHeader(b) for b in new_blocks] self.send_message(headers_message) + class AssumeValidTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 3 + self.rpc_timeout = 120 def setup_network(self): self.add_nodes(3) @@ -187,5 +190,6 @@ class AssumeValidTest(BitcoinTestFramework): self.send_blocks_until_disconnected(p2p2) self.assert_blockchain_height(self.nodes[2], 101) + if __name__ == '__main__': AssumeValidTest().main() diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py index 7a6e3df702..0db74432e2 100755 --- a/test/functional/feature_backwards_compatibility.py +++ b/test/functional/feature_backwards_compatibility.py @@ -113,7 +113,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): # Create another conflicting transaction using RBF tx3_id = self.nodes[1].sendtoaddress(return_address, 1) tx4_id = self.nodes[1].bumpfee(tx3_id)["txid"] - # Abondon transaction, but don't confirm + # Abandon transaction, but don't confirm self.nodes[1].abandontransaction(tx3_id) # w1_v19: regular wallet, created with v0.19 diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 95905f477b..0c591de869 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -630,17 +630,19 @@ class FullBlockTest(BitcoinTestFramework): self.log.info("Reject a block with invalid work") self.move_tip(44) - b47 = self.next_block(47, solve=False) + b47 = self.next_block(47) target = uint256_from_compact(b47.nBits) - while b47.sha256 < target: + while b47.sha256 <= target: + # Rehash nonces until an invalid too-high-hash block is found. b47.nNonce += 1 b47.rehash() self.send_blocks([b47], False, force_send=True, reject_reason='high-hash', reconnect=True) self.log.info("Reject a block with a timestamp >2 hours in the future") self.move_tip(44) - b48 = self.next_block(48, solve=False) + b48 = self.next_block(48) b48.nTime = int(time.time()) + 60 * 60 * 3 + # Header timestamp has changed. Re-solve the block. b48.solve() self.send_blocks([b48], False, force_send=True, reject_reason='time-too-new') @@ -1261,7 +1263,7 @@ class FullBlockTest(BitcoinTestFramework): self.save_spendable_output() spend = self.get_spendable_output() - self.send_blocks(blocks, True, timeout=960) + self.send_blocks(blocks, True, timeout=2440) chain1_tip = i # now create alt chain of same length @@ -1273,14 +1275,14 @@ class FullBlockTest(BitcoinTestFramework): # extend alt chain to trigger re-org block = self.next_block("alt" + str(chain1_tip + 1), version=4) - self.send_blocks([block], True, timeout=960) + self.send_blocks([block], True, timeout=2440) # ... and re-org back to the first chain self.move_tip(chain1_tip) block = self.next_block(chain1_tip + 1, version=4) self.send_blocks([block], False, force_send=True) block = self.next_block(chain1_tip + 2, version=4) - self.send_blocks([block], True, timeout=960) + self.send_blocks([block], True, timeout=2440) self.log.info("Reject a block with an invalid block header version") b_v1 = self.next_block('b_v1', version=1) @@ -1321,7 +1323,7 @@ class FullBlockTest(BitcoinTestFramework): tx.rehash() return tx - def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), solve=True, *, version=1): + def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), *, version=1): if self.tip is None: base_block_hash = self.genesis_hash block_time = int(time.time()) + 1 @@ -1343,8 +1345,8 @@ class FullBlockTest(BitcoinTestFramework): self.sign_tx(tx, spend) self.add_transactions_to_block(block, [tx]) block.hashMerkleRoot = block.calc_merkle_root() - if solve: - block.solve() + # Block is created. Find a valid nonce. + block.solve() self.tip = block self.block_heights[block.sha256] = height assert number not in self.blocks diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index e2b347f925..073ed8d7c7 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -60,7 +60,7 @@ class BIP65Test(BitcoinTestFramework): '-acceptnonstdtxn=1', # cltv_invalidate is nonstandard ]] self.setup_clean_chain = True - self.rpc_timeout = 120 + self.rpc_timeout = 480 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 9628945a80..38cdf0501e 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -36,7 +36,6 @@ def unDERify(tx): tx.vin[0].scriptSig = CScript(newscript) - class BIP66Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 @@ -45,7 +44,7 @@ class BIP66Test(BitcoinTestFramework): '-par=1', # Use only one script thread to get the exact log msg for testing ]] self.setup_clean_chain = True - self.rpc_timeout = 120 + self.rpc_timeout = 240 def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index 1b33724594..5128485ec0 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -99,8 +99,20 @@ def split_inputs(from_node, txins, txouts, initial_split=False): txouts.append({"txid": txid, "vout": 0, "amount": half_change}) txouts.append({"txid": txid, "vout": 1, "amount": rem_change}) +def check_raw_estimates(node, fees_seen): + """Call estimaterawfee and verify that the estimates meet certain invariants.""" -def check_estimates(node, fees_seen): + delta = 1.0e-6 # account for rounding error + for i in range(1, 26): + for _, e in node.estimaterawfee(i).items(): + feerate = float(e["feerate"]) + assert_greater_than(feerate, 0) + + if feerate + delta < min(fees_seen) or feerate - delta > max(fees_seen): + raise AssertionError("Estimated fee (%f) out of range (%f,%f)" + % (feerate, min(fees_seen), max(fees_seen))) + +def check_smart_estimates(node, fees_seen): """Call estimatesmartfee and verify that the estimates meet certain invariants.""" delta = 1.0e-6 # account for rounding error @@ -123,6 +135,9 @@ def check_estimates(node, fees_seen): else: assert_greater_than_or_equal(i + 1, e["blocks"]) +def check_estimates(node, fees_seen): + check_raw_estimates(node, fees_seen) + check_smart_estimates(node, fees_seen) class EstimateFeeTest(BitcoinTestFramework): def set_test_params(self): diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index 82c7e55245..909a43c8d9 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -72,6 +72,7 @@ class SegWitTest(BitcoinTestFramework): "-addresstype=legacy", ], ] + self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/framework_test_script.py b/test/functional/framework_test_script.py new file mode 100755 index 0000000000..9d916c0022 --- /dev/null +++ b/test/functional/framework_test_script.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Tests for test_framework.script.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.script import bn2vch +from test_framework.util import assert_equal + +def test_bn2vch(): + assert_equal(bn2vch(0), bytes([])) + assert_equal(bn2vch(1), bytes([0x01])) + assert_equal(bn2vch(-1), bytes([0x81])) + assert_equal(bn2vch(0x7F), bytes([0x7F])) + assert_equal(bn2vch(-0x7F), bytes([0xFF])) + assert_equal(bn2vch(0x80), bytes([0x80, 0x00])) + assert_equal(bn2vch(-0x80), bytes([0x80, 0x80])) + assert_equal(bn2vch(0xFF), bytes([0xFF, 0x00])) + assert_equal(bn2vch(-0xFF), bytes([0xFF, 0x80])) + assert_equal(bn2vch(0x100), bytes([0x00, 0x01])) + assert_equal(bn2vch(-0x100), bytes([0x00, 0x81])) + assert_equal(bn2vch(0x7FFF), bytes([0xFF, 0x7F])) + assert_equal(bn2vch(-0x8000), bytes([0x00, 0x80, 0x80])) + assert_equal(bn2vch(-0x7FFFFF), bytes([0xFF, 0xFF, 0xFF])) + assert_equal(bn2vch(0x80000000), bytes([0x00, 0x00, 0x00, 0x80, 0x00])) + assert_equal(bn2vch(-0x80000000), bytes([0x00, 0x00, 0x00, 0x80, 0x80])) + assert_equal(bn2vch(0xFFFFFFFF), bytes([0xFF, 0xFF, 0xFF, 0xFF, 0x00])) + + assert_equal(bn2vch(123456789), bytes([0x15, 0xCD, 0x5B, 0x07])) + assert_equal(bn2vch(-54321), bytes([0x31, 0xD4, 0x80])) + +class FrameworkTestScript(BitcoinTestFramework): + def setup_network(self): + pass + + def set_test_params(self): + self.num_nodes = 0 + + def run_test(self): + test_bn2vch() + +if __name__ == '__main__': + FrameworkTestScript().main() diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index 900cabccda..9ade22a7eb 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -16,6 +16,7 @@ from test_framework.messages import ( CTransaction, CTxOut, MAX_BLOCK_BASE_SIZE, + MAX_MONEY, ) from test_framework.script import ( hash160, @@ -220,7 +221,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): # The following two validations prevent overflow of the output amounts (see CVE-2010-5139). self.log.info('A transaction with too large output value') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) - tx.vout[0].nValue = 21000000 * COIN + 1 + tx.vout[0].nValue = MAX_MONEY + 1 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-toolarge'}], rawtxs=[tx.serialize().hex()], @@ -229,7 +230,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('A transaction with too large sum of output values') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout = [tx.vout[0]] * 2 - tx.vout[0].nValue = 21000000 * COIN + tx.vout[0].nValue = MAX_MONEY self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-txouttotal-toolarge'}], rawtxs=[tx.serialize().hex()], diff --git a/test/functional/mempool_reorg.py b/test/functional/mempool_reorg.py index 3b148d5cf0..d797dff134 100755 --- a/test/functional/mempool_reorg.py +++ b/test/functional/mempool_reorg.py @@ -76,7 +76,7 @@ class MempoolCoinbaseTest(BitcoinTestFramework): spend_101_id = self.nodes[0].sendrawtransaction(spend_101_raw) spend_102_1_id = self.nodes[0].sendrawtransaction(spend_102_1_raw) - self.sync_all(timeout=360) + self.sync_all(timeout=720) assert_equal(set(self.nodes[0].getrawmempool()), {spend_101_id, spend_102_1_id, timelock_tx_id}) @@ -91,10 +91,11 @@ class MempoolCoinbaseTest(BitcoinTestFramework): for node in self.nodes: node.invalidateblock(new_blocks[0]) - self.sync_all(timeout=360) + self.sync_all(timeout=720) # mempool should be empty. assert_equal(set(self.nodes[0].getrawmempool()), set()) + if __name__ == '__main__': MempoolCoinbaseTest().main() diff --git a/test/functional/p2p_filter.py b/test/functional/p2p_filter.py new file mode 100755 index 0000000000..ad7a9dcf6e --- /dev/null +++ b/test/functional/p2p_filter.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Test BIP 37 +""" + +from test_framework.messages import ( + MSG_BLOCK, + MSG_FILTERED_BLOCK, + msg_getdata, + msg_filterload, + msg_filterclear, +) +from test_framework.mininode import ( + P2PInterface, + mininode_lock, +) +from test_framework.test_framework import BitcoinTestFramework + + +class FilterNode(P2PInterface): + # This is a P2SH watch-only wallet + watch_script_pubkey = 'a914ffffffffffffffffffffffffffffffffffffffff87' + # The initial filter (n=10, fp=0.000001) with just the above scriptPubKey added + watch_filter_init = msg_filterload( + data= + b'@\x00\x08\x00\x80\x00\x00 \x00\xc0\x00 \x04\x00\x08$\x00\x04\x80\x00\x00 \x00\x00\x00\x00\x80\x00\x00@\x00\x02@ \x00', + nHashFuncs=19, + nTweak=0, + nFlags=1, + ) + + def on_inv(self, message): + want = msg_getdata() + for i in message.inv: + # inv messages can only contain TX or BLOCK, so translate BLOCK to FILTERED_BLOCK + if i.type == MSG_BLOCK: + i.type = MSG_FILTERED_BLOCK + want.inv.append(i) + if len(want.inv): + self.send_message(want) + + def on_merkleblock(self, message): + self.merkleblock_received = True + + def on_tx(self, message): + self.tx_received = True + + +class FilterTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 1 + self.extra_args = [[ + '-peerbloomfilters', + '-whitelist=noban@127.0.0.1', # immediate tx relay + ]] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + self.log.info('Add filtered P2P connection to the node') + filter_node = self.nodes[0].add_p2p_connection(FilterNode()) + filter_node.send_message(filter_node.watch_filter_init) + filter_node.sync_with_ping() + filter_address = self.nodes[0].decodescript(filter_node.watch_script_pubkey)['addresses'][0] + + self.log.info('Check that we receive merkleblock and tx if the filter matches a tx in a block') + filter_node.merkleblock_received = False + block_hash = self.nodes[0].generatetoaddress(1, filter_address)[0] + txid = self.nodes[0].getblock(block_hash)['tx'][0] + filter_node.wait_for_tx(txid) + assert filter_node.merkleblock_received + + self.log.info('Check that we only receive a merkleblock if the filter does not match a tx in a block') + with mininode_lock: + filter_node.last_message.pop("merkleblock", None) + filter_node.tx_received = False + self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress()) + filter_node.wait_for_merkleblock() + assert not filter_node.tx_received + + self.log.info('Check that we not receive a tx if the filter does not match a mempool tx') + filter_node.merkleblock_received = False + filter_node.tx_received = False + self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 90) + filter_node.sync_with_ping() + filter_node.sync_with_ping() + assert not filter_node.merkleblock_received + assert not filter_node.tx_received + + self.log.info('Check that we receive a tx in reply to a mempool msg if the filter matches a mempool tx') + filter_node.merkleblock_received = False + txid = self.nodes[0].sendtoaddress(filter_address, 90) + filter_node.wait_for_tx(txid) + assert not filter_node.merkleblock_received + + self.log.info('Check that after deleting filter all txs get relayed again') + filter_node.send_message(msg_filterclear()) + filter_node.sync_with_ping() + for _ in range(5): + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 7) + filter_node.wait_for_tx(txid) + + +if __name__ == '__main__': + FilterTest().main() diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index ad5a124680..785c476e19 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -147,6 +147,11 @@ class TestP2PConn(P2PInterface): super().__init__() self.getdataset = set() + # Avoid sending out msg_getdata in the mininode thread as a reply to invs. + # They are not needed and would only lead to races because we send msg_getdata out in the test thread + def on_inv(self, message): + pass + def on_getdata(self, message): for inv in message.inv: self.getdataset.add(inv.hash) diff --git a/test/functional/rpc_estimatefee.py b/test/functional/rpc_estimatefee.py new file mode 100755 index 0000000000..8bdecfc8cd --- /dev/null +++ b/test/functional/rpc_estimatefee.py @@ -0,0 +1,51 @@ +#!/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 estimatefee RPCs. + +Test the following RPCs: + - estimatesmartfee + - estimaterawfee +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_raises_rpc_error + +class EstimateFeeTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 1 + + def run_test(self): + # missing required params + assert_raises_rpc_error(-1, "estimatesmartfee", self.nodes[0].estimatesmartfee) + assert_raises_rpc_error(-1, "estimaterawfee", self.nodes[0].estimaterawfee) + + # wrong type for conf_target + assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimatesmartfee, 'foo') + assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 'foo') + + # wrong type for estimatesmartfee(estimate_mode) + assert_raises_rpc_error(-3, "Expected type string, got number", self.nodes[0].estimatesmartfee, 1, 1) + assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", self.nodes[0].estimatesmartfee, 1, 'foo') + + # wrong type for estimaterawfee(threshold) + assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 1, 'foo') + + # extra params + assert_raises_rpc_error(-1, "estimatesmartfee", self.nodes[0].estimatesmartfee, 1, 'ECONOMICAL', 1) + assert_raises_rpc_error(-1, "estimaterawfee", self.nodes[0].estimaterawfee, 1, 1, 1) + + # valid calls + self.nodes[0].estimatesmartfee(1) + # self.nodes[0].estimatesmartfee(1, None) + self.nodes[0].estimatesmartfee(1, 'ECONOMICAL') + + self.nodes[0].estimaterawfee(1) + self.nodes[0].estimaterawfee(1, None) + self.nodes[0].estimaterawfee(1, 1) + + +if __name__ == '__main__': + EstimateFeeTest().main() diff --git a/test/functional/rpc_getdescriptorinfo.py b/test/functional/rpc_getdescriptorinfo.py new file mode 100755 index 0000000000..977dc805ef --- /dev/null +++ b/test/functional/rpc_getdescriptorinfo.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test getdescriptorinfo RPC. +""" + +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 DescriptorTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.extra_args = [["-disablewallet"]] + + def test_desc(self, desc, isrange, issolvable, hasprivatekeys): + info = self.nodes[0].getdescriptorinfo(desc) + assert_equal(info, self.nodes[0].getdescriptorinfo(descsum_create(desc))) + assert_equal(info['descriptor'], descsum_create(desc)) + assert_equal(info['isrange'], isrange) + assert_equal(info['issolvable'], issolvable) + assert_equal(info['hasprivatekeys'], hasprivatekeys) + + def run_test(self): + assert_raises_rpc_error(-1, 'getdescriptorinfo', self.nodes[0].getdescriptorinfo) + assert_raises_rpc_error(-3, 'Expected type string', self.nodes[0].getdescriptorinfo, 1) + assert_raises_rpc_error(-5, 'is not a valid descriptor function', self.nodes[0].getdescriptorinfo, '') + + # P2PK output with the specified public key. + self.test_desc('pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)', isrange=False, issolvable=True, hasprivatekeys=False) + # P2PKH output with the specified public key. + self.test_desc('pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)', isrange=False, issolvable=True, hasprivatekeys=False) + # P2WPKH output with the specified public key. + self.test_desc('wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)', isrange=False, issolvable=True, hasprivatekeys=False) + # P2SH-P2WPKH output with the specified public key. + self.test_desc('sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))', isrange=False, issolvable=True, hasprivatekeys=False) + # Any P2PK, P2PKH, P2WPKH, or P2SH-P2WPKH output with the specified public key. + self.test_desc('combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)', isrange=False, issolvable=True, hasprivatekeys=False) + # An (overly complicated) P2SH-P2WSH-P2PKH output with the specified public key. + self.test_desc('sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))', isrange=False, issolvable=True, hasprivatekeys=False) + # A bare *1-of-2* multisig output with keys in the specified order. + self.test_desc('multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)', isrange=False, issolvable=True, hasprivatekeys=False) + # A P2SH *2-of-2* multisig output with keys in the specified order. + self.test_desc('sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))', isrange=False, issolvable=True, hasprivatekeys=False) + # A P2WSH *2-of-3* multisig output with keys in the specified order. + self.test_desc('wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))', isrange=False, issolvable=True, hasprivatekeys=False) + # A P2SH-P2WSH *1-of-3* multisig output with keys in the specified order. + self.test_desc('sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))', isrange=False, issolvable=True, hasprivatekeys=False) + # A P2PK output with the public key of the specified xpub. + self.test_desc('pk(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B)', isrange=False, issolvable=True, hasprivatekeys=False) + # A P2PKH output with child key *1'/2* of the specified xpub. + self.test_desc("pkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1'/2)", isrange=False, issolvable=True, hasprivatekeys=False) + # A set of P2PKH outputs, but additionally specifies that the specified xpub is a child of a master with fingerprint `d34db33f`, and derived using path `44'/0'/0'`. + self.test_desc("pkh([d34db33f/44'/0'/0']tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/*)", isrange=True, issolvable=True, hasprivatekeys=False) + # A set of *1-of-2* P2WSH multisig outputs where the first multisig key is the *1/0/`i`* child of the first specified xpub and the second multisig key is the *0/0/`i`* child of the second specified xpub, and `i` is any number in a configurable range (`0-1000` by default). + self.test_desc("wsh(multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/0/*,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/0/0/*))", isrange=True, issolvable=True, hasprivatekeys=False) + + +if __name__ == '__main__': + DescriptorTest().main() diff --git a/test/functional/test_framework/bignum.py b/test/functional/test_framework/bignum.py deleted file mode 100644 index db5ccd62c2..0000000000 --- a/test/functional/test_framework/bignum.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -# -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Big number routines. - -This file is copied from python-bitcoinlib. -""" - -import struct - - -# generic big endian MPI format - -def bn_bytes(v, have_ext=False): - ext = 0 - if have_ext: - ext = 1 - return ((v.bit_length()+7)//8) + ext - -def bn2bin(v): - s = bytearray() - i = bn_bytes(v) - while i > 0: - s.append((v >> ((i-1) * 8)) & 0xff) - i -= 1 - return s - -def bn2mpi(v): - have_ext = False - if v.bit_length() > 0: - have_ext = (v.bit_length() & 0x07) == 0 - - neg = False - if v < 0: - neg = True - v = -v - - s = struct.pack(b">I", bn_bytes(v, have_ext)) - ext = bytearray() - if have_ext: - ext.append(0) - v_bin = bn2bin(v) - if neg: - if have_ext: - ext[0] |= 0x80 - else: - v_bin[0] |= 0x80 - return s + ext + v_bin - -# bitcoin-specific little endian format, with implicit size -def mpi2vch(s): - r = s[4:] # strip size - r = r[::-1] # reverse string, converting BE->LE - return r - -def bn2vch(v): - return bytes(mpi2vch(bn2mpi(v))) diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 4f7a9a8b13..ff0c763b72 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -39,6 +39,7 @@ MAX_LOCATOR_SZ = 101 MAX_BLOCK_BASE_SIZE = 1000000 COIN = 100000000 # 1 btc in satoshis +MAX_MONEY = 21000000 * COIN BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is BIP 125 opt-in and BIP 68-opt-out @@ -50,6 +51,7 @@ NODE_NETWORK_LIMITED = (1 << 10) MSG_TX = 1 MSG_BLOCK = 2 +MSG_FILTERED_BLOCK = 3 MSG_WITNESS_FLAG = 1 << 30 MSG_TYPE_MASK = 0xffffffff >> 2 @@ -224,10 +226,11 @@ class CInv: typemap = { 0: "Error", - 1: "TX", - 2: "Block", - 1|MSG_WITNESS_FLAG: "WitnessTx", - 2|MSG_WITNESS_FLAG : "WitnessBlock", + MSG_TX: "TX", + MSG_BLOCK: "Block", + MSG_TX | MSG_WITNESS_FLAG: "WitnessTx", + MSG_BLOCK | MSG_WITNESS_FLAG: "WitnessBlock", + MSG_FILTERED_BLOCK: "filtered Block", 4: "CompactBlock" } @@ -1317,6 +1320,58 @@ class msg_headers: return "msg_headers(headers=%s)" % repr(self.headers) +class msg_merkleblock: + command = b"merkleblock" + + def deserialize(self, f): + pass # Placeholder for now + + +class msg_filterload: + __slots__ = ("data", "nHashFuncs", "nTweak", "nFlags") + command = b"filterload" + + def __init__(self, data=b'00', nHashFuncs=0, nTweak=0, nFlags=0): + self.data = data + self.nHashFuncs = nHashFuncs + self.nTweak = nTweak + self.nFlags = nFlags + + def deserialize(self, f): + self.data = deser_string(f) + self.nHashFuncs = struct.unpack("<I", f.read(4))[0] + self.nTweak = struct.unpack("<I", f.read(4))[0] + self.nFlags = struct.unpack("<B", f.read(1))[0] + + def serialize(self): + r = b"" + r += ser_string(self.data) + r += struct.pack("<I", self.nHashFuncs) + r += struct.pack("<I", self.nTweak) + r += struct.pack("<B", self.nFlags) + return r + + def __repr__(self): + return "msg_filterload(data={}, nHashFuncs={}, nTweak={}, nFlags={})".format( + self.data, self.nHashFuncs, self.nTweak, self.nFlags) + + +class msg_filterclear: + __slots__ = () + command = b"filterclear" + + def __init__(self): + pass + + def deserialize(self, f): + pass + + def serialize(self): + return b"" + + def __repr__(self): + return "msg_filterclear()" + class msg_feefilter: __slots__ = ("feerate",) diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index a9e669fea9..ce51513ce9 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -30,6 +30,8 @@ from test_framework.messages import ( msg_blocktxn, msg_cmpctblock, msg_feefilter, + msg_filterclear, + msg_filterload, msg_getaddr, msg_getblocks, msg_getblocktxn, @@ -38,6 +40,7 @@ from test_framework.messages import ( msg_headers, msg_inv, msg_mempool, + msg_merkleblock, msg_notfound, msg_ping, msg_pong, @@ -62,6 +65,8 @@ MESSAGEMAP = { b"blocktxn": msg_blocktxn, b"cmpctblock": msg_cmpctblock, b"feefilter": msg_feefilter, + b"filterclear": msg_filterclear, + b"filterload": msg_filterload, b"getaddr": msg_getaddr, b"getblocks": msg_getblocks, b"getblocktxn": msg_getblocktxn, @@ -70,6 +75,7 @@ MESSAGEMAP = { b"headers": msg_headers, b"inv": msg_inv, b"mempool": msg_mempool, + b"merkleblock": msg_merkleblock, b"notfound": msg_notfound, b"ping": msg_ping, b"pong": msg_pong, @@ -318,6 +324,8 @@ class P2PInterface(P2PConnection): def on_blocktxn(self, message): pass def on_cmpctblock(self, message): pass def on_feefilter(self, message): pass + def on_filterclear(self, message): pass + def on_filterload(self, message): pass def on_getaddr(self, message): pass def on_getblocks(self, message): pass def on_getblocktxn(self, message): pass @@ -325,6 +333,7 @@ class P2PInterface(P2PConnection): def on_getheaders(self, message): pass def on_headers(self, message): pass def on_mempool(self, message): pass + def on_merkleblock(self, message): pass def on_notfound(self, message): pass def on_pong(self, message): pass def on_reject(self, message): pass @@ -385,6 +394,17 @@ class P2PInterface(P2PConnection): wait_until(test_function, timeout=timeout, lock=mininode_lock) + def wait_for_merkleblock(self, timeout=60): + def test_function(): + assert self.is_connected + last_filtered_block = self.last_message.get('merkleblock') + if not last_filtered_block: + return False + # TODO change this method to take a hash value and only return true if the correct block has been received + return True + + wait_until(test_function, timeout=timeout, lock=mininode_lock) + def wait_for_getdata(self, timeout=60): """Waits for a getdata message. diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 51aa9057f7..92725dfcf4 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -6,21 +6,35 @@ This file is modified from python-bitcoinlib. """ - -from .messages import CTransaction, CTxOut, sha256, hash256, uint256_from_str, ser_uint256, ser_string - import hashlib import struct -from .bignum import bn2vch +from .messages import ( + CTransaction, + CTxOut, + hash256, + ser_string, + ser_uint256, + sha256, + uint256_from_str, +) MAX_SCRIPT_ELEMENT_SIZE = 520 - OPCODE_NAMES = {} def hash160(s): return hashlib.new('ripemd160', sha256(s)).digest() +def bn2vch(v): + """Convert number to bitcoin-specific little endian format.""" + # We need v.bit_length() bits, plus a sign bit for every nonzero number. + n_bits = v.bit_length() + (v != 0) + # The number of bytes for that is: + n_bytes = (n_bits + 7) // 8 + # Convert number to absolute value + sign in top bit. + encoded_v = 0 if v == 0 else abs(v) | ((v < 0) << (n_bytes * 8 - 1)) + # Serialize to bytes + return encoded_v.to_bytes(n_bytes, 'little') _opcode_instances = [] class CScriptOp(int): @@ -31,13 +45,13 @@ class CScriptOp(int): def encode_op_pushdata(d): """Encode a PUSHDATA op, returning bytes""" if len(d) < 0x4c: - return b'' + bytes([len(d)]) + d # OP_PUSHDATA + return b'' + bytes([len(d)]) + d # OP_PUSHDATA elif len(d) <= 0xff: - return b'\x4c' + bytes([len(d)]) + d # OP_PUSHDATA1 + return b'\x4c' + bytes([len(d)]) + d # OP_PUSHDATA1 elif len(d) <= 0xffff: - return b'\x4d' + struct.pack(b'<H', len(d)) + d # OP_PUSHDATA2 + return b'\x4d' + struct.pack(b'<H', len(d)) + d # OP_PUSHDATA2 elif len(d) <= 0xffffffff: - return b'\x4e' + struct.pack(b'<I', len(d)) + d # OP_PUSHDATA4 + return b'\x4e' + struct.pack(b'<I', len(d)) + d # OP_PUSHDATA4 else: raise ValueError("Data too long to encode in a PUSHDATA op") @@ -50,7 +64,7 @@ class CScriptOp(int): if n == 0: return OP_0 else: - return CScriptOp(OP_1 + n-1) + return CScriptOp(OP_1 + n - 1) def decode_op_n(self): """Decode a small integer opcode, returning an integer""" @@ -60,7 +74,7 @@ class CScriptOp(int): if not (self == OP_0 or OP_1 <= self <= OP_16): raise ValueError('op %r is not an OP_N' % self) - return int(self - OP_1+1) + return int(self - OP_1 + 1) def is_small_int(self): """Return true if the op pushes a small integer to the stack""" @@ -87,7 +101,7 @@ class CScriptOp(int): return _opcode_instances[n] # Populate opcode instance table -for n in range(0xff+1): +for n in range(0xff + 1): CScriptOp(n) @@ -100,7 +114,7 @@ OP_PUSHDATA4 = CScriptOp(0x4e) OP_1NEGATE = CScriptOp(0x4f) OP_RESERVED = CScriptOp(0x50) OP_1 = CScriptOp(0x51) -OP_TRUE=OP_1 +OP_TRUE = OP_1 OP_2 = CScriptOp(0x52) OP_3 = CScriptOp(0x53) OP_4 = CScriptOp(0x54) @@ -232,122 +246,122 @@ OP_PUBKEY = CScriptOp(0xfe) OP_INVALIDOPCODE = CScriptOp(0xff) OPCODE_NAMES.update({ - OP_0 : 'OP_0', - OP_PUSHDATA1 : 'OP_PUSHDATA1', - OP_PUSHDATA2 : 'OP_PUSHDATA2', - OP_PUSHDATA4 : 'OP_PUSHDATA4', - OP_1NEGATE : 'OP_1NEGATE', - OP_RESERVED : 'OP_RESERVED', - OP_1 : 'OP_1', - OP_2 : 'OP_2', - OP_3 : 'OP_3', - OP_4 : 'OP_4', - OP_5 : 'OP_5', - OP_6 : 'OP_6', - OP_7 : 'OP_7', - OP_8 : 'OP_8', - OP_9 : 'OP_9', - OP_10 : 'OP_10', - OP_11 : 'OP_11', - OP_12 : 'OP_12', - OP_13 : 'OP_13', - OP_14 : 'OP_14', - OP_15 : 'OP_15', - OP_16 : 'OP_16', - OP_NOP : 'OP_NOP', - OP_VER : 'OP_VER', - OP_IF : 'OP_IF', - OP_NOTIF : 'OP_NOTIF', - OP_VERIF : 'OP_VERIF', - OP_VERNOTIF : 'OP_VERNOTIF', - OP_ELSE : 'OP_ELSE', - OP_ENDIF : 'OP_ENDIF', - OP_VERIFY : 'OP_VERIFY', - OP_RETURN : 'OP_RETURN', - OP_TOALTSTACK : 'OP_TOALTSTACK', - OP_FROMALTSTACK : 'OP_FROMALTSTACK', - OP_2DROP : 'OP_2DROP', - OP_2DUP : 'OP_2DUP', - OP_3DUP : 'OP_3DUP', - OP_2OVER : 'OP_2OVER', - OP_2ROT : 'OP_2ROT', - OP_2SWAP : 'OP_2SWAP', - OP_IFDUP : 'OP_IFDUP', - OP_DEPTH : 'OP_DEPTH', - OP_DROP : 'OP_DROP', - OP_DUP : 'OP_DUP', - OP_NIP : 'OP_NIP', - OP_OVER : 'OP_OVER', - OP_PICK : 'OP_PICK', - OP_ROLL : 'OP_ROLL', - OP_ROT : 'OP_ROT', - OP_SWAP : 'OP_SWAP', - OP_TUCK : 'OP_TUCK', - OP_CAT : 'OP_CAT', - OP_SUBSTR : 'OP_SUBSTR', - OP_LEFT : 'OP_LEFT', - OP_RIGHT : 'OP_RIGHT', - OP_SIZE : 'OP_SIZE', - OP_INVERT : 'OP_INVERT', - OP_AND : 'OP_AND', - OP_OR : 'OP_OR', - OP_XOR : 'OP_XOR', - OP_EQUAL : 'OP_EQUAL', - OP_EQUALVERIFY : 'OP_EQUALVERIFY', - OP_RESERVED1 : 'OP_RESERVED1', - OP_RESERVED2 : 'OP_RESERVED2', - OP_1ADD : 'OP_1ADD', - OP_1SUB : 'OP_1SUB', - OP_2MUL : 'OP_2MUL', - OP_2DIV : 'OP_2DIV', - OP_NEGATE : 'OP_NEGATE', - OP_ABS : 'OP_ABS', - OP_NOT : 'OP_NOT', - OP_0NOTEQUAL : 'OP_0NOTEQUAL', - OP_ADD : 'OP_ADD', - OP_SUB : 'OP_SUB', - OP_MUL : 'OP_MUL', - OP_DIV : 'OP_DIV', - OP_MOD : 'OP_MOD', - OP_LSHIFT : 'OP_LSHIFT', - OP_RSHIFT : 'OP_RSHIFT', - OP_BOOLAND : 'OP_BOOLAND', - OP_BOOLOR : 'OP_BOOLOR', - OP_NUMEQUAL : 'OP_NUMEQUAL', - OP_NUMEQUALVERIFY : 'OP_NUMEQUALVERIFY', - OP_NUMNOTEQUAL : 'OP_NUMNOTEQUAL', - OP_LESSTHAN : 'OP_LESSTHAN', - OP_GREATERTHAN : 'OP_GREATERTHAN', - OP_LESSTHANOREQUAL : 'OP_LESSTHANOREQUAL', - OP_GREATERTHANOREQUAL : 'OP_GREATERTHANOREQUAL', - OP_MIN : 'OP_MIN', - OP_MAX : 'OP_MAX', - OP_WITHIN : 'OP_WITHIN', - OP_RIPEMD160 : 'OP_RIPEMD160', - OP_SHA1 : 'OP_SHA1', - OP_SHA256 : 'OP_SHA256', - OP_HASH160 : 'OP_HASH160', - OP_HASH256 : 'OP_HASH256', - OP_CODESEPARATOR : 'OP_CODESEPARATOR', - OP_CHECKSIG : 'OP_CHECKSIG', - OP_CHECKSIGVERIFY : 'OP_CHECKSIGVERIFY', - OP_CHECKMULTISIG : 'OP_CHECKMULTISIG', - OP_CHECKMULTISIGVERIFY : 'OP_CHECKMULTISIGVERIFY', - OP_NOP1 : 'OP_NOP1', - OP_CHECKLOCKTIMEVERIFY : 'OP_CHECKLOCKTIMEVERIFY', - OP_CHECKSEQUENCEVERIFY : 'OP_CHECKSEQUENCEVERIFY', - OP_NOP4 : 'OP_NOP4', - OP_NOP5 : 'OP_NOP5', - OP_NOP6 : 'OP_NOP6', - OP_NOP7 : 'OP_NOP7', - OP_NOP8 : 'OP_NOP8', - OP_NOP9 : 'OP_NOP9', - OP_NOP10 : 'OP_NOP10', - OP_SMALLINTEGER : 'OP_SMALLINTEGER', - OP_PUBKEYS : 'OP_PUBKEYS', - OP_PUBKEYHASH : 'OP_PUBKEYHASH', - OP_PUBKEY : 'OP_PUBKEY', - OP_INVALIDOPCODE : 'OP_INVALIDOPCODE', + OP_0: 'OP_0', + OP_PUSHDATA1: 'OP_PUSHDATA1', + OP_PUSHDATA2: 'OP_PUSHDATA2', + OP_PUSHDATA4: 'OP_PUSHDATA4', + OP_1NEGATE: 'OP_1NEGATE', + OP_RESERVED: 'OP_RESERVED', + OP_1: 'OP_1', + OP_2: 'OP_2', + OP_3: 'OP_3', + OP_4: 'OP_4', + OP_5: 'OP_5', + OP_6: 'OP_6', + OP_7: 'OP_7', + OP_8: 'OP_8', + OP_9: 'OP_9', + OP_10: 'OP_10', + OP_11: 'OP_11', + OP_12: 'OP_12', + OP_13: 'OP_13', + OP_14: 'OP_14', + OP_15: 'OP_15', + OP_16: 'OP_16', + OP_NOP: 'OP_NOP', + OP_VER: 'OP_VER', + OP_IF: 'OP_IF', + OP_NOTIF: 'OP_NOTIF', + OP_VERIF: 'OP_VERIF', + OP_VERNOTIF: 'OP_VERNOTIF', + OP_ELSE: 'OP_ELSE', + OP_ENDIF: 'OP_ENDIF', + OP_VERIFY: 'OP_VERIFY', + OP_RETURN: 'OP_RETURN', + OP_TOALTSTACK: 'OP_TOALTSTACK', + OP_FROMALTSTACK: 'OP_FROMALTSTACK', + OP_2DROP: 'OP_2DROP', + OP_2DUP: 'OP_2DUP', + OP_3DUP: 'OP_3DUP', + OP_2OVER: 'OP_2OVER', + OP_2ROT: 'OP_2ROT', + OP_2SWAP: 'OP_2SWAP', + OP_IFDUP: 'OP_IFDUP', + OP_DEPTH: 'OP_DEPTH', + OP_DROP: 'OP_DROP', + OP_DUP: 'OP_DUP', + OP_NIP: 'OP_NIP', + OP_OVER: 'OP_OVER', + OP_PICK: 'OP_PICK', + OP_ROLL: 'OP_ROLL', + OP_ROT: 'OP_ROT', + OP_SWAP: 'OP_SWAP', + OP_TUCK: 'OP_TUCK', + OP_CAT: 'OP_CAT', + OP_SUBSTR: 'OP_SUBSTR', + OP_LEFT: 'OP_LEFT', + OP_RIGHT: 'OP_RIGHT', + OP_SIZE: 'OP_SIZE', + OP_INVERT: 'OP_INVERT', + OP_AND: 'OP_AND', + OP_OR: 'OP_OR', + OP_XOR: 'OP_XOR', + OP_EQUAL: 'OP_EQUAL', + OP_EQUALVERIFY: 'OP_EQUALVERIFY', + OP_RESERVED1: 'OP_RESERVED1', + OP_RESERVED2: 'OP_RESERVED2', + OP_1ADD: 'OP_1ADD', + OP_1SUB: 'OP_1SUB', + OP_2MUL: 'OP_2MUL', + OP_2DIV: 'OP_2DIV', + OP_NEGATE: 'OP_NEGATE', + OP_ABS: 'OP_ABS', + OP_NOT: 'OP_NOT', + OP_0NOTEQUAL: 'OP_0NOTEQUAL', + OP_ADD: 'OP_ADD', + OP_SUB: 'OP_SUB', + OP_MUL: 'OP_MUL', + OP_DIV: 'OP_DIV', + OP_MOD: 'OP_MOD', + OP_LSHIFT: 'OP_LSHIFT', + OP_RSHIFT: 'OP_RSHIFT', + OP_BOOLAND: 'OP_BOOLAND', + OP_BOOLOR: 'OP_BOOLOR', + OP_NUMEQUAL: 'OP_NUMEQUAL', + OP_NUMEQUALVERIFY: 'OP_NUMEQUALVERIFY', + OP_NUMNOTEQUAL: 'OP_NUMNOTEQUAL', + OP_LESSTHAN: 'OP_LESSTHAN', + OP_GREATERTHAN: 'OP_GREATERTHAN', + OP_LESSTHANOREQUAL: 'OP_LESSTHANOREQUAL', + OP_GREATERTHANOREQUAL: 'OP_GREATERTHANOREQUAL', + OP_MIN: 'OP_MIN', + OP_MAX: 'OP_MAX', + OP_WITHIN: 'OP_WITHIN', + OP_RIPEMD160: 'OP_RIPEMD160', + OP_SHA1: 'OP_SHA1', + OP_SHA256: 'OP_SHA256', + OP_HASH160: 'OP_HASH160', + OP_HASH256: 'OP_HASH256', + OP_CODESEPARATOR: 'OP_CODESEPARATOR', + OP_CHECKSIG: 'OP_CHECKSIG', + OP_CHECKSIGVERIFY: 'OP_CHECKSIGVERIFY', + OP_CHECKMULTISIG: 'OP_CHECKMULTISIG', + OP_CHECKMULTISIGVERIFY: 'OP_CHECKMULTISIGVERIFY', + OP_NOP1: 'OP_NOP1', + OP_CHECKLOCKTIMEVERIFY: 'OP_CHECKLOCKTIMEVERIFY', + OP_CHECKSEQUENCEVERIFY: 'OP_CHECKSEQUENCEVERIFY', + OP_NOP4: 'OP_NOP4', + OP_NOP5: 'OP_NOP5', + OP_NOP6: 'OP_NOP6', + OP_NOP7: 'OP_NOP7', + OP_NOP8: 'OP_NOP8', + OP_NOP9: 'OP_NOP9', + OP_NOP10: 'OP_NOP10', + OP_SMALLINTEGER: 'OP_SMALLINTEGER', + OP_PUBKEYS: 'OP_PUBKEYS', + OP_PUBKEYHASH: 'OP_PUBKEYHASH', + OP_PUBKEY: 'OP_PUBKEY', + OP_INVALIDOPCODE: 'OP_INVALIDOPCODE', }) class CScriptInvalidError(Exception): @@ -392,10 +406,10 @@ class CScriptNum: if len(value) == 0: return result for i, byte in enumerate(value): - result |= int(byte) << 8*i + result |= int(byte) << 8 * i if value[-1] >= 0x80: # Mask for all but the highest result bit - num_mask = (2**(len(value)*8) - 1) >> 1 + num_mask = (2**(len(value) * 8) - 1) >> 1 result &= num_mask result *= -1 return result @@ -493,21 +507,20 @@ class CScript(bytes): pushdata_type = 'PUSHDATA2' if i + 1 >= len(self): raise CScriptInvalidError('PUSHDATA2: missing data length') - datasize = self[i] + (self[i+1] << 8) + datasize = self[i] + (self[i + 1] << 8) i += 2 elif opcode == OP_PUSHDATA4: pushdata_type = 'PUSHDATA4' if i + 3 >= len(self): raise CScriptInvalidError('PUSHDATA4: missing data length') - datasize = self[i] + (self[i+1] << 8) + (self[i+2] << 16) + (self[i+3] << 24) + datasize = self[i] + (self[i + 1] << 8) + (self[i + 2] << 16) + (self[i + 3] << 24) i += 4 else: - assert False # shouldn't happen - + assert False # shouldn't happen - data = bytes(self[i:i+datasize]) + data = bytes(self[i:i + datasize]) # Check for truncation if len(data) < datasize: diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 5bb73aee7e..e89b4e9879 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -393,6 +393,7 @@ def connect_nodes(from_connection, node_num): # with transaction relaying wait_until(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo())) + def sync_blocks(rpc_connections, *, wait=1, timeout=60): """ Wait until everybody has the same tip. @@ -406,9 +407,12 @@ def sync_blocks(rpc_connections, *, wait=1, timeout=60): best_hash = [x.getbestblockhash() for x in rpc_connections] if best_hash.count(best_hash[0]) == len(rpc_connections): return + # Check that each peer has at least one connection + assert (all([len(x.getpeerinfo()) for x in rpc_connections])) time.sleep(wait) raise AssertionError("Block sync timed out:{}".format("".join("\n {!r}".format(b) for b in best_hash))) + def sync_mempools(rpc_connections, *, wait=1, timeout=60, flush_scheduler=True): """ Wait until everybody has the same transactions in their memory @@ -422,6 +426,8 @@ def sync_mempools(rpc_connections, *, wait=1, timeout=60, flush_scheduler=True): for r in rpc_connections: r.syncwithvalidationinterfacequeue() return + # Check that each peer has at least one connection + assert (all([len(x.getpeerinfo()) for x in rpc_connections])) time.sleep(wait) raise AssertionError("Mempool sync timed out:{}".format("".join("\n {!r}".format(m) for m in pool))) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 06d939afb7..2f307750a9 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -19,9 +19,8 @@ import datetime import os import time import shutil -import signal -import sys import subprocess +import sys import tempfile import re import logging @@ -148,6 +147,7 @@ BASE_SCRIPTS = [ 'rpc_net.py', 'wallet_keypool.py', 'p2p_mempool.py', + 'p2p_filter.py', 'rpc_setban.py', 'p2p_blocksonly.py', 'mining_prioritisetransaction.py', @@ -183,7 +183,6 @@ BASE_SCRIPTS = [ 'rpc_bind.py --nonloopback', 'mining_basic.py', 'wallet_bumpfee.py', - 'wallet_bumpfee_totalfee_deprecation.py', 'wallet_implicitsegwit.py', 'rpc_named_arguments.py', 'wallet_listsinceblock.py', @@ -196,6 +195,7 @@ BASE_SCRIPTS = [ 'wallet_fallbackfee.py', 'rpc_dumptxoutset.py', 'feature_minchainwork.py', + 'rpc_estimatefee.py', 'rpc_getblockstats.py', 'wallet_create_tx.py', 'p2p_fingerprint.py', @@ -206,6 +206,7 @@ BASE_SCRIPTS = [ 'p2p_dos_header_tree.py', 'p2p_unrequested_blocks.py', 'feature_includeconf.py', + 'feature_asmap.py', 'rpc_deriveaddresses.py', 'rpc_deriveaddresses.py --usecli', 'rpc_scantxoutset.py', @@ -216,9 +217,11 @@ BASE_SCRIPTS = [ 'feature_config_args.py', 'rpc_getaddressinfo_labels_purpose_deprecation.py', 'rpc_getaddressinfo_label_deprecation.py', + 'rpc_getdescriptorinfo.py', 'rpc_help.py', 'feature_help.py', 'feature_shutdown.py', + 'framework_test_script.py', # Don't append tests at the end to avoid merge conflicts # Put them in a random line within the section that fits their approximate run-time ] @@ -362,11 +365,10 @@ def main(): args=passon_args, combined_logs_len=args.combinedlogslen, failfast=args.failfast, - runs_ci=args.ci, use_term_control=args.ansi, ) -def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, runs_ci, use_term_control): +def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control): args = args or [] # Warn if bitcoind is already running @@ -408,7 +410,6 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= tmpdir=tmpdir, test_list=test_list, flags=flags, - timeout_duration=40 * 60 if runs_ci else float('inf'), # in seconds use_term_control=use_term_control, ) start_time = time.time() @@ -493,12 +494,11 @@ class TestHandler: Trigger the test scripts passed in via the list. """ - def __init__(self, *, num_tests_parallel, tests_dir, tmpdir, test_list, flags, timeout_duration, use_term_control): + def __init__(self, *, num_tests_parallel, tests_dir, tmpdir, test_list, flags, use_term_control): assert num_tests_parallel >= 1 self.num_jobs = num_tests_parallel self.tests_dir = tests_dir self.tmpdir = tmpdir - self.timeout_duration = timeout_duration self.test_list = test_list self.flags = flags self.num_running = 0 @@ -539,10 +539,6 @@ class TestHandler: time.sleep(.5) for job in self.jobs: (name, start_time, proc, testdir, log_out, log_err) = job - if int(time.time() - start_time) > self.timeout_duration: - # Timeout individual tests if timeout is specified (to stop - # tests hanging and not providing useful output). - proc.send_signal(signal.SIGINT) if proc.poll() is not None: log_out.seek(0), log_err.seek(0) [stdout, stderr] = [log_file.read().decode('utf-8') for log_file in (log_out, log_err)] @@ -611,7 +607,7 @@ class TestResult(): def check_script_prefixes(): """Check that test scripts start with one of the allowed name prefixes.""" - good_prefixes_re = re.compile("(example|feature|interface|mempool|mining|p2p|rpc|wallet|tool)_") + good_prefixes_re = re.compile("^(example|feature|interface|mempool|mining|p2p|rpc|wallet|tool|framework_test)_") bad_script_names = [script for script in ALL_SCRIPTS if good_prefixes_re.match(script) is None] if bad_script_names: diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 4eb0d19a4f..38c9807757 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -30,6 +30,13 @@ from test_framework.util import ( WALLET_PASSPHRASE = "test" WALLET_PASSPHRASE_TIMEOUT = 3600 +# Fee rates (in BTC per 1000 vbytes) +INSUFFICIENT = 0.00001000 +ECONOMICAL = 0.00050000 +NORMAL = 0.00100000 +HIGH = 0.00500000 +TOO_HIGH = 1.00000000 + class BumpFeeTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 @@ -37,7 +44,6 @@ class BumpFeeTest(BitcoinTestFramework): self.extra_args = [[ "-walletrbf={}".format(i), "-mintxfee=0.00002", - "-deprecatedrpc=totalFee", "-addresstype=bech32", ] for i in range(self.num_nodes)] @@ -71,34 +77,34 @@ class BumpFeeTest(BitcoinTestFramework): test_simple_bumpfee_succeeds(self, "default", rbf_node, peer_node, dest_address) test_simple_bumpfee_succeeds(self, "fee_rate", rbf_node, peer_node, dest_address) test_feerate_args(self, rbf_node, peer_node, dest_address) - test_segwit_bumpfee_succeeds(rbf_node, dest_address) - test_nonrbf_bumpfee_fails(peer_node, dest_address) - test_notmine_bumpfee_fails(rbf_node, peer_node, dest_address) - test_bumpfee_with_descendant_fails(rbf_node, rbf_node_address, dest_address) - test_small_output_fails(rbf_node, dest_address) - test_dust_to_fee(rbf_node, dest_address) - test_settxfee(rbf_node, dest_address) + test_segwit_bumpfee_succeeds(self, rbf_node, dest_address) + test_nonrbf_bumpfee_fails(self, peer_node, dest_address) + test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address) + test_bumpfee_with_descendant_fails(self, rbf_node, rbf_node_address, dest_address) + test_dust_to_fee(self, rbf_node, dest_address) + test_settxfee(self, rbf_node, dest_address) test_watchonly_psbt(self, peer_node, rbf_node, dest_address) - test_rebumping(rbf_node, dest_address) - test_rebumping_not_replaceable(rbf_node, dest_address) - test_unconfirmed_not_spendable(rbf_node, rbf_node_address) - test_bumpfee_metadata(rbf_node, dest_address) - test_locked_wallet_fails(rbf_node, dest_address) - test_change_script_match(rbf_node, dest_address) + test_rebumping(self, rbf_node, dest_address) + test_rebumping_not_replaceable(self, rbf_node, dest_address) + test_unconfirmed_not_spendable(self, rbf_node, rbf_node_address) + test_bumpfee_metadata(self, rbf_node, dest_address) + test_locked_wallet_fails(self, rbf_node, dest_address) + test_change_script_match(self, rbf_node, dest_address) test_maxtxfee_fails(self, rbf_node, dest_address) # These tests wipe out a number of utxos that are expected in other tests - test_small_output_with_feerate_succeeds(rbf_node, dest_address) - test_no_more_inputs_fails(rbf_node, dest_address) + test_small_output_with_feerate_succeeds(self, rbf_node, dest_address) + test_no_more_inputs_fails(self, rbf_node, dest_address) self.log.info("Success") def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address): + self.log.info('Test simple bumpfee: {}'.format(mode)) rbfid = spend_one_input(rbf_node, dest_address) rbftx = rbf_node.gettransaction(rbfid) self.sync_mempools((rbf_node, peer_node)) assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool() if mode == "fee_rate": - bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate":0.0015}) + bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": NORMAL}) else: bumped_tx = rbf_node.bumpfee(rbfid) assert_equal(bumped_tx["errors"], []) @@ -119,23 +125,25 @@ def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address): assert_equal(bumpedwtx["replaces_txid"], rbfid) def test_feerate_args(self, rbf_node, peer_node, dest_address): + self.log.info('Test fee_rate args') rbfid = spend_one_input(rbf_node, dest_address) self.sync_mempools((rbf_node, peer_node)) assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool() - assert_raises_rpc_error(-8, "confTarget can't be set with totalFee or fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.", rbf_node.bumpfee, rbfid, {"fee_rate":0.00001, "confTarget":1}) - assert_raises_rpc_error(-8, "confTarget can't be set with totalFee or fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.", rbf_node.bumpfee, rbfid, {"totalFee":0.00001, "confTarget":1}) - assert_raises_rpc_error(-8, "fee_rate can't be set along with totalFee.", rbf_node.bumpfee, rbfid, {"fee_rate":0.00001, "totalFee":0.001}) + assert_raises_rpc_error(-8, "confTarget can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.", rbf_node.bumpfee, rbfid, {"fee_rate": NORMAL, "confTarget": 1}) + + assert_raises_rpc_error(-3, "Unexpected key totalFee", rbf_node.bumpfee, rbfid, {"totalFee": NORMAL}) # Bumping to just above minrelay should fail to increase total fee enough, at least - assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, rbfid, {"fee_rate":0.00001000}) + assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, rbfid, {"fee_rate": INSUFFICIENT}) assert_raises_rpc_error(-3, "Amount out of range", rbf_node.bumpfee, rbfid, {"fee_rate":-1}) - assert_raises_rpc_error(-4, "is too high (cannot be higher than", rbf_node.bumpfee, rbfid, {"fee_rate":1}) + assert_raises_rpc_error(-4, "is too high (cannot be higher than", rbf_node.bumpfee, rbfid, {"fee_rate": TOO_HIGH}) -def test_segwit_bumpfee_succeeds(rbf_node, dest_address): +def test_segwit_bumpfee_succeeds(self, rbf_node, dest_address): + self.log.info('Test that segwit-sourcing bumpfee works') # Create a transaction with segwit output, then create an RBF transaction # which spends it, and make sure bumpfee can be called on it. @@ -165,14 +173,14 @@ def test_segwit_bumpfee_succeeds(rbf_node, dest_address): assert rbfid not in rbf_node.getrawmempool() -def test_nonrbf_bumpfee_fails(peer_node, dest_address): - # cannot replace a non RBF transaction (from node which did not enable RBF) +def test_nonrbf_bumpfee_fails(self, peer_node, dest_address): + self.log.info('Test that we cannot replace a non RBF transaction') not_rbfid = peer_node.sendtoaddress(dest_address, Decimal("0.00090000")) assert_raises_rpc_error(-4, "not BIP 125 replaceable", peer_node.bumpfee, not_rbfid) -def test_notmine_bumpfee_fails(rbf_node, peer_node, dest_address): - # cannot bump fee unless the tx has only inputs that we own. +def test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address): + self.log.info('Test that it cannot bump fee if non-owned inputs are included') # here, the rbftx has a peer_node coin and then adds a rbf_node input # Note that this test depends upon the RPC code checking input ownership prior to change outputs # (since it can't use fundrawtransaction, it lacks a proper change output) @@ -192,8 +200,8 @@ def test_notmine_bumpfee_fails(rbf_node, peer_node, dest_address): rbf_node.bumpfee, rbfid) -def test_bumpfee_with_descendant_fails(rbf_node, rbf_node_address, dest_address): - # cannot bump fee if the transaction has a descendant +def test_bumpfee_with_descendant_fails(self, rbf_node, rbf_node_address, dest_address): + self.log.info('Test that fee cannot be bumped when it has descendant') # parent is send-to-self, so we don't have to check which output is change when creating the child tx parent_id = spend_one_input(rbf_node, rbf_node_address) tx = rbf_node.createrawtransaction([{"txid": parent_id, "vout": 0}], {dest_address: 0.00020000}) @@ -201,15 +209,8 @@ def test_bumpfee_with_descendant_fails(rbf_node, rbf_node_address, dest_address) rbf_node.sendrawtransaction(tx["hex"]) assert_raises_rpc_error(-8, "Transaction has descendants in the wallet", rbf_node.bumpfee, parent_id) -def test_small_output_fails(rbf_node, dest_address): - # cannot bump fee with a too-small output - rbfid = spend_one_input(rbf_node, dest_address) - rbf_node.bumpfee(rbfid, {"totalFee": 50000}) - - rbfid = spend_one_input(rbf_node, dest_address) - assert_raises_rpc_error(-4, "Change output is too small", rbf_node.bumpfee, rbfid, {"totalFee": 50001}) - -def test_small_output_with_feerate_succeeds(rbf_node, dest_address): +def test_small_output_with_feerate_succeeds(self, rbf_node, dest_address): + self.log.info('Testing small output with feerate bump succeeds') # Make sure additional inputs exist rbf_node.generatetoaddress(101, rbf_node.getnewaddress()) @@ -217,9 +218,9 @@ def test_small_output_with_feerate_succeeds(rbf_node, dest_address): input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"] assert_equal(len(input_list), 1) original_txin = input_list[0] - # Keep bumping until we out-spend change output + self.log.info('Keep bumping until transaction fee out-spends non-destination value') tx_fee = 0 - while tx_fee < Decimal("0.0005"): + while True: input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"] new_item = list(input_list)[0] assert_equal(len(input_list), 1) @@ -231,7 +232,11 @@ def test_small_output_with_feerate_succeeds(rbf_node, dest_address): assert rbfid not in raw_pool assert rbfid_new in raw_pool rbfid = rbfid_new - tx_fee = rbfid_new_details["origfee"] + tx_fee = rbfid_new_details["fee"] + + # Total value from input not going to destination + if tx_fee > Decimal('0.00050000'): + break # input(s) have been added final_input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"] @@ -244,20 +249,25 @@ def test_small_output_with_feerate_succeeds(rbf_node, dest_address): rbf_node.generatetoaddress(1, rbf_node.getnewaddress()) assert_equal(rbf_node.gettransaction(rbfid)["confirmations"], 1) -def test_dust_to_fee(rbf_node, dest_address): - # check that if output is reduced to dust, it will be converted to fee - # the bumped tx sets fee=49,900, but it converts to 50,000 +def test_dust_to_fee(self, rbf_node, dest_address): + self.log.info('Test that bumped output that is dust is dropped to fee') rbfid = spend_one_input(rbf_node, dest_address) fulltx = rbf_node.getrawtransaction(rbfid, 1) - # (31-vbyte p2wpkh output size + 67-vbyte p2wpkh spend estimate) * 10k(discard_rate) / 1000 = 980 - bumped_tx = rbf_node.bumpfee(rbfid, {"totalFee": 50000 - 980}) + # size of transaction (p2wpkh, 1 input, 2 outputs): 141 vbytes + assert_equal(fulltx["vsize"], 141) + # bump with fee_rate of 0.00350000 BTC per 1000 vbytes + # expected bump fee of 141 vbytes * fee_rate 0.00350000 BTC / 1000 vbytes = 0.00049350 BTC + # but dust is dropped, so actual bump fee is 0.00050000 + bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": 0.0035}) full_bumped_tx = rbf_node.getrawtransaction(bumped_tx["txid"], 1) assert_equal(bumped_tx["fee"], Decimal("0.00050000")) assert_equal(len(fulltx["vout"]), 2) assert_equal(len(full_bumped_tx["vout"]), 1) # change output is eliminated + assert_equal(full_bumped_tx["vout"][0]['value'], Decimal("0.00050000")) -def test_settxfee(rbf_node, dest_address): +def test_settxfee(self, rbf_node, dest_address): + self.log.info('Test settxfee') assert_raises_rpc_error(-8, "txfee cannot be less than min relay tx fee", rbf_node.settxfee, Decimal('0.000005')) assert_raises_rpc_error(-8, "txfee cannot be less than wallet min fee", rbf_node.settxfee, Decimal('0.000015')) # check that bumpfee reacts correctly to the use of settxfee (paytxfee) @@ -272,17 +282,20 @@ def test_settxfee(rbf_node, dest_address): rbf_node.settxfee(Decimal("0.00000000")) # unset paytxfee -def test_maxtxfee_fails(test, rbf_node, dest_address): +def test_maxtxfee_fails(self, rbf_node, dest_address): + self.log.info('Test that bumpfee fails when it hits -matxfee') # size of bumped transaction (p2wpkh, 1 input, 2 outputs): 141 vbytes - # expected bumping feerate of 20 sats/vbyte => 141*20 sats = 0.00002820 btc - test.restart_node(1, ['-maxtxfee=0.000025'] + test.extra_args[1]) + # expected bump fee of 141 vbytes * 0.00200000 BTC / 1000 vbytes = 0.00002820 BTC + # which exceeds maxtxfee and is expected to raise + self.restart_node(1, ['-maxtxfee=0.000025'] + self.extra_args[1]) rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT) rbfid = spend_one_input(rbf_node, dest_address) assert_raises_rpc_error(-4, "Unable to create transaction: Fee exceeds maximum configured by -maxtxfee", rbf_node.bumpfee, rbfid) - test.restart_node(1, test.extra_args[1]) + self.restart_node(1, self.extra_args[1]) rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT) -def test_watchonly_psbt(test, peer_node, rbf_node, dest_address): +def test_watchonly_psbt(self, peer_node, rbf_node, dest_address): + self.log.info('Test that PSBT is returned for bumpfee in watchonly wallets') priv_rec_desc = "wpkh([00000001/84'/1'/0']tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0/*)#rweraev0" pub_rec_desc = rbf_node.getdescriptorinfo(priv_rec_desc)["descriptor"] priv_change_desc = "wpkh([00000001/84'/1'/0']tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/*)#j6uzqvuh" @@ -334,7 +347,7 @@ def test_watchonly_psbt(test, peer_node, rbf_node, dest_address): funding_address2 = watcher.getnewaddress(address_type='bech32') peer_node.sendmany("", {funding_address1: 0.001, funding_address2: 0.001}) peer_node.generate(1) - test.sync_all() + self.sync_all() # Create single-input PSBT for transaction to be bumped psbt = watcher.walletcreatefundedpsbt([], {dest_address:0.0005}, 0, {"feeRate": 0.00001}, True)['psbt'] @@ -344,7 +357,7 @@ def test_watchonly_psbt(test, peer_node, rbf_node, dest_address): assert_equal(len(watcher.decodepsbt(psbt)["tx"]["vin"]), 1) # Bump fee, obnoxiously high to add additional watchonly input - bumped_psbt = watcher.bumpfee(original_txid, {"fee_rate":0.005}) + bumped_psbt = watcher.bumpfee(original_txid, {"fee_rate": HIGH}) assert_greater_than(len(watcher.decodepsbt(bumped_psbt['psbt'])["tx"]["vin"]), 1) assert "txid" not in bumped_psbt assert_equal(bumped_psbt["origfee"], -watcher.gettransaction(original_txid)["fee"]) @@ -363,24 +376,24 @@ def test_watchonly_psbt(test, peer_node, rbf_node, dest_address): rbf_node.unloadwallet("watcher") rbf_node.unloadwallet("signer") -def test_rebumping(rbf_node, dest_address): - # check that re-bumping the original tx fails, but bumping the bumper succeeds +def test_rebumping(self, rbf_node, dest_address): + self.log.info('Test that re-bumping the original tx fails, but bumping successor works') rbfid = spend_one_input(rbf_node, dest_address) - bumped = rbf_node.bumpfee(rbfid, {"totalFee": 2000}) - assert_raises_rpc_error(-4, "already bumped", rbf_node.bumpfee, rbfid, {"totalFee": 3000}) - rbf_node.bumpfee(bumped["txid"], {"totalFee": 3000}) + bumped = rbf_node.bumpfee(rbfid, {"fee_rate": ECONOMICAL}) + assert_raises_rpc_error(-4, "already bumped", rbf_node.bumpfee, rbfid, {"fee_rate": NORMAL}) + rbf_node.bumpfee(bumped["txid"], {"fee_rate": NORMAL}) -def test_rebumping_not_replaceable(rbf_node, dest_address): - # check that re-bumping a non-replaceable bump tx fails +def test_rebumping_not_replaceable(self, rbf_node, dest_address): + self.log.info('Test that re-bumping non-replaceable fails') rbfid = spend_one_input(rbf_node, dest_address) - bumped = rbf_node.bumpfee(rbfid, {"totalFee": 10000, "replaceable": False}) + bumped = rbf_node.bumpfee(rbfid, {"fee_rate": ECONOMICAL, "replaceable": False}) assert_raises_rpc_error(-4, "Transaction is not BIP 125 replaceable", rbf_node.bumpfee, bumped["txid"], - {"totalFee": 20000}) + {"fee_rate": NORMAL}) -def test_unconfirmed_not_spendable(rbf_node, rbf_node_address): - # check that unconfirmed outputs from bumped transactions are not spendable +def test_unconfirmed_not_spendable(self, rbf_node, rbf_node_address): + self.log.info('Test that unconfirmed outputs from bumped txns are not spendable') rbfid = spend_one_input(rbf_node, rbf_node_address) rbftx = rbf_node.gettransaction(rbfid)["hex"] assert rbfid in rbf_node.getrawmempool() @@ -418,7 +431,8 @@ def test_unconfirmed_not_spendable(rbf_node, rbf_node_address): if t["txid"] == rbfid and t["address"] == rbf_node_address and t["spendable"]), 1) -def test_bumpfee_metadata(rbf_node, dest_address): +def test_bumpfee_metadata(self, rbf_node, dest_address): + self.log.info('Test that bumped txn metadata persists to new txn record') assert(rbf_node.getbalance() < 49) rbf_node.generatetoaddress(101, rbf_node.getnewaddress()) rbfid = rbf_node.sendtoaddress(dest_address, 49, "comment value", "to value") @@ -428,15 +442,17 @@ def test_bumpfee_metadata(rbf_node, dest_address): assert_equal(bumped_wtx["to"], "to value") -def test_locked_wallet_fails(rbf_node, dest_address): +def test_locked_wallet_fails(self, rbf_node, dest_address): + self.log.info('Test that locked wallet cannot bump txn') rbfid = spend_one_input(rbf_node, dest_address) rbf_node.walletlock() assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first.", rbf_node.bumpfee, rbfid) rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT) -def test_change_script_match(rbf_node, dest_address): - """Test that the same change addresses is used for the replacement transaction when possible.""" +def test_change_script_match(self, rbf_node, dest_address): + self.log.info('Test that the same change addresses is used for the replacement transaction when possible') + def get_change_address(tx): tx_details = rbf_node.getrawtransaction(tx, 1) txout_addresses = [txout['scriptPubKey']['addresses'][0] for txout in tx_details["vout"]] @@ -448,7 +464,7 @@ def test_change_script_match(rbf_node, dest_address): assert_equal(len(change_addresses), 1) # Now find that address in each subsequent tx, and no other change - bumped_total_tx = rbf_node.bumpfee(rbfid, {"totalFee": 2000}) + bumped_total_tx = rbf_node.bumpfee(rbfid, {"fee_rate": ECONOMICAL}) assert_equal(change_addresses, get_change_address(bumped_total_tx['txid'])) bumped_rate_tx = rbf_node.bumpfee(bumped_total_tx["txid"]) assert_equal(change_addresses, get_change_address(bumped_rate_tx['txid'])) @@ -480,12 +496,14 @@ def submit_block_with_tx(node, tx): node.submitblock(block.serialize().hex()) return block -def test_no_more_inputs_fails(rbf_node, dest_address): +def test_no_more_inputs_fails(self, rbf_node, dest_address): + self.log.info('Test that bumpfee fails when there are no available confirmed outputs') # feerate rbf requires confirmed outputs when change output doesn't exist or is insufficient rbf_node.generatetoaddress(1, dest_address) # spend all funds, no change output rbfid = rbf_node.sendtoaddress(rbf_node.getnewaddress(), rbf_node.getbalance(), "", "", True) assert_raises_rpc_error(-4, "Unable to create transaction: Insufficient funds", rbf_node.bumpfee, rbfid) + if __name__ == "__main__": BumpFeeTest().main() diff --git a/test/functional/wallet_bumpfee_totalfee_deprecation.py b/test/functional/wallet_bumpfee_totalfee_deprecation.py deleted file mode 100755 index b8e097c32e..0000000000 --- a/test/functional/wallet_bumpfee_totalfee_deprecation.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2019 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Test deprecation of passing `totalFee` to the bumpfee RPC.""" -from decimal import Decimal - -from test_framework.messages import BIP125_SEQUENCE_NUMBER -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_raises_rpc_error - -class BumpFeeWithTotalFeeArgumentDeprecationTest(BitcoinTestFramework): - def set_test_params(self): - self.num_nodes = 2 - self.extra_args = [[ - "-walletrbf={}".format(i), - "-mintxfee=0.00002", - ] for i in range(self.num_nodes)] - - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - - def run_test(self): - peer_node, rbf_node = self.nodes - peer_node.generate(110) - self.sync_all() - peer_node.sendtoaddress(rbf_node.getnewaddress(), 0.001) - self.sync_all() - peer_node.generate(1) - self.sync_all() - rbfid = spend_one_input(rbf_node, peer_node.getnewaddress()) - - self.log.info("Testing bumpfee with totalFee argument raises RPC error with deprecation message") - assert_raises_rpc_error( - -8, - "totalFee argument has been deprecated and will be removed in 0.20. " + - "Please use -deprecatedrpc=totalFee to continue using this argument until removal.", - rbf_node.bumpfee, rbfid, {"totalFee": 2000}) - - self.log.info("Testing bumpfee without totalFee argument does not raise") - rbf_node.bumpfee(rbfid) - -def spend_one_input(node, dest_address, change_size=Decimal("0.00049000")): - tx_input = dict(sequence=BIP125_SEQUENCE_NUMBER, - **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000"))) - destinations = {dest_address: Decimal("0.00050000")} - destinations[node.getrawchangeaddress()] = change_size - rawtx = node.createrawtransaction([tx_input], destinations) - signedtx = node.signrawtransactionwithwallet(rawtx) - txid = node.sendrawtransaction(signedtx["hex"]) - return txid - -if __name__ == "__main__": - BumpFeeWithTotalFeeArgumentDeprecationTest().main() diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py index 048b3127ff..b24d312e27 100755 --- a/test/functional/wallet_createwallet.py +++ b/test/functional/wallet_createwallet.py @@ -79,7 +79,7 @@ class CreateWalletTest(BitcoinTestFramework): 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.walletpassphrase('pass', 60) w4.sethdseed() w4.getnewaddress() w4.getrawchangeaddress() @@ -99,7 +99,7 @@ class CreateWalletTest(BitcoinTestFramework): self.nodes[0].createwallet(wallet_name='wblank', disable_private_keys=False, blank=True, passphrase='thisisapassphrase') wblank = node.get_wallet_rpc('wblank') assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", wblank.signmessage, "needanargument", "test") - wblank.walletpassphrase('thisisapassphrase', 10) + wblank.walletpassphrase('thisisapassphrase', 60) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", wblank.getnewaddress) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", wblank.getrawchangeaddress) @@ -108,7 +108,7 @@ class CreateWalletTest(BitcoinTestFramework): self.nodes[0].createwallet(wallet_name='w6', disable_private_keys=False, blank=False, passphrase='thisisapassphrase') w6 = node.get_wallet_rpc('w6') assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", w6.signmessage, "needanargument", "test") - w6.walletpassphrase('thisisapassphrase', 10) + w6.walletpassphrase('thisisapassphrase', 60) w6.signmessage(w6.getnewaddress('', 'legacy'), "test") w6.keypoolrefill(1) # There should only be 1 key @@ -119,12 +119,12 @@ class CreateWalletTest(BitcoinTestFramework): resp = self.nodes[0].createwallet(wallet_name='w7', disable_private_keys=False, blank=False, passphrase='') assert_equal(resp['warning'], 'Empty string given as passphrase, wallet will not be encrypted.') w7 = node.get_wallet_rpc('w7') - assert_raises_rpc_error(-15, 'Error: running with an unencrypted wallet, but walletpassphrase was called.', w7.walletpassphrase, '', 10) + assert_raises_rpc_error(-15, 'Error: running with an unencrypted wallet, but walletpassphrase was called.', w7.walletpassphrase, '', 60) self.log.info('Test making a wallet with avoid reuse flag') self.nodes[0].createwallet('w8', False, False, '', True) # Use positional arguments to check for bug where avoid_reuse could not be set for wallets without needing them to be encrypted w8 = node.get_wallet_rpc('w8') - assert_raises_rpc_error(-15, 'Error: running with an unencrypted wallet, but walletpassphrase was called.', w7.walletpassphrase, '', 10) + assert_raises_rpc_error(-15, 'Error: running with an unencrypted wallet, but walletpassphrase was called.', w7.walletpassphrase, '', 60) assert_equal(w8.getwalletinfo()["avoid_reuse"], True) self.log.info('Using a passphrase with private keys disabled returns error') diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py index f2fa1d3e40..261a43472b 100755 --- a/test/functional/wallet_groups.py +++ b/test/functional/wallet_groups.py @@ -11,12 +11,13 @@ from test_framework.util import ( assert_equal, ) + class WalletGroupTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 3 self.extra_args = [[], [], ['-avoidpartialspends']] - self.rpc_timeout = 240 + self.rpc_timeout = 480 def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -87,5 +88,6 @@ class WalletGroupTest(BitcoinTestFramework): # is way too big. assert self.nodes[2].sendtoaddress(address=addr2[0], amount=5) + if __name__ == '__main__': - WalletGroupTest().main () + WalletGroupTest().main() diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py index 6f248c9bd3..6d51ca6c93 100755 --- a/test/functional/wallet_listsinceblock.py +++ b/test/functional/wallet_listsinceblock.py @@ -2,7 +2,7 @@ # 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. -"""Test the listsincelast RPC.""" +"""Test the listsinceblock RPC.""" from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import BIP125_SEQUENCE_NUMBER @@ -38,6 +38,7 @@ class ListSinceBlockTest(BitcoinTestFramework): self.double_spends_filtered() def test_no_blockhash(self): + self.log.info("Test no blockhash") txid = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1) blockhash, = self.nodes[2].generate(1) blockheight = self.nodes[2].getblockheader(blockhash)['height'] @@ -63,6 +64,7 @@ class ListSinceBlockTest(BitcoinTestFramework): "transactions": txs}) def test_invalid_blockhash(self): + self.log.info("Test invalid blockhash") assert_raises_rpc_error(-5, "Block not found", self.nodes[0].listsinceblock, "42759cde25462784395a337460bde75f58e73d3f08bd31fdc3507cbac856a2c4") assert_raises_rpc_error(-5, "Block not found", self.nodes[0].listsinceblock, @@ -100,6 +102,7 @@ class ListSinceBlockTest(BitcoinTestFramework): This test only checks that [tx0] is present. ''' + self.log.info("Test reorg") # Split network into two self.split_network() @@ -108,23 +111,21 @@ class ListSinceBlockTest(BitcoinTestFramework): senttx = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1) # generate on both sides - lastblockhash = self.nodes[1].generate(6)[5] - self.nodes[2].generate(7) - self.log.info('lastblockhash=%s' % (lastblockhash)) + nodes1_last_blockhash = self.nodes[1].generate(6)[-1] + nodes2_first_blockhash = self.nodes[2].generate(7)[0] + self.log.debug("nodes[1] last blockhash = {}".format(nodes1_last_blockhash)) + self.log.debug("nodes[2] first blockhash = {}".format(nodes2_first_blockhash)) self.sync_all(self.nodes[:2]) self.sync_all(self.nodes[2:]) self.join_network() - # listsinceblock(lastblockhash) should now include tx, as seen from nodes[0] - lsbres = self.nodes[0].listsinceblock(lastblockhash) - found = False - for tx in lsbres['transactions']: - if tx['txid'] == senttx: - found = True - break - assert found + # listsinceblock(nodes1_last_blockhash) should now include tx as seen from nodes[0] + # and return the block height which listsinceblock now exposes since a5e7795. + transactions = self.nodes[0].listsinceblock(nodes1_last_blockhash)['transactions'] + found = next(tx for tx in transactions if tx['txid'] == senttx) + assert_equal(found['blockheight'], self.nodes[0].getblockheader(nodes2_first_blockhash)['height']) def test_double_spend(self): ''' @@ -155,6 +156,7 @@ class ListSinceBlockTest(BitcoinTestFramework): until the fork point, and to include all transactions that relate to the node wallet. ''' + self.log.info("Test double spend") self.sync_all() @@ -234,6 +236,7 @@ class ListSinceBlockTest(BitcoinTestFramework): 3. It is listed with a confirmation count of 2 (bb3, bb4), not 3 (aa1, aa2, aa3). ''' + self.log.info("Test double send") self.sync_all() @@ -302,6 +305,7 @@ class ListSinceBlockTest(BitcoinTestFramework): `listsinceblock` was returning conflicted transactions even if they occurred before the specified cutoff blockhash ''' + self.log.info("Test spends filtered") spending_node = self.nodes[2] dest_address = spending_node.getnewaddress() diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index f2fa41b647..78ead514a5 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -24,6 +24,7 @@ class MultiWalletTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 + self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py index 91d26e9cb3..d122e3db52 100755 --- a/test/functional/wallet_resendwallettransactions.py +++ b/test/functional/wallet_resendwallettransactions.py @@ -12,6 +12,7 @@ from test_framework.mininode import P2PInterface, mininode_lock from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, wait_until + class P2PStoreTxInvs(P2PInterface): def __init__(self): super().__init__() @@ -24,6 +25,7 @@ class P2PStoreTxInvs(P2PInterface): # save txid self.tx_invs_received[i.hash] += 1 + class ResendWalletTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 @@ -63,6 +65,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): node.submitblock(ToHex(block)) # Transaction should not be rebroadcast + node.syncwithvalidationinterfacequeue() node.p2ps[1].sync_with_ping() assert_equal(node.p2ps[1].tx_invs_received[txid], 0) @@ -72,5 +75,6 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): node.setmocktime(rebroadcast_time) wait_until(lambda: node.p2ps[1].tx_invs_received[txid] >= 1, lock=mininode_lock) + if __name__ == '__main__': ResendWalletTransactionsTest().main() diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py index 1a74c67503..1786c39c36 100755 --- a/test/fuzz/test_runner.py +++ b/test/fuzz/test_runner.py @@ -12,47 +12,12 @@ import sys import subprocess import logging -# Fuzzers known to lack a seed corpus in https://github.com/bitcoin-core/qa-assets/tree/master/fuzz_seed_corpus -FUZZERS_MISSING_CORPORA = [ - "addr_info_deserialize", - "asmap", - "base_encode_decode", - "block", - "block_file_info_deserialize", - "block_filter_deserialize", - "block_header_and_short_txids_deserialize", - "bloom_filter", - "decode_tx", - "fee_rate_deserialize", - "flat_file_pos_deserialize", - "hex", - "integer", - "key_origin_info_deserialize", - "merkle_block_deserialize", - "out_point_deserialize", - "p2p_transport_deserializer", - "parse_hd_keypath", - "parse_numbers", - "parse_script", - "parse_univalue", - "partial_merkle_tree_deserialize", - "partially_signed_transaction_deserialize", - "prefilled_transaction_deserialize", - "psbt_input_deserialize", - "psbt_output_deserialize", - "pub_key_deserialize", - "rolling_bloom_filter", - "script_deserialize", - "strprintf", - "sub_net_deserialize", - "tx_in", - "tx_in_deserialize", - "tx_out", -] - def main(): - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description='''Run the fuzz targets with all inputs from the seed_dir once.''', + ) parser.add_argument( "-l", "--loglevel", @@ -61,11 +26,6 @@ def main(): 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( '--valgrind', action='store_true', help='If true, run fuzzing binaries under the valgrind memory error detector', @@ -84,6 +44,10 @@ def main(): nargs='*', help='The target(s) to run. Default is to run all targets.', ) + parser.add_argument( + '--m_dir', + help='Merge inputs from this directory into the seed_dir. Needs /target subdirectory.', + ) args = parser.parse_args() @@ -128,13 +92,27 @@ def main(): logging.info("{} of {} detected fuzz target(s) selected: {}".format(len(test_list_selection), len(test_list_all), " ".join(test_list_selection))) + test_list_seedless = [] + for t in test_list_selection: + corpus_path = os.path.join(args.seed_dir, t) + if not os.path.exists(corpus_path) or len(os.listdir(corpus_path)) == 0: + test_list_seedless.append(t) + test_list_seedless.sort() + if test_list_seedless: + logging.info( + "Fuzzing harnesses lacking a seed corpus: {}".format( + " ".join(test_list_seedless) + ) + ) + logging.info("Please consider adding a fuzz seed corpus at https://github.com/bitcoin-core/qa-assets") + try: help_output = subprocess.run( args=[ os.path.join(config["environment"]["BUILDDIR"], 'src', 'test', 'fuzz', test_list_selection[0]), '-help=1', ], - timeout=10, + timeout=20, check=True, stderr=subprocess.PIPE, universal_newlines=True, @@ -146,20 +124,42 @@ def main(): logging.error("subprocess timed out: Currently only libFuzzer is supported") sys.exit(1) + if args.m_dir: + merge_inputs( + corpus=args.seed_dir, + test_list=test_list_selection, + build_dir=config["environment"]["BUILDDIR"], + merge_dir=args.m_dir, + ) + run_once( corpus=args.seed_dir, test_list=test_list_selection, build_dir=config["environment"]["BUILDDIR"], - export_coverage=args.export_coverage, use_valgrind=args.valgrind, ) -def run_once(*, corpus, test_list, build_dir, export_coverage, use_valgrind): +def merge_inputs(*, corpus, test_list, build_dir, merge_dir): + logging.info("Merge the inputs in the passed dir into the seed_dir. Passed dir {}".format(merge_dir)) + for t in test_list: + args = [ + os.path.join(build_dir, 'src', 'test', 'fuzz', t), + '-merge=1', + os.path.join(corpus, t), + os.path.join(merge_dir, t), + ] + os.makedirs(os.path.join(corpus, t), exist_ok=True) + os.makedirs(os.path.join(merge_dir, t), exist_ok=True) + 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)) + + +def run_once(*, corpus, test_list, build_dir, use_valgrind): for t in test_list: corpus_path = os.path.join(corpus, t) - if t in FUZZERS_MISSING_CORPORA: - os.makedirs(corpus_path, exist_ok=True) + os.makedirs(corpus_path, exist_ok=True) args = [ os.path.join(build_dir, 'src', 'test', 'fuzz', t), '-runs=1', @@ -180,13 +180,6 @@ def run_once(*, corpus, test_list, build_dir, export_coverage, use_valgrind): logging.info(e.stderr) logging.info("Target \"{}\" failed with exit code {}: {}".format(t, e.returncode, " ".join(args))) sys.exit(1) - 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): diff --git a/test/lint/extended-lint-cppcheck.sh b/test/lint/extended-lint-cppcheck.sh index 4d6c70660f..ae18d74ebf 100755 --- a/test/lint/extended-lint-cppcheck.sh +++ b/test/lint/extended-lint-cppcheck.sh @@ -66,7 +66,7 @@ function join_array { ENABLED_CHECKS_REGEXP=$(join_array "|" "${ENABLED_CHECKS[@]}") IGNORED_WARNINGS_REGEXP=$(join_array "|" "${IGNORED_WARNINGS[@]}") WARNINGS=$(git ls-files -- "*.cpp" "*.h" ":(exclude)src/leveldb/" ":(exclude)src/crc32c/" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/" | \ - xargs cppcheck --enable=all -j "$(getconf _NPROCESSORS_ONLN)" --language=c++ --std=c++11 --template=gcc -D__cplusplus -DCLIENT_VERSION_BUILD -DCLIENT_VERSION_IS_RELEASE -DCLIENT_VERSION_MAJOR -DCLIENT_VERSION_MINOR -DCLIENT_VERSION_REVISION -DCOPYRIGHT_YEAR -DDEBUG -DHAVE_WORKING_BOOST_SLEEP_FOR -I src/ -q 2>&1 | sort -u | \ + xargs cppcheck --enable=all -j "$(getconf _NPROCESSORS_ONLN)" --language=c++ --std=c++11 --template=gcc -D__cplusplus -DCLIENT_VERSION_BUILD -DCLIENT_VERSION_IS_RELEASE -DCLIENT_VERSION_MAJOR -DCLIENT_VERSION_MINOR -DCLIENT_VERSION_REVISION -DCOPYRIGHT_YEAR -DDEBUG -I src/ -q 2>&1 | sort -u | \ grep -E "${ENABLED_CHECKS_REGEXP}" | \ grep -vE "${IGNORED_WARNINGS_REGEXP}") if [[ ${WARNINGS} != "" ]]; then diff --git a/test/lint/lint-includes.sh b/test/lint/lint-includes.sh index ced2fd2bb6..1cece6a525 100755 --- a/test/lint/lint-includes.sh +++ b/test/lint/lint-includes.sh @@ -53,7 +53,6 @@ EXPECTED_BOOST_INCLUDES=( boost/algorithm/string/classification.hpp boost/algorithm/string/replace.hpp boost/algorithm/string/split.hpp - boost/chrono/chrono.hpp boost/date_time/posix_time/posix_time.hpp boost/filesystem.hpp boost/filesystem/fstream.hpp diff --git a/test/lint/lint-locale-dependence.sh b/test/lint/lint-locale-dependence.sh index 35e58c2df6..70410d7405 100755 --- a/test/lint/lint-locale-dependence.sh +++ b/test/lint/lint-locale-dependence.sh @@ -6,30 +6,16 @@ export LC_ALL=C KNOWN_VIOLATIONS=( "src/bitcoin-tx.cpp.*stoul" - "src/bitcoin-tx.cpp.*std::to_string" "src/bitcoin-tx.cpp.*trim_right" "src/dbwrapper.cpp.*stoul" "src/dbwrapper.cpp:.*vsnprintf" "src/httprpc.cpp.*trim" "src/init.cpp:.*atoi" - "src/qt/optionsmodel.cpp.*std::to_string" "src/qt/rpcconsole.cpp:.*atoi" "src/rest.cpp:.*strtol" - "src/rpc/net.cpp.*std::to_string" - "src/rpc/rawtransaction.cpp.*std::to_string" - "src/rpc/util.cpp.*std::to_string" - "src/test/addrman_tests.cpp.*std::to_string" - "src/test/blockchain_tests.cpp.*std::to_string" "src/test/dbwrapper_tests.cpp:.*snprintf" - "src/test/denialofservice_tests.cpp.*std::to_string" + "src/test/fuzz/locale.cpp" "src/test/fuzz/parse_numbers.cpp:.*atoi" - "src/test/key_tests.cpp.*std::to_string" - "src/test/net_tests.cpp.*std::to_string" - "src/test/settings_tests.cpp.*std::to_string" - "src/test/timedata_tests.cpp.*std::to_string" - "src/test/util/setup_common.cpp.*std::to_string" - "src/test/util_tests.cpp.*std::to_string" - "src/test/util_threadnames_tests.cpp.*std::to_string" "src/torcontrol.cpp:.*atoi" "src/torcontrol.cpp:.*strtol" "src/util/strencodings.cpp:.*atoi" @@ -37,7 +23,6 @@ KNOWN_VIOLATIONS=( "src/util/strencodings.cpp:.*strtoul" "src/util/strencodings.h:.*atoi" "src/util/system.cpp:.*atoi" - "src/wallet/scriptpubkeyman.cpp.*std::to_string" ) REGEXP_IGNORE_EXTERNAL_DEPENDENCIES="^src/(crypto/ctaes/|leveldb/|secp256k1/|tinyformat.h|univalue/)" diff --git a/test/lint/lint-shell.sh b/test/lint/lint-shell.sh index 63624e3ae0..f59b2c9945 100755 --- a/test/lint/lint-shell.sh +++ b/test/lint/lint-shell.sh @@ -41,7 +41,7 @@ if ! shellcheck "$EXCLUDE" $(git ls-files -- '*.sh' | grep -vE 'src/(leveldb|sec fi if ! command -v yq > /dev/null; then - echo "Skipping Gitian desriptor scripts checking since yq is not installed." + echo "Skipping Gitian descriptor scripts checking since yq is not installed." exit $EXIT_CODE fi diff --git a/test/lint/lint-spelling.ignore-words.txt b/test/lint/lint-spelling.ignore-words.txt index 576ae94098..a7a97eb41f 100644 --- a/test/lint/lint-spelling.ignore-words.txt +++ b/test/lint/lint-spelling.ignore-words.txt @@ -12,3 +12,5 @@ keyserver homogenous setban hist +ser +unselect diff --git a/test/lint/lint-submodule.sh b/test/lint/lint-submodule.sh new file mode 100755 index 0000000000..d9aa021df7 --- /dev/null +++ b/test/lint/lint-submodule.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2020 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# This script checks for git modules +export LC_ALL=C +EXIT_CODE=0 + +CMD=$(git submodule status --recursive) +if test -n "$CMD"; +then + echo These submodules were found, delete them: + echo "$CMD" + EXIT_CODE=1 +fi + +exit $EXIT_CODE + diff --git a/test/sanitizer_suppressions/tsan b/test/sanitizer_suppressions/tsan index b9c5c038d0..70eea34363 100644 --- a/test/sanitizer_suppressions/tsan +++ b/test/sanitizer_suppressions/tsan @@ -7,14 +7,6 @@ deadlock:WalletBatch # Intentional deadlock in tests deadlock:TestPotentialDeadLockDetected -# Race due to unprotected calls to thread-unsafe BOOST_TEST_MESSAGE from different threads: -# * G_TEST_LOG_FUN in the index thread -# * boost test case invoker (entering a test case) in the main thread -# TODO: get rid of BOOST_ macros, see also https://github.com/bitcoin/bitcoin/issues/8670 -race:blockfilter_index_initial_sync_invoker -race:txindex_initial_sync_invoker -race:validation_block_tests::TestSubscriber - # Wildcard for all gui tests, should be replaced with non-wildcard suppressions race:src/qt/test/* deadlock:src/qt/test/* diff --git a/test/util/data/bitcoin-util-test.json b/test/util/data/bitcoin-util-test.json index 761923a818..99cd4ab695 100644 --- a/test/util/data/bitcoin-util-test.json +++ b/test/util/data/bitcoin-util-test.json @@ -219,6 +219,12 @@ "description": "Parses a transaction with no inputs and a single output script (output in json)" }, { "exec": "./bitcoin-tx", + "args": ["-create", "outscript=0:123badscript"], + "return_code": 1, + "error_txt": "error: script parse error", + "description": "Create a new transaction with an invalid output script" + }, + { "exec": "./bitcoin-tx", "args": ["-create", "outscript=0:OP_DROP", "nversion=1"], "output_cmp": "txcreatescript1.hex", "description": "Create a new transaction with a single output script (OP_DROP)" @@ -259,6 +265,40 @@ "description": "Create a new transaction with a single output script (OP_DROP) in a P2SH, wrapped in a P2SH (output as json)" }, { "exec": "./bitcoin-tx", + "args": ["-create", "outscript=0:9999999999"], + "return_code": 1, + "error_txt": "error: script parse error: decimal numeric value only allowed in the range -0xFFFFFFFF...0xFFFFFFFF", + "description": "Try to parse an output script with a decimal number above the allowed range" + }, + { "exec": "./bitcoin-tx", + "args": ["-create", "outscript=0:4294967296"], + "return_code": 1, + "error_txt": "error: script parse error: decimal numeric value only allowed in the range -0xFFFFFFFF...0xFFFFFFFF", + "description": "Try to parse an output script with a decimal number just above the allowed range" + }, + { "exec": "./bitcoin-tx", + "args": ["-create", "outscript=0:4294967295"], + "output_cmp": "txcreatescript5.hex", + "description": "Try to parse an output script with a decimal number at the upper limit of the allowed range" + }, + { "exec": "./bitcoin-tx", + "args": ["-create", "outscript=0:-9999999999"], + "return_code": 1, + "error_txt": "error: script parse error: decimal numeric value only allowed in the range -0xFFFFFFFF...0xFFFFFFFF", + "description": "Try to parse an output script with a decimal number below the allowed range" + }, + { "exec": "./bitcoin-tx", + "args": ["-create", "outscript=0:-4294967296"], + "return_code": 1, + "error_txt": "error: script parse error: decimal numeric value only allowed in the range -0xFFFFFFFF...0xFFFFFFFF", + "description": "Try to parse an output script with a decimal number just below the allowed range" + }, + { "exec": "./bitcoin-tx", + "args": ["-create", "outscript=0:-4294967295"], + "output_cmp": "txcreatescript6.hex", + "description": "Try to parse an output script with a decimal number at the lower limit of the allowed range" + }, + { "exec": "./bitcoin-tx", "args": ["-create", "nversion=1", "in=4d49a71ec9da436f71ec4ee231d04f292a29cd316f598bb7068feccabdc59485:0", diff --git a/test/util/data/txcreatescript5.hex b/test/util/data/txcreatescript5.hex new file mode 100644 index 0000000000..48e0a12b0c --- /dev/null +++ b/test/util/data/txcreatescript5.hex @@ -0,0 +1 @@ +02000000000100000000000000000605ffffffff0000000000 diff --git a/test/util/data/txcreatescript6.hex b/test/util/data/txcreatescript6.hex new file mode 100644 index 0000000000..b98293813d --- /dev/null +++ b/test/util/data/txcreatescript6.hex @@ -0,0 +1 @@ +02000000000100000000000000000605ffffffff8000000000 |