aboutsummaryrefslogtreecommitdiff
path: root/qa
diff options
context:
space:
mode:
Diffstat (limited to 'qa')
-rwxr-xr-xqa/rpc-tests/assumevalid.py191
-rwxr-xr-xqa/rpc-tests/fundrawtransaction.py70
-rwxr-xr-xqa/rpc-tests/p2p-compactblocks.py26
-rwxr-xr-xqa/rpc-tests/preciousblock.py2
-rwxr-xr-xqa/rpc-tests/pruning.py145
-rw-r--r--qa/rpc-tests/test_framework/util.py10
6 files changed, 430 insertions, 14 deletions
diff --git a/qa/rpc-tests/assumevalid.py b/qa/rpc-tests/assumevalid.py
new file mode 100755
index 0000000000..e4bc22951b
--- /dev/null
+++ b/qa/rpc-tests/assumevalid.py
@@ -0,0 +1,191 @@
+#!/usr/bin/env python3
+# Copyright (c) 2014-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.
+'''
+assumevalid.py
+
+Test logic for skipping signature validation on blocks which we've assumed
+valid (https://github.com/bitcoin/bitcoin/pull/9484)
+
+We build a chain that includes and invalid signature for one of the
+transactions:
+
+ 0: genesis block
+ 1: block 1 with coinbase transaction output.
+ 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.
+ 103-2202: bury the bad block with just over two weeks' worth of blocks
+ (2100 blocks)
+
+Start three nodes:
+
+ - node0 has no -assumevalid parameter. Try to sync to block 2202. It will
+ reject block 102 and only sync as far as block 101
+ - node1 has -assumevalid set to the hash of block 102. Try to sync to
+ block 2202. node1 will sync all the way to block 2202.
+ - node2 has -assumevalid set to the hash of block 102. Try to sync to
+ block 200. node2 will reject block 102 since it's assumed valid, but it
+ isn't buried by at least two weeks' work.
+'''
+
+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.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
+
+ def send_header_for_blocks(self, new_blocks):
+ headers_message = msg_headers()
+ headers_message.headers = [ CBlockHeader(b) for b in new_blocks ]
+ self.send_message(headers_message)
+
+class SendHeadersTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.setup_clean_chain = True
+ self.num_nodes = 3
+
+ def setup_network(self):
+ # 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, ["-debug"]))
+
+ def run_test(self):
+
+ # Connect to node0
+ node0 = BaseNode()
+ connections = []
+ 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
+ node0.wait_for_verack()
+
+ # Build the blockchain
+ self.tip = int(self.nodes[0].getbestblockhash(), 16)
+ self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1
+
+ self.blocks = []
+
+ # Get a pubkey for the coinbase TXO
+ coinbase_key = CECKey()
+ coinbase_key.set_secretbytes(b"horsebattery")
+ coinbase_pubkey = coinbase_key.get_pubkey()
+
+ # Create the first block with a coinbase output to our key
+ height = 1
+ block = create_block(self.tip, create_coinbase(height, coinbase_pubkey), self.block_time)
+ self.blocks.append(block)
+ self.block_time += 1
+ block.solve()
+ # Save the coinbase for later
+ self.block1 = block
+ self.tip = block.sha256
+ height += 1
+
+ # Bury the block 100 deep so the coinbase output is spendable
+ for i in range(100):
+ block = create_block(self.tip, create_coinbase(height), self.block_time)
+ block.solve()
+ self.blocks.append(block)
+ self.tip = block.sha256
+ self.block_time += 1
+ height += 1
+
+ # 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.calc_sha256()
+
+ block102 = create_block(self.tip, create_coinbase(height), self.block_time)
+ self.block_time += 1
+ block102.vtx.extend([tx])
+ block102.hashMerkleRoot = block102.calc_merkle_root()
+ block102.rehash()
+ block102.solve()
+ self.blocks.append(block102)
+ self.tip = block102.sha256
+ self.block_time += 1
+ height += 1
+
+ # Bury the assumed valid block 2100 deep
+ for i in range(2100):
+ block = create_block(self.tip, create_coinbase(height), self.block_time)
+ block.nVersion = 4
+ block.solve()
+ self.blocks.append(block)
+ self.tip = block.sha256
+ self.block_time += 1
+ height += 1
+
+ # Start node1 and node2 with assumevalid so they accept a block with a bad signature.
+ self.nodes.append(start_node(1, self.options.tmpdir,
+ ["-debug", "-assumevalid=" + hex(block102.sha256)]))
+ node1 = BaseNode() # connects to node1
+ connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], node1))
+ node1.add_connection(connections[1])
+ node1.wait_for_verack()
+
+ self.nodes.append(start_node(2, self.options.tmpdir,
+ ["-debug", "-assumevalid=" + hex(block102.sha256)]))
+ node2 = BaseNode() # connects to node2
+ connections.append(NodeConn('127.0.0.1', p2p_port(2), self.nodes[2], node2))
+ node2.add_connection(connections[2])
+ node2.wait_for_verack()
+
+ # send header lists to all three nodes
+ node0.send_header_for_blocks(self.blocks[0:2000])
+ node0.send_header_for_blocks(self.blocks[2000:])
+ node1.send_header_for_blocks(self.blocks[0:2000])
+ 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 3102 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
+ 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)
+
+if __name__ == '__main__':
+ SendHeadersTest().main()
diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py
index 82e148c55a..b97e9aecdf 100755
--- a/qa/rpc-tests/fundrawtransaction.py
+++ b/qa/rpc-tests/fundrawtransaction.py
@@ -660,5 +660,75 @@ class RawTransactionsTest(BitcoinTestFramework):
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)
+ ######################################
+ # Test subtractFeeFromOutputs option #
+ ######################################
+
+ # Make sure there is exactly one input so coin selection can't skew the result
+ assert_equal(len(self.nodes[3].listunspent(1)), 1)
+
+ inputs = []
+ 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]})]
+
+ 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)]
+ change = [d['vout'][r['changepos']]['value'] for d, r in zip(dec_tx, result)]
+
+ assert_equal(result[0]['fee'], result[1]['fee'], result[2]['fee'])
+ assert_equal(result[3]['fee'], result[4]['fee'])
+ assert_equal(change[0], change[1])
+ assert_equal(output[0], output[1])
+ assert_equal(output[0], output[2] + result[2]['fee'])
+ assert_equal(change[0] + result[0]['fee'], change[2])
+ assert_equal(output[3], output[4] + result[4]['fee'])
+ assert_equal(change[3] + result[3]['fee'], change[4])
+
+ 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),
+ # split the fee between outputs 0, 2, and 3, but not output 1
+ self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": [0, 2, 3]})]
+
+ dec_tx = [self.nodes[3].decoderawtransaction(result[0]['hex']),
+ self.nodes[3].decoderawtransaction(result[1]['hex'])]
+
+ # Nested list of non-change output amounts for each transaction
+ output = [[out['value'] for i, out in enumerate(d['vout']) if i != r['changepos']]
+ for d, r in zip(dec_tx, result)]
+
+ # List of differences in output amounts between normal and subtractFee transactions
+ share = [o0 - o1 for o0, o1 in zip(output[0], output[1])]
+
+ # output 1 is the same in both transactions
+ assert_equal(share[1], 0)
+
+ # the other 3 outputs are smaller as a result of subtractFeeFromOutputs
+ assert_greater_than(share[0], 0)
+ assert_greater_than(share[2], 0)
+ assert_greater_than(share[3], 0)
+
+ # outputs 2 and 3 take the same share of the fee
+ assert_equal(share[2], share[3])
+
+ # output 0 takes at least as much share of the fee, and no more than 2 satoshis more, than outputs 2 and 3
+ assert_greater_than_or_equal(share[0], share[2])
+ assert_greater_than_or_equal(share[2] + Decimal(2e-8), share[0])
+
+ # the fee is the same in both transactions
+ assert_equal(result[0]['fee'], result[1]['fee'])
+
+ # the total subtracted from the outputs is equal to the fee
+ assert_equal(share[0] + share[2] + share[3], result[0]['fee'])
+
if __name__ == '__main__':
RawTransactionsTest().main()
diff --git a/qa/rpc-tests/p2p-compactblocks.py b/qa/rpc-tests/p2p-compactblocks.py
index fc1f16c6d2..9151ecf5de 100755
--- a/qa/rpc-tests/p2p-compactblocks.py
+++ b/qa/rpc-tests/p2p-compactblocks.py
@@ -310,6 +310,9 @@ class CompactBlocksTest(BitcoinTestFramework):
tip = int(node.getbestblockhash(), 16)
assert(test_node.wait_for_block_announcement(tip))
+ # Make sure we will receive a fast-announce compact block
+ self.request_cb_announcements(test_node, node, version)
+
# Now mine a block, and look at the resulting compact block.
test_node.clear_block_announcement()
block_hash = int(node.generate(1)[0], 16)
@@ -319,27 +322,36 @@ class CompactBlocksTest(BitcoinTestFramework):
[tx.calc_sha256() for tx in block.vtx]
block.rehash()
- # Don't care which type of announcement came back for this test; just
- # request the compact block if we didn't get one yet.
+ # Wait until the block was announced (via compact blocks)
wait_until(test_node.received_block_announcement, timeout=30)
assert(test_node.received_block_announcement())
+ # Now fetch and check the compact block
+ header_and_shortids = None
+ with mininode_lock:
+ assert(test_node.last_cmpctblock is not None)
+ # Convert the on-the-wire representation to absolute indexes
+ header_and_shortids = HeaderAndShortIDs(test_node.last_cmpctblock.header_and_shortids)
+ self.check_compactblock_construction_from_block(version, header_and_shortids, block_hash, block)
+
+ # Now fetch the compact block using a normal non-announce getdata
with mininode_lock:
- if test_node.last_cmpctblock is None:
- test_node.clear_block_announcement()
- inv = CInv(4, block_hash) # 4 == "CompactBlock"
- test_node.send_message(msg_getdata([inv]))
+ test_node.clear_block_announcement()
+ inv = CInv(4, block_hash) # 4 == "CompactBlock"
+ test_node.send_message(msg_getdata([inv]))
wait_until(test_node.received_block_announcement, timeout=30)
assert(test_node.received_block_announcement())
- # Now we should have the compactblock
+ # Now fetch and check the compact block
header_and_shortids = None
with mininode_lock:
assert(test_node.last_cmpctblock is not None)
# Convert the on-the-wire representation to absolute indexes
header_and_shortids = HeaderAndShortIDs(test_node.last_cmpctblock.header_and_shortids)
+ self.check_compactblock_construction_from_block(version, header_and_shortids, block_hash, block)
+ def check_compactblock_construction_from_block(self, version, header_and_shortids, block_hash, block):
# Check that we got the right block!
header_and_shortids.header.calc_sha256()
assert_equal(header_and_shortids.header.sha256, block_hash)
diff --git a/qa/rpc-tests/preciousblock.py b/qa/rpc-tests/preciousblock.py
index f43160e19a..a806294648 100755
--- a/qa/rpc-tests/preciousblock.py
+++ b/qa/rpc-tests/preciousblock.py
@@ -102,7 +102,7 @@ class PreciousTest(BitcoinTestFramework):
assert_equal(self.nodes[2].getblockcount(), 6)
hashL = self.nodes[2].getbestblockhash()
print("Connect nodes and check no reorg occurs")
- node_sync_via_rpc(self.nodes[0:3])
+ node_sync_via_rpc(self.nodes[1:3])
connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2)
assert_equal(self.nodes[0].getbestblockhash(), hashH)
diff --git a/qa/rpc-tests/pruning.py b/qa/rpc-tests/pruning.py
index 78b8938e4a..05e72e6078 100755
--- a/qa/rpc-tests/pruning.py
+++ b/qa/rpc-tests/pruning.py
@@ -16,6 +16,8 @@ from test_framework.util import *
import time
import os
+MIN_BLOCKS_TO_KEEP = 288
+
def calc_usage(blockdir):
return sum(os.path.getsize(blockdir+f) for f in os.listdir(blockdir) if os.path.isfile(blockdir+f)) / (1024. * 1024.)
@@ -25,7 +27,7 @@ class PruneTest(BitcoinTestFramework):
def __init__(self):
super().__init__()
self.setup_clean_chain = True
- self.num_nodes = 3
+ self.num_nodes = 6
# Cache for utxos, as the listunspent may take a long time later in the test
self.utxo_cache_0 = []
@@ -43,10 +45,22 @@ class PruneTest(BitcoinTestFramework):
self.nodes.append(start_node(2, self.options.tmpdir, ["-debug","-maxreceivebuffer=20000","-prune=550"], timewait=900))
self.prunedir = self.options.tmpdir+"/node2/regtest/blocks/"
+ # Create nodes 3 and 4 to test manual pruning (they will be re-started with manual pruning later)
+ self.nodes.append(start_node(3, self.options.tmpdir, ["-debug=0","-maxreceivebuffer=20000","-blockmaxsize=999000"], timewait=900))
+ self.nodes.append(start_node(4, self.options.tmpdir, ["-debug=0","-maxreceivebuffer=20000","-blockmaxsize=999000"], timewait=900))
+
+ # Create nodes 5 to test wallet in prune mode, but do not connect
+ self.nodes.append(start_node(5, self.options.tmpdir, ["-debug=0", "-prune=550"]))
+
+ # Determine default relay fee
+ self.relayfee = self.nodes[0].getnetworkinfo()["relayfee"]
+
connect_nodes(self.nodes[0], 1)
connect_nodes(self.nodes[1], 2)
connect_nodes(self.nodes[2], 0)
- sync_blocks(self.nodes[0:3])
+ connect_nodes(self.nodes[0], 3)
+ connect_nodes(self.nodes[0], 4)
+ sync_blocks(self.nodes[0:5])
def create_big_chain(self):
# Start by creating some coinbases we can spend later
@@ -57,7 +71,7 @@ class PruneTest(BitcoinTestFramework):
for i in range(645):
mine_large_block(self.nodes[0], self.utxo_cache_0)
- sync_blocks(self.nodes[0:3])
+ sync_blocks(self.nodes[0:5])
def test_height_min(self):
if not os.path.isfile(self.prunedir+"blk00000.dat"):
@@ -212,6 +226,118 @@ class PruneTest(BitcoinTestFramework):
# Verify we can now have the data for a block previously pruned
assert(self.nodes[2].getblock(self.forkhash)["height"] == self.forkheight)
+ def manual_test(self, node_number, use_timestamp):
+ # at this point, node has 995 blocks and has not yet run in prune mode
+ node = self.nodes[node_number] = start_node(node_number, self.options.tmpdir, ["-debug=0"], timewait=900)
+ assert_equal(node.getblockcount(), 995)
+ assert_raises_message(JSONRPCException, "not in prune mode", node.pruneblockchain, 500)
+ stop_node(node, node_number)
+
+ # now re-start in manual pruning mode
+ node = self.nodes[node_number] = start_node(node_number, self.options.tmpdir, ["-debug=0","-prune=1"], timewait=900)
+ assert_equal(node.getblockcount(), 995)
+
+ def height(index):
+ if use_timestamp:
+ return node.getblockheader(node.getblockhash(index))["time"]
+ else:
+ return index
+
+ def prune(index, expected_ret=None):
+ ret = node.pruneblockchain(height(index))
+ # Check the return value. When use_timestamp is True, just check
+ # that the return value is less than or equal to the expected
+ # value, because when more than one block is generated per second,
+ # a timestamp will not be granular enough to uniquely identify an
+ # individual block.
+ if expected_ret is None:
+ expected_ret = index
+ if use_timestamp:
+ assert_greater_than(ret, 0)
+ assert_greater_than(expected_ret + 1, ret)
+ else:
+ assert_equal(ret, expected_ret)
+
+ def has_block(index):
+ return os.path.isfile(self.options.tmpdir + "/node{}/regtest/blocks/blk{:05}.dat".format(node_number, index))
+
+ # should not prune because chain tip of node 3 (995) < PruneAfterHeight (1000)
+ assert_raises_message(JSONRPCException, "Blockchain is too short for pruning", node.pruneblockchain, height(500))
+
+ # mine 6 blocks so we are at height 1001 (i.e., above PruneAfterHeight)
+ node.generate(6)
+
+ # negative and zero inputs should raise an exception
+ try:
+ node.pruneblockchain(-10)
+ raise AssertionError("pruneblockchain(-10) should have failed.")
+ except:
+ pass
+
+ try:
+ node.pruneblockchain(0)
+ raise AssertionError("pruneblockchain(0) should have failed.")
+ except:
+ pass
+
+ # height=100 too low to prune first block file so this is a no-op
+ prune(100)
+ if not has_block(0):
+ raise AssertionError("blk00000.dat is missing when should still be there")
+
+ # height=500 should prune first file
+ prune(500)
+ if has_block(0):
+ raise AssertionError("blk00000.dat is still there, should be pruned by now")
+ if not has_block(1):
+ raise AssertionError("blk00001.dat is missing when should still be there")
+
+ # height=650 should prune second file
+ prune(650)
+ if has_block(1):
+ raise AssertionError("blk00001.dat is still there, should be pruned by now")
+
+ # height=1000 should not prune anything more, because tip-288 is in blk00002.dat.
+ prune(1000, 1001 - MIN_BLOCKS_TO_KEEP)
+ if not has_block(2):
+ raise AssertionError("blk00002.dat is still there, should be pruned by now")
+
+ # advance the tip so blk00002.dat and blk00003.dat can be pruned (the last 288 blocks should now be in blk00004.dat)
+ node.generate(288)
+ prune(1000)
+ if has_block(2):
+ raise AssertionError("blk00002.dat is still there, should be pruned by now")
+ if has_block(3):
+ raise AssertionError("blk00003.dat is still there, should be pruned by now")
+
+ # stop node, start back up with auto-prune at 550MB, make sure still runs
+ stop_node(node, node_number)
+ self.nodes[node_number] = start_node(node_number, self.options.tmpdir, ["-debug=0","-prune=550"], timewait=900)
+
+ print("Success")
+
+ def wallet_test(self):
+ # check that the pruning node's wallet is still in good shape
+ print("Stop and start pruning node to trigger wallet rescan")
+ try:
+ stop_node(self.nodes[2], 2)
+ start_node(2, self.options.tmpdir, ["-debug=1","-prune=550"])
+ print("Success")
+ except Exception as detail:
+ raise AssertionError("Wallet test: unable to re-start the pruning node")
+
+ # check that wallet loads loads successfully when restarting a pruned node after IBD.
+ # this was reported to fail in #7494.
+ print ("Syncing node 5 to test wallet")
+ connect_nodes(self.nodes[0], 5)
+ nds = [self.nodes[0], self.nodes[5]]
+ sync_blocks(nds)
+ try:
+ stop_node(self.nodes[5],5) #stop and start to trigger rescan
+ start_node(5, self.options.tmpdir, ["-debug=1","-prune=550"])
+ print ("Success")
+ except Exception as detail:
+ raise AssertionError("Wallet test: unable to re-start node5")
def run_test(self):
print("Warning! This test requires 4GB of disk space and takes over 30 mins (up to 2 hours)")
@@ -226,6 +352,10 @@ class PruneTest(BitcoinTestFramework):
# Start by mining a simple chain that all nodes have
# N0=N1=N2 **...*(995)
+ # stop manual-pruning node with 995 blocks
+ stop_node(self.nodes[3],3)
+ stop_node(self.nodes[4],4)
+
print("Check that we haven't started pruning yet because we're below PruneAfterHeight")
self.test_height_min()
# Extend this chain past the PruneAfterHeight
@@ -308,6 +438,15 @@ class PruneTest(BitcoinTestFramework):
#
# N1 doesn't change because 1033 on main chain (*) is invalid
+ print("Test manual pruning with block indices")
+ self.manual_test(3, use_timestamp=False)
+
+ print("Test manual pruning with timestamps")
+ self.manual_test(4, use_timestamp=True)
+
+ print("Test wallet re-scan")
+ self.wallet_test()
+
print("Done")
if __name__ == '__main__':
diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py
index c29033595d..aca82c8b6f 100644
--- a/qa/rpc-tests/test_framework/util.py
+++ b/qa/rpc-tests/test_framework/util.py
@@ -524,14 +524,18 @@ def assert_fee_amount(fee, tx_size, fee_per_kB):
if fee > (tx_size + 2) * fee_per_kB / 1000:
raise AssertionError("Fee of %s BTC too high! (Should be %s BTC)"%(str(fee), str(target_fee)))
-def assert_equal(thing1, thing2):
- if thing1 != thing2:
- raise AssertionError("%s != %s"%(str(thing1),str(thing2)))
+def assert_equal(thing1, thing2, *args):
+ if thing1 != thing2 or any(thing1 != arg for arg in args):
+ raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
def assert_greater_than(thing1, thing2):
if thing1 <= thing2:
raise AssertionError("%s <= %s"%(str(thing1),str(thing2)))
+def assert_greater_than_or_equal(thing1, thing2):
+ if thing1 < thing2:
+ raise AssertionError("%s < %s"%(str(thing1),str(thing2)))
+
def assert_raises(exc, fun, *args, **kwds):
assert_raises_message(exc, None, fun, *args, **kwds)