aboutsummaryrefslogtreecommitdiff
path: root/test/functional
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional')
-rwxr-xr-xtest/functional/feature_anchors.py18
-rwxr-xr-xtest/functional/feature_pruning.py7
-rwxr-xr-xtest/functional/interface_rest.py5
-rwxr-xr-xtest/functional/mempool_limit.py77
-rwxr-xr-xtest/functional/p2p_disconnect_ban.py12
-rwxr-xr-xtest/functional/rpc_packages.py40
-rwxr-xr-xtest/functional/rpc_psbt.py12
-rwxr-xr-xtest/functional/test_framework/messages.py22
-rw-r--r--test/functional/test_framework/util.py13
-rwxr-xr-xtest/functional/wallet_bumpfee.py16
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__":