diff options
Diffstat (limited to 'qa')
-rwxr-xr-x | qa/pull-tester/rpc-tests.py | 2 | ||||
-rwxr-xr-x | qa/rpc-tests/assumevalid.py | 191 | ||||
-rwxr-xr-x | qa/rpc-tests/bumpfee.py | 326 | ||||
-rwxr-xr-x | qa/rpc-tests/fundrawtransaction.py | 100 | ||||
-rwxr-xr-x | qa/rpc-tests/importmulti.py | 14 | ||||
-rwxr-xr-x | qa/rpc-tests/listsinceblock.py | 80 | ||||
-rwxr-xr-x | qa/rpc-tests/nodehandling.py | 1 | ||||
-rwxr-xr-x | qa/rpc-tests/p2p-compactblocks.py | 26 | ||||
-rwxr-xr-x | qa/rpc-tests/p2p-feefilter.py | 4 | ||||
-rwxr-xr-x | qa/rpc-tests/p2p-mempool.py | 1 | ||||
-rwxr-xr-x | qa/rpc-tests/p2p-segwit.py | 2 | ||||
-rwxr-xr-x | qa/rpc-tests/preciousblock.py | 2 | ||||
-rwxr-xr-x | qa/rpc-tests/pruning.py | 147 | ||||
-rwxr-xr-x | qa/rpc-tests/segwit.py | 2 | ||||
-rwxr-xr-x | qa/rpc-tests/signrawtransactions.py | 34 | ||||
-rw-r--r-- | qa/rpc-tests/test_framework/util.py | 10 | ||||
-rwxr-xr-x | qa/rpc-tests/zmq_test.py | 3 |
17 files changed, 907 insertions, 38 deletions
diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index 83b6bdfe48..26bc6a73df 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -151,7 +151,9 @@ testScripts = [ 'signmessages.py', 'nulldummy.py', 'import-rescan.py', + 'bumpfee.py', 'rpcnamedargs.py', + 'listsinceblock.py', ] if ENABLE_ZMQ: testScripts.append('zmq_test.py') 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/bumpfee.py b/qa/rpc-tests/bumpfee.py new file mode 100755 index 0000000000..ac282796c1 --- /dev/null +++ b/qa/rpc-tests/bumpfee.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python3 +# 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. + +from segwit import send_to_witness +from test_framework.test_framework import BitcoinTestFramework +from test_framework import blocktools +from test_framework.mininode import CTransaction +from test_framework.util import * +from test_framework.util import * + +import io +import time + +# Sequence number that is BIP 125 opt-in and BIP 68-compliant +BIP125_SEQUENCE_NUMBER = 0xfffffffd + +WALLET_PASSPHRASE = "test" +WALLET_PASSPHRASE_TIMEOUT = 3600 + + +class BumpFeeTest(BitcoinTestFramework): + def __init__(self): + super().__init__() + self.num_nodes = 2 + self.setup_clean_chain = True + + def setup_network(self, split=False): + extra_args = [["-debug", "-prematurewitness", "-walletprematurewitness", "-walletrbf={}".format(i)] + for i in range(self.num_nodes)] + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args) + + # Encrypt wallet for test_locked_wallet_fails test + self.nodes[1].encryptwallet(WALLET_PASSPHRASE) + bitcoind_processes[1].wait() + self.nodes[1] = start_node(1, self.options.tmpdir, extra_args[1]) + self.nodes[1].walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT) + + connect_nodes_bi(self.nodes, 0, 1) + self.is_network_split = False + self.sync_all() + + def run_test(self): + peer_node, rbf_node = self.nodes + rbf_node_address = rbf_node.getnewaddress() + + # fund rbf node with 10 coins of 0.001 btc (100,000 satoshis) + print("Mining blocks...") + peer_node.generate(110) + self.sync_all() + for i in range(25): + peer_node.sendtoaddress(rbf_node_address, 0.001) + self.sync_all() + peer_node.generate(1) + self.sync_all() + assert_equal(rbf_node.getbalance(), Decimal("0.025")) + + print("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_settxfee(rbf_node, dest_address) + test_rebumping(rbf_node, dest_address) + test_rebumping_not_replaceable(rbf_node, dest_address) + test_unconfirmed_not_spendable(rbf_node, rbf_node_address) + test_bumpfee_metadata(rbf_node, dest_address) + test_locked_wallet_fails(rbf_node, dest_address) + print("Success") + + +def test_simple_bumpfee_succeeds(rbf_node, peer_node, dest_address): + rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000}) + rbftx = rbf_node.gettransaction(rbfid) + sync_mempools((rbf_node, peer_node)) + assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool() + bumped_tx = rbf_node.bumpfee(rbfid) + assert bumped_tx["fee"] - abs(rbftx["fee"]) > 0 + # check that bumped_tx propogates, original tx was evicted and has a wallet conflict + sync_mempools((rbf_node, peer_node)) + assert bumped_tx["txid"] in rbf_node.getrawmempool() + assert bumped_tx["txid"] in peer_node.getrawmempool() + assert rbfid not in rbf_node.getrawmempool() + assert rbfid not in peer_node.getrawmempool() + oldwtx = rbf_node.gettransaction(rbfid) + assert len(oldwtx["walletconflicts"]) > 0 + # check wallet transaction replaces and replaced_by values + bumpedwtx = rbf_node.gettransaction(bumped_tx["txid"]) + assert_equal(oldwtx["replaced_by_txid"], bumped_tx["txid"]) + assert_equal(bumpedwtx["replaces_txid"], rbfid) + + +def test_segwit_bumpfee_succeeds(rbf_node, dest_address): + # Create a transaction with segwit output, then create an RBF transaction + # which spends it, and make sure bumpfee can be called on it. + + segwit_in = next(u for u in rbf_node.listunspent() if u["amount"] == Decimal("0.001")) + segwit_out = rbf_node.validateaddress(rbf_node.getnewaddress()) + rbf_node.addwitnessaddress(segwit_out["address"]) + segwitid = send_to_witness( + version=0, + node=rbf_node, + utxo=segwit_in, + pubkey=segwit_out["pubkey"], + encode_p2sh=False, + amount=Decimal("0.0009"), + sign=True) + + rbfraw = rbf_node.createrawtransaction([{ + 'txid': segwitid, + 'vout': 0, + "sequence": BIP125_SEQUENCE_NUMBER + }], {dest_address: Decimal("0.0005"), + get_change_address(rbf_node): Decimal("0.0003")}) + rbfsigned = rbf_node.signrawtransaction(rbfraw) + rbfid = rbf_node.sendrawtransaction(rbfsigned["hex"]) + assert rbfid in rbf_node.getrawmempool() + + bumped_tx = rbf_node.bumpfee(rbfid) + assert bumped_tx["txid"] in rbf_node.getrawmempool() + assert rbfid not in rbf_node.getrawmempool() + + +def test_nonrbf_bumpfee_fails(peer_node, dest_address): + # cannot replace a non RBF transaction (from node which did not enable RBF) + not_rbfid = create_fund_sign_send(peer_node, {dest_address: 0.00090000}) + assert_raises_message(JSONRPCException, "not BIP 125 replaceable", peer_node.bumpfee, not_rbfid) + + +def test_notmine_bumpfee_fails(rbf_node, peer_node, dest_address): + # cannot bump fee unless the tx has only inputs that we own. + # here, the rbftx has a peer_node coin and then adds a rbf_node input + # Note that this test depends upon the RPC code checking input ownership prior to change outputs + # (since it can't use fundrawtransaction, it lacks a proper change output) + utxos = [node.listunspent()[-1] for node in (rbf_node, peer_node)] + inputs = [{ + "txid": utxo["txid"], + "vout": utxo["vout"], + "address": utxo["address"], + "sequence": BIP125_SEQUENCE_NUMBER + } for utxo in utxos] + output_val = sum(utxo["amount"] for utxo in utxos) - Decimal("0.001") + rawtx = rbf_node.createrawtransaction(inputs, {dest_address: output_val}) + signedtx = rbf_node.signrawtransaction(rawtx) + signedtx = peer_node.signrawtransaction(signedtx["hex"]) + rbfid = rbf_node.sendrawtransaction(signedtx["hex"]) + assert_raises_message(JSONRPCException, "Transaction contains inputs that don't belong to this wallet", + rbf_node.bumpfee, rbfid) + + +def test_bumpfee_with_descendant_fails(rbf_node, rbf_node_address, dest_address): + # cannot bump fee if the transaction has a descendant + # 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}) + tx = rbf_node.createrawtransaction([{"txid": parent_id, "vout": 0}], {dest_address: 0.00020000}) + tx = rbf_node.signrawtransaction(tx) + txid = rbf_node.sendrawtransaction(tx["hex"]) + assert_raises_message(JSONRPCException, "Transaction has descendants in the wallet", rbf_node.bumpfee, parent_id) + + +def test_small_output_fails(rbf_node, dest_address): + # cannot bump fee with a too-small output + rbfid = spend_one_input(rbf_node, + 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, + Decimal("0.00100000"), + {dest_address: 0.00080000, + get_change_address(rbf_node): Decimal("0.00010000")}) + assert_raises_message(JSONRPCException, "Change output is too small", rbf_node.bumpfee, rbfid, {"totalFee": 20001}) + + +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")}) + fulltx = rbf_node.getrawtransaction(rbfid, 1) + bumped_tx = rbf_node.bumpfee(rbfid, {"totalFee": 19900}) + full_bumped_tx = rbf_node.getrawtransaction(bumped_tx["txid"], 1) + assert_equal(bumped_tx["fee"], Decimal("0.00020000")) + 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")) + bumped_tx = rbf_node.bumpfee(rbfid) + assert bumped_tx["fee"] > 2 * abs(rbftx["fee"]) + 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_message(JSONRPCException, "already bumped", rbf_node.bumpfee, rbfid, {"totalFee": 2000}) + rbf_node.bumpfee(bumped["txid"], {"totalFee": 2000}) + + +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}) + bumped = rbf_node.bumpfee(rbfid, {"totalFee": 10000, "replaceable": False}) + assert_raises_message(JSONRPCException, "Transaction is not BIP 125 replaceable", rbf_node.bumpfee, bumped["txid"], + {"totalFee": 20000}) + + +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}) + rbftx = rbf_node.gettransaction(rbfid)["hex"] + assert rbfid in rbf_node.getrawmempool() + bumpid = rbf_node.bumpfee(rbfid)["txid"] + assert bumpid in rbf_node.getrawmempool() + assert rbfid not in rbf_node.getrawmempool() + + # check that outputs from the bump transaction are not spendable + # due to the replaces_txid check in CWallet::AvailableCoins + assert_equal([t for t in rbf_node.listunspent(minconf=0, include_unsafe=False) if t["txid"] == bumpid], []) + + # submit a block with the rbf tx to clear the bump tx out of the mempool, + # then call abandon to make sure the wallet doesn't attempt to resubmit the + # bump tx, then invalidate the block so the rbf tx will be put back in the + # mempool. this makes it possible to check whether the rbf tx outputs are + # spendable before the rbf tx is confirmed. + block = submit_block_with_tx(rbf_node, rbftx) + rbf_node.abandontransaction(bumpid) + rbf_node.invalidateblock(block.hash) + assert bumpid not in rbf_node.getrawmempool() + assert rbfid in rbf_node.getrawmempool() + + # check that outputs from the rbf tx are not spendable before the + # transaction is confirmed, due to the replaced_by_txid check in + # CWallet::AvailableCoins + assert_equal([t for t in rbf_node.listunspent(minconf=0, include_unsafe=False) if t["txid"] == rbfid], []) + + # check that the main output from the rbf tx is spendable after confirmed + rbf_node.generate(1) + assert_equal( + sum(1 for t in rbf_node.listunspent(minconf=0, include_unsafe=False) + if t["txid"] == rbfid and t["address"] == rbf_node_address and t["spendable"]), 1) + + +def test_bumpfee_metadata(rbf_node, dest_address): + rbfid = rbf_node.sendtoaddress(dest_address, 0.00090000, "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") + assert_equal(bumped_wtx["to"], "to value") + + +def test_locked_wallet_fails(rbf_node, dest_address): + rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000}) + rbf_node.walletlock() + assert_raises_message(JSONRPCException, "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) + 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))) + + tip = node.getbestblockhash() + height = node.getblockcount() + 1 + block_time = node.getblockheader(tip)["mediantime"] + 1 + block = blocktools.create_block(int(tip, 16), blocktools.create_coinbase(height), block_time) + block.vtx.append(ctx) + block.rehash() + block.hashMerkleRoot = block.calc_merkle_root() + block.solve() + error = node.submitblock(bytes_to_hex_str(block.serialize(True))) + if error is not None: + raise Exception(error) + return block + + +if __name__ == "__main__": + BumpFeeTest().main() diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py index 82e148c55a..7396ba46a4 100755 --- a/qa/rpc-tests/fundrawtransaction.py +++ b/qa/rpc-tests/fundrawtransaction.py @@ -226,7 +226,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert(False) rawtxfund = self.nodes[2].fundrawtransaction(rawtx, {'changeAddress': change, 'changePosition': 0}) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) - out = dec_tx['vout'][0]; + out = dec_tx['vout'][0] assert_equal(change, out['scriptPubKey']['addresses'][0]) @@ -651,7 +651,7 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(len(self.nodes[3].listunspent(1)), 1) inputs = [] - outputs = {self.nodes[2].getnewaddress() : 1} + outputs = {self.nodes[3].getnewaddress() : 1} rawtx = self.nodes[3].createrawtransaction(inputs, outputs) result = self.nodes[3].fundrawtransaction(rawtx) # uses min_relay_tx_fee (set by settxfee) result2 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2*min_relay_tx_fee}) @@ -660,5 +660,101 @@ 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 address reuse option # + ############################# + + result3 = self.nodes[3].fundrawtransaction(rawtx, {"reserveChangeKey": False}) + res_dec = self.nodes[0].decoderawtransaction(result3["hex"]) + changeaddress = "" + for out in res_dec['vout']: + if out['value'] > 1.0: + changeaddress += out['scriptPubKey']['addresses'][0] + assert(changeaddress != "") + nextaddr = self.nodes[3].getnewaddress() + # frt should not have removed the key from the keypool + assert(changeaddress == nextaddr) + + result3 = self.nodes[3].fundrawtransaction(rawtx) + res_dec = self.nodes[0].decoderawtransaction(result3["hex"]) + changeaddress = "" + for out in res_dec['vout']: + if out['value'] > 1.0: + changeaddress += out['scriptPubKey']['addresses'][0] + assert(changeaddress != "") + nextaddr = self.nodes[3].getnewaddress() + # Now the change address key should be removed from the keypool + assert(changeaddress != nextaddr) + + ###################################### + # 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/importmulti.py b/qa/rpc-tests/importmulti.py index 5c536f2f49..e100a3af9d 100755 --- a/qa/rpc-tests/importmulti.py +++ b/qa/rpc-tests/importmulti.py @@ -108,7 +108,7 @@ class ImportMultiTest (BitcoinTestFramework): "scriptPubKey": address['scriptPubKey'], "pubkeys": [ address['pubkey'] ], "internal": True - }]; + }] result = self.nodes[1].importmulti(request) assert_equal(result[0]['success'], True) address_assert = self.nodes[1].validateaddress(address['address']) @@ -121,7 +121,7 @@ class ImportMultiTest (BitcoinTestFramework): request = [{ "scriptPubKey": address['scriptPubKey'], "pubkeys": [ address['pubkey'] ] - }]; + }] result = self.nodes[1].importmulti(request) assert_equal(result[0]['success'], False) assert_equal(result[0]['error']['code'], -8) @@ -197,7 +197,7 @@ class ImportMultiTest (BitcoinTestFramework): self.nodes[1].generate(100) transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) self.nodes[1].generate(1) - transaction = self.nodes[1].gettransaction(transactionid); + transaction = self.nodes[1].gettransaction(transactionid) print("Should import a p2sh") result = self.nodes[1].importmulti([{ @@ -222,7 +222,7 @@ class ImportMultiTest (BitcoinTestFramework): self.nodes[1].generate(100) transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) self.nodes[1].generate(1) - transaction = self.nodes[1].gettransaction(transactionid); + transaction = self.nodes[1].gettransaction(transactionid) print("Should import a p2sh with respective redeem script") result = self.nodes[1].importmulti([{ @@ -246,7 +246,7 @@ class ImportMultiTest (BitcoinTestFramework): self.nodes[1].generate(100) transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) self.nodes[1].generate(1) - transaction = self.nodes[1].gettransaction(transactionid); + transaction = self.nodes[1].gettransaction(transactionid) print("Should import a p2sh with respective redeem script and private keys") result = self.nodes[1].importmulti([{ @@ -270,7 +270,7 @@ class ImportMultiTest (BitcoinTestFramework): self.nodes[1].generate(100) transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) self.nodes[1].generate(1) - transaction = self.nodes[1].gettransaction(transactionid); + transaction = self.nodes[1].gettransaction(transactionid) print("Should import a p2sh with respective redeem script and private keys") result = self.nodes[1].importmulti([{ @@ -312,7 +312,7 @@ class ImportMultiTest (BitcoinTestFramework): "scriptPubKey": address['scriptPubKey'], "pubkeys": [ address2['pubkey'] ], "internal": True - }]; + }] result = self.nodes[1].importmulti(request) assert_equal(result[0]['success'], False) assert_equal(result[0]['error']['code'], -5) diff --git a/qa/rpc-tests/listsinceblock.py b/qa/rpc-tests/listsinceblock.py new file mode 100755 index 0000000000..ca67b8eceb --- /dev/null +++ b/qa/rpc-tests/listsinceblock.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + +class ListSinceBlockTest (BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 4 + + def run_test (self): + ''' + `listsinceblock` did not behave correctly when handed a block that was + no longer in the main chain: + + ab0 + / \ + aa1 [tx0] bb1 + | | + aa2 bb2 + | | + aa3 bb3 + | + bb4 + + Consider a client that has only seen block `aa3` above. It asks the node + to `listsinceblock aa3`. But at some point prior the main chain switched + to the bb chain. + + Previously: listsinceblock would find height=4 for block aa3 and compare + this to height=5 for the tip of the chain (bb4). It would then return + results restricted to bb3-bb4. + + Now: listsinceblock finds the fork at ab0 and returns results in the + range bb1-bb4. + + This test only checks that [tx0] is present. + ''' + + assert_equal(self.is_network_split, False) + self.nodes[2].generate(101) + self.sync_all() + + assert_equal(self.nodes[0].getbalance(), 0) + assert_equal(self.nodes[1].getbalance(), 0) + assert_equal(self.nodes[2].getbalance(), 50) + assert_equal(self.nodes[3].getbalance(), 0) + + # Split network into two + self.split_network() + assert_equal(self.is_network_split, True) + + # send to nodes[0] from nodes[2] + senttx = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1) + + # generate on both sides + lastblockhash = self.nodes[1].generate(6)[5] + self.nodes[2].generate(7) + print('lastblockhash=%s' % (lastblockhash)) + + self.sync_all() + + self.join_network() + + # listsinceblock(lastblockhash) should now include tx, as seen from nodes[0] + lsbres = self.nodes[0].listsinceblock(lastblockhash) + found = False + for tx in lsbres['transactions']: + if tx['txid'] == senttx: + found = True + break + assert_equal(found, True) + +if __name__ == '__main__': + ListSinceBlockTest().main() diff --git a/qa/rpc-tests/nodehandling.py b/qa/rpc-tests/nodehandling.py index e9682c4908..7313c79b54 100755 --- a/qa/rpc-tests/nodehandling.py +++ b/qa/rpc-tests/nodehandling.py @@ -10,7 +10,6 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -import http.client import urllib.parse class NodeHandlingTest (BitcoinTestFramework): 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/p2p-feefilter.py b/qa/rpc-tests/p2p-feefilter.py index 96d99d38a7..86ce0b42e6 100755 --- a/qa/rpc-tests/p2p-feefilter.py +++ b/qa/rpc-tests/p2p-feefilter.py @@ -21,9 +21,9 @@ def allInvsMatch(invsExpected, testnode): for x in range(60): with mininode_lock: if (sorted(invsExpected) == sorted(testnode.txinvs)): - return True; + return True time.sleep(1) - return False; + return False # TestNode: bare-bones "peer". Used to track which invs are received from a node # and to send the node feefilter messages. diff --git a/qa/rpc-tests/p2p-mempool.py b/qa/rpc-tests/p2p-mempool.py index 382d7f1e82..1fd125fdcd 100755 --- a/qa/rpc-tests/p2p-mempool.py +++ b/qa/rpc-tests/p2p-mempool.py @@ -6,7 +6,6 @@ from test_framework.mininode import * from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * -import time class TestNode(NodeConnCB): def __init__(self): diff --git a/qa/rpc-tests/p2p-segwit.py b/qa/rpc-tests/p2p-segwit.py index b0ff3beace..2f339bb54f 100755 --- a/qa/rpc-tests/p2p-segwit.py +++ b/qa/rpc-tests/p2p-segwit.py @@ -1661,7 +1661,7 @@ class SegWitTest(BitcoinTestFramework): # too many sigops (contributing to legacy sigop count). checksig_count = (extra_sigops_available // 4) + 1 scriptPubKey_checksigs = CScript([OP_CHECKSIG]*checksig_count) - tx2.vout.append(CTxOut(0, scriptPubKey_checksigs)); + tx2.vout.append(CTxOut(0, scriptPubKey_checksigs)) tx2.vin.pop() tx2.wit.vtxinwit.pop() tx2.vout[0].nValue -= tx.vout[-2].nValue 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..9d155478e2 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"): @@ -89,7 +103,7 @@ class PruneTest(BitcoinTestFramework): # Disconnect node 0 so it can mine a longer reorg chain without knowing about node 1's soon-to-be-stale chain # Node 2 stays connected, so it hears about the stale blocks and then reorg's when node0 reconnects # Stopping node 0 also clears its mempool, so it doesn't have node1's transactions to accidentally mine - stop_node(self.nodes[0],0) + self.stop_node(0) self.nodes[0]=start_node(0, self.options.tmpdir, ["-debug","-maxreceivebuffer=20000","-blockmaxsize=999000", "-checkblocks=5"], timewait=900) # Mine 24 blocks in node 1 for i in range(24): @@ -114,7 +128,7 @@ class PruneTest(BitcoinTestFramework): # This will cause Node 2 to do a reorg requiring 288 blocks of undo data to the reorg_test chain # Reboot node 1 to clear its mempool (hopefully make the invalidate faster) # Lower the block max size so we don't keep mining all our big mempool transactions (from disconnected blocks) - stop_node(self.nodes[1],1) + self.stop_node(1) self.nodes[1]=start_node(1, self.options.tmpdir, ["-debug","-maxreceivebuffer=20000","-blockmaxsize=5000", "-checkblocks=5", "-disablesafemode"], timewait=900) height = self.nodes[1].getblockcount() @@ -137,7 +151,7 @@ class PruneTest(BitcoinTestFramework): print("New best height", self.nodes[1].getblockcount()) # Reboot node1 to clear those giant tx's from mempool - stop_node(self.nodes[1],1) + self.stop_node(1) self.nodes[1]=start_node(1, self.options.tmpdir, ["-debug","-maxreceivebuffer=20000","-blockmaxsize=5000", "-checkblocks=5", "-disablesafemode"], timewait=900) print("Generating new longer chain of 300 more blocks") @@ -212,6 +226,114 @@ 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) + self.stop_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) + assert_equal(node.getblockchaininfo()["blocks"], 1001) + + # negative heights should raise an exception + assert_raises_message(JSONRPCException, "Negative", node.pruneblockchain, -10) + + # 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") + + # Does nothing + node.pruneblockchain(height(0)) + 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 + self.stop_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: + self.stop_node(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, wait=5, timeout=300) + try: + self.stop_node(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 +348,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 + self.stop_node(3) + self.stop_node(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 +434,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/segwit.py b/qa/rpc-tests/segwit.py index be6fae5088..299f5387e7 100755 --- a/qa/rpc-tests/segwit.py +++ b/qa/rpc-tests/segwit.py @@ -39,7 +39,7 @@ def addlength(script): return scriptlen + script def create_witnessprogram(version, node, utxo, pubkey, encode_p2sh, amount): - pkscript = witness_script(version, pubkey); + pkscript = witness_script(version, pubkey) if (encode_p2sh): p2sh_hash = bytes_to_hex_str(ripemd160(sha256(hex_str_to_bytes(pkscript)))) pkscript = "a914"+p2sh_hash+"87" diff --git a/qa/rpc-tests/signrawtransactions.py b/qa/rpc-tests/signrawtransactions.py index c61a280616..109312bd5f 100755 --- a/qa/rpc-tests/signrawtransactions.py +++ b/qa/rpc-tests/signrawtransactions.py @@ -26,12 +26,14 @@ class SignRawTransactionsTest(BitcoinTestFramework): 1) The transaction has a complete set of signatures 2) No script verification error occurred""" - privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N'] + privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N', 'cVKpPfVKSJxKqVpE9awvXNWuLHCa5j5tiE7K6zbUSptFpTEtiFrA'] inputs = [ - # Valid pay-to-pubkey script + # Valid pay-to-pubkey scripts {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0, - 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'} + 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'}, + {'txid': '83a4f6a6b73660e13ee6cb3c6063fa3759c50c9b7521d0536022961898f4fb02', 'vout': 0, + 'scriptPubKey': '76a914669b857c03a5ed269d5d85a1ffac9ed5d663072788ac'}, ] outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1} @@ -46,6 +48,22 @@ class SignRawTransactionsTest(BitcoinTestFramework): # 2) No script verification error occurred assert 'errors' not in rawTxSigned + # Check that signrawtransaction doesn't blow up on garbage merge attempts + dummyTxInconsistent = self.nodes[0].createrawtransaction([inputs[0]], outputs) + rawTxUnsigned = self.nodes[0].signrawtransaction(rawTx + dummyTxInconsistent, inputs) + + assert 'complete' in rawTxUnsigned + assert_equal(rawTxUnsigned['complete'], False) + + # Check that signrawtransaction properly merges unsigned and signed txn, even with garbage in the middle + rawTxSigned2 = self.nodes[0].signrawtransaction(rawTxUnsigned["hex"] + dummyTxInconsistent + rawTxSigned["hex"], inputs) + + assert 'complete' in rawTxSigned2 + assert_equal(rawTxSigned2['complete'], True) + + assert 'errors' not in rawTxSigned2 + + def script_verification_error_test(self): """Creates and signs a raw transaction with valid (vin 0), invalid (vin 1) and one missing (vin 2) input script. @@ -78,6 +96,16 @@ class SignRawTransactionsTest(BitcoinTestFramework): outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1} rawTx = self.nodes[0].createrawtransaction(inputs, outputs) + + # Make sure decoderawtransaction is at least marginally sane + decodedRawTx = self.nodes[0].decoderawtransaction(rawTx) + for i, inp in enumerate(inputs): + assert_equal(decodedRawTx["vin"][i]["txid"], inp["txid"]) + assert_equal(decodedRawTx["vin"][i]["vout"], inp["vout"]) + + # Make sure decoderawtransaction throws if there is extra data + assert_raises(JSONRPCException, self.nodes[0].decoderawtransaction, rawTx + "00") + rawTxSigned = self.nodes[0].signrawtransaction(rawTx, scripts, privKeys) # 3) The transaction has no complete set of signatures 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) diff --git a/qa/rpc-tests/zmq_test.py b/qa/rpc-tests/zmq_test.py index 3a116317fe..e9afb2b09a 100755 --- a/qa/rpc-tests/zmq_test.py +++ b/qa/rpc-tests/zmq_test.py @@ -12,9 +12,6 @@ from test_framework.util import * import zmq import struct -import http.client -import urllib.parse - class ZMQTest (BitcoinTestFramework): def __init__(self): |