diff options
Diffstat (limited to 'test')
35 files changed, 588 insertions, 333 deletions
diff --git a/test/functional/assumevalid.py b/test/functional/assumevalid.py index c60c8e6d1a..8e301c4379 100755 --- a/test/functional/assumevalid.py +++ b/test/functional/assumevalid.py @@ -15,7 +15,7 @@ transactions: 2-101: bury that block with 100 blocks so the coinbase transaction output can be spent 102: a block containing a transaction spending the coinbase - transaction output. The transaction has an invalid signature. + transaction output. The transaction has an invalid signature. 103-2202: bury the bad block with just over two weeks' worth of blocks (2100 blocks) @@ -29,40 +29,31 @@ Start three nodes: block 200. node2 will reject block 102 since it's assumed valid, but it isn't buried by at least two weeks' work. """ +import time -from test_framework.mininode import * -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * -from test_framework.blocktools import create_block, create_coinbase +from test_framework.blocktools import (create_block, create_coinbase) from test_framework.key import CECKey -from test_framework.script import * - -class BaseNode(SingleNodeConnCB): - def __init__(self): - SingleNodeConnCB.__init__(self) - self.last_inv = None - self.last_headers = None - self.last_block = None - self.last_getdata = None - self.block_announced = False - self.last_getheaders = None - self.disconnected = False - self.last_blockhash_announced = None - - def on_close(self, conn): - self.disconnected = True - - def wait_for_disconnect(self, timeout=60): - test_function = lambda: self.disconnected - assert(wait_until(test_function, timeout=timeout)) - return +from test_framework.mininode import (CBlockHeader, + COutPoint, + CTransaction, + CTxIn, + CTxOut, + NetworkThread, + NodeConn, + NodeConnCB, + msg_block, + msg_headers) +from test_framework.script import (CScript, OP_TRUE) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import (start_node, p2p_port, assert_equal) +class BaseNode(NodeConnCB): def send_header_for_blocks(self, new_blocks): headers_message = msg_headers() - headers_message.headers = [ CBlockHeader(b) for b in new_blocks ] + headers_message.headers = [CBlockHeader(b) for b in new_blocks] self.send_message(headers_message) -class SendHeadersTest(BitcoinTestFramework): +class AssumeValidTest(BitcoinTestFramework): def __init__(self): super().__init__() self.setup_clean_chain = True @@ -72,8 +63,34 @@ class SendHeadersTest(BitcoinTestFramework): # Start node0. We don't start the other nodes yet since # we need to pre-mine a block with an invalid transaction # signature so we can pass in the block hash as assumevalid. - self.nodes = [] - self.nodes.append(start_node(0, self.options.tmpdir)) + self.nodes = [start_node(0, self.options.tmpdir)] + + def send_blocks_until_disconnected(self, node): + """Keep sending blocks to the node until we're disconnected.""" + for i in range(len(self.blocks)): + try: + node.send_message(msg_block(self.blocks[i])) + except IOError as e: + assert str(e) == 'Not connected, no pushbuf' + break + + def assert_blockchain_height(self, node, height): + """Wait until the blockchain is no longer advancing and verify it's reached the expected height.""" + last_height = node.getblock(node.getbestblockhash())['height'] + timeout = 10 + while True: + time.sleep(0.25) + current_height = node.getblock(node.getbestblockhash())['height'] + if current_height != last_height: + last_height = current_height + if timeout < 0: + assert False, "blockchain too short after timeout: %d" % current_height + timeout - 0.25 + continue + elif current_height > height: + assert False, "blockchain too long: %d" % current_height + elif current_height == height: + break def run_test(self): @@ -83,7 +100,7 @@ class SendHeadersTest(BitcoinTestFramework): connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)) node0.add_connection(connections[0]) - NetworkThread().start() # Start up network handling in another thread + NetworkThread().start() # Start up network handling in another thread node0.wait_for_verack() # Build the blockchain @@ -120,7 +137,7 @@ class SendHeadersTest(BitcoinTestFramework): # Create a transaction spending the coinbase output with an invalid (null) signature tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.block1.vtx[0].sha256, 0), scriptSig=b"")) - tx.vout.append(CTxOut(49*100000000, CScript([OP_TRUE]))) + tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE]))) tx.calc_sha256() block102 = create_block(self.tip, create_coinbase(height), self.block_time) @@ -166,25 +183,20 @@ class SendHeadersTest(BitcoinTestFramework): node1.send_header_for_blocks(self.blocks[2000:]) node2.send_header_for_blocks(self.blocks[0:200]) - # Send 102 blocks to node0. Block 102 will be rejected. - for i in range(101): - node0.send_message(msg_block(self.blocks[i])) - node0.sync_with_ping() # make sure the most recent block is synced - node0.send_message(msg_block(self.blocks[101])) - assert_equal(self.nodes[0].getblock(self.nodes[0].getbestblockhash())['height'], 101) + # Send blocks to node0. Block 102 will be rejected. + self.send_blocks_until_disconnected(node0) + self.assert_blockchain_height(self.nodes[0], 101) - # Send 3102 blocks to node1. All blocks will be accepted. + # Send all blocks to node1. All blocks will be accepted. for i in range(2202): node1.send_message(msg_block(self.blocks[i])) - node1.sync_with_ping() # make sure the most recent block is synced + # Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync. + node1.sync_with_ping(120) assert_equal(self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 2202) - # Send 102 blocks to node2. Block 102 will be rejected. - for i in range(101): - node2.send_message(msg_block(self.blocks[i])) - node2.sync_with_ping() # make sure the most recent block is synced - node2.send_message(msg_block(self.blocks[101])) - assert_equal(self.nodes[2].getblock(self.nodes[2].getbestblockhash())['height'], 101) + # Send blocks to node2. Block 102 will be rejected. + self.send_blocks_until_disconnected(node2) + self.assert_blockchain_height(self.nodes[2], 101) if __name__ == '__main__': - SendHeadersTest().main() + AssumeValidTest().main() diff --git a/test/functional/bip68-sequence.py b/test/functional/bip68-sequence.py index 3ed6ebe044..89d42a710c 100755 --- a/test/functional/bip68-sequence.py +++ b/test/functional/bip68-sequence.py @@ -378,8 +378,8 @@ class BIP68Test(BitcoinTestFramework): # activation should happen at block height 432 (3 periods) min_activation_height = 432 height = self.nodes[0].getblockcount() - assert(height < 432) - self.nodes[0].generate(432-height) + assert(height < min_activation_height) + self.nodes[0].generate(min_activation_height-height) assert(get_bip9_status(self.nodes[0], 'csv')['status'] == 'active') sync_blocks(self.nodes) diff --git a/test/functional/bumpfee.py b/test/functional/bumpfee.py index 8f75e9ed4d..c51a75cc26 100755 --- a/test/functional/bumpfee.py +++ b/test/functional/bumpfee.py @@ -2,7 +2,17 @@ # Copyright (c) 2016 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 bumpfee RPC.""" +"""Test the bumpfee RPC. + +Verifies that the bumpfee RPC creates replacement transactions successfully when +its preconditions are met, and returns appropriate errors in other cases. + +This module consists of around a dozen individual test cases implemented in the +top-level functions named as test_<test_case_description>. The test functions +can be disabled or reordered if needed for debugging. If new test cases are +added in the the future, they should try to follow the same convention and not +make assumptions about execution order. +""" from segwit import send_to_witness from test_framework.test_framework import BitcoinTestFramework @@ -57,13 +67,13 @@ class BumpFeeTest(BitcoinTestFramework): self.log.info("Running tests") dest_address = peer_node.getnewaddress() - test_small_output_fails(rbf_node, dest_address) - test_dust_to_fee(rbf_node, dest_address) test_simple_bumpfee_succeeds(rbf_node, peer_node, dest_address) test_segwit_bumpfee_succeeds(rbf_node, dest_address) test_nonrbf_bumpfee_fails(peer_node, dest_address) test_notmine_bumpfee_fails(rbf_node, peer_node, dest_address) test_bumpfee_with_descendant_fails(rbf_node, rbf_node_address, dest_address) + test_small_output_fails(rbf_node, dest_address) + test_dust_to_fee(rbf_node, dest_address) test_settxfee(rbf_node, dest_address) test_rebumping(rbf_node, dest_address) test_rebumping_not_replaceable(rbf_node, dest_address) @@ -74,7 +84,7 @@ class BumpFeeTest(BitcoinTestFramework): def test_simple_bumpfee_succeeds(rbf_node, peer_node, dest_address): - rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000}) + rbfid = spend_one_input(rbf_node, dest_address) rbftx = rbf_node.gettransaction(rbfid) sync_mempools((rbf_node, peer_node)) assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool() @@ -115,7 +125,7 @@ def test_segwit_bumpfee_succeeds(rbf_node, dest_address): 'vout': 0, "sequence": BIP125_SEQUENCE_NUMBER }], {dest_address: Decimal("0.0005"), - get_change_address(rbf_node): Decimal("0.0003")}) + rbf_node.getrawchangeaddress(): Decimal("0.0003")}) rbfsigned = rbf_node.signrawtransaction(rbfraw) rbfid = rbf_node.sendrawtransaction(rbfsigned["hex"]) assert rbfid in rbf_node.getrawmempool() @@ -127,7 +137,7 @@ def test_segwit_bumpfee_succeeds(rbf_node, dest_address): def test_nonrbf_bumpfee_fails(peer_node, dest_address): # cannot replace a non RBF transaction (from node which did not enable RBF) - not_rbfid = create_fund_sign_send(peer_node, {dest_address: 0.00090000}) + not_rbfid = peer_node.sendtoaddress(dest_address, Decimal("0.00090000")) assert_raises_jsonrpc(-4, "not BIP 125 replaceable", peer_node.bumpfee, not_rbfid) @@ -155,7 +165,7 @@ def test_notmine_bumpfee_fails(rbf_node, peer_node, dest_address): def test_bumpfee_with_descendant_fails(rbf_node, rbf_node_address, dest_address): # cannot bump fee if the transaction has a descendant # parent is send-to-self, so we don't have to check which output is change when creating the child tx - parent_id = create_fund_sign_send(rbf_node, {rbf_node_address: 0.00050000}) + parent_id = spend_one_input(rbf_node, rbf_node_address) tx = rbf_node.createrawtransaction([{"txid": parent_id, "vout": 0}], {dest_address: 0.00020000}) tx = rbf_node.signrawtransaction(tx) txid = rbf_node.sendrawtransaction(tx["hex"]) @@ -164,58 +174,49 @@ def test_bumpfee_with_descendant_fails(rbf_node, rbf_node_address, dest_address) def test_small_output_fails(rbf_node, dest_address): # cannot bump fee with a too-small output - rbfid = spend_one_input(rbf_node, - Decimal("0.00100000"), - {dest_address: 0.00080000, - get_change_address(rbf_node): Decimal("0.00010000")}) - rbf_node.bumpfee(rbfid, {"totalFee": 20000}) + rbfid = spend_one_input(rbf_node, dest_address) + rbf_node.bumpfee(rbfid, {"totalFee": 50000}) - rbfid = spend_one_input(rbf_node, - Decimal("0.00100000"), - {dest_address: 0.00080000, - get_change_address(rbf_node): Decimal("0.00010000")}) - assert_raises_jsonrpc(-4, "Change output is too small", rbf_node.bumpfee, rbfid, {"totalFee": 20001}) + rbfid = spend_one_input(rbf_node, dest_address) + assert_raises_jsonrpc(-4, "Change output is too small", rbf_node.bumpfee, rbfid, {"totalFee": 50001}) def test_dust_to_fee(rbf_node, dest_address): # check that if output is reduced to dust, it will be converted to fee - # the bumped tx sets fee=9900, but it converts to 10,000 - rbfid = spend_one_input(rbf_node, - Decimal("0.00100000"), - {dest_address: 0.00080000, - get_change_address(rbf_node): Decimal("0.00010000")}) + # the bumped tx sets fee=49,900, but it converts to 50,000 + rbfid = spend_one_input(rbf_node, dest_address) fulltx = rbf_node.getrawtransaction(rbfid, 1) - bumped_tx = rbf_node.bumpfee(rbfid, {"totalFee": 19900}) + bumped_tx = rbf_node.bumpfee(rbfid, {"totalFee": 49900}) full_bumped_tx = rbf_node.getrawtransaction(bumped_tx["txid"], 1) - assert_equal(bumped_tx["fee"], Decimal("0.00020000")) + assert_equal(bumped_tx["fee"], Decimal("0.00050000")) assert_equal(len(fulltx["vout"]), 2) assert_equal(len(full_bumped_tx["vout"]), 1) #change output is eliminated def test_settxfee(rbf_node, dest_address): # check that bumpfee reacts correctly to the use of settxfee (paytxfee) - # increase feerate by 2.5x, test that fee increased at least 2x - rbf_node.settxfee(Decimal("0.00001000")) - rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000}) - rbftx = rbf_node.gettransaction(rbfid) - rbf_node.settxfee(Decimal("0.00002500")) + rbfid = spend_one_input(rbf_node, dest_address) + requested_feerate = Decimal("0.00025000") + rbf_node.settxfee(requested_feerate) bumped_tx = rbf_node.bumpfee(rbfid) - assert bumped_tx["fee"] > 2 * abs(rbftx["fee"]) + actual_feerate = bumped_tx["fee"] * 1000 / rbf_node.getrawtransaction(bumped_tx["txid"], True)["size"] + # Assert that the difference between the requested feerate and the actual + # feerate of the bumped transaction is small. + assert_greater_than(Decimal("0.00001000"), abs(requested_feerate - actual_feerate)) rbf_node.settxfee(Decimal("0.00000000")) # unset paytxfee def test_rebumping(rbf_node, dest_address): # check that re-bumping the original tx fails, but bumping the bumper succeeds - rbf_node.settxfee(Decimal("0.00001000")) - rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000}) - bumped = rbf_node.bumpfee(rbfid, {"totalFee": 1000}) - assert_raises_jsonrpc(-4, "already bumped", rbf_node.bumpfee, rbfid, {"totalFee": 2000}) - rbf_node.bumpfee(bumped["txid"], {"totalFee": 2000}) + rbfid = spend_one_input(rbf_node, dest_address) + bumped = rbf_node.bumpfee(rbfid, {"totalFee": 2000}) + assert_raises_jsonrpc(-4, "already bumped", rbf_node.bumpfee, rbfid, {"totalFee": 3000}) + rbf_node.bumpfee(bumped["txid"], {"totalFee": 3000}) def test_rebumping_not_replaceable(rbf_node, dest_address): # check that re-bumping a non-replaceable bump tx fails - rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000}) + rbfid = spend_one_input(rbf_node, dest_address) bumped = rbf_node.bumpfee(rbfid, {"totalFee": 10000, "replaceable": False}) assert_raises_jsonrpc(-4, "Transaction is not BIP 125 replaceable", rbf_node.bumpfee, bumped["txid"], {"totalFee": 20000}) @@ -223,7 +224,7 @@ def test_rebumping_not_replaceable(rbf_node, dest_address): def test_unconfirmed_not_spendable(rbf_node, rbf_node_address): # check that unconfirmed outputs from bumped transactions are not spendable - rbfid = create_fund_sign_send(rbf_node, {rbf_node_address: 0.00090000}) + rbfid = spend_one_input(rbf_node, rbf_node_address) rbftx = rbf_node.gettransaction(rbfid)["hex"] assert rbfid in rbf_node.getrawmempool() bumpid = rbf_node.bumpfee(rbfid)["txid"] @@ -258,7 +259,7 @@ def test_unconfirmed_not_spendable(rbf_node, rbf_node_address): def test_bumpfee_metadata(rbf_node, dest_address): - rbfid = rbf_node.sendtoaddress(dest_address, 0.00090000, "comment value", "to value") + rbfid = rbf_node.sendtoaddress(dest_address, Decimal("0.00100000"), "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") @@ -266,43 +267,23 @@ def test_bumpfee_metadata(rbf_node, dest_address): def test_locked_wallet_fails(rbf_node, dest_address): - rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000}) + rbfid = spend_one_input(rbf_node, dest_address) rbf_node.walletlock() assert_raises_jsonrpc(-13, "Please enter the wallet passphrase with walletpassphrase first.", rbf_node.bumpfee, rbfid) -def create_fund_sign_send(node, outputs): - rawtx = node.createrawtransaction([], outputs) - fundtx = node.fundrawtransaction(rawtx) - signedtx = node.signrawtransaction(fundtx["hex"]) - txid = node.sendrawtransaction(signedtx["hex"]) - return txid - - -def spend_one_input(node, input_amount, outputs): - input = dict(sequence=BIP125_SEQUENCE_NUMBER, **next(u for u in node.listunspent() if u["amount"] == input_amount)) - rawtx = node.createrawtransaction([input], outputs) +def spend_one_input(node, dest_address): + tx_input = dict( + sequence=BIP125_SEQUENCE_NUMBER, **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000"))) + rawtx = node.createrawtransaction( + [tx_input], {dest_address: Decimal("0.00050000"), + node.getrawchangeaddress(): Decimal("0.00049000")}) signedtx = node.signrawtransaction(rawtx) txid = node.sendrawtransaction(signedtx["hex"]) return txid -def get_change_address(node): - """Get a wallet change address. - - There is no wallet RPC to access unused change addresses, so this creates a - dummy transaction, calls fundrawtransaction to give add an input and change - output, then returns the change address.""" - dest_address = node.getnewaddress() - dest_amount = Decimal("0.00012345") - rawtx = node.createrawtransaction([], {dest_address: dest_amount}) - fundtx = node.fundrawtransaction(rawtx) - info = node.decoderawtransaction(fundtx["hex"]) - return next(address for out in info["vout"] - if out["value"] != dest_amount for address in out["scriptPubKey"]["addresses"]) - - def submit_block_with_tx(node, tx): ctx = CTransaction() ctx.deserialize(io.BytesIO(hex_str_to_bytes(tx))) diff --git a/test/functional/combine_logs.py b/test/functional/combine_logs.py new file mode 100755 index 0000000000..3ca74ea35e --- /dev/null +++ b/test/functional/combine_logs.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""Combine logs from multiple bitcoin nodes as well as the test_framework log. + +This streams the combined log output to stdout. Use combine_logs.py > outputfile +to write to an outputfile.""" + +import argparse +from collections import defaultdict, namedtuple +import heapq +import itertools +import os +import re +import sys + +# Matches on the date format at the start of the log event +TIMESTAMP_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}") + +LogEvent = namedtuple('LogEvent', ['timestamp', 'source', 'event']) + +def main(): + """Main function. Parses args, reads the log files and renders them as text or html.""" + + parser = argparse.ArgumentParser(usage='%(prog)s [options] <test temporary directory>', description=__doc__) + parser.add_argument('-c', '--color', dest='color', action='store_true', help='outputs the combined log with events colored by source (requires posix terminal colors. Use less -r for viewing)') + parser.add_argument('--html', dest='html', action='store_true', help='outputs the combined log as html. Requires jinja2. pip install jinja2') + args, unknown_args = parser.parse_known_args() + + if args.color and os.name != 'posix': + print("Color output requires posix terminal colors.") + sys.exit(1) + + if args.html and args.color: + print("Only one out of --color or --html should be specified") + sys.exit(1) + + # There should only be one unknown argument - the path of the temporary test directory + if len(unknown_args) != 1: + print("Unexpected arguments" + str(unknown_args)) + sys.exit(1) + + log_events = read_logs(unknown_args[0]) + + print_logs(log_events, color=args.color, html=args.html) + +def read_logs(tmp_dir): + """Reads log files. + + Delegates to generator function get_log_events() to provide individual log events + for each of the input log files.""" + + files = [("test", "%s/test_framework.log" % tmp_dir)] + for i in itertools.count(): + logfile = "{}/node{}/regtest/debug.log".format(tmp_dir, i) + if not os.path.isfile(logfile): + break + files.append(("node%d" % i, logfile)) + + return heapq.merge(*[get_log_events(source, f) for source, f in files]) + +def get_log_events(source, logfile): + """Generator function that returns individual log events. + + Log events may be split over multiple lines. We use the timestamp + regex match as the marker for a new log event.""" + try: + with open(logfile, 'r') as infile: + event = '' + timestamp = '' + for line in infile: + # skip blank lines + if line == '\n': + continue + # if this line has a timestamp, it's the start of a new log event. + time_match = TIMESTAMP_PATTERN.match(line) + if time_match: + if event: + yield LogEvent(timestamp=timestamp, source=source, event=event.rstrip()) + event = line + timestamp = time_match.group() + # if it doesn't have a timestamp, it's a continuation line of the previous log. + else: + event += "\n" + line + # Flush the final event + yield LogEvent(timestamp=timestamp, source=source, event=event.rstrip()) + except FileNotFoundError: + print("File %s could not be opened. Continuing without it." % logfile, file=sys.stderr) + +def print_logs(log_events, color=False, html=False): + """Renders the iterator of log events into text or html.""" + if not html: + colors = defaultdict(lambda: '') + if color: + colors["test"] = "\033[0;36m" # CYAN + colors["node0"] = "\033[0;34m" # BLUE + colors["node1"] = "\033[0;32m" # GREEN + colors["node2"] = "\033[0;31m" # RED + colors["node3"] = "\033[0;33m" # YELLOW + colors["reset"] = "\033[0m" # Reset font color + + for event in log_events: + print("{0} {1: <5} {2} {3}".format(colors[event.source.rstrip()], event.source, event.event, colors["reset"])) + + else: + 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/combined_log_template.html b/test/functional/combined_log_template.html new file mode 100644 index 0000000000..c0b854b080 --- /dev/null +++ b/test/functional/combined_log_template.html @@ -0,0 +1,40 @@ +<html lang="en"> +<head> + <title> {{ title }} </title> + <style> + ul { + list-style-type: none; + font-family: monospace; + } + li { + border: 1px solid slategray; + margin-bottom: 1px; + } + li:hover { + filter: brightness(85%); + } + li.log-test { + background-color: cyan; + } + li.log-node0 { + background-color: lightblue; + } + li.log-node1 { + background-color: lightgreen; + } + li.log-node2 { + background-color: lightsalmon; + } + li.log-node3 { + background-color: lightyellow; + } + </style> +</head> +<body> +<ul> +{% for event in log_events %} +<li class="log-{{ event.source }}"> {{ event.source }} {{ event.timestamp }} {{event.event}}</li> +{% endfor %} +</ul> +</body> +</html> diff --git a/test/functional/fundrawtransaction.py b/test/functional/fundrawtransaction.py index fd330ef277..b86ea2d877 100755 --- a/test/functional/fundrawtransaction.py +++ b/test/functional/fundrawtransaction.py @@ -322,8 +322,8 @@ class RawTransactionsTest(BitcoinTestFramework): #compare fee of a standard pubkeyhash transaction inputs = [] outputs = {self.nodes[1].getnewaddress():1.1} - rawTx = self.nodes[0].createrawtransaction(inputs, outputs) - fundedTx = self.nodes[0].fundrawtransaction(rawTx) + rawtx = self.nodes[0].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[0].fundrawtransaction(rawtx) #create same transaction over sendtoaddress txId = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1.1) @@ -338,8 +338,8 @@ class RawTransactionsTest(BitcoinTestFramework): #compare fee of a standard pubkeyhash transaction with multiple outputs inputs = [] outputs = {self.nodes[1].getnewaddress():1.1,self.nodes[1].getnewaddress():1.2,self.nodes[1].getnewaddress():0.1,self.nodes[1].getnewaddress():1.3,self.nodes[1].getnewaddress():0.2,self.nodes[1].getnewaddress():0.3} - rawTx = self.nodes[0].createrawtransaction(inputs, outputs) - fundedTx = self.nodes[0].fundrawtransaction(rawTx) + rawtx = self.nodes[0].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[0].fundrawtransaction(rawtx) #create same transaction over sendtoaddress txId = self.nodes[0].sendmany("", outputs) signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] @@ -364,8 +364,8 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [] outputs = {mSigObj:1.1} - rawTx = self.nodes[0].createrawtransaction(inputs, outputs) - fundedTx = self.nodes[0].fundrawtransaction(rawTx) + rawtx = self.nodes[0].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[0].fundrawtransaction(rawtx) #create same transaction over sendtoaddress txId = self.nodes[0].sendtoaddress(mSigObj, 1.1) @@ -397,8 +397,8 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [] outputs = {mSigObj:1.1} - rawTx = self.nodes[0].createrawtransaction(inputs, outputs) - fundedTx = self.nodes[0].fundrawtransaction(rawTx) + rawtx = self.nodes[0].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[0].fundrawtransaction(rawtx) #create same transaction over sendtoaddress txId = self.nodes[0].sendtoaddress(mSigObj, 1.1) @@ -432,8 +432,8 @@ class RawTransactionsTest(BitcoinTestFramework): oldBalance = self.nodes[1].getbalance() inputs = [] outputs = {self.nodes[1].getnewaddress():1.1} - rawTx = self.nodes[2].createrawtransaction(inputs, outputs) - fundedTx = self.nodes[2].fundrawtransaction(rawTx) + rawtx = self.nodes[2].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[2].fundrawtransaction(rawtx) signedTx = self.nodes[2].signrawtransaction(fundedTx['hex']) txId = self.nodes[2].sendrawtransaction(signedTx['hex']) @@ -467,15 +467,17 @@ class RawTransactionsTest(BitcoinTestFramework): # drain the keypool self.nodes[1].getnewaddress() + self.nodes[1].getrawchangeaddress() inputs = [] outputs = {self.nodes[0].getnewaddress():1.1} - rawTx = self.nodes[1].createrawtransaction(inputs, outputs) + rawtx = self.nodes[1].createrawtransaction(inputs, outputs) # fund a transaction that requires a new key for the change output # creating the key must be impossible because the wallet is locked - assert_raises_jsonrpc(-4, "Insufficient funds", self.nodes[1].fundrawtransaction, rawtx) + assert_raises_jsonrpc(-4, "Keypool ran out, please call keypoolrefill first", self.nodes[1].fundrawtransaction, rawtx) #refill the keypool self.nodes[1].walletpassphrase("test", 100) + self.nodes[1].keypoolrefill(8) #need to refill the keypool to get an internal change address self.nodes[1].walletlock() assert_raises_jsonrpc(-13, "walletpassphrase", self.nodes[1].sendtoaddress, self.nodes[0].getnewaddress(), 1.2) @@ -484,8 +486,8 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [] outputs = {self.nodes[0].getnewaddress():1.1} - rawTx = self.nodes[1].createrawtransaction(inputs, outputs) - fundedTx = self.nodes[1].fundrawtransaction(rawTx) + rawtx = self.nodes[1].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[1].fundrawtransaction(rawtx) #now we need to unlock self.nodes[1].walletpassphrase("test", 600) @@ -516,8 +518,8 @@ class RawTransactionsTest(BitcoinTestFramework): #fund a tx with ~20 small inputs inputs = [] outputs = {self.nodes[0].getnewaddress():0.15,self.nodes[0].getnewaddress():0.04} - rawTx = self.nodes[1].createrawtransaction(inputs, outputs) - fundedTx = self.nodes[1].fundrawtransaction(rawTx) + rawtx = self.nodes[1].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[1].fundrawtransaction(rawtx) #create same transaction over sendtoaddress txId = self.nodes[1].sendmany("", outputs) @@ -548,8 +550,8 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [] outputs = {self.nodes[0].getnewaddress():0.15,self.nodes[0].getnewaddress():0.04} - rawTx = self.nodes[1].createrawtransaction(inputs, outputs) - fundedTx = self.nodes[1].fundrawtransaction(rawTx) + rawtx = self.nodes[1].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[1].fundrawtransaction(rawtx) fundedAndSignedTx = self.nodes[1].signrawtransaction(fundedTx['hex']) txId = self.nodes[1].sendrawtransaction(fundedAndSignedTx['hex']) self.sync_all() @@ -644,7 +646,7 @@ class RawTransactionsTest(BitcoinTestFramework): if out['value'] > 1.0: changeaddress += out['scriptPubKey']['addresses'][0] assert(changeaddress != "") - nextaddr = self.nodes[3].getnewaddress() + nextaddr = self.nodes[3].getrawchangeaddress() # frt should not have removed the key from the keypool assert(changeaddress == nextaddr) @@ -691,7 +693,6 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [] outputs = {self.nodes[2].getnewaddress(): value for value in (1.0, 1.1, 1.2, 1.3)} - keys = list(outputs.keys()) rawtx = self.nodes[3].createrawtransaction(inputs, outputs) result = [self.nodes[3].fundrawtransaction(rawtx), diff --git a/test/functional/import-rescan.py b/test/functional/import-rescan.py index 0218a46168..5be095e62d 100755 --- a/test/functional/import-rescan.py +++ b/test/functional/import-rescan.py @@ -22,7 +22,6 @@ happened previously. from test_framework.authproxy import JSONRPCException from test_framework.test_framework import BitcoinTestFramework from test_framework.util import (start_nodes, connect_nodes, sync_blocks, assert_equal, set_node_times) -from decimal import Decimal import collections import enum diff --git a/test/functional/importmulti.py b/test/functional/importmulti.py index aa03c6780a..e049302632 100755 --- a/test/functional/importmulti.py +++ b/test/functional/importmulti.py @@ -434,7 +434,7 @@ class ImportMultiTest (BitcoinTestFramework): address_assert = self.nodes[1].validateaddress(watchonly_address) assert_equal(address_assert['iswatchonly'], True) assert_equal(address_assert['ismine'], False) - assert_equal(address_assert['timestamp'], watchonly_timestamp); + assert_equal(address_assert['timestamp'], watchonly_timestamp) # Bad or missing timestamps self.log.info("Should throw on invalid or missing timestamp values") diff --git a/test/functional/keypool.py b/test/functional/keypool.py index cee58563f0..cb9ab688d1 100755 --- a/test/functional/keypool.py +++ b/test/functional/keypool.py @@ -27,28 +27,42 @@ class KeyPoolTest(BitcoinTestFramework): wallet_info = nodes[0].getwalletinfo() assert(addr_before_encrypting_data['hdmasterkeyid'] != wallet_info['hdmasterkeyid']) assert(addr_data['hdmasterkeyid'] == wallet_info['hdmasterkeyid']) - assert_raises_jsonrpc(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress) - # put three new keys in the keypool + # put six (plus 2) new keys in the keypool (100% external-, +100% internal-keys, 1 in min) nodes[0].walletpassphrase('test', 12000) - nodes[0].keypoolrefill(3) + nodes[0].keypoolrefill(6) nodes[0].walletlock() + wi = nodes[0].getwalletinfo() + assert_equal(wi['keypoolsize_hd_internal'], 6) + assert_equal(wi['keypoolsize'], 6) - # drain the keys + # drain the internal keys + nodes[0].getrawchangeaddress() + nodes[0].getrawchangeaddress() + nodes[0].getrawchangeaddress() + nodes[0].getrawchangeaddress() + nodes[0].getrawchangeaddress() + nodes[0].getrawchangeaddress() addr = set() - addr.add(nodes[0].getrawchangeaddress()) - addr.add(nodes[0].getrawchangeaddress()) - addr.add(nodes[0].getrawchangeaddress()) - addr.add(nodes[0].getrawchangeaddress()) - # assert that four unique addresses were returned - assert(len(addr) == 4) # the next one should fail assert_raises_jsonrpc(-12, "Keypool ran out", nodes[0].getrawchangeaddress) + # drain the external keys + addr.add(nodes[0].getnewaddress()) + addr.add(nodes[0].getnewaddress()) + addr.add(nodes[0].getnewaddress()) + addr.add(nodes[0].getnewaddress()) + addr.add(nodes[0].getnewaddress()) + addr.add(nodes[0].getnewaddress()) + assert(len(addr) == 6) + # the next one should fail + assert_raises_jsonrpc(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress) + # refill keypool with three new addresses nodes[0].walletpassphrase('test', 1) nodes[0].keypoolrefill(3) + # test walletpassphrase timeout time.sleep(1.1) assert_equal(nodes[0].getwalletinfo()["unlocked_until"], 0) @@ -57,9 +71,14 @@ class KeyPoolTest(BitcoinTestFramework): nodes[0].generate(1) nodes[0].generate(1) nodes[0].generate(1) - nodes[0].generate(1) assert_raises_jsonrpc(-12, "Keypool ran out", nodes[0].generate, 1) + nodes[0].walletpassphrase('test', 100) + nodes[0].keypoolrefill(100) + wi = nodes[0].getwalletinfo() + assert_equal(wi['keypoolsize_hd_internal'], 100) + assert_equal(wi['keypoolsize'], 100) + def __init__(self): super().__init__() self.setup_clean_chain = False diff --git a/test/functional/maxblocksinflight.py b/test/functional/maxblocksinflight.py index 2c3766125a..4ef2a35a44 100755 --- a/test/functional/maxblocksinflight.py +++ b/test/functional/maxblocksinflight.py @@ -17,7 +17,6 @@ from test_framework.util import * MAX_REQUESTS = 128 class TestManager(NodeConnCB): - # set up NodeConnCB callbacks, overriding base class def on_getdata(self, conn, message): self.log.debug("got getdata %s" % repr(message)) # Log the requests @@ -30,11 +29,8 @@ class TestManager(NodeConnCB): if not self.disconnectOkay: raise EarlyDisconnectError(0) - def __init__(self): - NodeConnCB.__init__(self) - def add_new_connection(self, connection): - self.connection = connection + super().add_connection(connection) self.blockReqCounts = {} self.disconnectOkay = False diff --git a/test/functional/maxuploadtarget.py b/test/functional/maxuploadtarget.py index 40cd85c9ec..9b42bf276c 100755 --- a/test/functional/maxuploadtarget.py +++ b/test/functional/maxuploadtarget.py @@ -20,7 +20,7 @@ import time # p2p messages to a node, generating the messages in the main testing logic. class TestNode(NodeConnCB): def __init__(self): - NodeConnCB.__init__(self) + super().__init__() self.connection = None self.ping_counter = 1 self.last_pong = msg_pong() @@ -68,15 +68,6 @@ class TestNode(NodeConnCB): def on_close(self, conn): self.peer_disconnected = True - # Sync up with the node after delivery of a block - def sync_with_ping(self, timeout=30): - def received_pong(): - return (self.last_pong.nonce == self.ping_counter) - self.connection.send_message(msg_ping(nonce=self.ping_counter)) - success = wait_until(received_pong, timeout=timeout) - self.ping_counter += 1 - return success - class MaxUploadTest(BitcoinTestFramework): def __init__(self): diff --git a/test/functional/multi_rpc.py b/test/functional/multi_rpc.py index 3b74bf1c46..a9701c548b 100755 --- a/test/functional/multi_rpc.py +++ b/test/functional/multi_rpc.py @@ -41,11 +41,9 @@ class HTTPBasicsTest (BitcoinTestFramework): authpair = url.username + ':' + url.password #New authpair generated via share/rpcuser tool - rpcauth = "rpcauth=rt:93648e835a54c573682c2eb19f882535$7681e9c5b74bdd85e78166031d2058e1069b3ed7ed967c93fc63abba06f31144" password = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM=" #Second authpair with different username - rpcauth2 = "rpcauth=rt2:f8607b1a88861fac29dfccf9b52ff9f$ff36a0c23c8c62b4846112e50fa888416e94c17bfd4c42f88fd8f55ec6a3137e" password2 = "8/F3uMDw4KSEbw96U3CA1C4X05dkHDN2BPFjTgZW4KI=" authpairnew = "rt:"+password diff --git a/test/functional/p2p-acceptblock.py b/test/functional/p2p-acceptblock.py index e1111da4ae..c09945baa6 100755 --- a/test/functional/p2p-acceptblock.py +++ b/test/functional/p2p-acceptblock.py @@ -58,7 +58,7 @@ from test_framework.blocktools import create_block, create_coinbase # p2p messages to a node, generating the messages in the main testing logic. class TestNode(NodeConnCB): def __init__(self): - NodeConnCB.__init__(self) + super().__init__() self.connection = None self.ping_counter = 1 self.last_pong = msg_pong() @@ -88,21 +88,6 @@ class TestNode(NodeConnCB): def on_pong(self, conn, message): self.last_pong = message - # Sync up with the node after delivery of a block - def sync_with_ping(self, timeout=30): - self.connection.send_message(msg_ping(nonce=self.ping_counter)) - received_pong = False - sleep_time = 0.05 - while not received_pong and timeout > 0: - time.sleep(sleep_time) - timeout -= sleep_time - with mininode_lock: - if self.last_pong.nonce == self.ping_counter: - received_pong = True - self.ping_counter += 1 - return received_pong - - class AcceptBlockTest(BitcoinTestFramework): def add_options(self, parser): parser.add_option("--testbinary", dest="testbinary", diff --git a/test/functional/p2p-compactblocks.py b/test/functional/p2p-compactblocks.py index 1fc0312c34..bf8d113767 100755 --- a/test/functional/p2p-compactblocks.py +++ b/test/functional/p2p-compactblocks.py @@ -15,9 +15,9 @@ from test_framework.blocktools import create_block, create_coinbase, add_witness from test_framework.script import CScript, OP_TRUE # TestNode: A peer we use to send messages to bitcoind, and store responses. -class TestNode(SingleNodeConnCB): +class TestNode(NodeConnCB): def __init__(self): - SingleNodeConnCB.__init__(self) + super().__init__() self.last_sendcmpct = [] self.last_headers = None self.last_inv = None @@ -32,6 +32,13 @@ class TestNode(SingleNodeConnCB): # This is for synchronizing the p2p message traffic, # so we can eg wait until a particular block is announced. self.set_announced_blockhashes = set() + self.connected = False + + def on_open(self, conn): + self.connected = True + + def on_close(self, conn): + self.connected = False def on_sendcmpct(self, conn, message): self.last_sendcmpct.append(message) @@ -107,6 +114,18 @@ class TestNode(SingleNodeConnCB): return (block_hash in self.set_announced_blockhashes) return wait_until(received_hash, timeout=timeout) + def send_await_disconnect(self, message, timeout=30): + """Sends a message to the node and wait for disconnect. + + This is used when we want to send a message into the node that we expect + will get us disconnected, eg an invalid block.""" + self.send_message(message) + success = wait_until(lambda: not self.connected, timeout=timeout) + if not success: + logger.error("send_await_disconnect failed!") + raise AssertionError("send_await_disconnect failed!") + return success + class CompactBlocksTest(BitcoinTestFramework): def __init__(self): super().__init__() @@ -274,8 +293,8 @@ class CompactBlocksTest(BitcoinTestFramework): # This index will be too high prefilled_txn = PrefilledTransaction(1, block.vtx[0]) cmpct_block.prefilled_txn = [prefilled_txn] - self.test_node.send_and_ping(msg_cmpctblock(cmpct_block)) - assert(int(self.nodes[0].getbestblockhash(), 16) == block.hashPrevBlock) + self.test_node.send_await_disconnect(msg_cmpctblock(cmpct_block)) + assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.hashPrevBlock) # Compare the generated shortids to what we expect based on BIP 152, given # bitcoind's choice of nonce. diff --git a/test/functional/p2p-feefilter.py b/test/functional/p2p-feefilter.py index d8f07700d0..12539be950 100755 --- a/test/functional/p2p-feefilter.py +++ b/test/functional/p2p-feefilter.py @@ -24,9 +24,9 @@ def allInvsMatch(invsExpected, testnode): # TestNode: bare-bones "peer". Used to track which invs are received from a node # and to send the node feefilter messages. -class TestNode(SingleNodeConnCB): +class TestNode(NodeConnCB): def __init__(self): - SingleNodeConnCB.__init__(self) + super().__init__() self.txinvs = [] def on_inv(self, conn, message): diff --git a/test/functional/p2p-leaktests.py b/test/functional/p2p-leaktests.py index 3a843197fb..5853ec86f0 100755 --- a/test/functional/p2p-leaktests.py +++ b/test/functional/p2p-leaktests.py @@ -19,10 +19,10 @@ banscore = 10 class CLazyNode(NodeConnCB): def __init__(self): + super().__init__() self.connection = None self.unexpected_msg = False self.connected = False - super().__init__() def add_connection(self, conn): self.connection = conn diff --git a/test/functional/p2p-mempool.py b/test/functional/p2p-mempool.py index 0aa9c90e8f..5064ce74aa 100755 --- a/test/functional/p2p-mempool.py +++ b/test/functional/p2p-mempool.py @@ -14,7 +14,7 @@ from test_framework.util import * class TestNode(NodeConnCB): def __init__(self): - NodeConnCB.__init__(self) + super().__init__() self.connection = None self.ping_counter = 1 self.last_pong = msg_pong() @@ -62,15 +62,6 @@ class TestNode(NodeConnCB): def on_close(self, conn): self.peer_disconnected = True - # Sync up with the node after delivery of a block - def sync_with_ping(self, timeout=30): - def received_pong(): - return (self.last_pong.nonce == self.ping_counter) - self.connection.send_message(msg_ping(nonce=self.ping_counter)) - success = wait_until(received_pong, timeout=timeout) - self.ping_counter += 1 - return success - def send_mempool(self): self.lastInv = [] self.send_message(msg_mempool()) diff --git a/test/functional/p2p-segwit.py b/test/functional/p2p-segwit.py index dcf2b9a7de..cd7b788eb4 100755 --- a/test/functional/p2p-segwit.py +++ b/test/functional/p2p-segwit.py @@ -32,10 +32,9 @@ def get_virtual_size(witness_block): vsize = int((3*base_size + total_size + 3)/4) return vsize -# Note: we can reduce code by using SingleNodeConnCB (in master, not 0.12) class TestNode(NodeConnCB): def __init__(self): - NodeConnCB.__init__(self) + super().__init__() self.connection = None self.ping_counter = 1 self.last_pong = msg_pong(0) @@ -81,13 +80,6 @@ class TestNode(NodeConnCB): timeout -= self.sleep_time raise AssertionError("Sync failed to complete") - def sync_with_ping(self, timeout=60): - self.send_message(msg_ping(nonce=self.ping_counter)) - test_function = lambda: self.last_pong.nonce == self.ping_counter - self.sync(test_function, timeout) - self.ping_counter += 1 - return - def wait_for_block(self, blockhash, timeout=60): test_function = lambda: self.last_block != None and self.last_block.sha256 == blockhash self.sync(test_function, timeout) @@ -149,7 +141,7 @@ class TestNode(NodeConnCB): if with_witness: tx_message = msg_witness_tx(tx) self.send_message(tx_message) - self.sync_with_ping() + self.sync_with_ping(60) assert_equal(tx.hash in self.connection.rpc.getrawmempool(), accepted) if (reason != None and not accepted): # Check the rejection reason as well. @@ -162,7 +154,7 @@ class TestNode(NodeConnCB): self.send_message(msg_witness_block(block)) else: self.send_message(msg_block(block)) - self.sync_with_ping() + self.sync_with_ping(60) assert_equal(self.connection.rpc.getbestblockhash() == block.hash, accepted) @@ -236,7 +228,7 @@ class SegWitTest(BitcoinTestFramework): block = self.build_next_block(nVersion=1) block.solve() self.test_node.send_message(msg_block(block)) - self.test_node.sync_with_ping() # make sure the block was processed + self.test_node.sync_with_ping(60) # make sure the block was processed txid = block.vtx[0].sha256 self.nodes[0].generate(99) # let the block mature @@ -252,7 +244,7 @@ class SegWitTest(BitcoinTestFramework): assert_equal(msg_tx(tx).serialize(), msg_witness_tx(tx).serialize()) self.test_node.send_message(msg_witness_tx(tx)) - self.test_node.sync_with_ping() # make sure the tx was processed + self.test_node.sync_with_ping(60) # make sure the tx was processed assert(tx.hash in self.nodes[0].getrawmempool()) # Save this transaction for later self.utxo.append(UTXO(tx.sha256, 0, 49*100000000)) @@ -292,7 +284,7 @@ class SegWitTest(BitcoinTestFramework): # But it should not be permanently marked bad... # Resend without witness information. self.test_node.send_message(msg_block(block)) - self.test_node.sync_with_ping() + self.test_node.sync_with_ping(60) assert_equal(self.nodes[0].getbestblockhash(), block.hash) sync_blocks(self.nodes) @@ -1258,7 +1250,7 @@ class SegWitTest(BitcoinTestFramework): # Spending a higher version witness output is not allowed by policy, # even with fRequireStandard=false. self.test_node.test_transaction_acceptance(tx3, with_witness=True, accepted=False) - self.test_node.sync_with_ping() + self.test_node.sync_with_ping(60) with mininode_lock: assert(b"reserved for soft-fork upgrades" in self.test_node.last_reject.reason) @@ -1388,7 +1380,7 @@ class SegWitTest(BitcoinTestFramework): for i in range(NUM_TESTS): # Ping regularly to keep the connection alive if (not i % 100): - self.test_node.sync_with_ping() + self.test_node.sync_with_ping(60) # Choose random number of inputs to use. num_inputs = random.randint(1, 10) # Create a slight bias for producing more utxos diff --git a/test/functional/p2p-timeouts.py b/test/functional/p2p-timeouts.py index 498acb23fe..de4edd6800 100755 --- a/test/functional/p2p-timeouts.py +++ b/test/functional/p2p-timeouts.py @@ -27,9 +27,9 @@ from test_framework.mininode import * from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -class TestNode(SingleNodeConnCB): +class TestNode(NodeConnCB): def __init__(self): - SingleNodeConnCB.__init__(self) + super().__init__() self.connected = False self.received_version = False diff --git a/test/functional/p2p-versionbits-warning.py b/test/functional/p2p-versionbits-warning.py index dc714e9a4a..da960e2d80 100755 --- a/test/functional/p2p-versionbits-warning.py +++ b/test/functional/p2p-versionbits-warning.py @@ -28,7 +28,7 @@ VB_PATTERN = re.compile("^Warning.*versionbit") # p2p messages to a node, generating the messages in the main testing logic. class TestNode(NodeConnCB): def __init__(self): - NodeConnCB.__init__(self) + super().__init__() self.connection = None self.ping_counter = 1 self.last_pong = msg_pong() @@ -46,21 +46,6 @@ class TestNode(NodeConnCB): def on_pong(self, conn, message): self.last_pong = message - # Sync up with the node after delivery of a block - def sync_with_ping(self, timeout=30): - self.connection.send_message(msg_ping(nonce=self.ping_counter)) - received_pong = False - sleep_time = 0.05 - while not received_pong and timeout > 0: - time.sleep(sleep_time) - timeout -= sleep_time - with mininode_lock: - if self.last_pong.nonce == self.ping_counter: - received_pong = True - self.ping_counter += 1 - return received_pong - - class VersionBitsWarningTest(BitcoinTestFramework): def __init__(self): super().__init__() diff --git a/test/functional/rawtransactions.py b/test/functional/rawtransactions.py index 0374d8984a..d4c684136c 100755 --- a/test/functional/rawtransactions.py +++ b/test/functional/rawtransactions.py @@ -104,7 +104,6 @@ class RawTransactionsTest(BitcoinTestFramework): txId = self.nodes[0].sendtoaddress(mSigObj, 2.2) decTx = self.nodes[0].gettransaction(txId) rawTx = self.nodes[0].decoderawtransaction(decTx['hex']) - sPK = rawTx['vout'][0]['scriptPubKey']['hex'] self.sync_all() self.nodes[0].generate(1) self.sync_all() diff --git a/test/functional/rpcbind_test.py b/test/functional/rpcbind_test.py index 8720a345ce..efc36481d1 100755 --- a/test/functional/rpcbind_test.py +++ b/test/functional/rpcbind_test.py @@ -4,6 +4,9 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test running bitcoind with the -rpcbind and -rpcallowip options.""" +import socket +import sys + from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * from test_framework.netutil import * @@ -52,7 +55,9 @@ class RPCBindTest(BitcoinTestFramework): def run_test(self): # due to OS-specific network stats queries, this test works only on Linux - assert(sys.platform.startswith('linux')) + if not sys.platform.startswith('linux'): + self.log.warning("This test can only be run on linux. Skipping test.") + sys.exit(self.TEST_EXIT_SKIPPED) # find the first non-loopback interface for testing non_loopback_ip = None for name,ip in all_interfaces(): @@ -60,7 +65,16 @@ class RPCBindTest(BitcoinTestFramework): non_loopback_ip = ip break if non_loopback_ip is None: - assert(not 'This test requires at least one non-loopback IPv4 interface') + self.log.warning("This test requires at least one non-loopback IPv4 interface. Skipping test.") + sys.exit(self.TEST_EXIT_SKIPPED) + try: + s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + s.connect(("::1",1)) + s.close + except OSError: + self.log.warning("This test requires IPv6 support. Skipping test.") + sys.exit(self.TEST_EXIT_SKIPPED) + self.log.info("Using interface %s for testing" % non_loopback_ip) defaultport = rpc_port(0) diff --git a/test/functional/segwit.py b/test/functional/segwit.py index 5b1fba8eec..a1fffcb81a 100755 --- a/test/functional/segwit.py +++ b/test/functional/segwit.py @@ -6,11 +6,10 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -from test_framework.mininode import sha256, ripemd160, CTransaction, CTxIn, COutPoint, CTxOut, COIN +from test_framework.mininode import sha256, CTransaction, CTxIn, COutPoint, CTxOut, COIN, ToHex, FromHex from test_framework.address import script_to_p2sh, key_to_p2pkh -from test_framework.script import CScript, OP_HASH160, OP_CHECKSIG, OP_0, hash160, OP_EQUAL, OP_DUP, OP_EQUALVERIFY, OP_1, OP_2, OP_CHECKMULTISIG, hash160, OP_TRUE +from test_framework.script import CScript, OP_HASH160, OP_CHECKSIG, OP_0, hash160, OP_EQUAL, OP_DUP, OP_EQUALVERIFY, OP_1, OP_2, OP_CHECKMULTISIG, OP_TRUE from io import BytesIO -from test_framework.mininode import ToHex, FromHex, COIN NODE_0 = 0 NODE_1 = 1 diff --git a/test/functional/sendheaders.py b/test/functional/sendheaders.py index de7f5e0849..1a7475ae84 100755 --- a/test/functional/sendheaders.py +++ b/test/functional/sendheaders.py @@ -81,9 +81,9 @@ from test_framework.blocktools import create_block, create_coinbase direct_fetch_response_time = 0.05 -class BaseNode(SingleNodeConnCB): +class BaseNode(NodeConnCB): def __init__(self): - SingleNodeConnCB.__init__(self) + super().__init__() self.last_inv = None self.last_headers = None self.last_block = None diff --git a/test/functional/smartfees.py b/test/functional/smartfees.py index 49f2df5c37..b7f4b86e7b 100755 --- a/test/functional/smartfees.py +++ b/test/functional/smartfees.py @@ -4,11 +4,10 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test fee estimation code.""" -from collections import OrderedDict from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * from test_framework.script import CScript, OP_1, OP_DROP, OP_2, OP_HASH160, OP_EQUAL, hash160, OP_TRUE -from test_framework.mininode import CTransaction, CTxIn, CTxOut, COutPoint, ToHex, FromHex, COIN +from test_framework.mininode import CTransaction, CTxIn, CTxOut, COutPoint, ToHex, COIN # Construct 2 trivial P2SH's and the ScriptSigs that spend them # So we can create many many transactions without needing to spend diff --git a/test/functional/test_framework/comptool.py b/test/functional/test_framework/comptool.py index 70d1d700ef..25c18bda82 100755 --- a/test/functional/test_framework/comptool.py +++ b/test/functional/test_framework/comptool.py @@ -42,7 +42,7 @@ class RejectResult(object): class TestNode(NodeConnCB): def __init__(self, block_store, tx_store): - NodeConnCB.__init__(self) + super().__init__() self.conn = None self.bestblockhash = None self.block_store = block_store diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index aace17a043..ebb846a237 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -1476,25 +1476,9 @@ class NodeConnCB(object): self.deliver_sleep_time = None # Remember the services our peer has advertised self.peer_services = None - - def set_deliver_sleep_time(self, value): - with mininode_lock: - self.deliver_sleep_time = value - - def get_deliver_sleep_time(self): - with mininode_lock: - return self.deliver_sleep_time - - # Spin until verack message is received from the node. - # Tests may want to use this as a signal that the test can begin. - # This can be called from the testing thread, so it needs to acquire the - # global lock. - def wait_for_verack(self): - while True: - with mininode_lock: - if self.verack_received: - return - time.sleep(0.05) + self.connection = None + self.ping_counter = 1 + self.last_pong = msg_pong() def deliver(self, conn, message): deliver_sleep = self.get_deliver_sleep_time() @@ -1506,17 +1490,36 @@ class NodeConnCB(object): except: logger.exception("ERROR delivering %s" % repr(message)) - def on_version(self, conn, message): - if message.nVersion >= 209: - conn.send_message(msg_verack()) - conn.ver_send = min(MY_VERSION, message.nVersion) - if message.nVersion < 209: - conn.ver_recv = conn.ver_send - conn.nServices = message.nServices + def set_deliver_sleep_time(self, value): + with mininode_lock: + self.deliver_sleep_time = value - def on_verack(self, conn, message): - conn.ver_recv = conn.ver_send - self.verack_received = True + def get_deliver_sleep_time(self): + with mininode_lock: + return self.deliver_sleep_time + + # Callbacks which can be overridden by subclasses + ################################################# + + def on_addr(self, conn, message): pass + def on_alert(self, conn, message): pass + def on_block(self, conn, message): pass + def on_blocktxn(self, conn, message): pass + def on_close(self, conn): pass + def on_cmpctblock(self, conn, message): pass + def on_feefilter(self, conn, message): pass + def on_getaddr(self, conn, message): pass + def on_getblocks(self, conn, message): pass + def on_getblocktxn(self, conn, message): pass + def on_getdata(self, conn, message): pass + def on_getheaders(self, conn, message): pass + def on_headers(self, conn, message): pass + def on_mempool(self, conn): pass + def on_open(self, conn): pass + def on_reject(self, conn, message): pass + def on_sendcmpct(self, conn, message): pass + def on_sendheaders(self, conn, message): pass + def on_tx(self, conn, message): pass def on_inv(self, conn, message): want = msg_getdata() @@ -1526,37 +1529,27 @@ class NodeConnCB(object): if len(want.inv): conn.send_message(want) - def on_addr(self, conn, message): pass - def on_alert(self, conn, message): pass - def on_getdata(self, conn, message): pass - def on_getblocks(self, conn, message): pass - def on_tx(self, conn, message): pass - def on_block(self, conn, message): pass - def on_getaddr(self, conn, message): pass - def on_headers(self, conn, message): pass - def on_getheaders(self, conn, message): pass def on_ping(self, conn, message): if conn.ver_send > BIP0031_VERSION: conn.send_message(msg_pong(message.nonce)) - def on_reject(self, conn, message): pass - def on_open(self, conn): pass - def on_close(self, conn): pass - def on_mempool(self, conn): pass - def on_pong(self, conn, message): pass - def on_feefilter(self, conn, message): pass - def on_sendheaders(self, conn, message): pass - def on_sendcmpct(self, conn, message): pass - def on_cmpctblock(self, conn, message): pass - def on_getblocktxn(self, conn, message): pass - def on_blocktxn(self, conn, message): pass -# More useful callbacks and functions for NodeConnCB's which have a single NodeConn -class SingleNodeConnCB(NodeConnCB): - def __init__(self): - NodeConnCB.__init__(self) - self.connection = None - self.ping_counter = 1 - self.last_pong = msg_pong() + def on_pong(self, conn, message): + self.last_pong = message + + def on_verack(self, conn, message): + conn.ver_recv = conn.ver_send + self.verack_received = True + + def on_version(self, conn, message): + if message.nVersion >= 209: + conn.send_message(msg_verack()) + conn.ver_send = min(MY_VERSION, message.nVersion) + if message.nVersion < 209: + conn.ver_recv = conn.ver_send + conn.nServices = message.nServices + + # Helper functions + ################## def add_connection(self, conn): self.connection = conn @@ -1569,18 +1562,30 @@ class SingleNodeConnCB(NodeConnCB): self.send_message(message) self.sync_with_ping() - def on_pong(self, conn, message): - self.last_pong = message - # Sync up with the node - def sync_with_ping(self, timeout=30): + def sync_with_ping(self, timeout=60): def received_pong(): return (self.last_pong.nonce == self.ping_counter) self.send_message(msg_ping(nonce=self.ping_counter)) success = wait_until(received_pong, timeout=timeout) + if not success: + logger.error("sync_with_ping failed!") + raise AssertionError("sync_with_ping failed!") self.ping_counter += 1 + return success + # Spin until verack message is received from the node. + # Tests may want to use this as a signal that the test can begin. + # This can be called from the testing thread, so it needs to acquire the + # global lock. + def wait_for_verack(self): + while True: + with mininode_lock: + if self.verack_received: + return + time.sleep(0.05) + # The actual NodeConn class # This class provides an interface for a p2p connection to a specified node class NodeConn(asyncore.dispatcher): diff --git a/test/functional/test_framework/socks5.py b/test/functional/test_framework/socks5.py index dd7624d454..a08b03ed24 100644 --- a/test/functional/test_framework/socks5.py +++ b/test/functional/test_framework/socks5.py @@ -5,7 +5,6 @@ """Dummy Socks5 server for testing.""" import socket, threading, queue -import traceback, sys import logging logger = logging.getLogger("TestFramework.socks5") diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index d7072fa78d..473b7c14a9 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -4,13 +4,14 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Base class for RPC testing.""" +from collections import deque import logging import optparse import os import sys import shutil import tempfile -import traceback +import time from .util import ( initialize_chain, @@ -27,9 +28,12 @@ from .util import ( ) from .authproxy import JSONRPCException - class BitcoinTestFramework(object): + TEST_EXIT_PASSED = 0 + TEST_EXIT_FAILED = 1 + TEST_EXIT_SKIPPED = 77 + def __init__(self): self.num_nodes = 4 self.setup_clean_chain = False @@ -174,19 +178,24 @@ class BitcoinTestFramework(object): # Dump the end of the debug logs, to aid in debugging rare # travis failures. import glob - filenames = glob.glob(self.options.tmpdir + "/node*/regtest/debug.log") + filenames = [self.options.tmpdir + "/test_framework.log"] + filenames += glob.glob(self.options.tmpdir + "/node*/regtest/debug.log") MAX_LINES_TO_PRINT = 1000 - for f in filenames: - print("From" , f, ":") - from collections import deque - print("".join(deque(open(f), MAX_LINES_TO_PRINT))) + for fn in filenames: + try: + with open(fn, 'r') as f: + print("From" , fn, ":") + print("".join(deque(f, MAX_LINES_TO_PRINT))) + except OSError: + print("Opening file %s failed." % fn) + traceback.print_exc() if success: self.log.info("Tests successful") - sys.exit(0) + sys.exit(self.TEST_EXIT_PASSED) else: self.log.error("Test failed. Test logging available at %s/test_framework.log", self.options.tmpdir) logging.shutdown() - sys.exit(1) + sys.exit(self.TEST_EXIT_FAILED) def _start_logging(self): # Add logger and logging handlers @@ -202,6 +211,7 @@ class BitcoinTestFramework(object): ch.setLevel(ll) # Format logs the same as bitcoind's debug.log with microprecision (so log files can be concatenated and sorted) formatter = logging.Formatter(fmt = '%(asctime)s.%(msecs)03d000 %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%d %H:%M:%S') + formatter.converter = time.gmtime fh.setFormatter(formatter) ch.setFormatter(formatter) # add the handlers to the logger diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 12eb92028f..37a2ab62a4 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -23,6 +23,10 @@ import sys import subprocess import tempfile import re +import logging + +TEST_EXIT_PASSED = 0 +TEST_EXIT_SKIPPED = 77 BASE_SCRIPTS= [ # Scripts that are run by the travis build process. @@ -87,7 +91,7 @@ BASE_SCRIPTS= [ ZMQ_SCRIPTS = [ # ZMQ test can only be run if bitcoin was built with zmq-enabled. # call test_runner.py with -nozmq to explicitly exclude these tests. - "zmq_test.py"] + 'zmq_test.py'] EXTENDED_SCRIPTS = [ # These tests are not run by the travis build process. @@ -107,6 +111,7 @@ EXTENDED_SCRIPTS = [ 'p2p-feefilter.py', 'rpcbind_test.py', # vv Tests less than 30s vv + 'assumevalid.py', 'bip65-cltv.py', 'bip65-cltv-p2p.py', 'bipdersig-p2p.py', @@ -123,6 +128,13 @@ EXTENDED_SCRIPTS = [ ALL_SCRIPTS = BASE_SCRIPTS + ZMQ_SCRIPTS + EXTENDED_SCRIPTS +NON_SCRIPTS = [ + # These are python files that live in the functional tests directory, but are not test scripts. + "combine_logs.py", + "create_cache.py", + "test_runner.py", +] + def main(): # Parse arguments and pass through unrecognised args parser = argparse.ArgumentParser(add_help=False, @@ -137,6 +149,7 @@ def main(): parser.add_argument('--force', '-f', action='store_true', help='run tests even on platforms where they are disabled by default (e.g. windows).') parser.add_argument('--help', '-h', '-?', action='store_true', help='print help text and exit') parser.add_argument('--jobs', '-j', type=int, default=4, help='how many test scripts to run in parallel. Default=4.') + parser.add_argument('--quiet', '-q', action='store_true', help='only print results summary and failure logs') parser.add_argument('--nozmq', action='store_true', help='do not run the zmq tests') args, unknown_args = parser.parse_known_args() @@ -148,6 +161,10 @@ def main(): config = configparser.ConfigParser() config.read_file(open(os.path.dirname(__file__) + "/config.ini")) + # Set up logging + logging_level = logging.INFO if args.quiet else logging.DEBUG + logging.basicConfig(format='%(message)s', level=logging_level) + enable_wallet = config["components"].getboolean("ENABLE_WALLET") enable_utils = config["components"].getboolean("ENABLE_UTILS") enable_bitcoind = config["components"].getboolean("ENABLE_BITCOIND") @@ -203,11 +220,13 @@ def main(): sys.exit(0) if args.help: - # Print help for test_runner.py, then print help of the first script and exit. + # Print help for test_runner.py, then print help of the first script (with args removed) and exit. parser.print_help() - subprocess.check_call((config["environment"]["SRCDIR"] + '/test/functional/' + test_list[0]).split() + ['-h']) + subprocess.check_call([(config["environment"]["SRCDIR"] + '/test/functional/' + test_list[0].split()[0])] + ['-h']) sys.exit(0) + check_script_list(config["environment"]["SRCDIR"]) + run_tests(test_list, config["environment"]["SRCDIR"], config["environment"]["BUILDDIR"], config["environment"]["EXEEXT"], args.jobs, args.coverage, passon_args) def run_tests(test_list, src_dir, build_dir, exeext, jobs=1, enable_coverage=False, args=[]): @@ -229,7 +248,7 @@ def run_tests(test_list, src_dir, build_dir, exeext, jobs=1, enable_coverage=Fal if enable_coverage: coverage = RPCCoverage() flags.append(coverage.flag) - print("Initializing coverage directory at %s\n" % coverage.dir) + logging.debug("Initializing coverage directory at %s" % coverage.dir) else: coverage = None @@ -245,27 +264,31 @@ def run_tests(test_list, src_dir, build_dir, exeext, jobs=1, enable_coverage=Fal job_queue = TestHandler(jobs, tests_dir, test_list, flags) max_len_name = len(max(test_list, key=len)) - results = BOLD[1] + "%s | %s | %s\n\n" % ("TEST".ljust(max_len_name), "PASSED", "DURATION") + BOLD[0] + results = "\n" + BOLD[1] + "%s | %s | %s\n\n" % ("TEST".ljust(max_len_name), "STATUS ", "DURATION") + BOLD[0] for _ in range(len(test_list)): - (name, stdout, stderr, passed, duration) = job_queue.get_next() - all_passed = all_passed and passed + (name, stdout, stderr, status, duration) = job_queue.get_next() + all_passed = all_passed and status != "Failed" time_sum += duration - print('\n' + BOLD[1] + name + BOLD[0] + ":") - print('' if passed else stdout + '\n', end='') - print('' if stderr == '' else 'stderr:\n' + stderr + '\n', end='') - print("Pass: %s%s%s, Duration: %s s\n" % (BOLD[1], passed, BOLD[0], duration)) + if status == "Passed": + logging.debug("\n%s%s%s passed, Duration: %s s" % (BOLD[1], name, BOLD[0], duration)) + elif status == "Skipped": + logging.debug("\n%s%s%s skipped" % (BOLD[1], name, BOLD[0])) + else: + print("\n%s%s%s failed, Duration: %s s\n" % (BOLD[1], name, BOLD[0], duration)) + print(BOLD[1] + 'stdout:\n' + BOLD[0] + stdout + '\n') + print(BOLD[1] + 'stderr:\n' + BOLD[0] + stderr + '\n') - results += "%s | %s | %s s\n" % (name.ljust(max_len_name), str(passed).ljust(6), duration) + results += "%s | %s | %s s\n" % (name.ljust(max_len_name), status.ljust(7), duration) - results += BOLD[1] + "\n%s | %s | %s s (accumulated)" % ("ALL".ljust(max_len_name), str(all_passed).ljust(6), time_sum) + BOLD[0] + results += BOLD[1] + "\n%s | %s | %s s (accumulated)" % ("ALL".ljust(max_len_name), str(all_passed).ljust(7), time_sum) + BOLD[0] print(results) print("\nRuntime: %s s" % (int(time.time() - time0))) if coverage: coverage.report_rpc_coverage() - print("Cleaning up coverage data") + logging.debug("Cleaning up coverage data") coverage.cleanup() sys.exit(not all_passed) @@ -296,9 +319,10 @@ class TestHandler: port_seed = ["--portseed={}".format(len(self.test_list) + self.portseed_offset)] log_stdout = tempfile.SpooledTemporaryFile(max_size=2**16) log_stderr = tempfile.SpooledTemporaryFile(max_size=2**16) + test_argv = t.split() self.jobs.append((t, time.time(), - subprocess.Popen((self.tests_dir + t).split() + self.flags + port_seed, + subprocess.Popen([self.tests_dir + test_argv[0]] + test_argv[1:] + self.flags + port_seed, universal_newlines=True, stdout=log_stdout, stderr=log_stderr), @@ -315,12 +339,29 @@ class TestHandler: log_out.seek(0), log_err.seek(0) [stdout, stderr] = [l.read().decode('utf-8') for l in (log_out, log_err)] log_out.close(), log_err.close() - passed = stderr == "" and proc.returncode == 0 + if proc.returncode == TEST_EXIT_PASSED and stderr == "": + status = "Passed" + elif proc.returncode == TEST_EXIT_SKIPPED: + status = "Skipped" + else: + status = "Failed" self.num_running -= 1 self.jobs.remove(j) - return name, stdout, stderr, passed, int(time.time() - time0) + return name, stdout, stderr, status, int(time.time() - time0) print('.', end='', flush=True) +def check_script_list(src_dir): + """Check scripts directory. + + Check that there are no scripts in the functional tests directory which are + not being run by pull-tester.py.""" + script_dir = src_dir + '/test/functional/' + python_files = set([t for t in os.listdir(script_dir) if t[-3:] == ".py"]) + missed_tests = list(python_files - set(map(lambda x: x.split()[0], ALL_SCRIPTS + NON_SCRIPTS))) + if len(missed_tests) != 0: + print("The following scripts are not being run:" + str(missed_tests)) + print("Check the test lists in test_runner.py") + sys.exit(1) class RPCCoverage(object): """ diff --git a/test/functional/wallet-dump.py b/test/functional/wallet-dump.py index b819b72b75..8876f935a4 100755 --- a/test/functional/wallet-dump.py +++ b/test/functional/wallet-dump.py @@ -88,7 +88,7 @@ class WalletDumpTest(BitcoinTestFramework): read_dump(tmpdir + "/node0/wallet.unencrypted.dump", addrs, None) assert_equal(found_addr, test_addr_count) # all keys must be in the dump assert_equal(found_addr_chg, 50) # 50 blocks where mined - assert_equal(found_addr_rsv, 90 + 1) # keypool size (TODO: fix off-by-one) + assert_equal(found_addr_rsv, 90*2) # 90 keys plus 100% internal keys #encrypt wallet, restart, unlock and dump self.nodes[0].encryptwallet('test') @@ -102,8 +102,8 @@ class WalletDumpTest(BitcoinTestFramework): found_addr, found_addr_chg, found_addr_rsv, hd_master_addr_enc = \ read_dump(tmpdir + "/node0/wallet.encrypted.dump", addrs, hd_master_addr_unenc) assert_equal(found_addr, test_addr_count) - assert_equal(found_addr_chg, 90 + 1 + 50) # old reserve keys are marked as change now - assert_equal(found_addr_rsv, 90 + 1) # keypool size (TODO: fix off-by-one) + assert_equal(found_addr_chg, 90*2 + 50) # old reserve keys are marked as change now + assert_equal(found_addr_rsv, 90*2) if __name__ == '__main__': WalletDumpTest().main () diff --git a/test/functional/wallet-hd.py b/test/functional/wallet-hd.py index c40662dc3d..64a6c92782 100755 --- a/test/functional/wallet-hd.py +++ b/test/functional/wallet-hd.py @@ -42,6 +42,11 @@ class WalletHDTest(BitcoinTestFramework): masterkeyid = self.nodes[1].getwalletinfo()['hdmasterkeyid'] assert_equal(len(masterkeyid), 40) + # create an internal key + change_addr = self.nodes[1].getrawchangeaddress() + change_addrV= self.nodes[1].validateaddress(change_addr); + assert_equal(change_addrV["hdkeypath"], "m/0'/1'/0'") #first internal child key + # Import a non-HD private key in the HD wallet non_hd_add = self.nodes[0].getnewaddress() self.nodes[1].importprivkey(self.nodes[0].dumpprivkey(non_hd_add)) @@ -65,6 +70,11 @@ class WalletHDTest(BitcoinTestFramework): self.nodes[0].sendtoaddress(non_hd_add, 1) self.nodes[0].generate(1) + # create an internal key (again) + change_addr = self.nodes[1].getrawchangeaddress() + change_addrV= self.nodes[1].validateaddress(change_addr); + assert_equal(change_addrV["hdkeypath"], "m/0'/1'/1'") #second internal child key + self.sync_all() assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1) @@ -90,6 +100,15 @@ class WalletHDTest(BitcoinTestFramework): #connect_nodes_bi(self.nodes, 0, 1) assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1) + # send a tx and make sure its using the internal chain for the changeoutput + txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1) + outs = self.nodes[1].decoderawtransaction(self.nodes[1].gettransaction(txid)['hex'])['vout']; + keypath = "" + for out in outs: + if out['value'] != 1: + keypath = self.nodes[1].validateaddress(out['scriptPubKey']['addresses'][0])['hdkeypath'] + + assert_equal(keypath[0:7], "m/0'/1'") if __name__ == '__main__': WalletHDTest().main () diff --git a/test/functional/zmq_test.py b/test/functional/zmq_test.py index e6f18b0b93..9e27b46381 100755 --- a/test/functional/zmq_test.py +++ b/test/functional/zmq_test.py @@ -41,7 +41,6 @@ class ZMQTest (BitcoinTestFramework): topic = msg[0] assert_equal(topic, b"hashtx") body = msg[1] - nseq = msg[2] msgSequence = struct.unpack('<I', msg[-1])[-1] assert_equal(msgSequence, 0) #must be sequence 0 on hashtx diff --git a/test/util/bctest.py b/test/util/bctest.py index dfe3a123d1..b17cf77ae3 100644 --- a/test/util/bctest.py +++ b/test/util/bctest.py @@ -102,6 +102,18 @@ def bctest(testDir, testObj, buildenv): logging.error("Return code mismatch for " + outputFn) raise Exception + if "error_txt" in testObj: + want_error = testObj["error_txt"] + # Compare error text + # TODO: ideally, we'd compare the strings exactly and also assert + # That stderr is empty if no errors are expected. However, bitcoin-tx + # emits DISPLAY errors when running as a windows application on + # linux through wine. Just assert that the expected error text appears + # somewhere in stderr. + if want_error not in outs[1]: + logging.error("Error mismatch:\n" + "Expected: " + want_error + "\nReceived: " + outs[1].rstrip()) + raise Exception + def bctester(testDir, input_basename, buildenv): """ Loads and parses the input file, runs all tests and reports results""" input_filename = testDir + "/" + input_basename diff --git a/test/util/data/bitcoin-util-test.json b/test/util/data/bitcoin-util-test.json index a80ab51901..b61a4f7f8f 100644 --- a/test/util/data/bitcoin-util-test.json +++ b/test/util/data/bitcoin-util-test.json @@ -42,6 +42,7 @@ "args": ["-", "delin=31"], "input": "tx394b54bb.hex", "return_code": 1, + "error_txt": "error: Invalid TX input index '31'", "description": "Attempts to delete an input with a bad index from a transaction. Expected to fail." }, { "exec": "./bitcoin-tx", @@ -60,6 +61,7 @@ "args": ["-", "delout=2"], "input": "tx394b54bb.hex", "return_code": 1, + "error_txt": "error: Invalid TX output index '2'", "description": "Attempts to delete an output with a bad index from a transaction. Expected to fail." }, { "exec": "./bitcoin-tx", @@ -77,6 +79,38 @@ { "exec": "./bitcoin-tx", "args": ["-create", + "outaddr=1"], + "return_code": 1, + "error_txt": "error: TX output missing or too many separators", + "description": "Malformed outaddr argument (no address specified). Expected to fail." + }, + { "exec": "./bitcoin-tx", + "args": + ["-create", + "outaddr=1:13tuJJDR2RgArmgfv6JScSdreahzgc4T6o:garbage"], + "return_code": 1, + "error_txt": "error: TX output missing or too many separators", + "description": "Malformed outaddr argument (too many separators). Expected to fail." + }, + { "exec": "./bitcoin-tx", + "args": + ["-create", + "outpubkey=0"], + "return_code": 1, + "error_txt": "error: TX output missing or too many separators", + "description": "Malformed outpubkey argument (no pubkey specified). Expected to fail." + }, + { "exec": "./bitcoin-tx", + "args": + ["-create", + "outpubkey=0:02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397:W:non53nse"], + "return_code": 1, + "error_txt": "error: TX output missing or too many separators", + "description": "Malformed outpubkey argument (too many separators). Expected to fail." + }, + { "exec": "./bitcoin-tx", + "args": + ["-create", "in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0", "in=bf829c6bcf84579331337659d31f89dfd138f7f7785802d5501c92333145ca7c:18", "in=22a6f904655d53ae2ff70e701a0bbd90aa3975c0f40bfc6cc996a9049e31cdfc:1", @@ -233,6 +267,7 @@ "in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0", "outdata=4:badhexdata"], "return_code": 1, + "error_txt": "error: invalid TX output data", "description": "Attempts to create a new transaction with one input and an output with malformed hex data. Expected to fail" }, { "exec": "./bitcoin-tx", @@ -241,6 +276,7 @@ "in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0", "outdata=badhexdata"], "return_code": 1, + "error_txt": "error: invalid TX output data", "description": "Attempts to create a new transaction with one input and an output with no value and malformed hex data. Expected to fail" }, { "exec": "./bitcoin-tx", |