diff options
Diffstat (limited to 'test/functional')
-rwxr-xr-x | test/functional/feature_anchors.py | 18 | ||||
-rwxr-xr-x | test/functional/feature_pruning.py | 7 | ||||
-rwxr-xr-x | test/functional/interface_rest.py | 5 | ||||
-rwxr-xr-x | test/functional/mempool_limit.py | 77 | ||||
-rwxr-xr-x | test/functional/p2p_disconnect_ban.py | 12 | ||||
-rwxr-xr-x | test/functional/rpc_packages.py | 40 | ||||
-rwxr-xr-x | test/functional/rpc_psbt.py | 12 | ||||
-rwxr-xr-x | test/functional/test_framework/messages.py | 22 | ||||
-rw-r--r-- | test/functional/test_framework/util.py | 13 | ||||
-rwxr-xr-x | test/functional/wallet_bumpfee.py | 16 |
10 files changed, 138 insertions, 84 deletions
diff --git a/test/functional/feature_anchors.py b/test/functional/feature_anchors.py index 713c0826d3..468ad1eafa 100755 --- a/test/functional/feature_anchors.py +++ b/test/functional/feature_anchors.py @@ -63,17 +63,25 @@ class AnchorsTest(BitcoinTestFramework): self.log.info("Check the addresses in anchors.dat") with open(node_anchors_path, "rb") as file_handler: - anchors = file_handler.read().hex() + anchors = file_handler.read() + anchors_hex = anchors.hex() for port in block_relay_nodes_port: ip_port = ip + port - assert ip_port in anchors + assert ip_port in anchors_hex for port in inbound_nodes_port: ip_port = ip + port - assert ip_port not in anchors + assert ip_port not in anchors_hex - self.log.info("Start node") - self.start_node(0) + self.log.info("Perturb anchors.dat to test it doesn't throw an error during initialization") + with self.nodes[0].assert_debug_log(["0 block-relay-only anchors will be tried for connections."]): + with open(node_anchors_path, "wb") as out_file_handler: + tweaked_contents = bytearray(anchors) + tweaked_contents[20:20] = b'1' + out_file_handler.write(bytes(tweaked_contents)) + + self.log.info("Start node") + self.start_node(0) self.log.info("When node starts, check if anchors.dat doesn't exist anymore") assert not os.path.exists(node_anchors_path) diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index 519877ac5b..b0c6138bcf 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -135,6 +135,10 @@ class PruneTest(BitcoinTestFramework): extra_args=['-prune=550', '-reindex-chainstate'], ) + def test_rescan_blockchain(self): + self.restart_node(0, ["-prune=550"]) + assert_raises_rpc_error(-1, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.", self.nodes[0].rescanblockchain) + def test_height_min(self): assert os.path.isfile(os.path.join(self.prunedir, "blk00000.dat")), "blk00000.dat is missing, pruning too early" self.log.info("Success") @@ -469,6 +473,9 @@ class PruneTest(BitcoinTestFramework): self.log.info("Test wallet re-scan") self.wallet_test() + self.log.info("Test it's not possible to rescan beyond pruned data") + self.test_rescan_blockchain() + self.log.info("Test invalid pruning command line options") self.test_invalid_command_line_options() diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index 23109093d6..5017f77d18 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -277,6 +277,11 @@ class RESTTest (BitcoinTestFramework): assert_equal(len(json_obj), 1) # ensure that there is one header in the json response assert_equal(json_obj[0]['hash'], bb_hash) # request/response hash should be the same + # Check invalid uri (% symbol at the end of the request) + for invalid_uri in [f"/headers/{bb_hash}%", f"/blockfilterheaders/basic/{bb_hash}%", "/mempool/contents.json?%"]: + resp = self.test_rest_request(invalid_uri, ret_type=RetType.OBJ, status=400) + assert_equal(resp.read().decode('utf-8').rstrip(), "URI parsing failed, it likely contained RFC 3986 invalid characters") + # Compare with normal RPC block response rpc_block_json = self.nodes[0].getblock(bb_hash) for key in ['hash', 'confirmations', 'height', 'version', 'merkleroot', 'time', 'nonce', 'bits', 'difficulty', 'chainwork', 'previousblockhash']: diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py index d38a37f952..f3f4b42ad0 100755 --- a/test/functional/mempool_limit.py +++ b/test/functional/mempool_limit.py @@ -7,15 +7,21 @@ from decimal import Decimal from test_framework.blocktools import COINBASE_MATURITY +from test_framework.p2p import P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + assert_fee_amount, assert_greater_than, assert_raises_rpc_error, create_lots_of_big_transactions, gen_return_txouts, ) -from test_framework.wallet import MiniWallet +from test_framework.wallet import ( + COIN, + DEFAULT_FEE, + MiniWallet, +) class MempoolLimitTest(BitcoinTestFramework): @@ -38,13 +44,14 @@ class MempoolLimitTest(BitcoinTestFramework): assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000')) assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000')) - tx_batch_size = 25 - num_of_batches = 3 + tx_batch_size = 1 + num_of_batches = 75 # Generate UTXOs to flood the mempool # 1 to create a tx initially that will be evicted from the mempool later # 3 batches of multiple transactions with a fee rate much higher than the previous UTXO # And 1 more to verify that this tx does not get added to the mempool with a fee rate less than the mempoolminfee - self.generate(miniwallet, 1 + (num_of_batches * tx_batch_size) + 1) + # And 2 more for the package cpfp test + self.generate(miniwallet, 1 + (num_of_batches * tx_batch_size) + 1 + 2) # Mine 99 blocks so that the UTXOs are allowed to be spent self.generate(node, COINBASE_MATURITY - 1) @@ -76,6 +83,68 @@ class MempoolLimitTest(BitcoinTestFramework): self.log.info('Create a mempool tx that will not pass mempoolminfee') assert_raises_rpc_error(-26, "mempool min fee not met", miniwallet.send_self_transfer, from_node=node, fee_rate=relayfee) + self.log.info("Check that submitpackage allows cpfp of a parent below mempool min feerate") + node = self.nodes[0] + peer = node.add_p2p_connection(P2PTxInvStore()) + + # Package with 2 parents and 1 child. One parent has a high feerate due to modified fees, + # another is below the mempool minimum feerate but bumped by the child. + tx_poor = miniwallet.create_self_transfer(fee_rate=relayfee) + tx_rich = miniwallet.create_self_transfer(fee=0, fee_rate=0) + node.prioritisetransaction(tx_rich["txid"], 0, int(DEFAULT_FEE * COIN)) + package_txns = [tx_rich, tx_poor] + coins = [tx["new_utxo"] for tx in package_txns] + tx_child = miniwallet.create_self_transfer_multi(utxos_to_spend=coins, fee_per_output=10000) #DEFAULT_FEE + package_txns.append(tx_child) + + submitpackage_result = node.submitpackage([tx["hex"] for tx in package_txns]) + + rich_parent_result = submitpackage_result["tx-results"][tx_rich["wtxid"]] + poor_parent_result = submitpackage_result["tx-results"][tx_poor["wtxid"]] + child_result = submitpackage_result["tx-results"][tx_child["tx"].getwtxid()] + assert_fee_amount(poor_parent_result["fees"]["base"], tx_poor["tx"].get_vsize(), relayfee) + assert_equal(rich_parent_result["fees"]["base"], 0) + assert_equal(child_result["fees"]["base"], DEFAULT_FEE) + # The "rich" parent does not require CPFP so its effective feerate is just its individual feerate. + assert_fee_amount(DEFAULT_FEE, tx_rich["tx"].get_vsize(), rich_parent_result["fees"]["effective-feerate"]) + assert_equal(rich_parent_result["fees"]["effective-includes"], [tx_rich["wtxid"]]) + # The "poor" parent and child's effective feerates are the same, composed of their total + # fees divided by their combined vsize. + package_fees = poor_parent_result["fees"]["base"] + child_result["fees"]["base"] + package_vsize = tx_poor["tx"].get_vsize() + tx_child["tx"].get_vsize() + assert_fee_amount(package_fees, package_vsize, poor_parent_result["fees"]["effective-feerate"]) + assert_fee_amount(package_fees, package_vsize, child_result["fees"]["effective-feerate"]) + assert_equal([tx_poor["wtxid"], tx_child["tx"].getwtxid()], poor_parent_result["fees"]["effective-includes"]) + assert_equal([tx_poor["wtxid"], tx_child["tx"].getwtxid()], child_result["fees"]["effective-includes"]) + + # The node will broadcast each transaction, still abiding by its peer's fee filter + peer.wait_for_broadcast([tx["tx"].getwtxid() for tx in package_txns]) + + self.log.info("Check a package that passes mempoolminfee but is evicted immediately after submission") + mempoolmin_feerate = node.getmempoolinfo()["mempoolminfee"] + current_mempool = node.getrawmempool(verbose=False) + worst_feerate_btcvb = Decimal("21000000") + for txid in current_mempool: + entry = node.getmempoolentry(txid) + worst_feerate_btcvb = min(worst_feerate_btcvb, entry["fees"]["descendant"] / entry["descendantsize"]) + # Needs to be large enough to trigger eviction + target_weight_each = 200000 + assert_greater_than(target_weight_each * 2, node.getmempoolinfo()["maxmempool"] - node.getmempoolinfo()["bytes"]) + # Should be a true CPFP: parent's feerate is just below mempool min feerate + parent_fee = (mempoolmin_feerate / 1000) * (target_weight_each // 4) - Decimal("0.00001") + # Parent + child is above mempool minimum feerate + child_fee = (worst_feerate_btcvb) * (target_weight_each // 4) - Decimal("0.00001") + # However, when eviction is triggered, these transactions should be at the bottom. + # This assertion assumes parent and child are the same size. + miniwallet.rescan_utxos() + tx_parent_just_below = miniwallet.create_self_transfer(fee=parent_fee, target_weight=target_weight_each) + tx_child_just_above = miniwallet.create_self_transfer(utxo_to_spend=tx_parent_just_below["new_utxo"], fee=child_fee, target_weight=target_weight_each) + # This package ranks below the lowest descendant package in the mempool + assert_greater_than(worst_feerate_btcvb, (parent_fee + child_fee) / (tx_parent_just_below["tx"].get_vsize() + tx_child_just_above["tx"].get_vsize())) + assert_greater_than(mempoolmin_feerate, (parent_fee) / (tx_parent_just_below["tx"].get_vsize())) + assert_greater_than((parent_fee + child_fee) / (tx_parent_just_below["tx"].get_vsize() + tx_child_just_above["tx"].get_vsize()), mempoolmin_feerate / 1000) + assert_raises_rpc_error(-26, "mempool full", node.submitpackage, [tx_parent_just_below["hex"], tx_child_just_above["hex"]]) + self.log.info('Test passing a value below the minimum (5 MB) to -maxmempool throws an error') self.stop_node(0) self.nodes[0].assert_start_raises_init_error(["-maxmempool=4"], "Error: -maxmempool must be at least 5 MB") diff --git a/test/functional/p2p_disconnect_ban.py b/test/functional/p2p_disconnect_ban.py index 394009f30f..c389ff732f 100755 --- a/test/functional/p2p_disconnect_ban.py +++ b/test/functional/p2p_disconnect_ban.py @@ -4,6 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test node disconnect and ban behavior""" import time +from pathlib import Path from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -36,6 +37,17 @@ class DisconnectBanTest(BitcoinTestFramework): self.log.info("clearbanned: successfully clear ban list") self.nodes[1].clearbanned() assert_equal(len(self.nodes[1].listbanned()), 0) + + self.log.info('Test banlist database recreation') + self.stop_node(1) + target_file = self.nodes[1].chain_path / "banlist.json" + Path.unlink(target_file) + with self.nodes[1].assert_debug_log(["Recreating the banlist database"]): + self.start_node(1) + + assert Path.exists(target_file) + assert_equal(self.nodes[1].listbanned(), []) + self.nodes[1].setban("127.0.0.0/24", "add") self.log.info("setban: fail to ban an already banned subnet") diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py index 6cb9760b3d..ae1a498e28 100755 --- a/test/functional/rpc_packages.py +++ b/test/functional/rpc_packages.py @@ -20,7 +20,6 @@ from test_framework.util import ( assert_raises_rpc_error, ) from test_framework.wallet import ( - COIN, DEFAULT_FEE, MiniWallet, ) @@ -325,42 +324,6 @@ class RPCPackagesTest(BitcoinTestFramework): peer.wait_for_broadcast([tx["tx"].getwtxid() for tx in package_txns]) self.generate(node, 1) - def test_submit_cpfp(self): - node = self.nodes[0] - peer = node.add_p2p_connection(P2PTxInvStore()) - - # Package with 2 parents and 1 child. One parent pays for itself using modified fees, and - # another has 0 fees but is bumped by child. - tx_poor = self.wallet.create_self_transfer(fee=0, fee_rate=0) - tx_rich = self.wallet.create_self_transfer(fee=0, fee_rate=0) - node.prioritisetransaction(tx_rich["txid"], 0, int(DEFAULT_FEE * COIN)) - package_txns = [tx_rich, tx_poor] - coins = [tx["new_utxo"] for tx in package_txns] - tx_child = self.wallet.create_self_transfer_multi(utxos_to_spend=coins, fee_per_output=10000) #DEFAULT_FEE - package_txns.append(tx_child) - - submitpackage_result = node.submitpackage([tx["hex"] for tx in package_txns]) - - rich_parent_result = submitpackage_result["tx-results"][tx_rich["wtxid"]] - poor_parent_result = submitpackage_result["tx-results"][tx_poor["wtxid"]] - child_result = submitpackage_result["tx-results"][tx_child["tx"].getwtxid()] - assert_equal(rich_parent_result["fees"]["base"], 0) - assert_equal(poor_parent_result["fees"]["base"], 0) - assert_equal(child_result["fees"]["base"], DEFAULT_FEE) - # The "rich" parent does not require CPFP so its effective feerate. - assert_fee_amount(DEFAULT_FEE, tx_rich["tx"].get_vsize(), rich_parent_result["fees"]["effective-feerate"]) - assert_equal(rich_parent_result["fees"]["effective-includes"], [tx_rich["wtxid"]]) - # The "poor" parent and child's effective feerates are the same, composed of the child's fee - # divided by their combined vsize. - assert_fee_amount(DEFAULT_FEE, tx_poor["tx"].get_vsize() + tx_child["tx"].get_vsize(), poor_parent_result["fees"]["effective-feerate"]) - assert_fee_amount(DEFAULT_FEE, tx_poor["tx"].get_vsize() + tx_child["tx"].get_vsize(), child_result["fees"]["effective-feerate"]) - assert_equal([tx_poor["wtxid"], tx_child["tx"].getwtxid()], poor_parent_result["fees"]["effective-includes"]) - assert_equal([tx_poor["wtxid"], tx_child["tx"].getwtxid()], child_result["fees"]["effective-includes"]) - - # The node will broadcast each transaction, still abiding by its peer's fee filter - peer.wait_for_broadcast([tx["tx"].getwtxid() for tx in package_txns]) - self.generate(node, 1) - def test_submitpackage(self): node = self.nodes[0] @@ -369,9 +332,6 @@ class RPCPackagesTest(BitcoinTestFramework): self.test_submit_child_with_parents(num_parents, False) self.test_submit_child_with_parents(num_parents, True) - self.log.info("Submitpackage valid packages with CPFP") - self.test_submit_cpfp() - self.log.info("Submitpackage only allows packages of 1 child with its parents") # Chain of 3 transactions has too many generations chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=25)] diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 0fc0c0df8b..ef773463d8 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -621,17 +621,17 @@ class PSBTTest(BitcoinTestFramework): # Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness updated = self.nodes[1].utxoupdatepsbt(psbt) decoded = self.nodes[1].decodepsbt(updated) - test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo']) - test_psbt_input_keys(decoded['inputs'][1], []) - test_psbt_input_keys(decoded['inputs'][2], []) + test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo']) + test_psbt_input_keys(decoded['inputs'][1], ['non_witness_utxo']) + test_psbt_input_keys(decoded['inputs'][2], ['non_witness_utxo']) # Try again, now while providing descriptors, making P2SH-segwit work, and causing bip32_derivs and redeem_script to be filled in descs = [self.nodes[1].getaddressinfo(addr)['desc'] for addr in [addr1,addr2,addr3]] updated = self.nodes[1].utxoupdatepsbt(psbt=psbt, descriptors=descs) decoded = self.nodes[1].decodepsbt(updated) - test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'bip32_derivs']) - test_psbt_input_keys(decoded['inputs'][1], []) - test_psbt_input_keys(decoded['inputs'][2], ['witness_utxo', 'bip32_derivs', 'redeem_script']) + test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo', 'bip32_derivs']) + test_psbt_input_keys(decoded['inputs'][1], ['non_witness_utxo', 'bip32_derivs']) + test_psbt_input_keys(decoded['inputs'][2], ['non_witness_utxo','witness_utxo', 'bip32_derivs', 'redeem_script']) # Two PSBTs with a common input should not be joinable psbt1 = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], {self.nodes[0].getnewaddress():Decimal('10.999')}) diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 8c6f68cacb..a6764365c5 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -93,6 +93,7 @@ def ser_compact_size(l): r = struct.pack("<BQ", 255, l) return r + def deser_compact_size(f): nit = struct.unpack("<B", f.read(1))[0] if nit == 253: @@ -103,35 +104,26 @@ def deser_compact_size(f): nit = struct.unpack("<Q", f.read(8))[0] return nit + def deser_string(f): nit = deser_compact_size(f) return f.read(nit) + def ser_string(s): return ser_compact_size(len(s)) + s + def deser_uint256(f): - r = 0 - for i in range(8): - t = struct.unpack("<I", f.read(4))[0] - r += t << (i * 32) - return r + return int.from_bytes(f.read(32), 'little') def ser_uint256(u): - rs = b"" - for _ in range(8): - rs += struct.pack("<I", u & 0xFFFFFFFF) - u >>= 32 - return rs + return u.to_bytes(32, 'little') def uint256_from_str(s): - r = 0 - t = struct.unpack("<IIIIIIII", s[:32]) - for i in range(8): - r += t[i] << (i * 32) - return r + return int.from_bytes(s[:32], 'little') def uint256_from_compact(c): diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index a1b90860f6..5eeb67c00a 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -542,18 +542,7 @@ def modinv(a, n): """Compute the modular inverse of a modulo n using the extended Euclidean Algorithm. See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers. """ - # TODO: Change to pow(a, -1, n) available in Python 3.8 - t1, t2 = 0, 1 - r1, r2 = n, a - while r2 != 0: - q = r1 // r2 - t1, t2 = t2, t1 - q * t2 - r1, r2 = r2, r1 - q * r2 - if r1 > 1: - return None - if t1 < 0: - t1 += n - return t1 + return pow(a, -1, n) class TestFrameworkUtil(unittest.TestCase): def test_modinv(self): diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index f0b8728933..19c8022600 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -105,12 +105,14 @@ class BumpFeeTest(BitcoinTestFramework): test_change_script_match(self, rbf_node, dest_address) test_settxfee(self, rbf_node, dest_address) test_maxtxfee_fails(self, rbf_node, dest_address) - test_feerate_checks_replaced_outputs(self, rbf_node) # These tests wipe out a number of utxos that are expected in other tests test_small_output_with_feerate_succeeds(self, rbf_node, dest_address) test_no_more_inputs_fails(self, rbf_node, dest_address) self.test_bump_back_to_yourself() + # Context independent tests + test_feerate_checks_replaced_outputs(self, rbf_node, peer_node) + def test_invalid_parameters(self, rbf_node, peer_node, dest_address): self.log.info('Test invalid parameters') rbfid = spend_one_input(rbf_node, dest_address) @@ -640,6 +642,11 @@ def test_unconfirmed_not_spendable(self, rbf_node, rbf_node_address): # Call abandon to make sure the wallet doesn't attempt to resubmit # the bump tx and hope the wallet does not rebroadcast before we call. rbf_node.abandontransaction(bumpid) + + tx_bump_abandoned = rbf_node.gettransaction(bumpid) + for tx in tx_bump_abandoned['details']: + assert_equal(tx['abandoned'], True) + assert bumpid not in rbf_node.getrawmempool() assert rbfid in rbf_node.getrawmempool() @@ -718,7 +725,11 @@ def test_no_more_inputs_fails(self, rbf_node, dest_address): self.clear_mempool() -def test_feerate_checks_replaced_outputs(self, rbf_node): +def test_feerate_checks_replaced_outputs(self, rbf_node, peer_node): + # Make sure there is enough balance + peer_node.sendtoaddress(rbf_node.getnewaddress(), 60) + self.generate(peer_node, 1) + self.log.info("Test that feerate checks use replaced outputs") outputs = [] for i in range(50): @@ -741,6 +752,7 @@ def test_feerate_checks_replaced_outputs(self, rbf_node): # Bumpfee and replace all outputs with a single one using the minimum feerate rbf_node.bumpfee(tx_res["txid"], {"fee_rate": min_fee_rate, "outputs": new_outputs}) + self.clear_mempool() if __name__ == "__main__": |