diff options
Diffstat (limited to 'test')
84 files changed, 2050 insertions, 678 deletions
diff --git a/test/README.md b/test/README.md index ecea3213ab..8f08b7afe4 100644 --- a/test/README.md +++ b/test/README.md @@ -49,6 +49,29 @@ You can run any combination (incl. duplicates) of tests by calling: test/functional/test_runner.py <testname1> <testname2> <testname3> ... ``` +Wildcard test names can be passed, if the paths are coherent and the test runner +is called from a `bash` shell or similar that does the globbing. For example, +to run all the wallet tests: + +``` +test/functional/test_runner.py test/functional/wallet* +functional/test_runner.py functional/wallet* (called from the test/ directory) +test_runner.py wallet* (called from the test/functional/ directory) +``` + +but not + +``` +test/functional/test_runner.py wallet* +``` + +Combinations of wildcards can be passed: + +``` +test/functional/test_runner.py ./test/functional/tool* test/functional/mempool* +test_runner.py tool* mempool* +``` + Run the regression test suite with: ``` diff --git a/test/functional/combine_logs.py b/test/functional/combine_logs.py index 45ecaabe14..367d0f6916 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(r'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..fe6f9eade1 100755 --- a/test/functional/feature_bip68_sequence.py +++ b/test/functional/feature_bip68_sequence.py @@ -14,8 +14,8 @@ from test_framework.util import ( assert_equal, assert_greater_than, assert_raises_rpc_error, - get_bip9_status, satoshi_round, + softfork_active, ) SEQUENCE_LOCKTIME_DISABLE_FLAG = (1<<31) @@ -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() @@ -49,7 +52,7 @@ class BIP68Test(BitcoinTestFramework): self.log.info("Running test sequence-lock-unconfirmed-inputs") self.test_sequence_lock_unconfirmed_inputs() - self.log.info("Running test BIP68 not consensus before versionbits activation") + self.log.info("Running test BIP68 not consensus before activation") self.test_bip68_not_consensus() self.log.info("Activating BIP68 (and 112/113)") @@ -333,12 +336,12 @@ class BIP68Test(BitcoinTestFramework): self.nodes[0].invalidateblock(self.nodes[0].getblockhash(cur_height+1)) self.nodes[0].generate(10) - # Make sure that BIP68 isn't being used to validate blocks, prior to - # versionbits activation. If more blocks are mined prior to this test + # Make sure that BIP68 isn't being used to validate blocks prior to + # activation height. If more blocks are mined prior to this test # being run, then it's possible the test has activated the soft fork, and # this test should be moved to run earlier, or deleted. def test_bip68_not_consensus(self): - assert get_bip9_status(self.nodes[0], 'csv')['status'] != 'active' + assert not softfork_active(self.nodes[0], 'csv') txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2) tx1 = FromHex(CTransaction(), self.nodes[0].getrawtransaction(txid)) @@ -388,9 +391,9 @@ class BIP68Test(BitcoinTestFramework): height = self.nodes[0].getblockcount() assert_greater_than(min_activation_height - height, 2) self.nodes[0].generate(min_activation_height - height - 2) - assert_equal(get_bip9_status(self.nodes[0], 'csv')['status'], "locked_in") + assert not softfork_active(self.nodes[0], 'csv') self.nodes[0].generate(1) - assert_equal(get_bip9_status(self.nodes[0], 'csv')['status'], "active") + assert softfork_active(self.nodes[0], 'csv') self.sync_blocks() # Use self.nodes[1] to test that version 2 transactions are standard. diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 3ad83cd2b3..12a52935ef 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -74,11 +74,15 @@ class CBrokenBlock(CBlock): def normal_serialize(self): return super().serialize() + +DUPLICATE_COINBASE_SCRIPT_SIG = b'\x01\x78' # Valid for block at height 120 + + 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 @@ -96,6 +100,13 @@ class FullBlockTest(BitcoinTestFramework): self.spendable_outputs = [] # Create a new block + b_dup_cb = self.next_block('dup_cb') + b_dup_cb.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG + b_dup_cb.vtx[0].rehash() + duplicate_tx = b_dup_cb.vtx[0] + b_dup_cb = self.update_block('dup_cb', []) + self.send_blocks([b_dup_cb]) + b0 = self.next_block(0) self.save_spendable_output() self.send_blocks([b0]) @@ -486,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() @@ -750,7 +769,7 @@ class FullBlockTest(BitcoinTestFramework): # Test a few invalid tx types # - # -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) + # -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 () # \-> ??? (17) # @@ -776,14 +795,14 @@ class FullBlockTest(BitcoinTestFramework): # reset to good chain self.move_tip(57) - b60 = self.next_block(60, spend=out[17]) + b60 = self.next_block(60) self.send_blocks([b60], True) self.save_spendable_output() - # Test BIP30 + # Test BIP30 (reject duplicate) # - # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) - # \-> b61 (18) + # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 () + # \-> b61 () # # Blocks are not allowed to contain a transaction whose id matches that of an earlier, # not-fully-spent transaction in the same chain. To test, make identical coinbases; @@ -791,20 +810,44 @@ class FullBlockTest(BitcoinTestFramework): # self.log.info("Reject a block with a transaction with a duplicate hash of a previous transaction (BIP30)") self.move_tip(60) - b61 = self.next_block(61, spend=out[18]) - b61.vtx[0].vin[0].scriptSig = b60.vtx[0].vin[0].scriptSig # Equalize the coinbases + b61 = self.next_block(61) + b61.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG b61.vtx[0].rehash() b61 = self.update_block(61, []) - assert_equal(b60.vtx[0].serialize(), b61.vtx[0].serialize()) + assert_equal(duplicate_tx.serialize(), b61.vtx[0].serialize()) self.send_blocks([b61], success=False, reject_reason='bad-txns-BIP30', reconnect=True) + # Test BIP30 (allow duplicate if spent) + # + # -> b57 (16) -> b60 () + # \-> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () + # + self.move_tip(57) + b_spend_dup_cb = self.next_block('spend_dup_cb') + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(duplicate_tx.sha256, 0))) + tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) + self.sign_tx(tx, duplicate_tx) + tx.rehash() + b_spend_dup_cb = self.update_block('spend_dup_cb', [tx]) + + b_dup_2 = self.next_block('dup_2') + b_dup_2.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG + b_dup_2.vtx[0].rehash() + b_dup_2 = self.update_block('dup_2', []) + assert_equal(duplicate_tx.serialize(), b_dup_2.vtx[0].serialize()) + assert_equal(self.nodes[0].gettxout(txid=duplicate_tx.hash, n=0)['confirmations'], 119) + self.send_blocks([b_spend_dup_cb, b_dup_2], success=True) + # The duplicate has less confirmations + assert_equal(self.nodes[0].gettxout(txid=duplicate_tx.hash, n=0)['confirmations'], 1) + # Test tx.isFinal is properly rejected (not an exhaustive tx.isFinal test, that should be in data-driven transaction tests) # - # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) - # \-> b62 (18) + # -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () + # \-> b62 (18) # self.log.info("Reject a block with a transaction with a nonfinal locktime") - self.move_tip(60) + self.move_tip('dup_2') b62 = self.next_block(62) tx = CTransaction() tx.nLockTime = 0xffffffff # this locktime is non-final @@ -817,11 +860,11 @@ class FullBlockTest(BitcoinTestFramework): # Test a non-final coinbase is also rejected # - # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) - # \-> b63 (-) + # -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () + # \-> b63 (-) # self.log.info("Reject a block with a coinbase transaction with a nonfinal locktime") - self.move_tip(60) + self.move_tip('dup_2') b63 = self.next_block(63) b63.vtx[0].nLockTime = 0xffffffff b63.vtx[0].vin[0].nSequence = 0xDEADBEEF @@ -837,14 +880,14 @@ class FullBlockTest(BitcoinTestFramework): # What matters is that the receiving node should not reject the bloated block, and then reject the canonical # block on the basis that it's the same as an already-rejected block (which would be a consensus failure.) # - # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) - # \ - # b64a (18) + # -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () -> b64 (18) + # \ + # b64a (18) # b64a is a bloated block (non-canonical varint) # b64 is a good block (same as b64 but w/ canonical varint) # self.log.info("Accept a valid block even if a bloated version of the block has previously been sent") - self.move_tip(60) + self.move_tip('dup_2') regular_block = self.next_block("64a", spend=out[18]) # make it a "broken_block," with non-canonical serialization @@ -870,7 +913,7 @@ class FullBlockTest(BitcoinTestFramework): node.disconnect_p2ps() self.reconnect_p2p() - self.move_tip(60) + self.move_tip('dup_2') b64 = CBlock(b64a) b64.vtx = copy.deepcopy(b64a.vtx) assert_equal(b64.hash, b64a.hash) @@ -882,7 +925,7 @@ class FullBlockTest(BitcoinTestFramework): # Spend an output created in the block itself # - # -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) + # -> b_dup_2 () -> b64 (18) -> b65 (19) # self.log.info("Accept a block with a transaction spending an output created in the same block") self.move_tip(64) @@ -895,8 +938,8 @@ class FullBlockTest(BitcoinTestFramework): # Attempt to spend an output created later in the same block # - # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) - # \-> b66 (20) + # -> b64 (18) -> b65 (19) + # \-> b66 (20) self.log.info("Reject a block with a transaction spending an output created later in the same block") self.move_tip(65) b66 = self.next_block(66) @@ -907,8 +950,8 @@ class FullBlockTest(BitcoinTestFramework): # Attempt to double-spend a transaction created in a block # - # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) - # \-> b67 (20) + # -> b64 (18) -> b65 (19) + # \-> b67 (20) # # self.log.info("Reject a block with a transaction double spending a transaction created in the same block") @@ -922,8 +965,8 @@ class FullBlockTest(BitcoinTestFramework): # More tests of block subsidy # - # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) - # \-> b68 (20) + # -> b64 (18) -> b65 (19) -> b69 (20) + # \-> b68 (20) # # b68 - coinbase with an extra 10 satoshis, # creates a tx that has 9 satoshis from out[20] go to fees @@ -949,8 +992,8 @@ class FullBlockTest(BitcoinTestFramework): # Test spending the outpoint of a non-existent transaction # - # -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) - # \-> b70 (21) + # -> b65 (19) -> b69 (20) + # \-> b70 (21) # self.log.info("Reject a block containing a transaction spending from a non-existent input") self.move_tip(69) @@ -965,8 +1008,8 @@ class FullBlockTest(BitcoinTestFramework): # Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks) # - # -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) - # \-> b71 (21) + # -> b65 (19) -> b69 (20) -> b72 (21) + # \-> b71 (21) # # b72 is a good block. # b71 is a copy of 72, but re-adds one of its transactions. However, it has the same hash as b72. @@ -994,8 +1037,8 @@ class FullBlockTest(BitcoinTestFramework): # Test some invalid scripts and MAX_BLOCK_SIGOPS # - # -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) - # \-> b** (22) + # -> b69 (20) -> b72 (21) + # \-> b** (22) # # b73 - tx with excessive sigops that are placed after an excessively large script element. 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 b16eafccca..e00219ca4a 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -57,16 +57,31 @@ def cltv_validate(node, tx, height): class BIP65Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - self.extra_args = [['-whitelist=127.0.0.1', '-par=1']] # Use only one script thread to get the exact reject reason for testing + self.extra_args = [[ + '-whitelist=127.0.0.1', + '-par=1', # Use only one script thread to get the exact reject reason for testing + '-acceptnonstdtxn=1', # cltv_invalidate is nonstandard + ]] self.setup_clean_chain = True self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() + def test_cltv_info(self, *, is_active): + assert_equal(self.nodes[0].getblockchaininfo()['softforks']['bip65'], + { + "active": is_active, + "height": CLTV_HEIGHT, + "type": "buried", + }, + ) + def run_test(self): self.nodes[0].add_p2p_connection(P2PInterface()) + self.test_cltv_info(is_active=False) + self.log.info("Mining %d blocks", CLTV_HEIGHT - 2) self.coinbase_txids = [self.nodes[0].getblock(b)['tx'][0] for b in self.nodes[0].generate(CLTV_HEIGHT - 2)] self.nodeaddress = self.nodes[0].getnewaddress() @@ -86,7 +101,9 @@ class BIP65Test(BitcoinTestFramework): block.hashMerkleRoot = block.calc_merkle_root() block.solve() + self.test_cltv_info(is_active=False) # Not active as of current tip and next block does not need to obey rules self.nodes[0].p2p.send_and_ping(msg_block(block)) + self.test_cltv_info(is_active=True) # Not active as of current tip, but next block must obey rules assert_equal(self.nodes[0].getbestblockhash(), block.hash) self.log.info("Test that blocks must now be at least version 4") @@ -135,7 +152,9 @@ class BIP65Test(BitcoinTestFramework): block.hashMerkleRoot = block.calc_merkle_root() block.solve() + self.test_cltv_info(is_active=True) # Not active as of current tip, but next block must obey rules self.nodes[0].p2p.send_and_ping(msg_block(block)) + self.test_cltv_info(is_active=True) # Active as of current tip assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index f0d6bc21e6..b997c76025 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: @@ -103,17 +109,15 @@ class ConfArgsTest(BitcoinTestFramework): f.write("datadir=" + new_data_dir + "\n") f.write(conf_file_contents) - # Temporarily disabled, because this test would access the user's home dir (~/.bitcoin) - #self.nodes[0].assert_start_raises_init_error(['-conf=' + conf_file], 'Error reading configuration file: specified data directory "' + new_data_dir + '" does not exist.') + self.nodes[0].assert_start_raises_init_error(['-conf=' + conf_file], 'Error: Error reading configuration file: specified data directory "' + new_data_dir + '" does not exist.') # Create the directory and ensure the config file now works os.mkdir(new_data_dir) - # Temporarily disabled, because this test would access the user's home dir (~/.bitcoin) - #self.start_node(0, ['-conf='+conf_file, '-wallet=w1']) - #self.stop_node(0) - #assert os.path.exists(os.path.join(new_data_dir, 'regtest', 'blocks')) - #if self.is_wallet_compiled(): - #assert os.path.exists(os.path.join(new_data_dir, 'regtest', 'wallets', 'w1')) + self.start_node(0, ['-conf='+conf_file, '-wallet=w1']) + self.stop_node(0) + assert os.path.exists(os.path.join(new_data_dir, 'regtest', 'blocks')) + if self.is_wallet_compiled(): + assert os.path.exists(os.path.join(new_data_dir, 'regtest', 'wallets', 'w1')) # Ensure command line argument overrides datadir in conf os.mkdir(new_data_dir_2) diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py index 887e9dafa3..6bd321992a 100755 --- a/test/functional/feature_csv_activation.py +++ b/test/functional/feature_csv_activation.py @@ -2,23 +2,17 @@ # Copyright (c) 2015-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Test activation of the first version bits soft fork. +"""Test CSV soft fork activation. This soft fork will activate the following BIPS: BIP 68 - nSequence relative lock times BIP 112 - CHECKSEQUENCEVERIFY BIP 113 - MedianTimePast semantics for nLockTime -regtest lock-in with 108/144 block signalling -activation after a further 144 blocks - mine 82 blocks whose coinbases will be used to generate inputs for our tests -mine 61 blocks to transition from DEFINED to STARTED -mine 144 blocks only 100 of which are signaling readiness in order to fail to change state this period -mine 144 blocks with 108 signaling and verify STARTED->LOCKED_IN -mine 140 blocks and seed block chain with the 82 inputs will use for our tests at height 572 -mine 3 blocks and verify still at LOCKED_IN and test that enforcement has not triggered -mine 1 block and test that enforcement has triggered (which triggers ACTIVE) +mine 345 blocks and seed block chain with the 82 inputs will use for our tests at height 427 +mine 2 blocks and verify soft fork not yet activated +mine 1 block and test that soft fork is activated (rules enforced for next block) Test BIP 113 is enforced Mine 4 blocks so next height is 580 and test BIP 68 is enforced for time and height Mine 1 block so next height is 581 and test BIP 68 now passes time but not height @@ -58,11 +52,12 @@ from test_framework.script import ( from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, - get_bip9_status, hex_str_to_bytes, + softfork_active, ) BASE_RELATIVE_LOCKTIME = 10 +CSV_ACTIVATION_HEIGHT = 432 SEQ_DISABLE_FLAG = 1 << 31 SEQ_RANDOM_HIGH_BIT = 1 << 25 SEQ_TYPE_FLAG = 1 << 22 @@ -148,20 +143,19 @@ class BIP68_112_113Test(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() - def generate_blocks(self, number, version, test_blocks=None): - if test_blocks is None: - test_blocks = [] + def generate_blocks(self, number): + test_blocks = [] for i in range(number): - block = self.create_test_block([], version) + block = self.create_test_block([]) test_blocks.append(block) self.last_block_time += 600 self.tip = block.sha256 self.tipheight += 1 return test_blocks - def create_test_block(self, txs, version=536870912): + def create_test_block(self, txs): block = create_block(self.tip, create_coinbase(self.tipheight + 1), self.last_block_time + 600) - block.nVersion = version + block.nVersion = 4 block.vtx.extend(txs) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() @@ -187,45 +181,14 @@ class BIP68_112_113Test(BitcoinTestFramework): self.tip = int(self.nodes[0].getbestblockhash(), 16) self.nodeaddress = self.nodes[0].getnewaddress() - self.log.info("Test that the csv softfork is DEFINED") - assert_equal(get_bip9_status(self.nodes[0], 'csv')['status'], 'defined') - test_blocks = self.generate_blocks(61, 4) - self.send_blocks(test_blocks) - - self.log.info("Advance from DEFINED to STARTED, height = 143") - assert_equal(get_bip9_status(self.nodes[0], 'csv')['status'], 'started') - - self.log.info("Fail to achieve LOCKED_IN") - # 100 out of 144 signal bit 0. Use a variety of bits to simulate multiple parallel softforks - - test_blocks = self.generate_blocks(50, 536870913) # 0x20000001 (signalling ready) - test_blocks = self.generate_blocks(20, 4, test_blocks) # 0x00000004 (signalling not) - test_blocks = self.generate_blocks(50, 536871169, test_blocks) # 0x20000101 (signalling ready) - test_blocks = self.generate_blocks(24, 536936448, test_blocks) # 0x20010000 (signalling not) - self.send_blocks(test_blocks) - - self.log.info("Failed to advance past STARTED, height = 287") - assert_equal(get_bip9_status(self.nodes[0], 'csv')['status'], 'started') - - self.log.info("Generate blocks to achieve LOCK-IN") - # 108 out of 144 signal bit 0 to achieve lock-in - # using a variety of bits to simulate multiple parallel softforks - test_blocks = self.generate_blocks(58, 536870913) # 0x20000001 (signalling ready) - test_blocks = self.generate_blocks(26, 4, test_blocks) # 0x00000004 (signalling not) - test_blocks = self.generate_blocks(50, 536871169, test_blocks) # 0x20000101 (signalling ready) - test_blocks = self.generate_blocks(10, 536936448, test_blocks) # 0x20010000 (signalling not) - self.send_blocks(test_blocks) - - self.log.info("Advanced from STARTED to LOCKED_IN, height = 431") - assert_equal(get_bip9_status(self.nodes[0], 'csv')['status'], 'locked_in') - - # Generate 140 more version 4 blocks - test_blocks = self.generate_blocks(140, 4) + # Activation height is hardcoded + test_blocks = self.generate_blocks(345) self.send_blocks(test_blocks) + assert not softfork_active(self.nodes[0], 'csv') - # Inputs at height = 572 + # Inputs at height = 431 # - # Put inputs for all tests in the chain at height 572 (tip now = 571) (time increases by 600s per block) + # Put inputs for all tests in the chain at height 431 (tip now = 430) (time increases by 600s per block) # Note we reuse inputs for v1 and v2 txs so must test these separately # 16 normal inputs bip68inputs = [] @@ -255,7 +218,7 @@ class BIP68_112_113Test(BitcoinTestFramework): bip113input = send_generic_input_tx(self.nodes[0], self.coinbase_blocks, self.nodeaddress) self.nodes[0].setmocktime(self.last_block_time + 600) - inputblockhash = self.nodes[0].generate(1)[0] # 1 block generated for inputs to be in chain at height 572 + inputblockhash = self.nodes[0].generate(1)[0] # 1 block generated for inputs to be in chain at height 431 self.nodes[0].setmocktime(0) self.tip = int(inputblockhash, 16) self.tipheight += 1 @@ -263,11 +226,12 @@ class BIP68_112_113Test(BitcoinTestFramework): assert_equal(len(self.nodes[0].getblock(inputblockhash, True)["tx"]), 82 + 1) # 2 more version 4 blocks - test_blocks = self.generate_blocks(2, 4) + test_blocks = self.generate_blocks(2) self.send_blocks(test_blocks) - self.log.info("Not yet advanced to ACTIVE, height = 574 (will activate for block 576, not 575)") - assert_equal(get_bip9_status(self.nodes[0], 'csv')['status'], 'locked_in') + assert_equal(self.tipheight, CSV_ACTIVATION_HEIGHT - 2) + self.log.info("Height = {}, CSV not yet active (will activate for block {}, not {})".format(self.tipheight, CSV_ACTIVATION_HEIGHT, CSV_ACTIVATION_HEIGHT - 1)) + assert not softfork_active(self.nodes[0], 'csv') # Test both version 1 and version 2 transactions for all tests # BIP113 test transaction will be modified before each use to put in appropriate block time @@ -340,10 +304,11 @@ class BIP68_112_113Test(BitcoinTestFramework): self.send_blocks([self.create_test_block(success_txs)]) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) - # 1 more version 4 block to get us to height 575 so the fork should now be active for the next block - test_blocks = self.generate_blocks(1, 4) + # 1 more version 4 block to get us to height 432 so the fork should now be active for the next block + assert not softfork_active(self.nodes[0], 'csv') + test_blocks = self.generate_blocks(1) self.send_blocks(test_blocks) - assert_equal(get_bip9_status(self.nodes[0], 'csv')['status'], 'active') + assert softfork_active(self.nodes[0], 'csv') self.log.info("Post-Soft Fork Tests.") @@ -364,8 +329,8 @@ class BIP68_112_113Test(BitcoinTestFramework): self.send_blocks([self.create_test_block([bip113tx])]) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) - # Next block height = 580 after 4 blocks of random version - test_blocks = self.generate_blocks(4, 1234) + # Next block height = 437 after 4 blocks of random version + test_blocks = self.generate_blocks(4) self.send_blocks(test_blocks) self.log.info("BIP 68 tests") @@ -392,8 +357,8 @@ class BIP68_112_113Test(BitcoinTestFramework): for tx in bip68heighttxs: self.send_blocks([self.create_test_block([tx])], success=False) - # Advance one block to 581 - test_blocks = self.generate_blocks(1, 1234) + # Advance one block to 438 + test_blocks = self.generate_blocks(1) self.send_blocks(test_blocks) # Height txs should fail and time txs should now pass 9 * 600 > 10 * 512 @@ -403,8 +368,8 @@ class BIP68_112_113Test(BitcoinTestFramework): for tx in bip68heighttxs: self.send_blocks([self.create_test_block([tx])], success=False) - # Advance one block to 582 - test_blocks = self.generate_blocks(1, 1234) + # Advance one block to 439 + test_blocks = self.generate_blocks(1) self.send_blocks(test_blocks) # All BIP 68 txs should pass diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index 62062926a6..b86f6af4ca 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 @@ -48,13 +58,14 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): self.base_args = ["-limitdescendantsize=0", "-maxmempool=0", "-rpcservertimeout=900", "-dbbatchsize=200000"] # Set different crash ratios and cache sizes. Note that not all of - # -dbcache goes to pcoinsTip. + # -dbcache goes to the in-memory coins cache. self.node0_args = ["-dbcrashratio=8", "-dbcache=4"] + self.base_args self.node1_args = ["-dbcrashratio=16", "-dbcache=8"] + self.base_args 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_dersig.py b/test/functional/feature_dersig.py index 7480e5c5ba..1bd9586364 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -51,9 +51,20 @@ class BIP66Test(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() + def test_dersig_info(self, *, is_active): + assert_equal(self.nodes[0].getblockchaininfo()['softforks']['bip66'], + { + "active": is_active, + "height": DERSIG_HEIGHT, + "type": "buried", + }, + ) + def run_test(self): self.nodes[0].add_p2p_connection(P2PInterface()) + self.test_dersig_info(is_active=False) + self.log.info("Mining %d blocks", DERSIG_HEIGHT - 2) self.coinbase_txids = [self.nodes[0].getblock(b)['tx'][0] for b in self.nodes[0].generate(DERSIG_HEIGHT - 2)] self.nodeaddress = self.nodes[0].getnewaddress() @@ -74,7 +85,9 @@ class BIP66Test(BitcoinTestFramework): block.rehash() block.solve() + self.test_dersig_info(is_active=False) # Not active as of current tip and next block does not need to obey rules self.nodes[0].p2p.send_and_ping(msg_block(block)) + self.test_dersig_info(is_active=True) # Not active as of current tip, but next block must obey rules assert_equal(self.nodes[0].getbestblockhash(), block.hash) self.log.info("Test that blocks must now be at least version 3") @@ -128,8 +141,11 @@ class BIP66Test(BitcoinTestFramework): block.rehash() block.solve() + self.test_dersig_info(is_active=True) # Not active as of current tip, but next block must obey rules self.nodes[0].p2p.send_and_ping(msg_block(block)) + self.test_dersig_info(is_active=True) # Active as of current tip assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) + if __name__ == '__main__': BIP66Test().main() diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index a4b9f213a1..d2d41b1206 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -28,6 +28,7 @@ P2SH_2 = CScript([OP_HASH160, hash160(REDEEM_SCRIPT_2), OP_EQUAL]) # Associated ScriptSig's to spend satisfy P2SH_1 and P2SH_2 SCRIPT_SIG = [CScript([OP_TRUE, REDEEM_SCRIPT_1]), CScript([OP_TRUE, REDEEM_SCRIPT_2])] + def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee_increment): """Create and send a transaction with a random fee. @@ -69,6 +70,7 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee return (ToHex(tx), fee) + def split_inputs(from_node, txins, txouts, initial_split=False): """Generate a lot of inputs so we can generate a ton of transactions. @@ -97,6 +99,7 @@ def split_inputs(from_node, txins, txouts, initial_split=False): txouts.append({"txid": txid, "vout": 0, "amount": half_change}) txouts.append({"txid": txid, "vout": 1, "amount": rem_change}) + def check_estimates(node, fees_seen): """Call estimatesmartfee and verify that the estimates meet certain invariants.""" @@ -120,9 +123,17 @@ 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) + # Force fSendTrickle to true (via whitelist) + self.extra_args = [ + ["-acceptnonstdtxn", "-whitelist=127.0.0.1"], + ["-acceptnonstdtxn", "-whitelist=127.0.0.1", "-blockmaxweight=68000"], + ["-acceptnonstdtxn", "-whitelist=127.0.0.1", "-blockmaxweight=32000"], + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -133,9 +144,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, @@ -160,9 +169,9 @@ class EstimateFeeTest(BitcoinTestFramework): self.memutxo, Decimal("0.005"), min_fee, min_fee) tx_kbytes = (len(txhex) // 2) / 1000.0 self.fees_per_kb.append(float(fee) / tx_kbytes) - self.sync_mempools(self.nodes[0:3], wait=.1) + self.sync_mempools(wait=.1) mined = mining_node.getblock(mining_node.generate(1)[0], True)["tx"] - self.sync_blocks(self.nodes[0:3], wait=.1) + self.sync_blocks(wait=.1) # update which txouts are confirmed newmem = [] for utx in self.memutxo: @@ -184,22 +193,22 @@ class EstimateFeeTest(BitcoinTestFramework): split_inputs(self.nodes[0], self.nodes[0].listunspent(0), self.txouts, True) # Mine - while (len(self.nodes[0].getrawmempool()) > 0): + while len(self.nodes[0].getrawmempool()) > 0: self.nodes[0].generate(1) # Repeatedly split those 2 outputs, doubling twice for each rep # Use txouts to monitor the available utxo, since these won't be tracked in wallet reps = 0 - while (reps < 5): + while reps < 5: # Double txouts to txouts2 - while (len(self.txouts) > 0): + while len(self.txouts) > 0: split_inputs(self.nodes[0], self.txouts, self.txouts2) - while (len(self.nodes[0].getrawmempool()) > 0): + while len(self.nodes[0].getrawmempool()) > 0: self.nodes[0].generate(1) # Double txouts2 to txouts - while (len(self.txouts2) > 0): + while len(self.txouts2) > 0: split_inputs(self.nodes[0], self.txouts2, self.txouts) - while (len(self.nodes[0].getrawmempool()) > 0): + while len(self.nodes[0].getrawmempool()) > 0: self.nodes[0].generate(1) reps += 1 self.log.info("Finished splitting") @@ -239,5 +248,6 @@ class EstimateFeeTest(BitcoinTestFramework): self.log.info("Final estimates after emptying mempools") check_estimates(self.nodes[1], self.fees_per_kb) + if __name__ == '__main__': EstimateFeeTest().main() diff --git a/test/functional/feature_includeconf.py b/test/functional/feature_includeconf.py index d06f6826f0..2cd6a05d08 100755 --- a/test/functional/feature_includeconf.py +++ b/test/functional/feature_includeconf.py @@ -43,7 +43,7 @@ class IncludeConfTest(BitcoinTestFramework): self.log.info("-includeconf cannot be used as command-line arg") self.stop_node(0) - self.nodes[0].assert_start_raises_init_error(extra_args=["-includeconf=relative2.conf"], expected_msg="Error parsing command line arguments: -includeconf cannot be used from commandline; -includeconf=relative2.conf") + self.nodes[0].assert_start_raises_init_error(extra_args=["-includeconf=relative2.conf"], expected_msg="Error: Error parsing command line arguments: -includeconf cannot be used from commandline; -includeconf=relative2.conf") self.log.info("-includeconf cannot be used recursively. subversion should end with 'main; relative)/'") with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "a", encoding="utf8") as f: @@ -59,11 +59,11 @@ class IncludeConfTest(BitcoinTestFramework): # Commented out as long as we ignore invalid arguments in configuration files #with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: # f.write("foo=bar\n") - #self.nodes[0].assert_start_raises_init_error(expected_msg="Error reading configuration file: Invalid configuration value foo") + #self.nodes[0].assert_start_raises_init_error(expected_msg="Error: Error reading configuration file: Invalid configuration value foo") self.log.info("-includeconf cannot be invalid path") os.remove(os.path.join(self.options.tmpdir, "node0", "relative.conf")) - self.nodes[0].assert_start_raises_init_error(expected_msg="Error reading configuration file: Failed to include configuration file relative.conf") + self.nodes[0].assert_start_raises_init_error(expected_msg="Error: Error reading configuration file: Failed to include configuration file relative.conf") self.log.info("multiple -includeconf args can be used from the base config file. subversion should end with 'main; relative; relative2)/'") with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: diff --git a/test/functional/feature_logging.py b/test/functional/feature_logging.py index 8bb7e02695..e6ff21ee9c 100755 --- a/test/functional/feature_logging.py +++ b/test/functional/feature_logging.py @@ -36,7 +36,7 @@ class LoggingTest(BitcoinTestFramework): invdir = self.relative_log_path("foo") invalidname = os.path.join("foo", "foo.log") self.stop_node(0) - exp_stderr = "Error: Could not open debug log file \S+$" + exp_stderr = r"Error: Could not open debug log file \S+$" self.nodes[0].assert_start_raises_init_error(["-debuglogfile=%s" % (invalidname)], exp_stderr, match=ErrorMatch.FULL_REGEX) assert not os.path.isfile(os.path.join(invdir, "foo.log")) 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_nulldummy.py b/test/functional/feature_nulldummy.py index 60a703c48f..250dee1528 100755 --- a/test/functional/feature_nulldummy.py +++ b/test/functional/feature_nulldummy.py @@ -41,7 +41,7 @@ class NULLDUMMYTest(BitcoinTestFramework): self.setup_clean_chain = True # This script tests NULLDUMMY activation, which is part of the 'segwit' deployment, so we go through # normal segwit activation here (and don't use the default always-on behaviour). - self.extra_args = [['-whitelist=127.0.0.1', '-vbparams=segwit:0:999999999999', '-addresstype=legacy']] + self.extra_args = [['-whitelist=127.0.0.1', '-segwitheight=432', '-addresstype=legacy']] def skip_test_if_missing_module(self): self.skip_if_no_wallet() 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..b9db618575 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -53,19 +53,22 @@ 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", + "-segwitheight=432", "-addresstype=legacy", ], [ + "-acceptnonstdtxn=1", "-blockversion=4", "-rpcserialversion=1", - "-vbparams=segwit:0:999999999999", + "-segwitheight=432", "-addresstype=legacy", ], [ + "-acceptnonstdtxn=1", "-blockversion=536870915", - "-vbparams=segwit:0:999999999999", + "-segwitheight=432", "-addresstype=legacy", ], ] @@ -223,6 +226,16 @@ class SegWitTest(BitcoinTestFramework): assert tx.wit.is_null() # This should not be a segwit input assert txid1 in self.nodes[0].getrawmempool() + tx1_hex = self.nodes[0].gettransaction(txid1)['hex'] + tx1 = FromHex(CTransaction(), tx1_hex) + + # Check that wtxid is properly reported in mempool entry (txid1) + assert_equal(int(self.nodes[0].getmempoolentry(txid1)["wtxid"], 16), tx1.calc_sha256(True)) + + # Check that weight and vsize are properly reported in mempool entry (txid1) + assert_equal(self.nodes[0].getmempoolentry(txid1)["vsize"], (self.nodes[0].getmempoolentry(txid1)["weight"] + 3) // 4) + assert_equal(self.nodes[0].getmempoolentry(txid1)["weight"], len(tx1.serialize_without_witness())*3 + len(tx1.serialize_with_witness())) + # Now create tx2, which will spend from txid1. tx = CTransaction() tx.vin.append(CTxIn(COutPoint(int(txid1, 16), 0), b'')) @@ -232,6 +245,13 @@ class SegWitTest(BitcoinTestFramework): tx = FromHex(CTransaction(), tx2_hex) assert not tx.wit.is_null() + # Check that wtxid is properly reported in mempool entry (txid2) + assert_equal(int(self.nodes[0].getmempoolentry(txid2)["wtxid"], 16), tx.calc_sha256(True)) + + # Check that weight and vsize are properly reported in mempool entry (txid2) + assert_equal(self.nodes[0].getmempoolentry(txid2)["vsize"], (self.nodes[0].getmempoolentry(txid2)["weight"] + 3) // 4) + assert_equal(self.nodes[0].getmempoolentry(txid2)["weight"], len(tx.serialize_without_witness())*3 + len(tx.serialize_with_witness())) + # Now create tx3, which will spend from txid2 tx = CTransaction() tx.vin.append(CTxIn(COutPoint(int(txid2, 16), 0), b"")) @@ -248,9 +268,13 @@ class SegWitTest(BitcoinTestFramework): assert txid2 in template_txids assert txid3 in template_txids - # Check that wtxid is properly reported in mempool entry + # Check that wtxid is properly reported in mempool entry (txid3) assert_equal(int(self.nodes[0].getmempoolentry(txid3)["wtxid"], 16), tx.calc_sha256(True)) + # Check that weight and vsize are properly reported in mempool entry (txid3) + assert_equal(self.nodes[0].getmempoolentry(txid3)["vsize"], (self.nodes[0].getmempoolentry(txid3)["weight"] + 3) // 4) + assert_equal(self.nodes[0].getmempoolentry(txid3)["weight"], len(tx.serialize_without_witness())*3 + len(tx.serialize_with_witness())) + # Mine a block to clear the gbt cache again. self.nodes[0].generate(1) diff --git a/test/functional/feature_uacomment.py b/test/functional/feature_uacomment.py index fb4ad21359..85c250173f 100755 --- a/test/functional/feature_uacomment.py +++ b/test/functional/feature_uacomment.py @@ -27,12 +27,12 @@ class UacommentTest(BitcoinTestFramework): self.log.info("test -uacomment max length") self.stop_node(0) - expected = "Error: Total length of network version string \([0-9]+\) exceeds maximum length \(256\). Reduce the number or size of uacomments." + expected = r"Error: Total length of network version string \([0-9]+\) exceeds maximum length \(256\). Reduce the number or size of uacomments." self.nodes[0].assert_start_raises_init_error(["-uacomment=" + 'a' * 256], expected, match=ErrorMatch.FULL_REGEX) self.log.info("test -uacomment unsafe characters") for unsafe_char in ['/', ':', '(', ')', '₿', '🏃']: - expected = "Error: User Agent comment \(" + re.escape(unsafe_char) + "\) contains unsafe characters." + expected = r"Error: User Agent comment \(" + re.escape(unsafe_char) + r"\) contains unsafe characters." self.nodes[0].assert_start_raises_init_error(["-uacomment=" + unsafe_char], expected, match=ErrorMatch.FULL_REGEX) 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/interface_rpc.py b/test/functional/interface_rpc.py index 49ae0fb1a9..e99fa22646 100755 --- a/test/functional/interface_rpc.py +++ b/test/functional/interface_rpc.py @@ -4,6 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Tests some generic aspects of the RPC interface.""" +import os from test_framework.authproxy import JSONRPCException from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_greater_than_or_equal @@ -31,6 +32,7 @@ class RPCInterfaceTest(BitcoinTestFramework): command = info['active_commands'][0] assert_equal(command['method'], 'getrpcinfo') assert_greater_than_or_equal(command['duration'], 0) + assert_equal(info['logpath'], os.path.join(self.nodes[0].datadir, 'regtest', 'debug.log')) def test_batch_request(self): self.log.info("Testing basic JSON-RPC batch request...") diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py index 8e58c85c15..5aea10fbce 100755 --- a/test/functional/interface_zmq.py +++ b/test/functional/interface_zmq.py @@ -7,14 +7,13 @@ import struct from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE from test_framework.test_framework import BitcoinTestFramework -from test_framework.messages import CTransaction -from test_framework.util import ( - assert_equal, - hash256, -) +from test_framework.messages import CTransaction, hash256 +from test_framework.util import assert_equal, connect_nodes from io import BytesIO +from time import sleep -ADDRESS = "tcp://127.0.0.1:28332" +def hash256_reversed(byte_str): + return hash256(byte_str)[::-1] class ZMQSubscriber: def __init__(self, socket, topic): @@ -43,67 +42,65 @@ class ZMQTest (BitcoinTestFramework): self.skip_if_no_py3_zmq() self.skip_if_no_bitcoind_zmq() - def setup_nodes(self): + def run_test(self): import zmq + self.ctx = zmq.Context() + try: + self.test_basic() + self.test_reorg() + finally: + # Destroy the ZMQ context. + self.log.debug("Destroying ZMQ context") + self.ctx.destroy(linger=None) - # Initialize ZMQ context and socket. + def test_basic(self): # All messages are received in the same socket which means # that this test fails if the publishing order changes. # Note that the publishing order is not defined in the documentation and # is subject to change. - self.zmq_context = zmq.Context() - socket = self.zmq_context.socket(zmq.SUB) + import zmq + address = 'tcp://127.0.0.1:28332' + socket = self.ctx.socket(zmq.SUB) socket.set(zmq.RCVTIMEO, 60000) - socket.connect(ADDRESS) # Subscribe to all available topics. - self.hashblock = ZMQSubscriber(socket, b"hashblock") - self.hashtx = ZMQSubscriber(socket, b"hashtx") - self.rawblock = ZMQSubscriber(socket, b"rawblock") - self.rawtx = ZMQSubscriber(socket, b"rawtx") - - self.extra_args = [ - ["-zmqpub%s=%s" % (sub.topic.decode(), ADDRESS) for sub in [self.hashblock, self.hashtx, self.rawblock, self.rawtx]], - [], - ] - self.add_nodes(self.num_nodes, self.extra_args) - self.start_nodes() - self.import_deterministic_coinbase_privkeys() + hashblock = ZMQSubscriber(socket, b"hashblock") + hashtx = ZMQSubscriber(socket, b"hashtx") + rawblock = ZMQSubscriber(socket, b"rawblock") + rawtx = ZMQSubscriber(socket, b"rawtx") - def run_test(self): - try: - self._zmq_test() - finally: - # Destroy the ZMQ context. - self.log.debug("Destroying ZMQ context") - self.zmq_context.destroy(linger=None) + self.restart_node(0, ["-zmqpub%s=%s" % (sub.topic.decode(), address) for sub in [hashblock, hashtx, rawblock, rawtx]]) + connect_nodes(self.nodes[0], 1) + socket.connect(address) + # Relax so that the subscriber is ready before publishing zmq messages + sleep(0.2) - def _zmq_test(self): num_blocks = 5 self.log.info("Generate %(n)d blocks (and %(n)d coinbase txes)" % {"n": num_blocks}) genhashes = self.nodes[0].generatetoaddress(num_blocks, ADDRESS_BCRT1_UNSPENDABLE) + self.sync_all() for x in range(num_blocks): # Should receive the coinbase txid. - txid = self.hashtx.receive() + txid = hashtx.receive() # Should receive the coinbase raw transaction. - hex = self.rawtx.receive() + hex = rawtx.receive() tx = CTransaction() tx.deserialize(BytesIO(hex)) tx.calc_sha256() assert_equal(tx.hash, txid.hex()) # Should receive the generated block hash. - hash = self.hashblock.receive().hex() + hash = hashblock.receive().hex() assert_equal(genhashes[x], hash) # The block should only have the coinbase txid. assert_equal([txid.hex()], self.nodes[1].getblock(hash)["tx"]) # Should receive the generated raw block. - block = self.rawblock.receive() - assert_equal(genhashes[x], hash256(block[:80]).hex()) + block = rawblock.receive() + assert_equal(genhashes[x], hash256_reversed(block[:80]).hex()) if self.is_wallet_compiled(): self.log.info("Wait for tx from second node") @@ -111,23 +108,49 @@ class ZMQTest (BitcoinTestFramework): self.sync_all() # Should receive the broadcasted txid. - txid = self.hashtx.receive() + txid = hashtx.receive() assert_equal(payment_txid, txid.hex()) # Should receive the broadcasted raw transaction. - hex = self.rawtx.receive() - assert_equal(payment_txid, hash256(hex).hex()) + hex = rawtx.receive() + assert_equal(payment_txid, hash256_reversed(hex).hex()) self.log.info("Test the getzmqnotifications RPC") assert_equal(self.nodes[0].getzmqnotifications(), [ - {"type": "pubhashblock", "address": ADDRESS, "hwm": 1000}, - {"type": "pubhashtx", "address": ADDRESS, "hwm": 1000}, - {"type": "pubrawblock", "address": ADDRESS, "hwm": 1000}, - {"type": "pubrawtx", "address": ADDRESS, "hwm": 1000}, + {"type": "pubhashblock", "address": address, "hwm": 1000}, + {"type": "pubhashtx", "address": address, "hwm": 1000}, + {"type": "pubrawblock", "address": address, "hwm": 1000}, + {"type": "pubrawtx", "address": address, "hwm": 1000}, ]) assert_equal(self.nodes[1].getzmqnotifications(), []) + def test_reorg(self): + import zmq + address = 'tcp://127.0.0.1:28333' + socket = self.ctx.socket(zmq.SUB) + socket.set(zmq.RCVTIMEO, 60000) + hashblock = ZMQSubscriber(socket, b'hashblock') + + # Should only notify the tip if a reorg occurs + self.restart_node(0, ['-zmqpub%s=%s' % (hashblock.topic.decode(), address)]) + socket.connect(address) + # Relax so that the subscriber is ready before publishing zmq messages + sleep(0.2) + + # Generate 1 block in nodes[0] and receive all notifications + self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE) + assert_equal(self.nodes[0].getbestblockhash(), hashblock.receive().hex()) + + # Generate 2 blocks in nodes[1] + self.nodes[1].generatetoaddress(2, ADDRESS_BCRT1_UNSPENDABLE) + + # nodes[0] will reorg chain after connecting back nodes[1] + connect_nodes(self.nodes[0], 1) + + # Should receive nodes[1] tip + assert_equal(self.nodes[1].getbestblockhash(), hashblock.receive().hex()) + if __name__ == '__main__': ZMQTest().main() 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..0739d7e29b --- /dev/null +++ b/test/functional/mempool_package_onemore.py @@ -0,0 +1,92 @@ +#!/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, 0, True) + 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 + (replacable_txid, replacable_orig_value) = 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) + + # Make sure we can RBF the chain which used our carve-out rule + second_tx_outputs = {self.nodes[0].getrawtransaction(replacable_txid, True)["vout"][0]['scriptPubKey']['addresses'][0]: replacable_orig_value - (Decimal(1) / Decimal(100))} + second_tx = self.nodes[0].createrawtransaction([{'txid': chain[0][0], 'vout': 1}], second_tx_outputs) + signed_second_tx = self.nodes[0].signrawtransactionwithwallet(second_tx) + self.nodes[0].sendrawtransaction(signed_second_tx['hex']) + + # 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_blocksonly.py b/test/functional/p2p_blocksonly.py index 12cb06a407..3258a38e3c 100755 --- a/test/functional/p2p_blocksonly.py +++ b/test/functional/p2p_blocksonly.py @@ -19,7 +19,7 @@ class P2PBlocksOnly(BitcoinTestFramework): def run_test(self): self.nodes[0].add_p2p_connection(P2PInterface()) - self.log.info('Check that txs from p2p are rejected') + self.log.info('Check that txs from p2p are rejected and result in disconnect') prevtx = self.nodes[0].getblock(self.nodes[0].getblockhash(1), 2)['tx'][0] rawtx = self.nodes[0].createrawtransaction( inputs=[{ @@ -42,13 +42,17 @@ class P2PBlocksOnly(BitcoinTestFramework): assert_equal(self.nodes[0].getnetworkinfo()['localrelay'], False) with self.nodes[0].assert_debug_log(['transaction sent in violation of protocol peer=0']): self.nodes[0].p2p.send_message(msg_tx(FromHex(CTransaction(), sigtx))) - self.nodes[0].p2p.sync_with_ping() + self.nodes[0].p2p.wait_for_disconnect() assert_equal(self.nodes[0].getmempoolinfo()['size'], 0) + # Remove the disconnected peer and add a new one. + del self.nodes[0].p2ps[0] + self.nodes[0].add_p2p_connection(P2PInterface()) + self.log.info('Check that txs from rpc are not rejected and relayed to other peers') assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], True) txid = self.nodes[0].testmempoolaccept([sigtx])[0]['txid'] - with self.nodes[0].assert_debug_log(['received getdata for: tx {} peer=0'.format(txid)]): + with self.nodes[0].assert_debug_log(['received getdata for: tx {} peer=1'.format(txid)]): self.nodes[0].sendrawtransaction(sigtx) self.nodes[0].p2p.wait_for_tx(txid) assert_equal(self.nodes[0].getmempoolinfo()['size'], 1) diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index 0994857912..7905cf5018 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -14,7 +14,7 @@ from test_framework.messages import BlockTransactions, BlockTransactionsRequest, from test_framework.mininode import mininode_lock, P2PInterface from test_framework.script import CScript, OP_TRUE, OP_DROP from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, get_bip9_status, wait_until +from test_framework.util import assert_equal, wait_until, softfork_active # TestP2PConn: A peer we use to send messages to bitcoind, and store responses. class TestP2PConn(P2PInterface): @@ -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): @@ -800,7 +803,7 @@ class CompactBlocksTest(BitcoinTestFramework): # We will need UTXOs to construct transactions in later tests. self.make_utxos() - assert_equal(get_bip9_status(self.nodes[0], "segwit")["status"], 'active') + assert softfork_active(self.nodes[0], "segwit") self.log.info("Testing SENDCMPCT p2p message... ") self.test_sendcmpct(self.segwit_node, old_node=self.old_node) 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_permissions.py b/test/functional/p2p_permissions.py new file mode 100755 index 0000000000..40b28d7533 --- /dev/null +++ b/test/functional/p2p_permissions.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test p2p permission message. + +Test that permissions are correctly calculated and applied +""" + +from test_framework.test_node import ErrorMatch +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + connect_nodes, + p2p_port, +) + +class P2PPermissionsTests(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.setup_clean_chain = True + self.extra_args = [[],[]] + + def run_test(self): + self.checkpermission( + # default permissions (no specific permissions) + ["-whitelist=127.0.0.1"], + ["relay", "noban", "mempool"], + True) + + self.checkpermission( + # relay permission removed (no specific permissions) + ["-whitelist=127.0.0.1", "-whitelistrelay=0"], + ["noban", "mempool"], + True) + + self.checkpermission( + # forcerelay and relay permission added + # Legacy parameter interaction which set whitelistrelay to true + # if whitelistforcerelay is true + ["-whitelist=127.0.0.1", "-whitelistforcerelay"], + ["forcerelay", "relay", "noban", "mempool"], + True) + + # Let's make sure permissions are merged correctly + # For this, we need to use whitebind instead of bind + # by modifying the configuration file. + ip_port = "127.0.0.1:{}".format(p2p_port(1)) + self.replaceinconfig(1, "bind=127.0.0.1", "whitebind=bloomfilter,forcerelay@" + ip_port) + self.checkpermission( + ["-whitelist=noban@127.0.0.1" ], + # Check parameter interaction forcerelay should activate relay + ["noban", "bloomfilter", "forcerelay", "relay" ], + False) + self.replaceinconfig(1, "whitebind=bloomfilter,forcerelay@" + ip_port, "bind=127.0.0.1") + + self.checkpermission( + # legacy whitelistrelay should be ignored + ["-whitelist=noban,mempool@127.0.0.1", "-whitelistrelay"], + ["noban", "mempool"], + False) + + self.checkpermission( + # legacy whitelistforcerelay should be ignored + ["-whitelist=noban,mempool@127.0.0.1", "-whitelistforcerelay"], + ["noban", "mempool"], + False) + + self.checkpermission( + # missing mempool permission to be considered legacy whitelisted + ["-whitelist=noban@127.0.0.1"], + ["noban"], + False) + + self.checkpermission( + # all permission added + ["-whitelist=all@127.0.0.1"], + ["forcerelay", "noban", "mempool", "bloomfilter", "relay"], + False) + + self.stop_node(1) + self.nodes[1].assert_start_raises_init_error(["-whitelist=oopsie@127.0.0.1"], "Invalid P2P permission", match=ErrorMatch.PARTIAL_REGEX) + self.nodes[1].assert_start_raises_init_error(["-whitelist=noban@127.0.0.1:230"], "Invalid netmask specified in", match=ErrorMatch.PARTIAL_REGEX) + self.nodes[1].assert_start_raises_init_error(["-whitebind=noban@127.0.0.1/10"], "Cannot resolve -whitebind address", match=ErrorMatch.PARTIAL_REGEX) + + def checkpermission(self, args, expectedPermissions, whitelisted): + self.restart_node(1, args) + connect_nodes(self.nodes[0], 1) + peerinfo = self.nodes[1].getpeerinfo()[0] + assert_equal(peerinfo['whitelisted'], whitelisted) + assert_equal(len(expectedPermissions), len(peerinfo['permissions'])) + for p in expectedPermissions: + if not p in peerinfo['permissions']: + raise AssertionError("Expected permissions %r is not granted." % p) + + def replaceinconfig(self, nodeid, old, new): + with open(self.nodes[nodeid].bitcoinconf, encoding="utf8") as f: + newText=f.read().replace(old, new) + with open(self.nodes[nodeid].bitcoinconf, 'w', encoding="utf8") as f: + f.write(newText) + +if __name__ == '__main__': + P2PPermissionsTests().main() diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index b7fa42f593..98f6b1d71d 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -76,7 +76,7 @@ from test_framework.util import ( assert_equal, connect_nodes, disconnect_nodes, - get_bip9_status, + softfork_active, hex_str_to_bytes, assert_raises_rpc_error, ) @@ -88,6 +88,8 @@ VB_TOP_BITS = 0x20000000 MAX_SIGOP_COST = 80000 +SEGWIT_HEIGHT = 120 + class UTXO(): """Used to keep track of anyone-can-spend outputs that we can use in the tests.""" def __init__(self, sha256, n, value): @@ -184,7 +186,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", "-segwitheight={}".format(SEGWIT_HEIGHT)], + ["-whitelist=127.0.0.1", "-acceptnonstdtxn=0", "-segwitheight={}".format(SEGWIT_HEIGHT)], + ["-whitelist=127.0.0.1", "-acceptnonstdtxn=1", "-segwitheight=-1"] + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -227,26 +233,18 @@ class SegWitTest(BitcoinTestFramework): # Keep a place to store utxo's that can be used in later tests self.utxo = [] - # Segwit status 'defined' - self.segwit_status = 'defined' + self.log.info("Starting tests before segwit activation") + self.segwit_active = False self.test_non_witness_transaction() - self.test_unnecessary_witness_before_segwit_activation() self.test_v0_outputs_arent_spendable() self.test_block_relay() - self.advance_to_segwit_started() - - # Segwit status 'started' - self.test_getblocktemplate_before_lockin() - self.advance_to_segwit_lockin() - - # Segwit status 'locked_in' - self.test_unnecessary_witness_before_segwit_activation() self.test_witness_tx_relay_before_segwit_activation() - self.test_block_relay() self.test_standardness_v0() + + self.log.info("Advancing to segwit activation") self.advance_to_segwit_active() # Segwit status 'active' @@ -278,15 +276,15 @@ class SegWitTest(BitcoinTestFramework): def subtest(func): # noqa: N805 """Wraps the subtests for logging and state assertions.""" def func_wrapper(self, *args, **kwargs): - self.log.info("Subtest: {} (Segwit status = {})".format(func.__name__, self.segwit_status)) + self.log.info("Subtest: {} (Segwit active = {})".format(func.__name__, self.segwit_active)) # Assert segwit status is as expected - assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], self.segwit_status) + assert_equal(softfork_active(self.nodes[0], 'segwit'), self.segwit_active) func(self, *args, **kwargs) # Each subtest should leave some utxos for the next subtest assert self.utxo self.sync_blocks() # Assert segwit status is as expected at end of subtest - assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], self.segwit_status) + assert_equal(softfork_active(self.nodes[0], 'segwit'), self.segwit_active) return func_wrapper @@ -388,7 +386,7 @@ class SegWitTest(BitcoinTestFramework): # Check that we can getdata for witness blocks or regular blocks, # and the right thing happens. - if self.segwit_status != 'active': + if not self.segwit_active: # Before activation, we should be able to request old blocks with # or without witness, and they should be the same. chain_height = self.nodes[0].getblockcount() @@ -532,32 +530,18 @@ class SegWitTest(BitcoinTestFramework): self.utxo.append(UTXO(txid, 2, value)) @subtest - def advance_to_segwit_started(self): - """Mine enough blocks for segwit's vb state to be 'started'.""" - height = self.nodes[0].getblockcount() - # Will need to rewrite the tests here if we are past the first period - assert height < VB_PERIOD - 1 - # Advance to end of period, status should now be 'started' - self.nodes[0].generate(VB_PERIOD - height - 1) - assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'started') - self.segwit_status = 'started' - - @subtest def test_getblocktemplate_before_lockin(self): txid = int(self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1), 16) for node in [self.nodes[0], self.nodes[2]]: gbt_results = node.getblocktemplate({"rules": ["segwit"]}) - block_version = gbt_results['version'] if node == self.nodes[2]: # If this is a non-segwit node, we should not get a witness - # commitment, nor a version bit signalling segwit. - assert_equal(block_version & (1 << VB_WITNESS_BIT), 0) + # commitment. assert 'default_witness_commitment' not in gbt_results else: - # For segwit-aware nodes, check the version bit and the witness - # commitment are correct. - assert block_version & (1 << VB_WITNESS_BIT) != 0 + # For segwit-aware nodes, check the witness + # commitment is correct. assert 'default_witness_commitment' in gbt_results witness_commitment = gbt_results['default_witness_commitment'] @@ -567,18 +551,9 @@ class SegWitTest(BitcoinTestFramework): script = get_witness_script(witness_root, 0) assert_equal(witness_commitment, script.hex()) - @subtest - def advance_to_segwit_lockin(self): - """Mine enough blocks to lock in segwit, but don't activate.""" - height = self.nodes[0].getblockcount() - # Advance to end of period, and verify lock-in happens at the end - self.nodes[0].generate(VB_PERIOD - 1) - height = self.nodes[0].getblockcount() - assert (height % VB_PERIOD) == VB_PERIOD - 2 - assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'started') + # Clear out the mempool self.nodes[0].generate(1) - assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'locked_in') - self.segwit_status = 'locked_in' + self.sync_blocks() @subtest def test_witness_tx_relay_before_segwit_activation(self): @@ -682,7 +657,7 @@ class SegWitTest(BitcoinTestFramework): tx3.wit.vtxinwit.append(CTxInWitness()) tx3.wit.vtxinwit[0].scriptWitness.stack = [witness_program] tx3.rehash() - if self.segwit_status != 'active': + if not self.segwit_active: # Just check mempool acceptance, but don't add the transaction to the mempool, since witness is disallowed # in blocks and the tx is impossible to mine right now. assert_equal(self.nodes[0].testmempoolaccept([tx3.serialize_with_witness().hex()]), [{'txid': tx3.hash, 'allowed': True}]) @@ -703,12 +678,13 @@ class SegWitTest(BitcoinTestFramework): @subtest def advance_to_segwit_active(self): """Mine enough blocks to activate segwit.""" + assert not softfork_active(self.nodes[0], 'segwit') height = self.nodes[0].getblockcount() - self.nodes[0].generate(VB_PERIOD - (height % VB_PERIOD) - 2) - assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'locked_in') + self.nodes[0].generate(SEGWIT_HEIGHT - height - 2) + assert not softfork_active(self.nodes[0], 'segwit') self.nodes[0].generate(1) - assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'active') - self.segwit_status = 'active' + assert softfork_active(self.nodes[0], 'segwit') + self.segwit_active = True @subtest def test_p2sh_witness(self): @@ -1920,13 +1896,13 @@ class SegWitTest(BitcoinTestFramework): # Restart with the new binary self.stop_node(2) - self.start_node(2, extra_args=["-vbparams=segwit:0:999999999999"]) + self.start_node(2, extra_args=["-segwitheight={}".format(SEGWIT_HEIGHT)]) connect_nodes(self.nodes[0], 2) self.sync_blocks() # Make sure that this peer thinks segwit has activated. - assert get_bip9_status(self.nodes[2], 'segwit')['status'] == "active" + assert softfork_active(self.nodes[2], 'segwit') # Make sure this peer's blocks match those of node0. height = self.nodes[2].getblockcount() 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_blockchain.py b/test/functional/rpc_blockchain.py index facb05b54c..266a0d6cd2 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -78,7 +78,6 @@ class BlockchainTest(BitcoinTestFramework): keys = [ 'bestblockhash', - 'bip9_softforks', 'blocks', 'chain', 'chainwork', @@ -124,6 +123,31 @@ class BlockchainTest(BitcoinTestFramework): assert_equal(res['prune_target_size'], 576716800) assert_greater_than(res['size_on_disk'], 0) + assert_equal(res['softforks'], { + 'bip34': {'type': 'buried', 'active': False, 'height': 500}, + 'bip66': {'type': 'buried', 'active': False, 'height': 1251}, + 'bip65': {'type': 'buried', 'active': False, 'height': 1351}, + 'csv': {'type': 'buried', 'active': False, 'height': 432}, + 'segwit': {'type': 'buried', 'active': True, 'height': 0}, + 'testdummy': { + 'type': 'bip9', + 'bip9': { + 'status': 'started', + 'bit': 28, + 'startTime': 0, + 'timeout': 0x7fffffffffffffff, # testdummy does not have a timeout so is set to the max int64 value + 'since': 144, + 'statistics': { + 'period': 144, + 'threshold': 108, + 'elapsed': 57, + 'count': 57, + 'possible': True, + }, + }, + 'active': False} + }) + def _test_getchaintxstats(self): self.log.info("Test getchaintxstats") @@ -162,6 +186,7 @@ class BlockchainTest(BitcoinTestFramework): assert_equal(chaintxstats['time'], b200['time']) assert_equal(chaintxstats['txcount'], 201) assert_equal(chaintxstats['window_final_block_hash'], b200_hash) + assert_equal(chaintxstats['window_final_block_height'], 200) assert_equal(chaintxstats['window_block_count'], 199) assert_equal(chaintxstats['window_tx_count'], 199) assert_equal(chaintxstats['window_interval'], time_diff) @@ -171,6 +196,7 @@ class BlockchainTest(BitcoinTestFramework): assert_equal(chaintxstats['time'], b1['time']) assert_equal(chaintxstats['txcount'], 2) assert_equal(chaintxstats['window_final_block_hash'], b1_hash) + assert_equal(chaintxstats['window_final_block_height'], 1) assert_equal(chaintxstats['window_block_count'], 0) assert 'window_tx_count' not in chaintxstats assert 'window_interval' not in chaintxstats diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index 58010f7c2e..62f3843756 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -129,6 +129,11 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): outval = value - decimal.Decimal("0.00001000") rawtx = node2.createrawtransaction([{"txid": txid, "vout": vout}], [{self.final: outval}]) + prevtx_err = dict(prevtxs[0]) + del prevtx_err["redeemScript"] + + assert_raises_rpc_error(-8, "Missing redeemScript/witnessScript", node2.signrawtransactionwithkey, rawtx, self.priv[0:self.nsigs-1], [prevtx_err]) + rawtx2 = node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs - 1], prevtxs) rawtx3 = node2.signrawtransactionwithkey(rawtx2["hex"], [self.priv[-1]], prevtxs) diff --git a/test/functional/rpc_deriveaddresses.py b/test/functional/rpc_deriveaddresses.py index 1984694692..42128d5767 100755 --- a/test/functional/rpc_deriveaddresses.py +++ b/test/functional/rpc_deriveaddresses.py @@ -13,14 +13,14 @@ class DeriveaddressesTest(BitcoinTestFramework): self.supports_cli = 1 def run_test(self): - assert_raises_rpc_error(-5, "Invalid descriptor", self.nodes[0].deriveaddresses, "a") + assert_raises_rpc_error(-5, "Missing checksum", self.nodes[0].deriveaddresses, "a") descriptor = "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)#t6wfjs64" address = "bcrt1qjqmxmkpmxt80xz4y3746zgt0q3u3ferr34acd5" assert_equal(self.nodes[0].deriveaddresses(descriptor), [address]) descriptor = descriptor[:-9] - assert_raises_rpc_error(-5, "Invalid descriptor", self.nodes[0].deriveaddresses, descriptor) + assert_raises_rpc_error(-5, "Missing checksum", self.nodes[0].deriveaddresses, descriptor) descriptor_pubkey = "wpkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)#s9ga3alw" address = "bcrt1qjqmxmkpmxt80xz4y3746zgt0q3u3ferr34acd5" diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index d89fd6461f..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,17 +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 = "" @@ -681,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 # ###################################### @@ -692,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 430be6dd37..5a04e0c8d8 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -9,6 +9,7 @@ from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + assert_greater_than, assert_raises_rpc_error, connect_nodes_bi, disconnect_nodes, @@ -26,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() @@ -129,6 +135,15 @@ class PSBTTest(BitcoinTestFramework): assert_equal(walletprocesspsbt_out['complete'], True) self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex']) + # feeRate of 0.1 BTC / KB produces a total fee slightly below -maxtxfee (~0.05280000): + res = 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": 0.1}) + assert_greater_than(res["fee"], 0.05) + assert_greater_than(0.06, res["fee"]) + + # feeRate of 10 BTC / KB produces a total fee well above -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 psbtx = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], {self.nodes[1].getnewaddress():29.99})['psbt'] walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(psbtx) @@ -197,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) @@ -216,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) @@ -315,18 +337,32 @@ class PSBTTest(BitcoinTestFramework): vout3 = find_output(self.nodes[0], txid3, 11) self.sync_all() - # Update a PSBT with UTXOs from the node - # Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness + def test_psbt_input_keys(psbt_input, keys): + """Check that the psbt input has only the expected keys.""" + assert_equal(set(keys), set(psbt_input.keys())) + + # Create a PSBT. None of the inputs are filled initially psbt = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1},{"txid":txid2, "vout":vout2},{"txid":txid3, "vout":vout3}], {self.nodes[0].getnewaddress():32.999}) decoded = self.nodes[1].decodepsbt(psbt) - assert "witness_utxo" not in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0] - assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1] - assert "witness_utxo" not in decoded['inputs'][2] and "non_witness_utxo" not in decoded['inputs'][2] + test_psbt_input_keys(decoded['inputs'][0], []) + test_psbt_input_keys(decoded['inputs'][1], []) + test_psbt_input_keys(decoded['inputs'][2], []) + + # Update a PSBT with UTXOs from the node + # Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness updated = self.nodes[1].utxoupdatepsbt(psbt) decoded = self.nodes[1].decodepsbt(updated) - assert "witness_utxo" in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0] - assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1] - assert "witness_utxo" not in decoded['inputs'][2] and "non_witness_utxo" not in decoded['inputs'][2] + test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo']) + test_psbt_input_keys(decoded['inputs'][1], []) + test_psbt_input_keys(decoded['inputs'][2], []) + + # Try again, now while providing descriptors, making P2SH-segwit work, and causing bip32_derivs and redeem_script to be filled in + descs = [self.nodes[1].getaddressinfo(addr)['desc'] for addr in [addr1,addr2,addr3]] + updated = self.nodes[1].utxoupdatepsbt(psbt=psbt, descriptors=descs) + decoded = self.nodes[1].decodepsbt(updated) + test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'bip32_derivs']) + test_psbt_input_keys(decoded['inputs'][1], []) + test_psbt_input_keys(decoded['inputs'][2], ['witness_utxo', 'bip32_derivs', 'redeem_script']) # Two PSBTs with a common input should not be joinable psbt1 = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], {self.nodes[0].getnewaddress():Decimal('10.999')}) diff --git a/test/functional/rpc_setban.py b/test/functional/rpc_setban.py new file mode 100755 index 0000000000..b1d2b6f431 --- /dev/null +++ b/test/functional/rpc_setban.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test the setban rpc call.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + connect_nodes, + p2p_port +) + +class SetBanTests(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.setup_clean_chain = True + self.extra_args = [[],[]] + + def run_test(self): + # Node 0 connects to Node 1, check that the noban permission is not granted + connect_nodes(self.nodes[0], 1) + peerinfo = self.nodes[1].getpeerinfo()[0] + assert(not 'noban' in peerinfo['permissions']) + + # Node 0 get banned by Node 1 + self.nodes[1].setban("127.0.0.1", "add") + + # Node 0 should not be able to reconnect + with self.nodes[1].assert_debug_log(expected_msgs=['dropped (banned)\n'], timeout=5): + self.restart_node(1, []) + self.nodes[0].addnode("127.0.0.1:" + str(p2p_port(1)), "onetry") + + # However, node 0 should be able to reconnect if it has noban permission + self.restart_node(1, ['-whitelist=127.0.0.1']) + connect_nodes(self.nodes[0], 1) + peerinfo = self.nodes[1].getpeerinfo()[0] + assert('noban' in peerinfo['permissions']) + + # If we remove the ban, Node 0 should be able to reconnect even without noban permission + self.nodes[1].setban("127.0.0.1", "remove") + self.restart_node(1, []) + connect_nodes(self.nodes[0], 1) + peerinfo = self.nodes[1].getpeerinfo()[0] + assert(not 'noban' in peerinfo['permissions']) + +if __name__ == '__main__': + SetBanTests().main() 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/address.py b/test/functional/test_framework/address.py index f36cffe957..194f2f061b 100644 --- a/test/functional/test_framework/address.py +++ b/test/functional/test_framework/address.py @@ -4,6 +4,8 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Encode and decode BASE58, P2PKH and P2SH addresses.""" +import enum + from .script import hash256, hash160, sha256, CScript, OP_0 from .util import hex_str_to_bytes @@ -11,6 +13,13 @@ from . import segwit_addr ADDRESS_BCRT1_UNSPENDABLE = 'bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj' + +class AddressType(enum.Enum): + bech32 = 'bech32' + p2sh_segwit = 'p2sh-segwit' + legacy = 'legacy' # P2PKH + + chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 7ac044d0d0..d741b00ba0 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -22,13 +22,14 @@ from .messages import ( ToHex, hash256, hex_str_to_bytes, - ser_string, ser_uint256, sha256, uint256_from_str, ) from .script import ( CScript, + CScriptNum, + CScriptOp, OP_0, OP_1, OP_CHECKMULTISIG, @@ -89,20 +90,14 @@ def add_witness_commitment(block, nonce=0): block.hashMerkleRoot = block.calc_merkle_root() block.rehash() -def serialize_script_num(value): - r = bytearray(0) - if value == 0: - return r - neg = value < 0 - absvalue = -value if neg else value - while (absvalue): - r.append(int(absvalue & 0xff)) - absvalue >>= 8 - if r[-1] & 0x80: - r.append(0x80 if neg else 0) - elif neg: - r[-1] |= 0x80 - return r + +def script_BIP34_coinbase_height(height): + if height <= 16: + res = CScriptOp.encode_op_n(height) + # Append dummy to increase scriptSig size above 2 (see bad-cb-length consensus rule) + return CScript([res, OP_1]) + return CScript([CScriptNum(height)]) + def create_coinbase(height, pubkey=None): """Create a coinbase transaction, assuming no miner fees. @@ -110,8 +105,7 @@ def create_coinbase(height, pubkey=None): If pubkey is passed in, the coinbase output will be a P2PK output; otherwise an anyone-can-spend output.""" coinbase = CTransaction() - coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), - ser_string(serialize_script_num(height)), 0xffffffff)) + coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff)) coinbaseoutput = CTxOut() coinbaseoutput.nValue = 50 * COIN halvings = int(height / 150) # regtest diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index e454ed5987..917efaa833 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) @@ -803,7 +803,9 @@ class HeaderAndShortIDs: return [ key0, key1 ] # Version 2 compact blocks use wtxid in shortids (rather than txid) - def initialize_from_block(self, block, nonce=0, prefill_list = [0], use_witness = False): + def initialize_from_block(self, block, nonce=0, prefill_list=None, use_witness=False): + if prefill_list is None: + prefill_list = [0] self.header = CBlockHeader(block) self.nonce = nonce self.prefilled_txn = [ PrefilledTransaction(i, block.vtx[i]) for i in prefill_list ] 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..55e6d4caa6 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 @@ -68,8 +68,10 @@ class TestNode(): self.index = i self.datadir = datadir + self.bitcoinconf = os.path.join(self.datadir, "bitcoin.conf") 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 +199,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 +221,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") @@ -305,21 +307,30 @@ class TestNode(): wait_until(self.is_node_stopped, timeout=timeout) @contextlib.contextmanager - def assert_debug_log(self, expected_msgs): - debug_log = os.path.join(self.datadir, 'regtest', 'debug.log') + def assert_debug_log(self, expected_msgs, timeout=2): + time_end = time.time() + timeout + 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() - try: - yield - finally: + + yield + + while True: + found = True with open(debug_log, encoding='utf-8') as dl: dl.seek(prev_size) log = dl.read() print_log = " - " + "\n - ".join(log.splitlines()) for expected_msg in expected_msgs: if re.search(re.escape(expected_msg), log, flags=re.MULTILINE) is None: - self._raise_assertion_error('Expected message "{}" does not partially match log:\n\n{}\n\n'.format(expected_msg, print_log)) + found = False + if found: + return + if time.time() >= time_end: + break + time.sleep(0.05) + self._raise_assertion_error('Expected messages "{}" does not partially match log:\n\n{}\n\n'.format(str(expected_msgs), print_log)) @contextlib.contextmanager def assert_memory_usage_stable(self, *, increase_allowed=0.03): diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 26215083fb..821e1cd3c5 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -7,7 +7,6 @@ from base64 import b64encode from binascii import unhexlify from decimal import Decimal, ROUND_DOWN -import hashlib import inspect import json import logging @@ -19,6 +18,7 @@ import time from . import coverage from .authproxy import AuthServiceProxy, JSONRPCException +from io import BytesIO logger = logging.getLogger("TestFramework.utils") @@ -182,12 +182,6 @@ def check_json_precision(): def count_bytes(hex_string): return len(bytearray.fromhex(hex_string)) -def hash256(byte_str): - sha256 = hashlib.sha256() - sha256.update(byte_str) - sha256d = hashlib.sha256() - sha256d.update(sha256.digest()) - return sha256d.digest()[::-1] def hex_str_to_bytes(hex_str): return unhexlify(hex_str.encode('ascii')) @@ -270,8 +264,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 +279,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") @@ -299,6 +293,7 @@ def initialize_datadir(dirname, n): f.write("discover=0\n") f.write("listenonion=0\n") f.write("printtoconsole=0\n") + f.write("upnp=0\n") os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True) os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True) return datadir @@ -311,7 +306,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 +319,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,14 +331,14 @@ 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() - return info['bip9_softforks'][key] +def softfork_active(node, key): + """Return whether a softfork is active.""" + return node.getblockchaininfo()['softforks'][key]['active'] def set_node_times(nodes, t): for node in nodes: @@ -515,14 +510,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 +524,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 +532,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..dd61efa757 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -76,7 +76,6 @@ EXTENDED_SCRIPTS = [ BASE_SCRIPTS = [ # Scripts that are run by default. # Longest test should go first, to favor running tests in parallel - 'feature_fee_estimation.py', 'wallet_hd.py', 'wallet_backup.py', # vv Tests less than 5m vv @@ -91,6 +90,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,8 +107,10 @@ 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', + 'feature_fee_estimation.py', 'interface_zmq.py', 'interface_bitcoin_cli.py', 'mempool_resurrect.py', @@ -127,6 +129,9 @@ BASE_SCRIPTS = [ 'wallet_multiwallet.py --usecli', 'wallet_createwallet.py', 'wallet_createwallet.py --usecli', + 'wallet_watchonly.py', + 'wallet_watchonly.py --usecli', + 'wallet_reorgsrestore.py', 'interface_http.py', 'interface_rpc.py', 'rpc_psbt.py', @@ -142,6 +147,7 @@ BASE_SCRIPTS = [ 'rpc_net.py', 'wallet_keypool.py', 'p2p_mempool.py', + 'rpc_setban.py', 'p2p_blocksonly.py', 'mining_prioritisetransaction.py', 'p2p_invalid_locator.py', @@ -157,6 +163,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 +180,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', @@ -196,6 +204,7 @@ BASE_SCRIPTS = [ 'rpc_scantxoutset.py', 'feature_logging.py', 'p2p_node_network_limited.py', + 'p2p_permissions.py', 'feature_blocksdir.py', 'feature_config_args.py', 'rpc_help.py', @@ -223,6 +232,7 @@ def main(): epilog=''' Help text and arguments for individual test script:''', formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('--ansi', action='store_true', default=sys.stdout.isatty(), help="Use ANSI colors and dots in output (enabled by default when standard output is a TTY)") parser.add_argument('--combinedlogslen', '-c', type=int, default=0, metavar='n', help='On failure, print a log (of length n lines) to the console, combined from the test framework and all test nodes.') parser.add_argument('--coverage', action='store_true', help='generate a basic coverage report for the RPC interface') parser.add_argument('--ci', action='store_true', help='Run checks and code that are usually only enabled in a continuous integration environment') @@ -234,7 +244,15 @@ 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() + if not args.ansi: + global BOLD, GREEN, RED, GREY + BOLD = ("", "") + GREEN = ("", "") + RED = ("", "") + GREY = ("", "") # args to be passed on always start with two dashes; tests are the remaining unknown args tests = [arg for arg in unknown_args if arg[:2] != "--"] @@ -269,11 +287,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 +323,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") @@ -322,9 +354,10 @@ def main(): combined_logs_len=args.combinedlogslen, failfast=args.failfast, runs_ci=args.ci, + use_term_control=args.ansi, ) -def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, runs_ci): +def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, runs_ci, use_term_control): args = args or [] # Warn if bitcoind is already running (unix only) @@ -366,6 +399,7 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= test_list=test_list, flags=flags, timeout_duration=40 * 60 if runs_ci else float('inf'), # in seconds + use_term_control=use_term_control, ) start_time = time.time() test_results = [] @@ -449,7 +483,7 @@ class TestHandler: Trigger the test scripts passed in via the list. """ - def __init__(self, *, num_tests_parallel, tests_dir, tmpdir, test_list, flags, timeout_duration): + def __init__(self, *, num_tests_parallel, tests_dir, tmpdir, test_list, flags, timeout_duration, use_term_control): assert num_tests_parallel >= 1 self.num_jobs = num_tests_parallel self.tests_dir = tests_dir @@ -459,6 +493,7 @@ class TestHandler: self.flags = flags self.num_running = 0 self.jobs = [] + self.use_term_control = use_term_control def get_next(self): while self.num_running < self.num_jobs and self.test_list: @@ -510,11 +545,13 @@ class TestHandler: status = "Failed" self.num_running -= 1 self.jobs.remove(job) - clearline = '\r' + (' ' * dot_count) + '\r' - print(clearline, end='', flush=True) + if self.use_term_control: + clearline = '\r' + (' ' * dot_count) + '\r' + print(clearline, end='', flush=True) dot_count = 0 return TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr - print('.', end='', flush=True) + if self.use_term_control: + print('.', end='', flush=True) dot_count += 1 def kill_and_join(self): diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index fbcf21e729..28a65f7823 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -1,14 +1,20 @@ #!/usr/bin/env python3 -# Copyright (c) 2018 The Bitcoin Core developers +# Copyright (c) 2018-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test bitcoin-wallet.""" + +import hashlib +import os +import stat import subprocess import textwrap from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal +BUFFER_SIZE = 16 * 1024 + class ToolWalletTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 @@ -32,23 +38,54 @@ class ToolWalletTest(BitcoinTestFramework): def assert_tool_output(self, output, *args): p = self.bitcoin_wallet_process(*args) stdout, stderr = p.communicate() - assert_equal(p.poll(), 0) assert_equal(stderr, '') assert_equal(stdout, output) + assert_equal(p.poll(), 0) - def run_test(self): + def wallet_shasum(self): + h = hashlib.sha1() + mv = memoryview(bytearray(BUFFER_SIZE)) + with open(self.wallet_path, 'rb', buffering=0) as f: + for n in iter(lambda : f.readinto(mv), 0): + h.update(mv[:n]) + return h.hexdigest() + def wallet_timestamp(self): + return os.path.getmtime(self.wallet_path) + + def wallet_permissions(self): + return oct(os.lstat(self.wallet_path).st_mode)[-3:] + + def log_wallet_timestamp_comparison(self, old, new): + result = 'unchanged' if new == old else 'increased!' + self.log.debug('Wallet file timestamp {}'.format(result)) + + def test_invalid_tool_commands_and_args(self): + self.log.info('Testing that various invalid commands raise with specific error messages') self.assert_raises_tool_error('Invalid command: foo', 'foo') - # `bitcoin-wallet help` is an error. Use `bitcoin-wallet -help` + # `bitcoin-wallet help` raises an error. Use `bitcoin-wallet -help`. self.assert_raises_tool_error('Invalid command: help', 'help') self.assert_raises_tool_error('Error: two methods provided (info and create). Only one method should be provided.', 'info', 'create') self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo') self.assert_raises_tool_error('Error loading wallet.dat. Is wallet being used by other process?', '-wallet=wallet.dat', 'info') self.assert_raises_tool_error('Error: no wallet file at nonexistent.dat', '-wallet=nonexistent.dat', 'info') - # stop the node to close the wallet to call info command + def test_tool_wallet_info(self): + # Stop the node to close the wallet to call the info command. self.stop_node(0) - + self.log.info('Calling wallet tool info, testing output') + # + # TODO: Wallet tool info should work with wallet file permissions set to + # read-only without raising: + # "Error loading wallet.dat. Is wallet being used by another process?" + # The following lines should be uncommented and the tests still succeed: + # + # self.log.debug('Setting wallet file permissions to 400 (read-only)') + # os.chmod(self.wallet_path, stat.S_IRUSR) + # assert(self.wallet_permissions() in ['400', '666']) # Sanity check. 666 because Appveyor. + # shasum_before = self.wallet_shasum() + timestamp_before = self.wallet_timestamp() + self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before)) out = textwrap.dedent('''\ Wallet info =========== @@ -59,12 +96,35 @@ class ToolWalletTest(BitcoinTestFramework): Address Book: 3 ''') self.assert_tool_output(out, '-wallet=wallet.dat', 'info') - - # mutate the wallet to check the info command output changes accordingly + timestamp_after = self.wallet_timestamp() + self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after)) + self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after) + self.log.debug('Setting wallet file permissions back to 600 (read/write)') + os.chmod(self.wallet_path, stat.S_IRUSR | stat.S_IWUSR) + assert(self.wallet_permissions() in ['600', '666']) # Sanity check. 666 because Appveyor. + # + # TODO: Wallet tool info should not write to the wallet file. + # The following lines should be uncommented and the tests still succeed: + # + # assert_equal(timestamp_before, timestamp_after) + # shasum_after = self.wallet_shasum() + # assert_equal(shasum_before, shasum_after) + # self.log.debug('Wallet file shasum unchanged\n') + + def test_tool_wallet_info_after_transaction(self): + """ + Mutate the wallet with a transaction to verify that the info command + output changes accordingly. + """ self.start_node(0) + self.log.info('Generating transaction to mutate wallet') self.nodes[0].generate(1) self.stop_node(0) + self.log.info('Calling wallet tool info after generating a transaction, testing output') + shasum_before = self.wallet_shasum() + timestamp_before = self.wallet_timestamp() + self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before)) out = textwrap.dedent('''\ Wallet info =========== @@ -75,7 +135,22 @@ class ToolWalletTest(BitcoinTestFramework): Address Book: 3 ''') self.assert_tool_output(out, '-wallet=wallet.dat', 'info') - + shasum_after = self.wallet_shasum() + timestamp_after = self.wallet_timestamp() + self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after)) + self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after) + # + # TODO: Wallet tool info should not write to the wallet file. + # This assertion should be uncommented and succeed: + # assert_equal(timestamp_before, timestamp_after) + assert_equal(shasum_before, shasum_after) + self.log.debug('Wallet file shasum unchanged\n') + + def test_tool_wallet_create_on_existing_wallet(self): + self.log.info('Calling wallet tool create on an existing wallet, testing output') + shasum_before = self.wallet_shasum() + timestamp_before = self.wallet_timestamp() + self.log.debug('Wallet file timestamp before calling create: {}'.format(timestamp_before)) out = textwrap.dedent('''\ Topping up keypool... Wallet info @@ -87,15 +162,48 @@ class ToolWalletTest(BitcoinTestFramework): Address Book: 0 ''') self.assert_tool_output(out, '-wallet=foo', 'create') - + shasum_after = self.wallet_shasum() + timestamp_after = self.wallet_timestamp() + self.log.debug('Wallet file timestamp after calling create: {}'.format(timestamp_after)) + self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after) + assert_equal(timestamp_before, timestamp_after) + assert_equal(shasum_before, shasum_after) + self.log.debug('Wallet file shasum unchanged\n') + + def test_getwalletinfo_on_different_wallet(self): + self.log.info('Starting node with arg -wallet=foo') self.start_node(0, ['-wallet=foo']) + + self.log.info('Calling getwalletinfo on a different wallet ("foo"), testing output') + shasum_before = self.wallet_shasum() + timestamp_before = self.wallet_timestamp() + self.log.debug('Wallet file timestamp before calling getwalletinfo: {}'.format(timestamp_before)) out = self.nodes[0].getwalletinfo() self.stop_node(0) + shasum_after = self.wallet_shasum() + timestamp_after = self.wallet_timestamp() + self.log.debug('Wallet file timestamp after calling getwalletinfo: {}'.format(timestamp_after)) + assert_equal(0, out['txcount']) assert_equal(1000, out['keypoolsize']) assert_equal(1000, out['keypoolsize_hd_internal']) assert_equal(True, 'hdseedid' in out) + self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after) + assert_equal(timestamp_before, timestamp_after) + assert_equal(shasum_after, shasum_before) + self.log.debug('Wallet file shasum unchanged\n') + + def run_test(self): + self.wallet_path = os.path.join(self.nodes[0].datadir, 'regtest', 'wallets', 'wallet.dat') + self.test_invalid_tool_commands_and_args() + # Warning: The following tests are order-dependent. + self.test_tool_wallet_info() + self.test_tool_wallet_info_after_transaction() + self.test_tool_wallet_create_on_existing_wallet() + self.test_getwalletinfo_on_different_wallet() + + if __name__ == '__main__': ToolWalletTest().main() diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py index a40613dfc7..4e4ed8f26b 100755 --- a/test/functional/wallet_address_types.py +++ b/test/functional/wallet_address_types.py @@ -175,6 +175,10 @@ class AddressTypeTest(BitcoinTestFramework): assert info['desc'] == descsum_create(info['desc'][:-9]) # Verify that stripping the checksum and feeding it to getdescriptorinfo roundtrips assert info['desc'] == self.nodes[0].getdescriptorinfo(info['desc'][:-9])['descriptor'] + assert_equal(info['desc'][-8:], self.nodes[0].getdescriptorinfo(info['desc'][:-9])['checksum']) + # Verify that keeping the checksum and feeding it to getdescriptorinfo roundtrips + assert info['desc'] == self.nodes[0].getdescriptorinfo(info['desc'])['descriptor'] + assert_equal(info['desc'][-8:], self.nodes[0].getdescriptorinfo(info['desc'])['checksum']) if not multisig and typ == 'legacy': # P2PKH diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index 15f2195e21..137c77a51e 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -203,6 +203,8 @@ class WalletTest(BitcoinTestFramework): self.log.info('Put txs back into mempool of node 1 (not node 0)') self.nodes[0].invalidateblock(block_reorg) self.nodes[1].invalidateblock(block_reorg) + self.sync_blocks() + self.nodes[0].syncwithvalidationinterfacequeue() assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted self.nodes[0].generatetoaddress(1, ADDRESS_WATCHONLY) assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index daa834b5b8..74350649c7 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): @@ -496,6 +499,11 @@ class WalletTest(BitcoinTestFramework): self.nodes[0].setlabel(change, 'foobar') assert_equal(self.nodes[0].getaddressinfo(change)['ischange'], False) + # Test "decoded" field value in gettransaction response + self.log.info("Testing gettransaction decoding...") + tx = self.nodes[0].gettransaction(txid=txid, decode=True) + assert_equal(tx["decoded"], self.nodes[0].decoderawtransaction(tx["hex"])) + if __name__ == '__main__': WalletTest().main() diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 4d9bacf299..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) @@ -304,7 +315,9 @@ def test_unconfirmed_not_spendable(rbf_node, rbf_node_address): def test_bumpfee_metadata(rbf_node, dest_address): - rbfid = rbf_node.sendtoaddress(dest_address, Decimal("0.00100000"), "comment value", "to value") + assert(rbf_node.getbalance() < 49) + rbf_node.generatetoaddress(101, rbf_node.getnewaddress()) + rbfid = rbf_node.sendtoaddress(dest_address, 49, "comment value", "to value") bumped_tx = rbf_node.bumpfee(rbfid) bumped_wtx = rbf_node.gettransaction(bumped_tx["txid"]) assert_equal(bumped_wtx["comment"], "comment value") 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 diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py index 47c97f62bf..4e20892596 100755 --- a/test/functional/wallet_import_rescan.py +++ b/test/functional/wallet_import_rescan.py @@ -20,6 +20,7 @@ happened previously. """ from test_framework.test_framework import BitcoinTestFramework +from test_framework.address import AddressType from test_framework.util import ( connect_nodes, assert_equal, @@ -27,21 +28,30 @@ from test_framework.util import ( ) import collections +from decimal import Decimal import enum import itertools +import random Call = enum.Enum("Call", "single multiaddress multiscript") Data = enum.Enum("Data", "address pub priv") Rescan = enum.Enum("Rescan", "no yes late_timestamp") -class Variant(collections.namedtuple("Variant", "call data rescan prune")): +class Variant(collections.namedtuple("Variant", "call data address_type rescan prune")): """Helper for importing one key and verifying scanned transactions.""" def do_import(self, timestamp): """Call one key import RPC.""" rescan = self.rescan == Rescan.yes + assert_equal(self.address["solvable"], True) + assert_equal(self.address["isscript"], self.address_type == AddressType.p2sh_segwit) + assert_equal(self.address["iswitness"], self.address_type == AddressType.bech32) + if self.address["isscript"]: + assert_equal(self.address["embedded"]["isscript"], False) + assert_equal(self.address["embedded"]["iswitness"], True) + if self.call == Call.single: if self.data == Data.address: response = self.node.importaddress(address=self.address["address"], label=self.label, rescan=rescan) @@ -52,7 +62,7 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")): assert_equal(response, None) elif self.call in (Call.multiaddress, Call.multiscript): - response = self.node.importmulti([{ + request = { "scriptPubKey": { "address": self.address["address"] } if self.call == Call.multiaddress else self.address["scriptPubKey"], @@ -61,13 +71,21 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")): "keys": [self.key] if self.data == Data.priv else [], "label": self.label, "watchonly": self.data != Data.priv - }], {"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)}) + } + if self.address_type == AddressType.p2sh_segwit and self.data != Data.address: + # We need solving data when providing a pubkey or privkey as data + request.update({"redeemscript": self.address['embedded']['scriptPubKey']}) + response = self.node.importmulti( + requests=[request], + options={"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)}, + ) assert_equal(response, [{"success": True}]) - def check(self, txid=None, amount=None, confirmations=None): + def check(self, txid=None, amount=None, confirmation_height=None): """Verify that listtransactions/listreceivedbyaddress return expected values.""" txs = self.node.listtransactions(label=self.label, count=10000, include_watchonly=True) + current_height = self.node.getblockcount() assert_equal(len(txs), self.expected_txs) addresses = self.node.listreceivedbyaddress(minconf=0, include_watchonly=True, address_filter=self.address['address']) @@ -82,13 +100,13 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")): assert_equal(tx["category"], "receive") assert_equal(tx["label"], self.label) assert_equal(tx["txid"], txid) - assert_equal(tx["confirmations"], confirmations) + assert_equal(tx["confirmations"], 1 + current_height - confirmation_height) assert_equal("trusted" not in tx, True) address, = [ad for ad in addresses if txid in ad["txids"]] assert_equal(address["address"], self.address["address"]) assert_equal(address["amount"], self.expected_balance) - assert_equal(address["confirmations"], confirmations) + assert_equal(address["confirmations"], 1 + current_height - confirmation_height) # Verify the transaction is correctly marked watchonly depending on # whether the transaction pays to an imported public key or # imported private key. The test setup ensures that transaction @@ -102,7 +120,7 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")): # List of Variants for each way a key or address could be imported. -IMPORT_VARIANTS = [Variant(*variants) for variants in itertools.product(Call, Data, Rescan, (False, True))] +IMPORT_VARIANTS = [Variant(*variants) for variants in itertools.product(Call, Data, AddressType, Rescan, (False, True))] # List of nodes to import keys to. Half the nodes will have pruning disabled, # half will have it enabled. Different nodes will be used for imports that are @@ -116,6 +134,13 @@ IMPORT_NODES = [ImportNode(*fields) for fields in itertools.product((False, True # Rescans start at the earliest block up to 2 hours before the key timestamp. TIMESTAMP_WINDOW = 2 * 60 * 60 +AMOUNT_DUST = 0.00000546 + + +def get_rand_amount(): + r = random.uniform(AMOUNT_DUST, 1) + return Decimal(str(round(r, 8))) + class ImportRescanTest(BitcoinTestFramework): def set_test_params(self): @@ -125,12 +150,12 @@ class ImportRescanTest(BitcoinTestFramework): self.skip_if_no_wallet() def setup_network(self): - extra_args = [["-addresstype=legacy"] for _ in range(self.num_nodes)] + self.extra_args = [[]] * self.num_nodes for i, import_node in enumerate(IMPORT_NODES, 2): if import_node.prune: - extra_args[i] += ["-prune=1"] + self.extra_args[i] += ["-prune=1"] - self.add_nodes(self.num_nodes, extra_args=extra_args) + self.add_nodes(self.num_nodes, extra_args=self.extra_args) # Import keys with pruning disabled self.start_nodes(extra_args=[[]] * self.num_nodes) @@ -147,17 +172,23 @@ class ImportRescanTest(BitcoinTestFramework): # each possible type of wallet import RPC. for i, variant in enumerate(IMPORT_VARIANTS): variant.label = "label {} {}".format(i, variant) - variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress(variant.label)) + variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress( + label=variant.label, + address_type=variant.address_type.value, + )) variant.key = self.nodes[1].dumpprivkey(variant.address["address"]) - variant.initial_amount = 1 - (i + 1) / 64 + variant.initial_amount = get_rand_amount() variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount) + self.nodes[0].generate(1) # Generate one block for each send + variant.confirmation_height = self.nodes[0].getblockcount() + variant.timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"] - # Generate a block containing the initial transactions, then another - # block further in the future (past the rescan window). - self.nodes[0].generate(1) + # Generate a block further in the future (past the rescan window). assert_equal(self.nodes[0].getrawmempool(), []) - timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"] - set_node_times(self.nodes, timestamp + TIMESTAMP_WINDOW + 1) + set_node_times( + self.nodes, + self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"] + TIMESTAMP_WINDOW + 1, + ) self.nodes[0].generate(1) self.sync_all() @@ -167,11 +198,11 @@ class ImportRescanTest(BitcoinTestFramework): self.log.info('Run import for variant {}'.format(variant)) expect_rescan = variant.rescan == Rescan.yes variant.node = self.nodes[2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))] - variant.do_import(timestamp) + variant.do_import(variant.timestamp) if expect_rescan: variant.expected_balance = variant.initial_amount variant.expected_txs = 1 - variant.check(variant.initial_txid, variant.initial_amount, 2) + variant.check(variant.initial_txid, variant.initial_amount, variant.confirmation_height) else: variant.expected_balance = 0 variant.expected_txs = 0 @@ -179,11 +210,11 @@ class ImportRescanTest(BitcoinTestFramework): # Create new transactions sending to each address. for i, variant in enumerate(IMPORT_VARIANTS): - variant.sent_amount = 1 - (2 * i + 1) / 128 + variant.sent_amount = get_rand_amount() variant.sent_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.sent_amount) + self.nodes[0].generate(1) # Generate one block for each send + variant.confirmation_height = self.nodes[0].getblockcount() - # Generate a block containing the new transactions. - self.nodes[0].generate(1) assert_equal(self.nodes[0].getrawmempool(), []) self.sync_all() @@ -192,7 +223,7 @@ class ImportRescanTest(BitcoinTestFramework): self.log.info('Run check for variant {}'.format(variant)) variant.expected_balance += variant.sent_amount variant.expected_txs += 1 - variant.check(variant.sent_txid, variant.sent_amount, 1) + variant.check(variant.sent_txid, variant.sent_amount, variant.confirmation_height) if __name__ == "__main__": ImportRescanTest().main() diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py index e19c7919a9..23748e5dd7 100755 --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -44,8 +44,10 @@ class ImportMultiTest(BitcoinTestFramework): def setup_network(self): self.setup_nodes() - def test_importmulti(self, req, success, error_code=None, error_message=None, warnings=[]): + def test_importmulti(self, req, success, error_code=None, error_message=None, warnings=None): """Run importmulti and assert success""" + if warnings is None: + warnings = [] result = self.nodes[1].importmulti([req]) observed_warnings = [] if 'warnings' in result[0]: @@ -552,7 +554,7 @@ class ImportMultiTest(BitcoinTestFramework): "keys": [key.privkey]}, success=False, error_code=-5, - error_message="Descriptor is invalid") + error_message="Missing checksum") # Test importing of a P2SH-P2WPKH address via descriptor + private key key = get_key(self.nodes[0]) diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 984ffab5a4..99d2c77587 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -93,12 +93,12 @@ class MultiWalletTest(BitcoinTestFramework): # should not initialize if one wallet is a copy of another shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy')) - exp_stderr = "BerkeleyBatch: Can't open database w8_copy \(duplicates fileid \w+ from w8\)" + exp_stderr = r"BerkeleyBatch: Can't open database w8_copy \(duplicates fileid \w+ from w8\)" self.nodes[0].assert_start_raises_init_error(['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) # should not initialize if wallet file is a symlink os.symlink('w8', wallet_dir('w8_symlink')) - self.nodes[0].assert_start_raises_init_error(['-wallet=w8_symlink'], 'Error: Invalid -wallet path \'w8_symlink\'\. .*', match=ErrorMatch.FULL_REGEX) + self.nodes[0].assert_start_raises_init_error(['-wallet=w8_symlink'], r'Error: Invalid -wallet path \'w8_symlink\'\. .*', match=ErrorMatch.FULL_REGEX) # should not initialize if the specified walletdir does not exist self.nodes[0].assert_start_raises_init_error(['-walletdir=bad'], 'Error: Specified -walletdir "bad" does not exist') @@ -139,7 +139,7 @@ class MultiWalletTest(BitcoinTestFramework): competing_wallet_dir = os.path.join(self.options.tmpdir, 'competing_walletdir') os.mkdir(competing_wallet_dir) self.restart_node(0, ['-walletdir=' + competing_wallet_dir]) - exp_stderr = "Error: Error initializing wallet database environment \"\S+competing_walletdir\"!" + exp_stderr = r"Error: Error initializing wallet database environment \"\S+competing_walletdir\"!" self.nodes[1].assert_start_raises_init_error(['-walletdir=' + competing_wallet_dir], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) self.restart_node(0, extra_args) diff --git a/test/functional/wallet_reorgsrestore.py b/test/functional/wallet_reorgsrestore.py new file mode 100755 index 0000000000..f48018e9fb --- /dev/null +++ b/test/functional/wallet_reorgsrestore.py @@ -0,0 +1,105 @@ +#!/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 tx status in case of reorgs while wallet being shutdown. + +Wallet txn status rely on block connection/disconnection for its +accuracy. In case of reorgs happening while wallet being shutdown +block updates are not going to be received. At wallet loading, we +check against chain if confirmed txn are still in chain and change +their status if block in which they have been included has been +disconnected. +""" + +from decimal import Decimal +import os +import shutil + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + connect_nodes, + disconnect_nodes, +) + +class ReorgsRestoreTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 3 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + # Send a tx from which to conflict outputs later + txid_conflict_from = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) + self.nodes[0].generate(1) + self.sync_blocks() + + # Disconnect node1 from others to reorg its chain later + disconnect_nodes(self.nodes[0], 1) + disconnect_nodes(self.nodes[1], 2) + connect_nodes(self.nodes[0], 2) + + # Send a tx to be unconfirmed later + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) + tx = self.nodes[0].gettransaction(txid) + self.nodes[0].generate(4) + tx_before_reorg = self.nodes[0].gettransaction(txid) + assert_equal(tx_before_reorg["confirmations"], 4) + + # Disconnect node0 from node2 to broadcast a conflict on their respective chains + disconnect_nodes(self.nodes[0], 2) + nA = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txid_conflict_from)["details"] if tx_out["amount"] == Decimal("10")) + inputs = [] + inputs.append({"txid": txid_conflict_from, "vout": nA}) + outputs_1 = {} + outputs_2 = {} + + # Create a conflicted tx broadcast on node0 chain and conflicting tx broadcast on node1 chain. Both spend from txid_conflict_from + outputs_1[self.nodes[0].getnewaddress()] = Decimal("9.99998") + outputs_2[self.nodes[0].getnewaddress()] = Decimal("9.99998") + conflicted = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs_1)) + conflicting = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs_2)) + + conflicted_txid = self.nodes[0].sendrawtransaction(conflicted["hex"]) + self.nodes[0].generate(1) + conflicting_txid = self.nodes[2].sendrawtransaction(conflicting["hex"]) + self.nodes[2].generate(9) + + # Reconnect node0 and node2 and check that conflicted_txid is effectively conflicted + connect_nodes(self.nodes[0], 2) + self.sync_blocks([self.nodes[0], self.nodes[2]]) + conflicted = self.nodes[0].gettransaction(conflicted_txid) + conflicting = self.nodes[0].gettransaction(conflicting_txid) + assert_equal(conflicted["confirmations"], -9) + assert_equal(conflicted["walletconflicts"][0], conflicting["txid"]) + + # Node0 wallet is shutdown + self.stop_node(0) + self.start_node(0) + + # The block chain re-orgs and the tx is included in a different block + self.nodes[1].generate(9) + self.nodes[1].sendrawtransaction(tx["hex"]) + self.nodes[1].generate(1) + self.nodes[1].sendrawtransaction(conflicted["hex"]) + self.nodes[1].generate(1) + + # Node0 wallet file is loaded on longest sync'ed node1 + self.stop_node(1) + self.nodes[0].backupwallet(os.path.join(self.nodes[0].datadir, 'wallet.bak')) + shutil.copyfile(os.path.join(self.nodes[0].datadir, 'wallet.bak'), os.path.join(self.nodes[1].datadir, 'regtest', 'wallet.dat')) + self.start_node(1) + tx_after_reorg = self.nodes[1].gettransaction(txid) + # Check that normal confirmed tx is confirmed again but with different blockhash + assert_equal(tx_after_reorg["confirmations"], 2) + assert(tx_before_reorg["blockhash"] != tx_after_reorg["blockhash"]) + conflicted_after_reorg = self.nodes[1].gettransaction(conflicted_txid) + # Check that conflicted tx is confirmed again with blockhash different than previously conflicting tx + assert_equal(conflicted_after_reorg["confirmations"], 1) + assert(conflicting["blockhash"] != conflicted_after_reorg["blockhash"]) + +if __name__ == '__main__': + ReorgsRestoreTest().main() diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py index 5810e94938..91d26e9cb3 100755 --- a/test/functional/wallet_resendwallettransactions.py +++ b/test/functional/wallet_resendwallettransactions.py @@ -57,8 +57,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): # after the last time we tried to broadcast. Use mocktime and give an extra minute to be sure. block_time = int(time.time()) + 6 * 60 node.setmocktime(block_time) - block = create_block(int(node.getbestblockhash(), 16), create_coinbase(node.getblockchaininfo()['blocks']), block_time) - block.nVersion = 3 + block = create_block(int(node.getbestblockhash(), 16), create_coinbase(node.getblockcount() + 1), block_time) block.rehash() block.solve() node.submitblock(ToHex(block)) diff --git a/test/functional/wallet_watchonly.py b/test/functional/wallet_watchonly.py new file mode 100755 index 0000000000..be8d7714fb --- /dev/null +++ b/test/functional/wallet_watchonly.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018-2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test createwallet arguments. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error +) + + +class CreateWalletWatchonlyTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 1 + self.supports_cli = True + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + node = self.nodes[0] + + self.nodes[0].createwallet(wallet_name='default') + def_wallet = node.get_wallet_rpc('default') + + a1 = def_wallet.getnewaddress() + wo_change = def_wallet.getnewaddress() + wo_addr = def_wallet.getnewaddress() + + self.nodes[0].createwallet(wallet_name='wo', disable_private_keys=True) + wo_wallet = node.get_wallet_rpc('wo') + + wo_wallet.importpubkey(pubkey=def_wallet.getaddressinfo(wo_addr)['pubkey']) + wo_wallet.importpubkey(pubkey=def_wallet.getaddressinfo(wo_change)['pubkey']) + + # generate some btc for testing + node.generatetoaddress(101, a1) + + # send 1 btc to our watch-only address + txid = def_wallet.sendtoaddress(wo_addr, 1) + self.nodes[0].generate(1) + + # getbalance + self.log.info('include_watchonly should default to true for watch-only wallets') + self.log.info('Testing getbalance watch-only defaults') + assert_equal(wo_wallet.getbalance(), 1) + assert_equal(len(wo_wallet.listtransactions()), 1) + assert_equal(wo_wallet.getbalance(include_watchonly=False), 0) + + self.log.info('Testing listreceivedbyaddress watch-only defaults') + result = wo_wallet.listreceivedbyaddress() + assert_equal(len(result), 1) + assert_equal(result[0]["involvesWatchonly"], True) + result = wo_wallet.listreceivedbyaddress(include_watchonly=False) + assert_equal(len(result), 0) + + self.log.info('Testing listreceivedbylabel watch-only defaults') + result = wo_wallet.listreceivedbylabel() + assert_equal(len(result), 1) + assert_equal(result[0]["involvesWatchonly"], True) + result = wo_wallet.listreceivedbylabel(include_watchonly=False) + assert_equal(len(result), 0) + + self.log.info('Testing listtransactions watch-only defaults') + result = wo_wallet.listtransactions() + assert_equal(len(result), 1) + assert_equal(result[0]["involvesWatchonly"], True) + result = wo_wallet.listtransactions(include_watchonly=False) + assert_equal(len(result), 0) + + self.log.info('Testing listsinceblock watch-only defaults') + result = wo_wallet.listsinceblock() + assert_equal(len(result["transactions"]), 1) + assert_equal(result["transactions"][0]["involvesWatchonly"], True) + result = wo_wallet.listsinceblock(include_watchonly=False) + assert_equal(len(result["transactions"]), 0) + + self.log.info('Testing gettransaction watch-only defaults') + result = wo_wallet.gettransaction(txid) + assert_equal(result["details"][0]["involvesWatchonly"], True) + result = wo_wallet.gettransaction(txid=txid, include_watchonly=False) + assert_equal(len(result["details"]), 0) + + self.log.info('Testing walletcreatefundedpsbt watch-only defaults') + inputs = [] + outputs = [{a1: 0.5}] + options = {'changeAddress': wo_change} + no_wo_options = {'changeAddress': wo_change, 'includeWatching': False} + + result = wo_wallet.walletcreatefundedpsbt(inputs=inputs, outputs=outputs, options=options) + assert_equal("psbt" in result, True) + assert_raises_rpc_error(-4, "Insufficient funds", wo_wallet.walletcreatefundedpsbt, inputs, outputs, 0, no_wo_options) + + self.log.info('Testing fundrawtransaction watch-only defaults') + rawtx = wo_wallet.createrawtransaction(inputs=inputs, outputs=outputs) + result = wo_wallet.fundrawtransaction(hexstring=rawtx, options=options) + assert_equal("hex" in result, True) + assert_raises_rpc_error(-4, "Insufficient funds", wo_wallet.fundrawtransaction, rawtx, no_wo_options) + + + +if __name__ == '__main__': + CreateWalletWatchonlyTest().main() diff --git a/test/lint/check-doc.py b/test/lint/check-doc.py index 1d6122a13d..bd947d194c 100755 --- a/test/lint/check-doc.py +++ b/test/lint/check-doc.py @@ -15,8 +15,8 @@ import re FOLDER_GREP = 'src' FOLDER_TEST = 'src/test/' -REGEX_ARG = '(?:ForceSet|SoftSet|Get|Is)(?:Bool)?Args?(?:Set)?\("(-[^"]+)"' -REGEX_DOC = 'AddArg\("(-[^"=]+?)(?:=|")' +REGEX_ARG = r'(?:ForceSet|SoftSet|Get|Is)(?:Bool)?Args?(?:Set)?\("(-[^"]+)"' +REGEX_DOC = r'AddArg\("(-[^"=]+?)(?:=|")' CMD_ROOT_DIR = '$(git rev-parse --show-toplevel)/{}'.format(FOLDER_GREP) CMD_GREP_ARGS = r"git grep --perl-regexp '{}' -- {} ':(exclude){}'".format(REGEX_ARG, CMD_ROOT_DIR, FOLDER_TEST) CMD_GREP_WALLET_ARGS = r"git grep --function-context 'void WalletInit::AddWalletOptions' -- {} | grep AddArg".format(CMD_ROOT_DIR) diff --git a/test/lint/check-rpc-mappings.py b/test/lint/check-rpc-mappings.py index 137cc82b5d..a33ab17f3f 100755 --- a/test/lint/check-rpc-mappings.py +++ b/test/lint/check-rpc-mappings.py @@ -48,13 +48,13 @@ def process_commands(fname): for line in f: line = line.rstrip() if not in_rpcs: - if re.match("static const CRPCCommand .*\[\] =", line): + if re.match(r"static const CRPCCommand .*\[\] =", line): in_rpcs = True else: if line.startswith('};'): in_rpcs = False elif '{' in line and '"' in line: - m = re.search('{ *("[^"]*"), *("[^"]*"), *&([^,]*), *{([^}]*)} *},', line) + m = re.search(r'{ *("[^"]*"), *("[^"]*"), *&([^,]*), *{([^}]*)} *},', line) assert m, 'No match to table expression: %s' % line name = parse_string(m.group(2)) args_str = m.group(4).strip() @@ -80,7 +80,7 @@ def process_mapping(fname): if line.startswith('};'): in_rpcs = False elif '{' in line and '"' in line: - m = re.search('{ *("[^"]*"), *([0-9]+) *, *("[^"]*") *},', line) + m = re.search(r'{ *("[^"]*"), *([0-9]+) *, *("[^"]*") *},', line) assert m, 'No match to table expression: %s' % line name = parse_string(m.group(1)) idx = int(m.group(2)) diff --git a/test/lint/commit-script-check.sh b/test/lint/commit-script-check.sh index 4267f9fa0d..5603456e62 100755 --- a/test/lint/commit-script-check.sh +++ b/test/lint/commit-script-check.sh @@ -18,12 +18,12 @@ if test "x$1" = "x"; then fi RET=0 -PREV_BRANCH=`git name-rev --name-only HEAD` -PREV_HEAD=`git rev-parse HEAD` -for commit in `git rev-list --reverse $1`; do +PREV_BRANCH=$(git name-rev --name-only HEAD) +PREV_HEAD=$(git rev-parse HEAD) +for commit in $(git rev-list --reverse $1); do if git rev-list -n 1 --pretty="%s" $commit | grep -q "^scripted-diff:"; then git checkout --quiet $commit^ || exit - SCRIPT="`git rev-list --format=%b -n1 $commit | sed '/^-BEGIN VERIFY SCRIPT-$/,/^-END VERIFY SCRIPT-$/{//!b};d'`" + SCRIPT="$(git rev-list --format=%b -n1 $commit | sed '/^-BEGIN VERIFY SCRIPT-$/,/^-END VERIFY SCRIPT-$/{//!b};d')" if test "x$SCRIPT" = "x"; then echo "Error: missing script for: $commit" echo "Failed" diff --git a/test/lint/extended-lint-all.sh b/test/lint/extended-lint-all.sh new file mode 100755 index 0000000000..65c51e02f5 --- /dev/null +++ b/test/lint/extended-lint-all.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# This script runs all contrib/devtools/extended-lint-*.sh files, and fails if +# any exit with a non-zero status code. + +# This script is intentionally locale dependent by not setting "export LC_ALL=C" +# in order to allow for the executed lint scripts to opt in or opt out of locale +# dependence themselves. + +set -u + +SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}") +LINTALL=$(basename "${BASH_SOURCE[0]}") + +for f in "${SCRIPTDIR}"/extended-lint-*.sh; do + if [ "$(basename "$f")" != "$LINTALL" ]; then + if ! "$f"; then + echo "^---- failure generated from $f" + exit 1 + fi + fi +done diff --git a/test/lint/extended-lint-cppcheck.sh b/test/lint/extended-lint-cppcheck.sh new file mode 100755 index 0000000000..47df25ba6b --- /dev/null +++ b/test/lint/extended-lint-cppcheck.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# + +export LC_ALL=C + +ENABLED_CHECKS=( + "Class '.*' has a constructor with 1 argument that is not explicit." + "Struct '.*' has a constructor with 1 argument that is not explicit." +) + +IGNORED_WARNINGS=( + "src/arith_uint256.h:.* Class 'arith_uint256' has a constructor with 1 argument that is not explicit." + "src/arith_uint256.h:.* Class 'base_uint < 256 >' has a constructor with 1 argument that is not explicit." + "src/arith_uint256.h:.* Class 'base_uint' has a constructor with 1 argument that is not explicit." + "src/coins.h:.* Class 'CCoinsViewBacked' has a constructor with 1 argument that is not explicit." + "src/coins.h:.* Class 'CCoinsViewCache' has a constructor with 1 argument that is not explicit." + "src/coins.h:.* Class 'CCoinsViewCursor' has a constructor with 1 argument that is not explicit." + "src/net.h:.* Class 'CNetMessage' has a constructor with 1 argument that is not explicit." + "src/policy/feerate.h:.* Class 'CFeeRate' has a constructor with 1 argument that is not explicit." + "src/prevector.h:.* Class 'const_iterator' has a constructor with 1 argument that is not explicit." + "src/prevector.h:.* Class 'const_reverse_iterator' has a constructor with 1 argument that is not explicit." + "src/prevector.h:.* Class 'iterator' has a constructor with 1 argument that is not explicit." + "src/prevector.h:.* Class 'reverse_iterator' has a constructor with 1 argument that is not explicit." + "src/primitives/block.h:.* Class 'CBlock' has a constructor with 1 argument that is not explicit." + "src/primitives/transaction.h:.* Class 'CTransaction' has a constructor with 1 argument that is not explicit." + "src/protocol.h:.* Class 'CMessageHeader' has a constructor with 1 argument that is not explicit." + "src/qt/guiutil.h:.* Class 'ItemDelegate' has a constructor with 1 argument that is not explicit." + "src/rpc/util.h:.* Struct 'RPCResults' has a constructor with 1 argument that is not explicit." + "src/rpc/util.h:.* style: Struct 'UniValueType' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'AddressDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'ComboDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'ConstPubkeyProvider' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'PKDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'PKHDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'RawDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'SHDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'WPKHDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'WSHDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/script.h:.* Class 'CScript' has a constructor with 1 argument that is not explicit." + "src/script/standard.h:.* Class 'CScriptID' has a constructor with 1 argument that is not explicit." + "src/support/allocators/secure.h:.* Struct 'secure_allocator < char >' has a constructor with 1 argument that is not explicit." + "src/support/allocators/secure.h:.* Struct 'secure_allocator < RNGState >' has a constructor with 1 argument that is not explicit." + "src/support/allocators/secure.h:.* Struct 'secure_allocator < unsigned char >' has a constructor with 1 argument that is not explicit." + "src/support/allocators/zeroafterfree.h:.* Struct 'zero_after_free_allocator < char >' has a constructor with 1 argument that is not explicit." + "src/test/checkqueue_tests.cpp:.* Struct 'FailingCheck' has a constructor with 1 argument that is not explicit." + "src/test/checkqueue_tests.cpp:.* Struct 'MemoryCheck' has a constructor with 1 argument that is not explicit." + "src/test/checkqueue_tests.cpp:.* Struct 'UniqueCheck' has a constructor with 1 argument that is not explicit." + "src/wallet/db.h:.* Class 'BerkeleyEnvironment' has a constructor with 1 argument that is not explicit." +) + +if ! command -v cppcheck > /dev/null; then + echo "Skipping cppcheck linting since cppcheck is not installed. Install by running \"apt install cppcheck\"" + exit 0 +fi + +function join_array { + local IFS="$1" + shift + echo "$*" +} + +ENABLED_CHECKS_REGEXP=$(join_array "|" "${ENABLED_CHECKS[@]}") +IGNORED_WARNINGS_REGEXP=$(join_array "|" "${IGNORED_WARNINGS[@]}") +WARNINGS=$(git ls-files -- "*.cpp" "*.h" ":(exclude)src/leveldb/" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/" | \ + xargs cppcheck --enable=all -j "$(getconf _NPROCESSORS_ONLN)" --language=c++ --std=c++11 --template=gcc -D__cplusplus -DCLIENT_VERSION_BUILD -DCLIENT_VERSION_IS_RELEASE -DCLIENT_VERSION_MAJOR -DCLIENT_VERSION_MINOR -DCLIENT_VERSION_REVISION -DCOPYRIGHT_YEAR -DDEBUG -DHAVE_WORKING_BOOST_SLEEP_FOR -I src/ -q 2>&1 | sort -u | \ + grep -E "${ENABLED_CHECKS_REGEXP}" | \ + grep -vE "${IGNORED_WARNINGS_REGEXP}") +if [[ ${WARNINGS} != "" ]]; then + echo "${WARNINGS}" + echo + echo "Advice not applicable in this specific case? Add an exception by updating" + echo "IGNORED_WARNINGS in $0" + # Uncomment to enforce the developer note policy "By default, declare single-argument constructors `explicit`" + # exit 1 +fi +exit 0 diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index 70cc16337e..8607fc4371 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -39,7 +39,7 @@ CIRCULAR_DEPENDENCIES=() IFS=$'\n' for CIRC in $(cd src && ../contrib/devtools/circular-dependencies.py {*,*/*,*/*/*}.{h,cpp} | sed -e 's/^Circular dependency: //'); do - CIRCULAR_DEPENDENCIES+=($CIRC) + CIRCULAR_DEPENDENCIES+=( "$CIRC" ) IS_EXPECTED_CIRC=0 for EXPECTED_CIRC in "${EXPECTED_CIRCULAR_DEPENDENCIES[@]}"; do if [[ "${CIRC}" == "${EXPECTED_CIRC}" ]]; then diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py index 224e62f04a..9f34d0f4dd 100755 --- a/test/lint/lint-format-strings.py +++ b/test/lint/lint-format-strings.py @@ -16,8 +16,7 @@ FALSE_POSITIVES = [ ("src/dbwrapper.cpp", "vsnprintf(p, limit - p, format, backup_ap)"), ("src/index/base.cpp", "FatalError(const char* fmt, const Args&... args)"), ("src/netbase.cpp", "LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args)"), - ("src/util/system.cpp", "strprintf(_(COPYRIGHT_HOLDERS), _(COPYRIGHT_HOLDERS_SUBSTITUTION))"), - ("src/util/system.cpp", "strprintf(COPYRIGHT_HOLDERS, COPYRIGHT_HOLDERS_SUBSTITUTION)"), + ("src/util/system.cpp", "strprintf(_(COPYRIGHT_HOLDERS).translated, COPYRIGHT_HOLDERS_SUBSTITUTION)"), ("src/wallet/wallet.h", "WalletLogPrintf(std::string fmt, Params... parameters)"), ("src/wallet/wallet.h", "LogPrintf((\"%s \" + fmt).c_str(), GetDisplayName(), parameters...)"), ("src/logging.h", "LogPrintf(const char* fmt, const Args&... args)"), @@ -56,7 +55,7 @@ def normalize(s): assert type(s) is str s = s.replace("\n", " ") s = s.replace("\t", " ") - s = re.sub("/\*.*?\*/", " ", s) + s = re.sub(r"/\*.*?\*/", " ", s) s = re.sub(" {2,}", " ", s) return s.strip() diff --git a/test/lint/lint-includes.sh b/test/lint/lint-includes.sh index 4b9e2615b6..d27e45a23f 100755 --- a/test/lint/lint-includes.sh +++ b/test/lint/lint-includes.sh @@ -11,6 +11,9 @@ export LC_ALL=C IGNORE_REGEXP="/(leveldb|secp256k1|univalue)/" +# cd to root folder of git repo for git ls-files to work properly +cd "$(dirname $0)/../.." || exit 1 + filter_suffix() { git ls-files | grep -E "^src/.*\.${1}"'$' | grep -Ev "${IGNORE_REGEXP}" } diff --git a/test/lint/lint-logs.sh b/test/lint/lint-logs.sh index 1afd4cfc1a..632ed7c812 100755 --- a/test/lint/lint-logs.sh +++ b/test/lint/lint-logs.sh @@ -19,6 +19,7 @@ UNTERMINATED_LOGS=$(git grep --extended-regexp "LogPrintf?\(" -- "*.cpp" | \ grep -v "LogPrint()" | \ grep -v "LogPrintf()") if [[ ${UNTERMINATED_LOGS} != "" ]]; then + # shellcheck disable=SC2028 echo "All calls to LogPrintf() and LogPrint() should be terminated with \\n" echo echo "${UNTERMINATED_LOGS}" diff --git a/test/lint/lint-python-dead-code-whitelist b/test/lint/lint-python-dead-code-whitelist new file mode 100644 index 0000000000..2522c8fa1c --- /dev/null +++ b/test/lint/lint-python-dead-code-whitelist @@ -0,0 +1,45 @@ +BadInputOutpointIndex # unused class (test/functional/data/invalid_txs.py) +_.carbon_path # unused attribute (contrib/macdeploy/custom_dsstore.py) +connection_lost # unused function (test/functional/test_framework/mininode.py) +connection_made # unused function (test/functional/test_framework/mininode.py) +_.converter # unused attribute (test/functional/test_framework/test_framework.py) +_.daemon # unused attribute (test/functional/test_framework/socks5.py) +data_received # unused function (test/functional/test_framework/mininode.py) +DuplicateInput # unused class (test/functional/data/invalid_txs.py) +_.filename # unused attribute (contrib/macdeploy/custom_dsstore.py) +InvalidOPIFConstruction # unused class (test/functional/data/invalid_txs.py) +_.is_compressed # unused property (test/functional/test_framework/key.py) +legacy # unused variable (test/functional/test_framework/address.py) +msg_generic # unused class (test/functional/test_framework/messages.py) +NonexistentInput # unused class (test/functional/data/invalid_txs.py) +on_addr # unused function (test/functional/test_framework/mininode.py) +on_blocktxn # unused function (test/functional/test_framework/mininode.py) +on_block # unused function (test/functional/test_framework/mininode.py) +on_cmpctblock # unused function (test/functional/test_framework/mininode.py) +on_feefilter # unused function (test/functional/test_framework/mininode.py) +on_getaddr # unused function (test/functional/test_framework/mininode.py) +on_getblocks # unused function (test/functional/test_framework/mininode.py) +on_getblocktxn # unused function (test/functional/test_framework/mininode.py) +on_getdata # unused function (test/functional/test_framework/mininode.py) +on_getheaders # unused function (test/functional/test_framework/mininode.py) +on_headers # unused function (test/functional/test_framework/mininode.py) +on_inv # unused function (test/functional/test_framework/mininode.py) +on_mempool # unused function (test/functional/test_framework/mininode.py) +on_notfound # unused function (test/functional/test_framework/mininode.py) +on_ping # unused function (test/functional/test_framework/mininode.py) +on_pong # unused function (test/functional/test_framework/mininode.py) +on_reject # unused function (test/functional/test_framework/mininode.py) +on_sendcmpct # unused function (test/functional/test_framework/mininode.py) +on_sendheaders # unused function (test/functional/test_framework/mininode.py) +on_tx # unused function (test/functional/test_framework/mininode.py) +on_verack # unused function (test/functional/test_framework/mininode.py) +on_version # unused function (test/functional/test_framework/mininode.py) +_.optionxform # unused attribute (test/util/bitcoin-util-test.py) +OutputMissing # unused class (test/functional/data/invalid_txs.py) +_.posix_path # unused attribute (contrib/macdeploy/custom_dsstore.py) +profile_with_perf # unused function (test/functional/test_framework/test_node.py) +SizeTooSmall # unused class (test/functional/data/invalid_txs.py) +SpendNegative # unused class (test/functional/data/invalid_txs.py) +SpendTooMuch # unused class (test/functional/data/invalid_txs.py) +TooManySigops # unused class (test/functional/data/invalid_txs.py) +verify_ecdsa # unused function (test/functional/test_framework/key.py) diff --git a/test/lint/lint-python-dead-code.sh b/test/lint/lint-python-dead-code.sh index 588ba428d7..77bf5990a7 100755 --- a/test/lint/lint-python-dead-code.sh +++ b/test/lint/lint-python-dead-code.sh @@ -15,5 +15,5 @@ fi vulture \ --min-confidence 60 \ - --ignore-names "argtypes,connection_lost,connection_made,converter,data_received,daemon,errcheck,is_compressed,is_valid,verify_ecdsa,msg_generic,on_*,optionxform,restype,profile_with_perf" \ - $(git ls-files -- "*.py" ":(exclude)contrib/" ":(exclude)test/functional/data/invalid_txs.py") + $(git rev-parse --show-toplevel) \ + $(dirname "${BASH_SOURCE[0]}")/lint-python-dead-code-whitelist diff --git a/test/lint/lint-python-mutable-default-parameters.sh b/test/lint/lint-python-mutable-default-parameters.sh new file mode 100755 index 0000000000..1f9f035d30 --- /dev/null +++ b/test/lint/lint-python-mutable-default-parameters.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# Detect when a mutable list or dict is used as a default parameter value in a Python function. + +export LC_ALL=C +EXIT_CODE=0 +OUTPUT=$(git grep -E '^\s*def [a-zA-Z0-9_]+\(.*=\s*(\[|\{)' -- "*.py") +if [[ ${OUTPUT} != "" ]]; then + echo "A mutable list or dict seems to be used as default parameter value:" + echo + echo "${OUTPUT}" + echo + cat << EXAMPLE +This is how mutable list and dict default parameter values behave: + +>>> def f(i, j=[], k={}): +... j.append(i) +... k[i] = True +... return j, k +... +>>> f(1) +([1], {1: True}) +>>> f(1) +([1, 1], {1: True}) +>>> f(2) +([1, 1, 2], {1: True, 2: True}) + +The intended behaviour was likely: + +>>> def f(i, j=None, k=None): +... if j is None: +... j = [] +... if k is None: +... k = {} +... j.append(i) +... k[i] = True +... return j, k +... +>>> f(1) +([1], {1: True}) +>>> f(1) +([1], {1: True}) +>>> f(2) +([2], {2: True}) +EXAMPLE + EXIT_CODE=1 +fi +exit ${EXIT_CODE} diff --git a/test/lint/lint-python.sh b/test/lint/lint-python.sh index a76806003f..3c82ec19e3 100755 --- a/test/lint/lint-python.sh +++ b/test/lint/lint-python.sh @@ -73,7 +73,6 @@ enabled=( W291 # trailing whitespace W292 # no newline at end of file W293 # blank line contains whitespace - W504 # line break after binary operator W601 # .has_key() is deprecated, use "in" W602 # deprecated form of raising exception W603 # "<>" is deprecated, use "!=" diff --git a/test/lint/lint-shell.sh b/test/lint/lint-shell.sh index 6f5e6546c5..69fc3cf368 100755 --- a/test/lint/lint-shell.sh +++ b/test/lint/lint-shell.sh @@ -23,25 +23,9 @@ fi # Disabled warnings: disabled=( - SC1087 # Use braces when expanding arrays, e.g. ${array[idx]} (or ${var}[.. to quiet). - SC2001 # See if you can use ${variable//search/replace} instead. - SC2004 # $/${} is unnecessary on arithmetic variables. - SC2005 # Useless echo? Instead of 'echo $(cmd)', just use 'cmd'. - SC2006 # Use $(..) instead of legacy `..`. - SC2016 # Expressions don't expand in single quotes, use double quotes for that. - SC2028 # echo won't expand escape sequences. Consider printf. SC2046 # Quote this to prevent word splitting. - SC2048 # Use "$@" (with quotes) to prevent whitespace problems. - SC2066 # Since you double quoted this, it will not word split, and the loop will only run once. SC2086 # Double quote to prevent globbing and word splitting. - SC2116 # Useless echo? Instead of 'cmd $(echo foo)', just use 'cmd foo'. SC2162 # read without -r will mangle backslashes. - SC2166 # Prefer [ p ] {&&,||} [ q ] as [ p -{a,o} q ] is not well defined. - SC2181 # Check exit code directly with e.g. 'if mycmd;', not indirectly with $?. - SC2206 # Quote to prevent word splitting, or split robustly with mapfile or read -a. - SC2207 # Prefer mapfile or read -a to split command output (or quote to avoid splitting). - SC2230 # which is non-standard. Use builtin 'command -v' instead. - SC2236 # Don't force -n instead of ! -z. ) shellcheck -e "$(IFS=","; echo "${disabled[*]}")" \ $(git ls-files -- "*.sh" | grep -vE 'src/(secp256k1|univalue)/') diff --git a/test/lint/lint-spelling.ignore-words.txt b/test/lint/lint-spelling.ignore-words.txt index a25de2435b..b08837c1d4 100644 --- a/test/lint/lint-spelling.ignore-words.txt +++ b/test/lint/lint-spelling.ignore-words.txt @@ -12,3 +12,4 @@ cachable errorstring keyserver homogenous +setban diff --git a/test/lint/lint-spelling.sh b/test/lint/lint-spelling.sh index 5d672698a7..e70b73e1cc 100755 --- a/test/lint/lint-spelling.sh +++ b/test/lint/lint-spelling.sh @@ -9,6 +9,11 @@ export LC_ALL=C +if ! command -v codespell > /dev/null; then + echo "Skipping spell check linting since codespell is not installed." + exit 0 +fi + IGNORE_WORDS_FILE=test/lint/lint-spelling.ignore-words.txt if ! codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=${IGNORE_WORDS_FILE} $(git ls-files -- ":(exclude)build-aux/m4/" ":(exclude)contrib/seeds/*.txt" ":(exclude)depends/" ":(exclude)doc/release-notes/" ":(exclude)src/leveldb/" ":(exclude)src/qt/locale/" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/"); then echo "^ Warning: codespell identified likely spelling errors. Any false positives? Add them to the list of ignored words in ${IGNORE_WORDS_FILE}" |