diff options
Diffstat (limited to 'test/functional')
43 files changed, 820 insertions, 326 deletions
diff --git a/test/functional/combine_logs.py b/test/functional/combine_logs.py index 45ecaabe14..5364ac4b8c 100755 --- a/test/functional/combine_logs.py +++ b/test/functional/combine_logs.py @@ -8,6 +8,7 @@ If no argument is provided, the most recent test directory will be used.""" import argparse from collections import defaultdict, namedtuple +import glob import heapq import itertools import os @@ -76,9 +77,17 @@ def read_logs(tmp_dir): Delegates to generator function get_log_events() to provide individual log events for each of the input log files.""" + # Find out what the folder is called that holds the debug.log file + chain = glob.glob("{}/node0/*/debug.log".format(tmp_dir)) + if chain: + chain = chain[0] # pick the first one if more than one chain was found (should never happen) + chain = re.search('node0/(.+?)/debug\.log$', chain).group(1) # extract the chain name + else: + chain = 'regtest' # fallback to regtest (should only happen when none exists) + files = [("test", "%s/test_framework.log" % tmp_dir)] for i in itertools.count(): - logfile = "{}/node{}/regtest/debug.log".format(tmp_dir, i) + logfile = "{}/node{}/{}/debug.log".format(tmp_dir, i, chain) if not os.path.isfile(logfile): break files.append(("node%d" % i, logfile)) @@ -164,25 +173,26 @@ def get_log_events(source, logfile): def print_logs_plain(log_events, colors): - """Renders the iterator of log events into text.""" - for event in log_events: - lines = event.event.splitlines() - print("{0} {1: <5} {2} {3}".format(colors[event.source.rstrip()], event.source, lines[0], colors["reset"])) - if len(lines) > 1: - for line in lines[1:]: - print("{0}{1}{2}".format(colors[event.source.rstrip()], line, colors["reset"])) + """Renders the iterator of log events into text.""" + for event in log_events: + lines = event.event.splitlines() + print("{0} {1: <5} {2} {3}".format(colors[event.source.rstrip()], event.source, lines[0], colors["reset"])) + if len(lines) > 1: + for line in lines[1:]: + print("{0}{1}{2}".format(colors[event.source.rstrip()], line, colors["reset"])) def print_logs_html(log_events): - """Renders the iterator of log events into html.""" - try: - import jinja2 - except ImportError: - print("jinja2 not found. Try `pip install jinja2`") - sys.exit(1) - print(jinja2.Environment(loader=jinja2.FileSystemLoader('./')) + """Renders the iterator of log events into html.""" + try: + import jinja2 + except ImportError: + print("jinja2 not found. Try `pip install jinja2`") + sys.exit(1) + print(jinja2.Environment(loader=jinja2.FileSystemLoader('./')) .get_template('combined_log_template.html') .render(title="Combined Logs from testcase", log_events=[event._asdict() for event in log_events])) + if __name__ == '__main__': main() diff --git a/test/functional/example_test.py b/test/functional/example_test.py index a2726763d0..70dfe81d4e 100755 --- a/test/functional/example_test.py +++ b/test/functional/example_test.py @@ -186,12 +186,15 @@ class ExampleTest(BitcoinTestFramework): self.log.info("Connect node2 and node1") connect_nodes(self.nodes[1], 2) + self.log.info("Wait for node2 to receive all the blocks from node1") + self.sync_all() + self.log.info("Add P2P connection to node2") self.nodes[0].disconnect_p2ps() self.nodes[2].add_p2p_connection(BaseNode()) - self.log.info("Wait for node2 reach current tip. Test that it has propagated all the blocks to us") + self.log.info("Test that node2 propagates all the blocks to us") getdata_request = msg_getdata() for block in blocks: diff --git a/test/functional/feature_abortnode.py b/test/functional/feature_abortnode.py new file mode 100755 index 0000000000..62c3eca07d --- /dev/null +++ b/test/functional/feature_abortnode.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test bitcoind aborts if can't disconnect a block. + +- Start a single node and generate 3 blocks. +- Delete the undo data. +- Mine a fork that requires disconnecting the tip. +- Verify that bitcoind AbortNode's. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import wait_until, get_datadir_path, connect_nodes +import os + +class AbortNodeTest(BitcoinTestFramework): + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + + def setup_network(self): + self.setup_nodes() + # We'll connect the nodes later + + def run_test(self): + self.nodes[0].generate(3) + datadir = get_datadir_path(self.options.tmpdir, 0) + + # Deleting the undo file will result in reorg failure + os.unlink(os.path.join(datadir, 'regtest', 'blocks', 'rev00000.dat')) + + # Connecting to a node with a more work chain will trigger a reorg + # attempt. + self.nodes[1].generate(3) + with self.nodes[0].assert_debug_log(["Failed to disconnect block"]): + connect_nodes(self.nodes[0], 1) + self.nodes[1].generate(1) + + # Check that node0 aborted + self.log.info("Waiting for crash") + wait_until(lambda: self.nodes[0].is_node_stopped(), timeout=60) + self.log.info("Node crashed - now verifying restart fails") + self.nodes[0].assert_start_raises_init_error() + +if __name__ == '__main__': + AbortNodeTest().main() diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py index b7814bf33e..420a3a7688 100755 --- a/test/functional/feature_assumevalid.py +++ b/test/functional/feature_assumevalid.py @@ -72,7 +72,7 @@ class AssumeValidTest(BitcoinTestFramework): break try: p2p_conn.send_message(msg_block(self.blocks[i])) - except IOError as e: + except IOError: assert not p2p_conn.is_connected break diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py index d38eca6cbe..f0bf09e172 100755 --- a/test/functional/feature_bip68_sequence.py +++ b/test/functional/feature_bip68_sequence.py @@ -29,7 +29,10 @@ NOT_FINAL_ERROR = "non-BIP68-final (code 64)" class BIP68Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - self.extra_args = [[], ["-acceptnonstdtxn=0"]] + self.extra_args = [ + ["-acceptnonstdtxn=1"], + ["-acceptnonstdtxn=0"], + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 2f57d99f7c..12a52935ef 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -82,7 +82,7 @@ class FullBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True - self.extra_args = [[]] + self.extra_args = [['-acceptnonstdtxn=1']] # This is a consensus block test, we don't care about tx policy def run_test(self): node = self.nodes[0] # convenience reference to the node @@ -497,6 +497,14 @@ class FullBlockTest(BitcoinTestFramework): tx_last = tx_new b39_outputs += 1 + # The accounting in the loop above can be off, because it misses the + # compact size encoding of the number of transactions in the block. + # Make sure we didn't accidentally make too big a block. Note that the + # size of the block has non-determinism due to the ECDSA signature in + # the first transaction. + while (len(b39.serialize()) >= MAX_BLOCK_BASE_SIZE): + del b39.vtx[-1] + b39 = self.update_block(39, []) self.send_blocks([b39], True) self.save_spendable_output() diff --git a/test/functional/feature_blocksdir.py b/test/functional/feature_blocksdir.py index 3a4889bbe9..6f01f97ea2 100755 --- a/test/functional/feature_blocksdir.py +++ b/test/functional/feature_blocksdir.py @@ -18,10 +18,10 @@ class BlocksdirTest(BitcoinTestFramework): def run_test(self): self.stop_node(0) - assert os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest", "blocks")) + assert os.path.isdir(os.path.join(self.nodes[0].datadir, self.chain, "blocks")) assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "blocks")) shutil.rmtree(self.nodes[0].datadir) - initialize_datadir(self.options.tmpdir, 0) + initialize_datadir(self.options.tmpdir, 0, self.chain) self.log.info("Starting with nonexistent blocksdir ...") blocksdir_path = os.path.join(self.options.tmpdir, 'blocksdir') self.nodes[0].assert_start_raises_init_error(["-blocksdir=" + blocksdir_path], 'Error: Specified blocks directory "{}" does not exist.'.format(blocksdir_path)) @@ -30,8 +30,8 @@ class BlocksdirTest(BitcoinTestFramework): self.start_node(0, ["-blocksdir=" + blocksdir_path]) self.log.info("mining blocks..") self.nodes[0].generatetoaddress(10, self.nodes[0].get_deterministic_priv_key().address) - assert os.path.isfile(os.path.join(blocksdir_path, "regtest", "blocks", "blk00000.dat")) - assert os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest", "blocks", "index")) + assert os.path.isfile(os.path.join(blocksdir_path, self.chain, "blocks", "blk00000.dat")) + assert os.path.isdir(os.path.join(self.nodes[0].datadir, self.chain, "blocks", "index")) if __name__ == '__main__': diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index 7712e8bdf6..af34f9f0db 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -57,7 +57,11 @@ def cltv_validate(node, tx, height): class BIP65Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - self.extra_args = [['-whitelist=127.0.0.1', '-par=1']] # Use only one script thread to get the exact reject reason for testing + self.extra_args = [[ + '-whitelist=127.0.0.1', + '-par=1', # Use only one script thread to get the exact reject reason for testing + '-acceptnonstdtxn=1', # cltv_invalidate is nonstandard + ]] self.setup_clean_chain = True self.rpc_timeout = 120 diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index f0d6bc21e6..70a824b863 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -22,7 +22,7 @@ class ConfArgsTest(BitcoinTestFramework): conf.write('includeconf={}\n'.format(inc_conf_file_path)) self.nodes[0].assert_start_raises_init_error( - expected_msg='Error parsing command line arguments: Invalid parameter -dash_cli', + expected_msg='Error: Error parsing command line arguments: Invalid parameter -dash_cli', extra_args=['-dash_cli=1'], ) with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: @@ -33,27 +33,33 @@ class ConfArgsTest(BitcoinTestFramework): with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('-dash=1\n') - self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 1: -dash=1, options in configuration file must be specified without leading -') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 1: -dash=1, options in configuration file must be specified without leading -') - with open(inc_conf_file_path, 'w', encoding='utf8') as conf: - conf.write("wallet=foo\n") - self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Config setting for -wallet only applied on regtest network when in [regtest] section.') + if self.is_wallet_compiled(): + with open(inc_conf_file_path, 'w', encoding='utf8') as conf: + conf.write("wallet=foo\n") + self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Config setting for -wallet only applied on regtest network when in [regtest] section.') + + with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: + conf.write('regtest=0\n') # mainnet + conf.write('acceptnonstdtxn=1\n') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error: acceptnonstdtxn is not currently supported for main chain') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('nono\n') - self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 1: nono, if you intended to specify a negated option, use nono=1 instead') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 1: nono, if you intended to specify a negated option, use nono=1 instead') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('server=1\nrpcuser=someuser\nrpcpassword=some#pass') - self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 3, using # in rpcpassword can be ambiguous and should be avoided') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 3, using # in rpcpassword can be ambiguous and should be avoided') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('server=1\nrpcuser=someuser\nmain.rpcpassword=some#pass') - self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 3, using # in rpcpassword can be ambiguous and should be avoided') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 3, using # in rpcpassword can be ambiguous and should be avoided') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('server=1\nrpcuser=someuser\n[main]\nrpcpassword=some#pass') - self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 4, using # in rpcpassword can be ambiguous and should be avoided') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 4, using # in rpcpassword can be ambiguous and should be avoided') inc_conf_file2_path = os.path.join(self.nodes[0].datadir, 'include2.conf') with open(os.path.join(self.nodes[0].datadir, 'bitcoin.conf'), 'a', encoding='utf-8') as conf: diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index 62062926a6..ff014de0e0 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -30,17 +30,27 @@ import http.client import random import time -from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut, ToHex +from test_framework.messages import ( + COIN, + COutPoint, + CTransaction, + CTxIn, + CTxOut, + ToHex, +) from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, create_confirmed_utxos, hex_str_to_bytes +from test_framework.util import ( + assert_equal, + create_confirmed_utxos, + hex_str_to_bytes, +) class ChainstateWriteCrashTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = False - # Need a bit of extra time for the nodes to start up for this test - self.rpc_timeout = 90 + self.rpc_timeout = 180 # Set -maxmempool=0 to turn off mempool memory sharing with dbcache # Set -rpcservertimeout=900 to reduce socket disconnects in this @@ -54,7 +64,8 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): self.node2_args = ["-dbcrashratio=24", "-dbcache=16"] + self.base_args # Node3 is a normal node with default args, except will mine full blocks - self.node3_args = ["-blockmaxweight=4000000"] + # and non-standard txs (e.g. txs with "dust" outputs) + self.node3_args = ["-blockmaxweight=4000000", "-acceptnonstdtxn"] self.extra_args = [self.node0_args, self.node1_args, self.node2_args, self.node3_args] def skip_test_if_missing_module(self): @@ -267,7 +278,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): # Warn if any of the nodes escaped restart. for i in range(3): if self.restart_counts[i] == 0: - self.log.warn("Node %d never crashed during utxo flush!", i) + self.log.warning("Node %d never crashed during utxo flush!", i) if __name__ == "__main__": ChainstateWriteCrashTest().main() diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index a4b9f213a1..071f7d5cca 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -120,9 +120,16 @@ def check_estimates(node, fees_seen): else: assert_greater_than_or_equal(i + 1, e["blocks"]) + class EstimateFeeTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 3 + # mine non-standard txs (e.g. txs with "dust" outputs) + self.extra_args = [ + ["-acceptnonstdtxn", "-maxorphantx=1000", "-whitelist=127.0.0.1"], + ["-acceptnonstdtxn", "-blockmaxweight=68000", "-maxorphantx=1000"], + ["-acceptnonstdtxn", "-blockmaxweight=32000", "-maxorphantx=1000"], + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -133,9 +140,7 @@ class EstimateFeeTest(BitcoinTestFramework): But first we need to use one node to create a lot of outputs which we will use to generate our transactions. """ - self.add_nodes(3, extra_args=[["-maxorphantx=1000", "-whitelist=127.0.0.1"], - ["-blockmaxweight=68000", "-maxorphantx=1000"], - ["-blockmaxweight=32000", "-maxorphantx=1000"]]) + self.add_nodes(3, extra_args=self.extra_args) # Use node0 to mine blocks for input splitting # Node1 mines small blocks but that are bigger than the expected transaction rate. # NOTE: the CreateNewBlock code starts counting block weight at 4,000 weight, diff --git a/test/functional/feature_includeconf.py b/test/functional/feature_includeconf.py index d06f6826f0..2cd6a05d08 100755 --- a/test/functional/feature_includeconf.py +++ b/test/functional/feature_includeconf.py @@ -43,7 +43,7 @@ class IncludeConfTest(BitcoinTestFramework): self.log.info("-includeconf cannot be used as command-line arg") self.stop_node(0) - self.nodes[0].assert_start_raises_init_error(extra_args=["-includeconf=relative2.conf"], expected_msg="Error parsing command line arguments: -includeconf cannot be used from commandline; -includeconf=relative2.conf") + self.nodes[0].assert_start_raises_init_error(extra_args=["-includeconf=relative2.conf"], expected_msg="Error: Error parsing command line arguments: -includeconf cannot be used from commandline; -includeconf=relative2.conf") self.log.info("-includeconf cannot be used recursively. subversion should end with 'main; relative)/'") with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "a", encoding="utf8") as f: @@ -59,11 +59,11 @@ class IncludeConfTest(BitcoinTestFramework): # Commented out as long as we ignore invalid arguments in configuration files #with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: # f.write("foo=bar\n") - #self.nodes[0].assert_start_raises_init_error(expected_msg="Error reading configuration file: Invalid configuration value foo") + #self.nodes[0].assert_start_raises_init_error(expected_msg="Error: Error reading configuration file: Invalid configuration value foo") self.log.info("-includeconf cannot be invalid path") os.remove(os.path.join(self.options.tmpdir, "node0", "relative.conf")) - self.nodes[0].assert_start_raises_init_error(expected_msg="Error reading configuration file: Failed to include configuration file relative.conf") + self.nodes[0].assert_start_raises_init_error(expected_msg="Error: Error reading configuration file: Failed to include configuration file relative.conf") self.log.info("multiple -includeconf args can be used from the base config file. subversion should end with 'main; relative; relative2)/'") with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py index 87c318de9a..180ea0e51d 100755 --- a/test/functional/feature_maxuploadtarget.py +++ b/test/functional/feature_maxuploadtarget.py @@ -35,7 +35,7 @@ class MaxUploadTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 - self.extra_args = [["-maxuploadtarget=800"]] + self.extra_args = [["-maxuploadtarget=800", "-acceptnonstdtxn=1"]] # Cache for utxos, as the listunspent may take a long time later in the test self.utxo_cache = [] diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index 66c395d7a2..727f4b9589 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -204,6 +204,7 @@ class PruneTest(BitcoinTestFramework): self.log.info("Mine 220 more large blocks so we have requisite history") mine_large_blocks(self.nodes[0], 220) + self.sync_blocks(self.nodes[0:3], timeout=120) usage = calc_usage(self.prunedir) self.log.info("Usage should be below target: %d" % usage) diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 1496c5d958..fd79df0b07 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -67,8 +67,8 @@ class ReplaceByFeeTest(BitcoinTestFramework): self.num_nodes = 1 self.extra_args = [ [ + "-acceptnonstdtxn=1", "-maxorphantx=1000", - "-whitelist=127.0.0.1", "-limitancestorcount=50", "-limitancestorsize=101", "-limitdescendantcount=200", diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index 2d4dd96a1d..a71d4071d5 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -53,17 +53,20 @@ class SegWitTest(BitcoinTestFramework): # This test tests SegWit both pre and post-activation, so use the normal BIP9 activation. self.extra_args = [ [ + "-acceptnonstdtxn=1", "-rpcserialversion=0", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", ], [ + "-acceptnonstdtxn=1", "-blockversion=4", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", ], [ + "-acceptnonstdtxn=1", "-blockversion=536870915", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index 7bb7044cc0..0a378c5ef5 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -29,7 +29,7 @@ class TestBitcoinCli(BitcoinTestFramework): rpc_response = self.nodes[0].getblockchaininfo() assert_equal(cli_response, rpc_response) - user, password = get_auth_cookie(self.nodes[0].datadir) + user, password = get_auth_cookie(self.nodes[0].datadir, self.chain) self.log.info("Test -stdinrpcpass option") assert_equal(0, self.nodes[0].cli('-rpcuser=%s' % user, '-stdinrpcpass', input=password).getblockcount()) diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index 2bb5d8ab7d..209a222004 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -36,7 +36,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.num_nodes = 1 self.extra_args = [[ '-txindex', - '-acceptnonstdtxn=0', # Try to mimic main-net ]] * self.num_nodes def skip_test_if_missing_module(self): diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py index 351b27e94a..edf2069933 100755 --- a/test/functional/mempool_limit.py +++ b/test/functional/mempool_limit.py @@ -13,7 +13,11 @@ class MempoolLimitTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 - self.extra_args = [["-maxmempool=5", "-spendzeroconfchange=0"]] + self.extra_args = [[ + "-acceptnonstdtxn=1", + "-maxmempool=5", + "-spendzeroconfchange=0", + ]] def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py new file mode 100755 index 0000000000..30f851fb8e --- /dev/null +++ b/test/functional/mempool_package_onemore.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test descendant package tracking carve-out allowing one final transaction in + an otherwise-full package as long as it has only one parent and is <= 10k in + size. +""" + +from decimal import Decimal + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round + +MAX_ANCESTORS = 25 +MAX_DESCENDANTS = 25 + +class MempoolPackagesTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.extra_args = [["-maxorphantx=1000"]] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + # Build a transaction that spends parent_txid:vout + # Return amount sent + def chain_transaction(self, node, parent_txids, vouts, value, fee, num_outputs): + send_value = satoshi_round((value - fee)/num_outputs) + inputs = [] + for (txid, vout) in zip(parent_txids, vouts): + inputs.append({'txid' : txid, 'vout' : vout}) + outputs = {} + for i in range(num_outputs): + outputs[node.getnewaddress()] = send_value + rawtx = node.createrawtransaction(inputs, outputs) + signedtx = node.signrawtransactionwithwallet(rawtx) + txid = node.sendrawtransaction(signedtx['hex']) + fulltx = node.getrawtransaction(txid, 1) + assert len(fulltx['vout']) == num_outputs # make sure we didn't generate a change output + return (txid, send_value) + + def run_test(self): + # Mine some blocks and have them mature. + self.nodes[0].generate(101) + utxo = self.nodes[0].listunspent(10) + txid = utxo[0]['txid'] + vout = utxo[0]['vout'] + value = utxo[0]['amount'] + + fee = Decimal("0.0002") + # MAX_ANCESTORS transactions off a confirmed tx should be fine + chain = [] + for _ in range(4): + (txid, sent_value) = self.chain_transaction(self.nodes[0], [txid], [vout], value, fee, 2) + vout = 0 + value = sent_value + chain.append([txid, value]) + for _ in range(MAX_ANCESTORS - 4): + (txid, sent_value) = self.chain_transaction(self.nodes[0], [txid], [0], value, fee, 1) + value = sent_value + chain.append([txid, value]) + (second_chain, second_chain_value) = self.chain_transaction(self.nodes[0], [utxo[1]['txid']], [utxo[1]['vout']], utxo[1]['amount'], fee, 1) + + # Check mempool has MAX_ANCESTORS + 1 transactions in it + assert_equal(len(self.nodes[0].getrawmempool(True)), MAX_ANCESTORS + 1) + + # Adding one more transaction on to the chain should fail. + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", self.chain_transaction, self.nodes[0], [txid], [0], value, fee, 1) + # ...even if it chains on from some point in the middle of the chain. + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[2][0]], [1], chain[2][1], fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[1][0]], [1], chain[1][1], fee, 1) + # ...even if it chains on to two parent transactions with one in the chain. + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[0][0], second_chain], [1, 0], chain[0][1] + second_chain_value, fee, 1) + # ...especially if its > 40k weight + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 350) + # But not if it chains directly off the first transaction + self.chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1) + # and the second chain should work just fine + self.chain_transaction(self.nodes[0], [second_chain], [0], second_chain_value, fee, 1) + + # Finally, check that we added two transactions + assert_equal(len(self.nodes[0].getrawmempool(True)), MAX_ANCESTORS + 3) + +if __name__ == '__main__': + MempoolPackagesTest().main() diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py index b0a069be81..7e05a8e6c8 100755 --- a/test/functional/mining_prioritisetransaction.py +++ b/test/functional/mining_prioritisetransaction.py @@ -14,7 +14,10 @@ class PrioritiseTransactionTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 - self.extra_args = [["-printpriority=1"], ["-printpriority=1"]] + self.extra_args = [[ + "-printpriority=1", + "-acceptnonstdtxn=1", + ]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index 0994857912..eb3336bd3b 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -95,6 +95,9 @@ class CompactBlocksTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 + self.extra_args = [[ + "-acceptnonstdtxn=1", + ]] self.utxos = [] def skip_test_if_missing_module(self): diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py index 481d697e63..d5c68e622b 100755 --- a/test/functional/p2p_invalid_messages.py +++ b/test/functional/p2p_invalid_messages.py @@ -6,6 +6,7 @@ import asyncio import os import struct +import sys from test_framework import messages from test_framework.mininode import P2PDataStore, NetworkThread @@ -92,18 +93,25 @@ class InvalidMessagesTest(BitcoinTestFramework): # # Send an oversized message, ensure we're disconnected. # - msg_over_size = msg_unrecognized(str_data="b" * (valid_data_limit + 1)) - assert len(msg_over_size.serialize()) == (msg_limit + 1) + # Under macOS this test is skipped due to an unexpected error code + # returned from the closing socket which python/asyncio does not + # yet know how to handle. + # + if sys.platform != 'darwin': + msg_over_size = msg_unrecognized(str_data="b" * (valid_data_limit + 1)) + assert len(msg_over_size.serialize()) == (msg_limit + 1) - with node.assert_debug_log(["Oversized message from peer=4, disconnecting"]): - # An unknown message type (or *any* message type) over - # MAX_PROTOCOL_MESSAGE_LENGTH should result in a disconnect. - node.p2p.send_message(msg_over_size) - node.p2p.wait_for_disconnect(timeout=4) + with node.assert_debug_log(["Oversized message from peer=4, disconnecting"]): + # An unknown message type (or *any* message type) over + # MAX_PROTOCOL_MESSAGE_LENGTH should result in a disconnect. + node.p2p.send_message(msg_over_size) + node.p2p.wait_for_disconnect(timeout=4) - node.disconnect_p2ps() - conn = node.add_p2p_connection(P2PDataStore()) - conn.wait_for_verack() + node.disconnect_p2ps() + conn = node.add_p2p_connection(P2PDataStore()) + conn.wait_for_verack() + else: + self.log.info("Skipping test p2p_invalid_messages/1 (oversized message) under macOS") # # 2. diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py index 1b18dd3e58..3cca2d78db 100755 --- a/test/functional/p2p_invalid_tx.py +++ b/test/functional/p2p_invalid_tx.py @@ -25,6 +25,9 @@ from data import invalid_txs class InvalidTxRequestTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 + self.extra_args = [[ + "-acceptnonstdtxn=1", + ]] self.setup_clean_chain = True def bootstrap_p2p(self, *, num_connections=1): diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py index 573d5f5a5f..a4650df8ee 100755 --- a/test/functional/p2p_node_network_limited.py +++ b/test/functional/p2p_node_network_limited.py @@ -8,7 +8,7 @@ Tests that a node configured with -prune=550 signals NODE_NETWORK_LIMITED correc and that it responds to getdata requests for blocks correctly: - send a block within 288 + 2 of the tip - disconnect peers who request blocks older than that.""" -from test_framework.messages import CInv, msg_getdata, msg_verack, NODE_BLOOM, NODE_NETWORK_LIMITED, NODE_WITNESS +from test_framework.messages import CInv, msg_getdata, msg_verack, NODE_NETWORK_LIMITED, NODE_WITNESS from test_framework.mininode import P2PInterface, mininode_lock from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -55,7 +55,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): def run_test(self): node = self.nodes[0].add_p2p_connection(P2PIgnoreInv()) - expected_services = NODE_BLOOM | NODE_WITNESS | NODE_NETWORK_LIMITED + expected_services = NODE_WITNESS | NODE_NETWORK_LIMITED self.log.info("Check that node has signalled expected services.") assert_equal(node.nServices, expected_services) @@ -83,7 +83,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): node1.wait_for_addr() #must relay address with NODE_NETWORK_LIMITED - assert_equal(node1.firstAddrnServices, 1036) + assert_equal(node1.firstAddrnServices, expected_services) self.nodes[0].disconnect_p2ps() node1.wait_for_disconnect() diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index b7fa42f593..dca71aec43 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -184,7 +184,11 @@ class SegWitTest(BitcoinTestFramework): self.setup_clean_chain = True self.num_nodes = 3 # This test tests SegWit both pre and post-activation, so use the normal BIP9 activation. - self.extra_args = [["-whitelist=127.0.0.1", "-vbparams=segwit:0:999999999999"], ["-whitelist=127.0.0.1", "-acceptnonstdtxn=0", "-vbparams=segwit:0:999999999999"], ["-whitelist=127.0.0.1", "-vbparams=segwit:0:0"]] + self.extra_args = [ + ["-whitelist=127.0.0.1", "-acceptnonstdtxn=1", "-vbparams=segwit:0:999999999999"], + ["-whitelist=127.0.0.1", "-acceptnonstdtxn=0", "-vbparams=segwit:0:999999999999"], + ["-whitelist=127.0.0.1", "-acceptnonstdtxn=1", "-vbparams=segwit:0:0"], + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py new file mode 100755 index 0000000000..19d78ff303 --- /dev/null +++ b/test/functional/p2p_tx_download.py @@ -0,0 +1,175 @@ +#!/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 transaction download behavior +""" + +from test_framework.messages import ( + CInv, + CTransaction, + FromHex, + MSG_TX, + MSG_TYPE_MASK, + msg_inv, + msg_notfound, +) +from test_framework.mininode import ( + P2PInterface, + mininode_lock, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + wait_until, +) +from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE + +import time + + +class TestP2PConn(P2PInterface): + def __init__(self): + super().__init__() + self.tx_getdata_count = 0 + + def on_getdata(self, message): + for i in message.inv: + if i.type & MSG_TYPE_MASK == MSG_TX: + self.tx_getdata_count += 1 + + +# Constants from net_processing +GETDATA_TX_INTERVAL = 60 # seconds +MAX_GETDATA_RANDOM_DELAY = 2 # seconds +INBOUND_PEER_TX_DELAY = 2 # seconds +MAX_GETDATA_IN_FLIGHT = 100 +TX_EXPIRY_INTERVAL = GETDATA_TX_INTERVAL * 10 + +# Python test constants +NUM_INBOUND = 10 +MAX_GETDATA_INBOUND_WAIT = GETDATA_TX_INTERVAL + MAX_GETDATA_RANDOM_DELAY + INBOUND_PEER_TX_DELAY + + +class TxDownloadTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 2 + + def test_tx_requests(self): + self.log.info("Test that we request transactions from all our peers, eventually") + + txid = 0xdeadbeef + + self.log.info("Announce the txid from each incoming peer to node 0") + msg = msg_inv([CInv(t=1, h=txid)]) + for p in self.nodes[0].p2ps: + p.send_message(msg) + p.sync_with_ping() + + outstanding_peer_index = [i for i in range(len(self.nodes[0].p2ps))] + + def getdata_found(peer_index): + p = self.nodes[0].p2ps[peer_index] + with mininode_lock: + return p.last_message.get("getdata") and p.last_message["getdata"].inv[-1].hash == txid + + node_0_mocktime = int(time.time()) + while outstanding_peer_index: + node_0_mocktime += MAX_GETDATA_INBOUND_WAIT + self.nodes[0].setmocktime(node_0_mocktime) + wait_until(lambda: any(getdata_found(i) for i in outstanding_peer_index)) + for i in outstanding_peer_index: + if getdata_found(i): + outstanding_peer_index.remove(i) + + self.nodes[0].setmocktime(0) + self.log.info("All outstanding peers received a getdata") + + def test_inv_block(self): + self.log.info("Generate a transaction on node 0") + tx = self.nodes[0].createrawtransaction( + inputs=[{ # coinbase + "txid": self.nodes[0].getblock(self.nodes[0].getblockhash(1))['tx'][0], + "vout": 0 + }], + outputs={ADDRESS_BCRT1_UNSPENDABLE: 50 - 0.00025}, + ) + tx = self.nodes[0].signrawtransactionwithkey( + hexstring=tx, + privkeys=[self.nodes[0].get_deterministic_priv_key().key], + )['hex'] + ctx = FromHex(CTransaction(), tx) + txid = int(ctx.rehash(), 16) + + self.log.info( + "Announce the transaction to all nodes from all {} incoming peers, but never send it".format(NUM_INBOUND)) + msg = msg_inv([CInv(t=1, h=txid)]) + for p in self.peers: + p.send_message(msg) + p.sync_with_ping() + + self.log.info("Put the tx in node 0's mempool") + self.nodes[0].sendrawtransaction(tx) + + # Since node 1 is connected outbound to an honest peer (node 0), it + # should get the tx within a timeout. (Assuming that node 0 + # announced the tx within the timeout) + # The timeout is the sum of + # * the worst case until the tx is first requested from an inbound + # peer, plus + # * the first time it is re-requested from the outbound peer, plus + # * 2 seconds to avoid races + timeout = 2 + (MAX_GETDATA_RANDOM_DELAY + INBOUND_PEER_TX_DELAY) + ( + GETDATA_TX_INTERVAL + MAX_GETDATA_RANDOM_DELAY) + self.log.info("Tx should be received at node 1 after {} seconds".format(timeout)) + self.sync_mempools(timeout=timeout) + + def test_in_flight_max(self): + self.log.info("Test that we don't request more than {} transactions from any peer, every {} minutes".format( + MAX_GETDATA_IN_FLIGHT, TX_EXPIRY_INTERVAL / 60)) + txids = [i for i in range(MAX_GETDATA_IN_FLIGHT + 2)] + + p = self.nodes[0].p2ps[0] + + with mininode_lock: + p.tx_getdata_count = 0 + + p.send_message(msg_inv([CInv(t=1, h=i) for i in txids])) + wait_until(lambda: p.tx_getdata_count >= MAX_GETDATA_IN_FLIGHT, lock=mininode_lock) + with mininode_lock: + assert_equal(p.tx_getdata_count, MAX_GETDATA_IN_FLIGHT) + + self.log.info("Now check that if we send a NOTFOUND for a transaction, we'll get one more request") + p.send_message(msg_notfound(vec=[CInv(t=1, h=txids[0])])) + wait_until(lambda: p.tx_getdata_count >= MAX_GETDATA_IN_FLIGHT + 1, timeout=10, lock=mininode_lock) + with mininode_lock: + assert_equal(p.tx_getdata_count, MAX_GETDATA_IN_FLIGHT + 1) + + WAIT_TIME = TX_EXPIRY_INTERVAL // 2 + TX_EXPIRY_INTERVAL + self.log.info("if we wait about {} minutes, we should eventually get more requests".format(WAIT_TIME / 60)) + self.nodes[0].setmocktime(int(time.time() + WAIT_TIME)) + wait_until(lambda: p.tx_getdata_count == MAX_GETDATA_IN_FLIGHT + 2) + self.nodes[0].setmocktime(0) + + def run_test(self): + # Setup the p2p connections + self.peers = [] + for node in self.nodes: + for i in range(NUM_INBOUND): + self.peers.append(node.add_p2p_connection(TestP2PConn())) + + self.log.info("Nodes are setup with {} incoming connections each".format(NUM_INBOUND)) + + # Test the in-flight max first, because we want no transactions in + # flight ahead of this test. + self.test_in_flight_max() + + self.test_inv_block() + + self.test_tx_requests() + + +if __name__ == '__main__': + TxDownloadTest().main() diff --git a/test/functional/rpc_bind.py b/test/functional/rpc_bind.py index acc7cd811c..8979251a26 100755 --- a/test/functional/rpc_bind.py +++ b/test/functional/rpc_bind.py @@ -55,7 +55,7 @@ class RPCBindTest(BitcoinTestFramework): self.nodes[0].rpchost = None self.start_nodes([node_args]) # connect to node through non-loopback interface - node = get_rpc_proxy(rpc_url(self.nodes[0].datadir, 0, "%s:%d" % (rpchost, rpcport)), 0, coveragedir=self.options.coveragedir) + node = get_rpc_proxy(rpc_url(self.nodes[0].datadir, 0, self.chain, "%s:%d" % (rpchost, rpcport)), 0, coveragedir=self.options.coveragedir) node.getnetworkinfo() self.stop_nodes() diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index cdf636e200..b621081752 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -41,11 +41,11 @@ class RawTransactionsTest(BitcoinTestFramework): connect_nodes_bi(self.nodes, 0, 3) def run_test(self): - min_relay_tx_fee = self.nodes[0].getnetworkinfo()['relayfee'] + self.min_relay_tx_fee = self.nodes[0].getnetworkinfo()['relayfee'] # This test is not meant to test fee estimation and we'd like # to be sure all txs are sent at a consistent desired feerate for node in self.nodes: - node.settxfee(min_relay_tx_fee) + node.settxfee(self.min_relay_tx_fee) # if the fee's positive delta is higher than this value tests will fail, # neg. delta always fail the tests. @@ -53,13 +53,43 @@ class RawTransactionsTest(BitcoinTestFramework): # than a minimum sized signature. # = 2 bytes * minRelayTxFeePerByte - feeTolerance = 2 * min_relay_tx_fee/1000 + self.fee_tolerance = 2 * self.min_relay_tx_fee / 1000 self.nodes[2].generate(1) self.sync_all() self.nodes[0].generate(121) self.sync_all() + self.test_change_position() + self.test_simple() + self.test_simple_two_coins() + self.test_simple_two_outputs() + self.test_change() + self.test_no_change() + self.test_invalid_option() + self.test_invalid_change_address() + self.test_valid_change_address() + self.test_change_type() + self.test_coin_selection() + self.test_two_vin() + self.test_two_vin_two_vout() + self.test_invalid_input() + self.test_fee_p2pkh() + self.test_fee_p2pkh_multi_out() + self.test_fee_p2sh() + self.test_fee_4of5() + self.test_spend_2of2() + self.test_locked_wallet() + self.test_many_inputs_fee() + self.test_many_inputs_send() + self.test_op_return() + self.test_watchonly() + self.test_all_watched_funds() + self.test_option_feerate() + self.test_address_reuse() + self.test_option_subtract_fee_from_outputs() + + def test_change_position(self): # ensure that setting changePosition in fundraw with an exact match is handled properly rawmatch = self.nodes[2].createrawtransaction([], {self.nodes[2].getnewaddress():50}) rawmatch = self.nodes[2].fundrawtransaction(rawmatch, {"changePosition":1, "subtractFeeFromOutputs":[0]}) @@ -67,15 +97,15 @@ class RawTransactionsTest(BitcoinTestFramework): watchonly_address = self.nodes[0].getnewaddress() watchonly_pubkey = self.nodes[0].getaddressinfo(watchonly_address)["pubkey"] - watchonly_amount = Decimal(200) + self.watchonly_amount = Decimal(200) self.nodes[3].importpubkey(watchonly_pubkey, "", True) - watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, watchonly_amount) + self.watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, self.watchonly_amount) # Lock UTXO so nodes[0] doesn't accidentally spend it - watchonly_vout = find_vout_for_address(self.nodes[0], watchonly_txid, watchonly_address) - self.nodes[0].lockunspent(False, [{"txid": watchonly_txid, "vout": watchonly_vout}]) + self.watchonly_vout = find_vout_for_address(self.nodes[0], self.watchonly_txid, watchonly_address) + self.nodes[0].lockunspent(False, [{"txid": self.watchonly_txid, "vout": self.watchonly_vout}]) - self.nodes[0].sendtoaddress(self.nodes[3].getnewaddress(), watchonly_amount / 10) + self.nodes[0].sendtoaddress(self.nodes[3].getnewaddress(), self.watchonly_amount / 10) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.5) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0) @@ -84,6 +114,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[0].generate(1) self.sync_all() + def test_simple(self): ############### # simple test # ############### @@ -92,10 +123,10 @@ class RawTransactionsTest(BitcoinTestFramework): rawtx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawtx) rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) assert len(dec_tx['vin']) > 0 #test that we have enough inputs + def test_simple_two_coins(self): ############################## # simple test with two coins # ############################## @@ -105,25 +136,11 @@ class RawTransactionsTest(BitcoinTestFramework): dec_tx = self.nodes[2].decoderawtransaction(rawtx) rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) assert len(dec_tx['vin']) > 0 #test if we have enough inputs - - ############################## - # simple test with two coins # - ############################## - inputs = [ ] - outputs = { self.nodes[0].getnewaddress() : 2.6 } - rawtx = self.nodes[2].createrawtransaction(inputs, outputs) - dec_tx = self.nodes[2].decoderawtransaction(rawtx) - - rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - fee = rawtxfund['fee'] - dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) - assert len(dec_tx['vin']) > 0 assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') - + def test_simple_two_outputs(self): ################################ # simple test with two outputs # ################################ @@ -133,7 +150,6 @@ class RawTransactionsTest(BitcoinTestFramework): dec_tx = self.nodes[2].decoderawtransaction(rawtx) rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 for out in dec_tx['vout']: @@ -142,7 +158,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert len(dec_tx['vin']) > 0 assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') - + def test_change(self): ######################################################################### # test a fundrawtransaction with a VIN greater than the required amount # ######################################################################### @@ -156,6 +172,7 @@ class RawTransactionsTest(BitcoinTestFramework): rawtxfund = self.nodes[2].fundrawtransaction(rawtx) fee = rawtxfund['fee'] + self.test_no_change_fee = fee # Use the same fee for the next tx dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 for out in dec_tx['vout']: @@ -163,14 +180,14 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee - + def test_no_change(self): ##################################################################### # test a fundrawtransaction with which will not get a change output # ##################################################################### utx = get_unspent(self.nodes[2].listunspent(), 5) inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}] - outputs = { self.nodes[0].getnewaddress() : Decimal(5.0) - fee - feeTolerance } + outputs = {self.nodes[0].getnewaddress(): Decimal(5.0) - self.test_no_change_fee - self.fee_tolerance} rawtx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawtx) assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) @@ -185,7 +202,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(rawtxfund['changepos'], -1) assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee - + def test_invalid_option(self): #################################################### # test a fundrawtransaction with an invalid option # #################################################### @@ -202,6 +219,7 @@ class RawTransactionsTest(BitcoinTestFramework): # reserveChangeKey was deprecated and is now removed assert_raises_rpc_error(-3, "Unexpected key reserveChangeKey", lambda: self.nodes[2].fundrawtransaction(hexstring=rawtx, options={'reserveChangeKey': True})) + def test_invalid_change_address(self): ############################################################ # test a fundrawtransaction with an invalid change address # ############################################################ @@ -215,6 +233,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_raises_rpc_error(-5, "changeAddress must be a valid bitcoin address", self.nodes[2].fundrawtransaction, rawtx, {'changeAddress':'foobar'}) + def test_valid_change_address(self): ############################################################ # test a fundrawtransaction with a provided change address # ############################################################ @@ -233,6 +252,7 @@ class RawTransactionsTest(BitcoinTestFramework): out = dec_tx['vout'][0] assert_equal(change, out['scriptPubKey']['addresses'][0]) + def test_change_type(self): ######################################################### # test a fundrawtransaction with a provided change type # ######################################################### @@ -247,6 +267,7 @@ class RawTransactionsTest(BitcoinTestFramework): dec_tx = self.nodes[2].decoderawtransaction(rawtx['hex']) assert_equal('witness_v0_keyhash', dec_tx['vout'][rawtx['changepos']]['scriptPubKey']['type']) + def test_coin_selection(self): ######################################################################### # test a fundrawtransaction with a VIN smaller than the required amount # ######################################################################### @@ -264,7 +285,6 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal("00", dec_tx['vin'][0]['scriptSig']['hex']) rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 matchingOuts = 0 @@ -281,7 +301,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(matchingOuts, 1) assert_equal(len(dec_tx['vout']), 2) - + def test_two_vin(self): ########################################### # test a fundrawtransaction with two VINs # ########################################### @@ -295,7 +315,6 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 matchingOuts = 0 @@ -315,6 +334,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(matchingIns, 2) #we now must see two vins identical to vins given as params + def test_two_vin_two_vout(self): ######################################################### # test a fundrawtransaction with two VINs and two vOUTs # ######################################################### @@ -328,7 +348,6 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) rawtxfund = self.nodes[2].fundrawtransaction(rawtx) - fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 matchingOuts = 0 @@ -340,16 +359,16 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(matchingOuts, 2) assert_equal(len(dec_tx['vout']), 3) + def test_invalid_input(self): ############################################## # test a fundrawtransaction with invalid vin # ############################################## inputs = [ {'txid' : "1c7f966dab21119bac53213a2bc7532bff1fa844c124fd750a7d0b1332440bd1", 'vout' : 0} ] #invalid vin! outputs = { self.nodes[0].getnewaddress() : 1.0} rawtx = self.nodes[2].createrawtransaction(inputs, outputs) - dec_tx = self.nodes[2].decoderawtransaction(rawtx) - assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx) + def test_fee_p2pkh(self): ############################################################ #compare fee of a standard pubkeyhash transaction inputs = [] @@ -363,9 +382,10 @@ class RawTransactionsTest(BitcoinTestFramework): #compare fee feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) - assert feeDelta >= 0 and feeDelta <= feeTolerance + assert feeDelta >= 0 and feeDelta <= self.fee_tolerance ############################################################ + def test_fee_p2pkh_multi_out(self): ############################################################ #compare fee of a standard pubkeyhash transaction with multiple outputs inputs = [] @@ -378,10 +398,10 @@ class RawTransactionsTest(BitcoinTestFramework): #compare fee feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) - assert feeDelta >= 0 and feeDelta <= feeTolerance + assert feeDelta >= 0 and feeDelta <= self.fee_tolerance ############################################################ - + def test_fee_p2sh(self): ############################################################ #compare fee of a 2of2 multisig p2sh transaction @@ -405,10 +425,10 @@ class RawTransactionsTest(BitcoinTestFramework): #compare fee feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) - assert feeDelta >= 0 and feeDelta <= feeTolerance + assert feeDelta >= 0 and feeDelta <= self.fee_tolerance ############################################################ - + def test_fee_4of5(self): ############################################################ #compare fee of a standard pubkeyhash transaction @@ -438,10 +458,10 @@ class RawTransactionsTest(BitcoinTestFramework): #compare fee feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) - assert feeDelta >= 0 and feeDelta <= feeTolerance + assert feeDelta >= 0 and feeDelta <= self.fee_tolerance ############################################################ - + def test_spend_2of2(self): ############################################################ # spend a 2of2 multisig transaction over fundraw @@ -456,7 +476,7 @@ class RawTransactionsTest(BitcoinTestFramework): # send 1.2 BTC to msig addr - txId = self.nodes[0].sendtoaddress(mSigObj, 1.2) + self.nodes[0].sendtoaddress(mSigObj, 1.2) self.sync_all() self.nodes[1].generate(1) self.sync_all() @@ -468,7 +488,7 @@ class RawTransactionsTest(BitcoinTestFramework): fundedTx = self.nodes[2].fundrawtransaction(rawtx) signedTx = self.nodes[2].signrawtransactionwithwallet(fundedTx['hex']) - txId = self.nodes[2].sendrawtransaction(signedTx['hex']) + self.nodes[2].sendrawtransaction(signedTx['hex']) self.sync_all() self.nodes[1].generate(1) self.sync_all() @@ -476,6 +496,7 @@ class RawTransactionsTest(BitcoinTestFramework): # make sure funds are received at node1 assert_equal(oldBalance+Decimal('1.10000000'), self.nodes[1].getbalance()) + def test_locked_wallet(self): ############################################################ # locked wallet test self.nodes[1].encryptwallet("test") @@ -485,7 +506,7 @@ class RawTransactionsTest(BitcoinTestFramework): # This test is not meant to test fee estimation and we'd like # to be sure all txs are sent at a consistent desired feerate for node in self.nodes: - node.settxfee(min_relay_tx_fee) + node.settxfee(self.min_relay_tx_fee) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) @@ -493,7 +514,7 @@ class RawTransactionsTest(BitcoinTestFramework): connect_nodes_bi(self.nodes,0,3) # Again lock the watchonly UTXO or nodes[0] may spend it, because # lockunspent is memory-only and thus lost on restart - self.nodes[0].lockunspent(False, [{"txid": watchonly_txid, "vout": watchonly_vout}]) + self.nodes[0].lockunspent(False, [{"txid": self.watchonly_txid, "vout": self.watchonly_vout}]) self.sync_all() # drain the keypool @@ -523,14 +544,14 @@ class RawTransactionsTest(BitcoinTestFramework): #now we need to unlock self.nodes[1].walletpassphrase("test", 600) signedTx = self.nodes[1].signrawtransactionwithwallet(fundedTx['hex']) - txId = self.nodes[1].sendrawtransaction(signedTx['hex']) + self.nodes[1].sendrawtransaction(signedTx['hex']) self.nodes[1].generate(1) self.sync_all() # make sure funds are received at node1 assert_equal(oldBalance+Decimal('51.10000000'), self.nodes[0].getbalance()) - + def test_many_inputs_fee(self): ############################################### # multiple (~19) inputs tx test | Compare fee # ############################################### @@ -558,9 +579,9 @@ class RawTransactionsTest(BitcoinTestFramework): #compare fee feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) - assert feeDelta >= 0 and feeDelta <= feeTolerance*19 #~19 inputs - + assert feeDelta >= 0 and feeDelta <= self.fee_tolerance * 19 #~19 inputs + def test_many_inputs_send(self): ############################################# # multiple (~19) inputs tx test | sign/send # ############################################# @@ -584,12 +605,13 @@ class RawTransactionsTest(BitcoinTestFramework): rawtx = self.nodes[1].createrawtransaction(inputs, outputs) fundedTx = self.nodes[1].fundrawtransaction(rawtx) fundedAndSignedTx = self.nodes[1].signrawtransactionwithwallet(fundedTx['hex']) - txId = self.nodes[1].sendrawtransaction(fundedAndSignedTx['hex']) + self.nodes[1].sendrawtransaction(fundedAndSignedTx['hex']) self.sync_all() self.nodes[0].generate(1) self.sync_all() assert_equal(oldBalance+Decimal('50.19000000'), self.nodes[0].getbalance()) #0.19+block reward + def test_op_return(self): ##################################################### # test fundrawtransaction with OP_RETURN and no vin # ##################################################### @@ -606,40 +628,41 @@ class RawTransactionsTest(BitcoinTestFramework): assert_greater_than(len(dec_tx['vin']), 0) # at least one vin assert_equal(len(dec_tx['vout']), 2) # one change output added - + def test_watchonly(self): ################################################## # test a fundrawtransaction using only watchonly # ################################################## inputs = [] - outputs = {self.nodes[2].getnewaddress() : watchonly_amount / 2} + outputs = {self.nodes[2].getnewaddress(): self.watchonly_amount / 2} rawtx = self.nodes[3].createrawtransaction(inputs, outputs) result = self.nodes[3].fundrawtransaction(rawtx, {'includeWatching': True }) res_dec = self.nodes[0].decoderawtransaction(result["hex"]) assert_equal(len(res_dec["vin"]), 1) - assert_equal(res_dec["vin"][0]["txid"], watchonly_txid) + assert_equal(res_dec["vin"][0]["txid"], self.watchonly_txid) assert "fee" in result.keys() assert_greater_than(result["changepos"], -1) + def test_all_watched_funds(self): ############################################################### # test fundrawtransaction using the entirety of watched funds # ############################################################### inputs = [] - outputs = {self.nodes[2].getnewaddress() : watchonly_amount} + outputs = {self.nodes[2].getnewaddress(): self.watchonly_amount} rawtx = self.nodes[3].createrawtransaction(inputs, outputs) # Backward compatibility test (2nd param is includeWatching) result = self.nodes[3].fundrawtransaction(rawtx, True) res_dec = self.nodes[0].decoderawtransaction(result["hex"]) assert_equal(len(res_dec["vin"]), 2) - assert res_dec["vin"][0]["txid"] == watchonly_txid or res_dec["vin"][1]["txid"] == watchonly_txid + assert res_dec["vin"][0]["txid"] == self.watchonly_txid or res_dec["vin"][1]["txid"] == self.watchonly_txid assert_greater_than(result["fee"], 0) assert_greater_than(result["changepos"], -1) - assert_equal(result["fee"] + res_dec["vout"][result["changepos"]]["value"], watchonly_amount / 10) + assert_equal(result["fee"] + res_dec["vout"][result["changepos"]]["value"], self.watchonly_amount / 10) signedtx = self.nodes[3].signrawtransactionwithwallet(result["hex"]) assert not signedtx["complete"] @@ -649,6 +672,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[0].generate(1) self.sync_all() + def test_option_feerate(self): ####################### # Test feeRate option # ####################### @@ -659,18 +683,20 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [] outputs = {self.nodes[3].getnewaddress() : 1} rawtx = self.nodes[3].createrawtransaction(inputs, outputs) - result = self.nodes[3].fundrawtransaction(rawtx) # uses min_relay_tx_fee (set by settxfee) - result2 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2*min_relay_tx_fee}) - result3 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 10*min_relay_tx_fee}) + result = self.nodes[3].fundrawtransaction(rawtx) # uses self.min_relay_tx_fee (set by settxfee) + result2 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee}) + result3 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 10 * self.min_relay_tx_fee}) assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[3].fundrawtransaction, rawtx, {"feeRate": 1}) result_fee_rate = result['fee'] * 1000 / count_bytes(result['hex']) assert_fee_amount(result2['fee'], count_bytes(result2['hex']), 2 * result_fee_rate) assert_fee_amount(result3['fee'], count_bytes(result3['hex']), 10 * result_fee_rate) + def test_address_reuse(self): ################################ # Test no address reuse occurs # ################################ + rawtx = self.nodes[3].createrawtransaction(inputs=[], outputs={self.nodes[3].getnewaddress(): 1}) result3 = self.nodes[3].fundrawtransaction(rawtx) res_dec = self.nodes[0].decoderawtransaction(result3["hex"]) changeaddress = "" @@ -682,6 +708,7 @@ class RawTransactionsTest(BitcoinTestFramework): # Now the change address key should be removed from the keypool assert changeaddress != nextaddr + def test_option_subtract_fee_from_outputs(self): ###################################### # Test subtractFeeFromOutputs option # ###################################### @@ -693,11 +720,11 @@ class RawTransactionsTest(BitcoinTestFramework): outputs = {self.nodes[2].getnewaddress(): 1} rawtx = self.nodes[3].createrawtransaction(inputs, outputs) - result = [self.nodes[3].fundrawtransaction(rawtx), # uses min_relay_tx_fee (set by settxfee) - self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": []}), # empty subtraction list - self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": [0]}), # uses min_relay_tx_fee (set by settxfee) - self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2*min_relay_tx_fee}), - self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2*min_relay_tx_fee, "subtractFeeFromOutputs": [0]})] + result = [self.nodes[3].fundrawtransaction(rawtx), # uses self.min_relay_tx_fee (set by settxfee) + self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": []}), # empty subtraction list + self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": [0]}), # uses self.min_relay_tx_fee (set by settxfee) + self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee}), + self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee, "subtractFeeFromOutputs": [0]}),] dec_tx = [self.nodes[3].decoderawtransaction(tx_['hex']) for tx_ in result] output = [d['vout'][1 - r['changepos']]['value'] for d, r in zip(dec_tx, result)] diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index dc113da530..5a04e0c8d8 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -27,6 +27,11 @@ class PSBTTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = False self.num_nodes = 3 + self.extra_args = [ + ["-walletrbf=1"], + ["-walletrbf=0"], + [] + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -136,7 +141,7 @@ class PSBTTest(BitcoinTestFramework): assert_greater_than(0.06, res["fee"]) # feeRate of 10 BTC / KB produces a total fee well above -maxtxfee - # previously this was silenty capped at -maxtxfee + # previously this was silently capped at -maxtxfee assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[1].walletcreatefundedpsbt, [{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 10}) # partially sign multisig things with node 1 @@ -207,18 +212,18 @@ class PSBTTest(BitcoinTestFramework): # replaceable arg block_height = self.nodes[0].getblockcount() unspent = self.nodes[0].listunspent()[0] - psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"replaceable":True}, False) + psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"replaceable": False}, False) decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"]) for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]): - assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE) + assert_greater_than(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE) assert "bip32_derivs" not in psbt_in assert_equal(decoded_psbt["tx"]["locktime"], block_height+2) - # Same construction with only locktime set - psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height, {}, True) + # Same construction with only locktime set and RBF explicitly enabled + psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height, {"replaceable": True}, True) decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"]) for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]): - assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE + assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE) assert "bip32_derivs" in psbt_in assert_equal(decoded_psbt["tx"]["locktime"], block_height) @@ -226,9 +231,16 @@ class PSBTTest(BitcoinTestFramework): psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}]) decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"]) for tx_in in decoded_psbt["tx"]["vin"]: - assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE + assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE) assert_equal(decoded_psbt["tx"]["locktime"], 0) + # Same construction without optional arguments, for a node with -walletrbf=0 + unspent1 = self.nodes[1].listunspent()[0] + psbtx_info = self.nodes[1].walletcreatefundedpsbt([{"txid":unspent1["txid"], "vout":unspent1["vout"]}], [{self.nodes[2].getnewaddress():unspent1["amount"]+1}], block_height) + decoded_psbt = self.nodes[1].decodepsbt(psbtx_info["psbt"]) + for tx_in in decoded_psbt["tx"]["vin"]: + assert_greater_than(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE) + # Make sure change address wallet does not have P2SH innerscript access to results in success # when attempting BnB coin selection self.nodes[0].walletcreatefundedpsbt([], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"changeAddress":self.nodes[1].getnewaddress()}, False) diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py index 102dd22594..8bbb3c04fa 100755 --- a/test/functional/rpc_users.py +++ b/test/functional/rpc_users.py @@ -20,6 +20,17 @@ import string import configparser import sys +def call_with_auth(node, user, password): + url = urllib.parse.urlparse(node.url) + headers = {"Authorization": "Basic " + str_to_b64str('{}:{}'.format(user, password))} + + conn = http.client.HTTPConnection(url.hostname, url.port) + conn.connect() + conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) + resp = conn.getresponse() + conn.close() + return resp + class HTTPBasicsTest(BitcoinTestFramework): def set_test_params(self): @@ -28,15 +39,24 @@ class HTTPBasicsTest(BitcoinTestFramework): def setup_chain(self): super().setup_chain() #Append rpcauth to bitcoin.conf before initialization + self.rtpassword = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM=" rpcauth = "rpcauth=rt:93648e835a54c573682c2eb19f882535$7681e9c5b74bdd85e78166031d2058e1069b3ed7ed967c93fc63abba06f31144" - rpcauth2 = "rpcauth=rt2:f8607b1a88861fac29dfccf9b52ff9f$ff36a0c23c8c62b4846112e50fa888416e94c17bfd4c42f88fd8f55ec6a3137e" - rpcuser = "rpcuser=rpcuser💻" - rpcpassword = "rpcpassword=rpcpassword🔑" - self.user = ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10)) + self.rpcuser = "rpcuser💻" + self.rpcpassword = "rpcpassword🔑" + config = configparser.ConfigParser() config.read_file(open(self.options.configfile)) gen_rpcauth = config['environment']['RPCAUTH'] + + # Generate RPCAUTH with specified password + self.rt2password = "8/F3uMDw4KSEbw96U3CA1C4X05dkHDN2BPFjTgZW4KI=" + p = subprocess.Popen([sys.executable, gen_rpcauth, 'rt2', self.rt2password], stdout=subprocess.PIPE, universal_newlines=True) + lines = p.stdout.read().splitlines() + rpcauth2 = lines[1] + + # Generate RPCAUTH without specifying password + self.user = ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10)) p = subprocess.Popen([sys.executable, gen_rpcauth, self.user], stdout=subprocess.PIPE, universal_newlines=True) lines = p.stdout.read().splitlines() rpcauth3 = lines[1] @@ -47,160 +67,40 @@ class HTTPBasicsTest(BitcoinTestFramework): f.write(rpcauth2+"\n") f.write(rpcauth3+"\n") with open(os.path.join(get_datadir_path(self.options.tmpdir, 1), "bitcoin.conf"), 'a', encoding='utf8') as f: - f.write(rpcuser+"\n") - f.write(rpcpassword+"\n") - - def run_test(self): - - ################################################## - # Check correctness of the rpcauth config option # - ################################################## - url = urllib.parse.urlparse(self.nodes[0].url) - - #Old authpair - authpair = url.username + ':' + url.password - - #New authpair generated via share/rpcauth tool - password = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM=" - - #Second authpair with different username - password2 = "8/F3uMDw4KSEbw96U3CA1C4X05dkHDN2BPFjTgZW4KI=" - authpairnew = "rt:"+password + f.write("rpcuser={}\n".format(self.rpcuser)) + f.write("rpcpassword={}\n".format(self.rpcpassword)) + def test_auth(self, node, user, password): self.log.info('Correct...') - headers = {"Authorization": "Basic " + str_to_b64str(authpair)} + assert_equal(200, call_with_auth(node, user, password).status) - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() - - #Use new authpair to confirm both work - self.log.info('Correct...') - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() - - #Wrong login name with rt's password self.log.info('Wrong...') - authpairnew = "rtwrong:"+password - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() + assert_equal(401, call_with_auth(node, user, password+'wrong').status) - #Wrong password for rt self.log.info('Wrong...') - authpairnew = "rt:"+password+"wrong" - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} + assert_equal(401, call_with_auth(node, user+'wrong', password).status) - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() - - #Correct for rt2 - self.log.info('Correct...') - authpairnew = "rt2:"+password2 - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() - - #Wrong password for rt2 self.log.info('Wrong...') - authpairnew = "rt2:"+password2+"wrong" - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() + assert_equal(401, call_with_auth(node, user+'wrong', password+'wrong').status) - #Correct for randomly generated user - self.log.info('Correct...') - authpairnew = self.user+":"+self.password - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() + def run_test(self): - #Wrong password for randomly generated user - self.log.info('Wrong...') - authpairnew = self.user+":"+self.password+"Wrong" - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} + ################################################## + # Check correctness of the rpcauth config option # + ################################################## + url = urllib.parse.urlparse(self.nodes[0].url) - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() + self.test_auth(self.nodes[0], url.username, url.password) + self.test_auth(self.nodes[0], 'rt', self.rtpassword) + self.test_auth(self.nodes[0], 'rt2', self.rt2password) + self.test_auth(self.nodes[0], self.user, self.password) ############################################################### # Check correctness of the rpcuser/rpcpassword config options # ############################################################### url = urllib.parse.urlparse(self.nodes[1].url) - # rpcuser and rpcpassword authpair - self.log.info('Correct...') - rpcuserauthpair = "rpcuser💻:rpcpassword🔑" - - headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() - - #Wrong login name with rpcuser's password - rpcuserauthpair = "rpcuserwrong:rpcpassword" - headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() - - #Wrong password for rpcuser - self.log.info('Wrong...') - rpcuserauthpair = "rpcuser:rpcpasswordwrong" - headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() - + self.test_auth(self.nodes[1], self.rpcuser, self.rpcpassword) if __name__ == '__main__': HTTPBasicsTest ().main () diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index e454ed5987..89a5a65e64 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -44,7 +44,7 @@ BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is BIP 125 opt-in an NODE_NETWORK = (1 << 0) # NODE_GETUTXO = (1 << 1) -NODE_BLOOM = (1 << 2) +# NODE_BLOOM = (1 << 2) NODE_WITNESS = (1 << 3) NODE_NETWORK_LIMITED = (1 << 10) diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index cc3a4cc72a..779863df79 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -363,6 +363,7 @@ class P2PInterface(P2PConnection): def wait_for_tx(self, txid, timeout=60): def test_function(): + assert self.is_connected if not self.last_message.get('tx'): return False return self.last_message['tx'].tx.rehash() == txid @@ -370,11 +371,15 @@ class P2PInterface(P2PConnection): wait_until(test_function, timeout=timeout, lock=mininode_lock) def wait_for_block(self, blockhash, timeout=60): - test_function = lambda: self.last_message.get("block") and self.last_message["block"].block.rehash() == blockhash + def test_function(): + assert self.is_connected + return self.last_message.get("block") and self.last_message["block"].block.rehash() == blockhash + wait_until(test_function, timeout=timeout, lock=mininode_lock) def wait_for_header(self, blockhash, timeout=60): def test_function(): + assert self.is_connected last_headers = self.last_message.get('headers') if not last_headers: return False @@ -389,7 +394,11 @@ class P2PInterface(P2PConnection): value must be explicitly cleared before calling this method, or this will return immediately with success. TODO: change this method to take a hash value and only return true if the correct block/tx has been requested.""" - test_function = lambda: self.last_message.get("getdata") + + def test_function(): + assert self.is_connected + return self.last_message.get("getdata") + wait_until(test_function, timeout=timeout, lock=mininode_lock) def wait_for_getheaders(self, timeout=60): @@ -399,20 +408,30 @@ class P2PInterface(P2PConnection): value must be explicitly cleared before calling this method, or this will return immediately with success. TODO: change this method to take a hash value and only return true if the correct block header has been requested.""" - test_function = lambda: self.last_message.get("getheaders") + + def test_function(): + assert self.is_connected + return self.last_message.get("getheaders") + wait_until(test_function, timeout=timeout, lock=mininode_lock) def wait_for_inv(self, expected_inv, timeout=60): """Waits for an INV message and checks that the first inv object in the message was as expected.""" if len(expected_inv) > 1: raise NotImplementedError("wait_for_inv() will only verify the first inv object") - test_function = lambda: self.last_message.get("inv") and \ + + def test_function(): + assert self.is_connected + return self.last_message.get("inv") and \ self.last_message["inv"].inv[0].type == expected_inv[0].type and \ self.last_message["inv"].inv[0].hash == expected_inv[0].hash + wait_until(test_function, timeout=timeout, lock=mininode_lock) def wait_for_verack(self, timeout=60): - test_function = lambda: self.message_count["verack"] + def test_function(): + return self.message_count["verack"] + wait_until(test_function, timeout=timeout, lock=mininode_lock) # Message sending helper functions @@ -424,7 +443,11 @@ class P2PInterface(P2PConnection): # Sync up with the node def sync_with_ping(self, timeout=60): self.send_message(msg_ping(nonce=self.ping_counter)) - test_function = lambda: self.last_message.get("pong") and self.last_message["pong"].nonce == self.ping_counter + + def test_function(): + assert self.is_connected + return self.last_message.get("pong") and self.last_message["pong"].nonce == self.ping_counter + wait_until(test_function, timeout=timeout, lock=mininode_lock) self.ping_counter += 1 diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index aa674f9ebe..9aff08fdc7 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -91,6 +91,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): def __init__(self): """Sets test framework defaults. Do not override this method. Instead, override the set_test_params() method""" + self.chain = 'regtest' self.setup_clean_chain = False self.nodes = [] self.network_thread = None @@ -192,18 +193,18 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.setup_network() self.run_test() success = TestStatus.PASSED - except JSONRPCException as e: + except JSONRPCException: self.log.exception("JSONRPC error") except SkipTest as e: self.log.warning("Test Skipped: %s" % e.message) success = TestStatus.SKIPPED - except AssertionError as e: + except AssertionError: self.log.exception("Assertion failed") - except KeyError as e: + except KeyError: self.log.exception("Key error") - except Exception as e: + except Exception: self.log.exception("Unexpected exception caught during testing") - except KeyboardInterrupt as e: + except KeyboardInterrupt: self.log.warning("Exiting after keyboard interrupt") if success == TestStatus.FAILED and self.options.pdbonfailure: @@ -342,6 +343,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.nodes.append(TestNode( i, get_datadir_path(self.options.tmpdir, i), + chain=self.chain, rpchost=rpchost, timewait=self.rpc_timeout, bitcoind=binary[i], @@ -477,11 +479,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): if not os.path.isdir(cache_node_dir): self.log.debug("Creating cache directory {}".format(cache_node_dir)) - initialize_datadir(self.options.cachedir, CACHE_NODE_ID) + initialize_datadir(self.options.cachedir, CACHE_NODE_ID, self.chain) self.nodes.append( TestNode( CACHE_NODE_ID, cache_node_dir, + chain=self.chain, extra_conf=["bind=127.0.0.1"], extra_args=['-disablewallet'], rpchost=None, @@ -515,7 +518,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.nodes = [] def cache_path(*paths): - return os.path.join(cache_node_dir, "regtest", *paths) + return os.path.join(cache_node_dir, self.chain, *paths) os.rmdir(cache_path('wallets')) # Remove empty wallets dir for entry in os.listdir(cache_path()): @@ -526,7 +529,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.log.debug("Copy cache directory {} to node {}".format(cache_node_dir, i)) to_dir = get_datadir_path(self.options.tmpdir, i) shutil.copytree(cache_node_dir, to_dir) - initialize_datadir(self.options.tmpdir, i) # Overwrite port/rpcport in bitcoin.conf + initialize_datadir(self.options.tmpdir, i, self.chain) # Overwrite port/rpcport in bitcoin.conf def _initialize_chain_clean(self): """Initialize empty blockchain for use by the test. @@ -534,7 +537,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): Create an empty blockchain and num_nodes wallets. Useful if a test case wants complete control over initialization.""" for i in range(self.num_nodes): - initialize_datadir(self.options.tmpdir, i) + initialize_datadir(self.options.tmpdir, i, self.chain) def skip_if_no_py3_zmq(self): """Attempt to import the zmq package and skip the test if the import fails.""" diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 3311377090..cac5281764 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -59,7 +59,7 @@ class TestNode(): To make things easier for the test writer, any unrecognised messages will be dispatched to the RPC connection.""" - def __init__(self, i, datadir, *, rpchost, timewait, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False): + def __init__(self, i, datadir, *, chain, rpchost, timewait, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False): """ Kwargs: start_perf (bool): If True, begin profiling the node with `perf` as soon as @@ -70,6 +70,7 @@ class TestNode(): self.datadir = datadir self.stdout_dir = os.path.join(self.datadir, "stdout") self.stderr_dir = os.path.join(self.datadir, "stderr") + self.chain = chain self.rpchost = rpchost self.rpc_timeout = timewait self.binary = bitcoind @@ -197,7 +198,7 @@ class TestNode(): # Delete any existing cookie file -- if such a file exists (eg due to # unclean shutdown), it will get overwritten anyway by bitcoind, and # potentially interfere with our attempt to authenticate - delete_cookie_file(self.datadir) + delete_cookie_file(self.datadir, self.chain) # add environment variable LIBC_FATAL_STDERR_=1 so that libc errors are written to stderr and not the terminal subp_env = dict(os.environ, LIBC_FATAL_STDERR_="1") @@ -219,7 +220,7 @@ class TestNode(): raise FailedToStartError(self._node_msg( 'bitcoind exited with status {} during initialization'.format(self.process.returncode))) try: - rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir) + rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.chain, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir) rpc.getblockcount() # If the call to getblockcount() succeeds then the RPC connection is up self.log.debug("RPC successfully started") @@ -306,7 +307,7 @@ class TestNode(): @contextlib.contextmanager def assert_debug_log(self, expected_msgs): - debug_log = os.path.join(self.datadir, 'regtest', 'debug.log') + debug_log = os.path.join(self.datadir, self.chain, 'debug.log') with open(debug_log, encoding='utf-8') as dl: dl.seek(0, 2) prev_size = dl.tell() diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 26215083fb..8730157c74 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -19,6 +19,7 @@ import time from . import coverage from .authproxy import AuthServiceProxy, JSONRPCException +from io import BytesIO logger = logging.getLogger("TestFramework.utils") @@ -270,8 +271,8 @@ def p2p_port(n): def rpc_port(n): return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES) -def rpc_url(datadir, i, rpchost=None): - rpc_u, rpc_p = get_auth_cookie(datadir) +def rpc_url(datadir, i, chain, rpchost): + rpc_u, rpc_p = get_auth_cookie(datadir, chain) host = '127.0.0.1' port = rpc_port(i) if rpchost: @@ -285,13 +286,13 @@ def rpc_url(datadir, i, rpchost=None): # Node functions ################ -def initialize_datadir(dirname, n): +def initialize_datadir(dirname, n, chain): datadir = get_datadir_path(dirname, n) if not os.path.isdir(datadir): os.makedirs(datadir) with open(os.path.join(datadir, "bitcoin.conf"), 'w', encoding='utf8') as f: - f.write("regtest=1\n") - f.write("[regtest]\n") + f.write("{}=1\n".format(chain)) + f.write("[{}]\n".format(chain)) f.write("port=" + str(p2p_port(n)) + "\n") f.write("rpcport=" + str(rpc_port(n)) + "\n") f.write("server=1\n") @@ -311,7 +312,7 @@ def append_config(datadir, options): for option in options: f.write(option + "\n") -def get_auth_cookie(datadir): +def get_auth_cookie(datadir, chain): user = None password = None if os.path.isfile(os.path.join(datadir, "bitcoin.conf")): @@ -324,7 +325,7 @@ def get_auth_cookie(datadir): assert password is None # Ensure that there is only one rpcpassword line password = line.split("=")[1].strip("\n") try: - with open(os.path.join(datadir, "regtest", ".cookie"), 'r', encoding="ascii") as f: + with open(os.path.join(datadir, chain, ".cookie"), 'r', encoding="ascii") as f: userpass = f.read() split_userpass = userpass.split(':') user = split_userpass[0] @@ -336,10 +337,10 @@ def get_auth_cookie(datadir): return user, password # If a cookie file exists in the given datadir, delete it. -def delete_cookie_file(datadir): - if os.path.isfile(os.path.join(datadir, "regtest", ".cookie")): +def delete_cookie_file(datadir, chain): + if os.path.isfile(os.path.join(datadir, chain, ".cookie")): logger.debug("Deleting leftover cookie file") - os.remove(os.path.join(datadir, "regtest", ".cookie")) + os.remove(os.path.join(datadir, chain, ".cookie")) def get_bip9_status(node, key): info = node.getblockchaininfo() @@ -515,14 +516,13 @@ def gen_return_txouts(): for i in range(512): script_pubkey = script_pubkey + "01" # concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change - txouts = "81" + txouts = [] + from .messages import CTxOut + txout = CTxOut() + txout.nValue = 0 + txout.scriptPubKey = hex_str_to_bytes(script_pubkey) for k in range(128): - # add txout value - txouts = txouts + "0000000000000000" - # add length of script_pubkey - txouts = txouts + "fd0402" - # add script_pubkey - txouts = txouts + script_pubkey + txouts.append(txout) return txouts # Create a spend of each passed-in utxo, splicing in "txouts" to each raw @@ -530,6 +530,7 @@ def gen_return_txouts(): def create_lots_of_big_transactions(node, txouts, utxos, num, fee): addr = node.getnewaddress() txids = [] + from .messages import CTransaction for _ in range(num): t = utxos.pop() inputs = [{"txid": t["txid"], "vout": t["vout"]}] @@ -537,9 +538,11 @@ def create_lots_of_big_transactions(node, txouts, utxos, num, fee): change = t['amount'] - fee outputs[addr] = satoshi_round(change) rawtx = node.createrawtransaction(inputs, outputs) - newtx = rawtx[0:92] - newtx = newtx + txouts - newtx = newtx + rawtx[94:] + tx = CTransaction() + tx.deserialize(BytesIO(hex_str_to_bytes(rawtx))) + for txout in txouts: + tx.vout.append(txout) + newtx = tx.serialize().hex() signresult = node.signrawtransactionwithwallet(newtx, None, "NONE") txid = node.sendrawtransaction(signresult["hex"], 0) txids.append(txid) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 462547f44f..0e0e6ed18e 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -91,6 +91,7 @@ BASE_SCRIPTS = [ 'wallet_labels.py', 'p2p_segwit.py', 'p2p_timeouts.py', + 'p2p_tx_download.py', 'wallet_dump.py', 'wallet_listtransactions.py', # vv Tests less than 60s vv @@ -107,6 +108,7 @@ BASE_SCRIPTS = [ 'feature_bip68_sequence.py', 'p2p_feefilter.py', 'feature_reindex.py', + 'feature_abortnode.py', # vv Tests less than 30s vv 'wallet_keypool_topup.py', 'interface_zmq.py', @@ -157,6 +159,7 @@ BASE_SCRIPTS = [ 'rpc_invalidateblock.py', 'feature_rbf.py', 'mempool_packages.py', + 'mempool_package_onemore.py', 'rpc_createmultisig.py', 'feature_versionbits_warning.py', 'rpc_preciousblock.py', @@ -173,6 +176,7 @@ BASE_SCRIPTS = [ 'rpc_bind.py --nonloopback', 'mining_basic.py', 'wallet_bumpfee.py', + 'wallet_bumpfee_totalfee_deprecation.py', 'rpc_named_arguments.py', 'wallet_listsinceblock.py', 'p2p_leak.py', @@ -234,6 +238,7 @@ def main(): parser.add_argument('--quiet', '-q', action='store_true', help='only print dots, results summary and failure logs') parser.add_argument('--tmpdirprefix', '-t', default=tempfile.gettempdir(), help="Root directory for datadirs") parser.add_argument('--failfast', action='store_true', help='stop execution after the first test failure') + parser.add_argument('--filter', help='filter scripts to run by regular expression') args, unknown_args = parser.parse_known_args() # args to be passed on always start with two dashes; tests are the remaining unknown args @@ -269,11 +274,22 @@ def main(): test_list = [] if tests: # Individual tests have been specified. Run specified tests that exist - # in the ALL_SCRIPTS list. Accept the name with or without .py extension. - tests = [test + ".py" if ".py" not in test else test for test in tests] + # in the ALL_SCRIPTS list. Accept names with or without a .py extension. + # Specified tests can contain wildcards, but in that case the supplied + # paths should be coherent, e.g. the same path as that provided to call + # test_runner.py. Examples: + # `test/functional/test_runner.py test/functional/wallet*` + # `test/functional/test_runner.py ./test/functional/wallet*` + # `test_runner.py wallet*` + # but not: + # `test/functional/test_runner.py wallet*` + # Multiple wildcards can be passed: + # `test_runner.py tool* mempool*` for test in tests: - if test in ALL_SCRIPTS: - test_list.append(test) + script = test.split("/")[-1] + script = script + ".py" if ".py" not in script else script + if script in ALL_SCRIPTS: + test_list.append(script) else: print("{}WARNING!{} Test '{}' not found in full test list.".format(BOLD[1], BOLD[0], test)) elif args.extended: @@ -294,6 +310,9 @@ def main(): if not exclude_list: print("{}WARNING!{} Test '{}' not found in current test list.".format(BOLD[1], BOLD[0], exclude_test)) + if args.filter: + test_list = list(filter(re.compile(args.filter).search, test_list)) + if not test_list: print("No valid test scripts specified. Check that your test is in one " "of the test lists in test_runner.py, or run test_runner.py with no arguments to run all tests") diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index daa834b5b8..34e84fcf55 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -20,6 +20,9 @@ from test_framework.util import ( class WalletTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 + self.extra_args = [[ + "-acceptnonstdtxn=1", + ]] * self.num_nodes self.setup_clean_chain = True def skip_test_if_missing_module(self): diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 1fe029a6fb..bfc01e3f5e 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -37,6 +37,7 @@ class BumpFeeTest(BitcoinTestFramework): self.extra_args = [[ "-walletrbf={}".format(i), "-mintxfee=0.00002", + "-deprecatedrpc=totalFee", ] for i in range(self.num_nodes)] def skip_test_if_missing_module(self): @@ -80,6 +81,7 @@ class BumpFeeTest(BitcoinTestFramework): test_bumpfee_metadata(rbf_node, dest_address) test_locked_wallet_fails(rbf_node, dest_address) test_change_script_match(rbf_node, dest_address) + test_maxtxfee_fails(self, rbf_node, dest_address) # These tests wipe out a number of utxos that are expected in other tests test_small_output_with_feerate_succeeds(rbf_node, dest_address) test_no_more_inputs_fails(rbf_node, dest_address) @@ -248,6 +250,15 @@ def test_settxfee(rbf_node, dest_address): rbf_node.settxfee(Decimal("0.00000000")) # unset paytxfee +def test_maxtxfee_fails(test, rbf_node, dest_address): + test.restart_node(1, ['-maxtxfee=0.00003'] + test.extra_args[1]) + rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT) + rbfid = spend_one_input(rbf_node, dest_address) + assert_raises_rpc_error(-4, "Unable to create transaction: Fee exceeds maximum configured by -maxtxfee", rbf_node.bumpfee, rbfid) + test.restart_node(1, test.extra_args[1]) + rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT) + + def test_rebumping(rbf_node, dest_address): # check that re-bumping the original tx fails, but bumping the bumper succeeds rbfid = spend_one_input(rbf_node, dest_address) diff --git a/test/functional/wallet_bumpfee_totalfee_deprecation.py b/test/functional/wallet_bumpfee_totalfee_deprecation.py new file mode 100755 index 0000000000..b8e097c32e --- /dev/null +++ b/test/functional/wallet_bumpfee_totalfee_deprecation.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test deprecation of passing `totalFee` to the bumpfee RPC.""" +from decimal import Decimal + +from test_framework.messages import BIP125_SEQUENCE_NUMBER +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_raises_rpc_error + +class BumpFeeWithTotalFeeArgumentDeprecationTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.extra_args = [[ + "-walletrbf={}".format(i), + "-mintxfee=0.00002", + ] for i in range(self.num_nodes)] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + peer_node, rbf_node = self.nodes + peer_node.generate(110) + self.sync_all() + peer_node.sendtoaddress(rbf_node.getnewaddress(), 0.001) + self.sync_all() + peer_node.generate(1) + self.sync_all() + rbfid = spend_one_input(rbf_node, peer_node.getnewaddress()) + + self.log.info("Testing bumpfee with totalFee argument raises RPC error with deprecation message") + assert_raises_rpc_error( + -8, + "totalFee argument has been deprecated and will be removed in 0.20. " + + "Please use -deprecatedrpc=totalFee to continue using this argument until removal.", + rbf_node.bumpfee, rbfid, {"totalFee": 2000}) + + self.log.info("Testing bumpfee without totalFee argument does not raise") + rbf_node.bumpfee(rbfid) + +def spend_one_input(node, dest_address, change_size=Decimal("0.00049000")): + tx_input = dict(sequence=BIP125_SEQUENCE_NUMBER, + **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000"))) + destinations = {dest_address: Decimal("0.00050000")} + destinations[node.getrawchangeaddress()] = change_size + rawtx = node.createrawtransaction([tx_input], destinations) + signedtx = node.signrawtransactionwithwallet(rawtx) + txid = node.sendrawtransaction(signedtx["hex"]) + return txid + +if __name__ == "__main__": + BumpFeeWithTotalFeeArgumentDeprecationTest().main() diff --git a/test/functional/wallet_create_tx.py b/test/functional/wallet_create_tx.py index 0b584a0bb2..330de8b0fc 100755 --- a/test/functional/wallet_create_tx.py +++ b/test/functional/wallet_create_tx.py @@ -6,6 +6,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + assert_raises_rpc_error, ) from test_framework.blocktools import ( TIME_GENESIS_BLOCK, @@ -26,6 +27,10 @@ class CreateTxWalletTest(BitcoinTestFramework): self.nodes[0].generate(200) self.nodes[0].setmocktime(0) + self.test_anti_fee_sniping() + self.test_tx_size_too_large() + + def test_anti_fee_sniping(self): self.log.info('Check that we have some (old) blocks and that anti-fee-sniping is disabled') assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 200) txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) @@ -38,6 +43,40 @@ class CreateTxWalletTest(BitcoinTestFramework): tx = self.nodes[0].decoderawtransaction(self.nodes[0].gettransaction(txid)['hex']) assert 0 < tx['locktime'] <= 201 + def test_tx_size_too_large(self): + # More than 10kB of outputs, so that we hit -maxtxfee with a high feerate + outputs = {self.nodes[0].getnewaddress(address_type='bech32'): 0.000025 for i in range(400)} + raw_tx = self.nodes[0].createrawtransaction(inputs=[], outputs=outputs) + + for fee_setting in ['-minrelaytxfee=0.01', '-mintxfee=0.01', '-paytxfee=0.01']: + self.log.info('Check maxtxfee in combination with {}'.format(fee_setting)) + self.restart_node(0, extra_args=[fee_setting]) + assert_raises_rpc_error( + -6, + "Fee exceeds maximum configured by -maxtxfee", + lambda: self.nodes[0].sendmany(dummy="", amounts=outputs), + ) + assert_raises_rpc_error( + -4, + "Fee exceeds maximum configured by -maxtxfee", + lambda: self.nodes[0].fundrawtransaction(hexstring=raw_tx), + ) + + self.log.info('Check maxtxfee in combination with settxfee') + self.restart_node(0) + self.nodes[0].settxfee(0.01) + assert_raises_rpc_error( + -6, + "Fee exceeds maximum configured by -maxtxfee", + lambda: self.nodes[0].sendmany(dummy="", amounts=outputs), + ) + assert_raises_rpc_error( + -4, + "Fee exceeds maximum configured by -maxtxfee", + lambda: self.nodes[0].fundrawtransaction(hexstring=raw_tx), + ) + self.nodes[0].settxfee(0) + if __name__ == '__main__': CreateTxWalletTest().main() diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py index c17949a2f6..e302e499f4 100755 --- a/test/functional/wallet_createwallet.py +++ b/test/functional/wallet_createwallet.py @@ -116,8 +116,20 @@ class CreateWalletTest(BitcoinTestFramework): walletinfo = w6.getwalletinfo() assert_equal(walletinfo['keypoolsize'], 1) assert_equal(walletinfo['keypoolsize_hd_internal'], 1) - # Empty passphrase, error - assert_raises_rpc_error(-16, 'Cannot encrypt a wallet with a blank password', self.nodes[0].createwallet, 'w7', False, False, '') + # Allow empty passphrase, but there should be a warning + 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) + + 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_equal(w8.getwalletinfo()["avoid_reuse"], True) + + self.log.info('Using a passphrase with private keys disabled returns error') + assert_raises_rpc_error(-4, 'Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled.', self.nodes[0].createwallet, wallet_name='w9', disable_private_keys=True, passphrase='thisisapassphrase') if __name__ == '__main__': CreateWalletTest().main() diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py index c514b7e0b4..fbcb4e75ba 100755 --- a/test/functional/wallet_encryption.py +++ b/test/functional/wallet_encryption.py @@ -49,7 +49,7 @@ class WalletEncryptionTest(BitcoinTestFramework): assert_equal(privkey, self.nodes[0].dumpprivkey(address)) # Check that the timeout is right - time.sleep(2) + time.sleep(3) assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address) # Test wrong passphrase |