aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rwxr-xr-xtest/functional/feature_framework_unit_tests.py50
-rwxr-xr-xtest/functional/feature_maxtipage.py4
-rwxr-xr-xtest/functional/mempool_accept_v3.py4
-rwxr-xr-xtest/functional/mining_basic.py2
-rwxr-xr-xtest/functional/p2p_1p1c_network.py165
-rwxr-xr-xtest/functional/p2p_compactblocks.py2
-rwxr-xr-xtest/functional/p2p_initial_headers_sync.py15
-rwxr-xr-xtest/functional/p2p_opportunistic_1p1c.py414
-rwxr-xr-xtest/functional/p2p_segwit.py3
-rwxr-xr-xtest/functional/p2p_sendheaders.py21
-rwxr-xr-xtest/functional/test_framework/p2p.py16
-rw-r--r--test/functional/test_framework/script.py18
-rwxr-xr-xtest/functional/test_runner.py35
-rwxr-xr-xtest/fuzz/test_runner.py7
-rwxr-xr-xtest/lint/commit-script-check.sh5
15 files changed, 695 insertions, 66 deletions
diff --git a/test/functional/feature_framework_unit_tests.py b/test/functional/feature_framework_unit_tests.py
new file mode 100755
index 0000000000..c9754e083c
--- /dev/null
+++ b/test/functional/feature_framework_unit_tests.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+# Copyright (c) 2017-2024 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Framework unit tests
+
+Unit tests for the test framework.
+"""
+
+import sys
+import unittest
+
+from test_framework.test_framework import TEST_EXIT_PASSED, TEST_EXIT_FAILED
+
+# List of framework modules containing unit tests. Should be kept in sync with
+# the output of `git grep unittest.TestCase ./test/functional/test_framework`
+TEST_FRAMEWORK_MODULES = [
+ "address",
+ "crypto.bip324_cipher",
+ "blocktools",
+ "crypto.chacha20",
+ "crypto.ellswift",
+ "key",
+ "messages",
+ "crypto.muhash",
+ "crypto.poly1305",
+ "crypto.ripemd160",
+ "script",
+ "segwit_addr",
+ "wallet_util",
+]
+
+
+def run_unit_tests():
+ test_framework_tests = unittest.TestSuite()
+ for module in TEST_FRAMEWORK_MODULES:
+ test_framework_tests.addTest(
+ unittest.TestLoader().loadTestsFromName(f"test_framework.{module}")
+ )
+ result = unittest.TextTestRunner(stream=sys.stdout, verbosity=1, failfast=True).run(
+ test_framework_tests
+ )
+ if not result.wasSuccessful():
+ sys.exit(TEST_EXIT_FAILED)
+ sys.exit(TEST_EXIT_PASSED)
+
+
+if __name__ == "__main__":
+ run_unit_tests()
+
diff --git a/test/functional/feature_maxtipage.py b/test/functional/feature_maxtipage.py
index 51f37ef1e0..a1774a5395 100755
--- a/test/functional/feature_maxtipage.py
+++ b/test/functional/feature_maxtipage.py
@@ -43,6 +43,10 @@ class MaxTipAgeTest(BitcoinTestFramework):
self.generate(node_miner, 1)
assert_equal(node_ibd.getblockchaininfo()['initialblockdownload'], False)
+ # reset time to system time so we don't have a time offset with the ibd node the next
+ # time we connect to it, ensuring TimeOffsets::WarnIfOutOfSync() doesn't output to stderr
+ node_miner.setmocktime(0)
+
def run_test(self):
self.log.info("Test IBD with maximum tip age of 24 hours (default).")
self.test_maxtipage(DEFAULT_MAX_TIP_AGE, set_parameter=False)
diff --git a/test/functional/mempool_accept_v3.py b/test/functional/mempool_accept_v3.py
index 1b55cd0a0d..8285b82c19 100755
--- a/test/functional/mempool_accept_v3.py
+++ b/test/functional/mempool_accept_v3.py
@@ -533,10 +533,10 @@ class MempoolAcceptV3(BitcoinTestFramework):
tx_unrelated_replacee = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=utxo_unrelated_conflict)
assert tx_unrelated_replacee["txid"] in node.getrawmempool()
- fee_to_beat_child2 = int(tx_v3_child_2["fee"] * COIN)
+ fee_to_beat = max(int(tx_v3_child_2["fee"] * COIN), int(tx_unrelated_replacee["fee"]*COIN))
tx_v3_child_3 = self.wallet.create_self_transfer_multi(
- utxos_to_spend=[tx_v3_parent["new_utxos"][0], utxo_unrelated_conflict], fee_per_output=fee_to_beat_child2*5, version=3
+ utxos_to_spend=[tx_v3_parent["new_utxos"][0], utxo_unrelated_conflict], fee_per_output=fee_to_beat*2, version=3
)
node.sendrawtransaction(tx_v3_child_3["hex"])
self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_3["txid"]])
diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py
index da796d3f70..5f2dde8eac 100755
--- a/test/functional/mining_basic.py
+++ b/test/functional/mining_basic.py
@@ -308,7 +308,7 @@ class MiningTest(BitcoinTestFramework):
# Should ask for the block from a p2p node, if they announce the header as well:
peer = node.add_p2p_connection(P2PDataStore())
- peer.wait_for_getheaders(timeout=5) # Drop the first getheaders
+ peer.wait_for_getheaders(timeout=5, block_hash=block.hashPrevBlock)
peer.send_blocks_and_test(blocks=[block], node=node)
# Must be active now:
assert chain_tip(block.hash, status='active', branchlen=0) in node.getchaintips()
diff --git a/test/functional/p2p_1p1c_network.py b/test/functional/p2p_1p1c_network.py
new file mode 100755
index 0000000000..e88c826962
--- /dev/null
+++ b/test/functional/p2p_1p1c_network.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python3
+# Copyright (c) 2024-present The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""
+Test that 1p1c package submission allows a 1p1c package to propagate in a "network" of nodes. Send
+various packages from different nodes on a network in which some nodes have already received some of
+the transactions (and submitted them to mempool, kept them as orphans or rejected them as
+too-low-feerate transactions). The packages should be received and accepted by all nodes.
+"""
+
+from decimal import Decimal
+from math import ceil
+
+from test_framework.messages import (
+ msg_tx,
+)
+from test_framework.p2p import (
+ P2PInterface,
+)
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ assert_greater_than,
+ fill_mempool,
+)
+from test_framework.wallet import (
+ MiniWallet,
+ MiniWalletMode,
+)
+
+# 1sat/vB feerate denominated in BTC/KvB
+FEERATE_1SAT_VB = Decimal("0.00001000")
+
+class PackageRelayTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 4
+ # hugely speeds up the test, as it involves multiple hops of tx relay.
+ self.noban_tx_relay = True
+ self.extra_args = [[
+ "-datacarriersize=100000",
+ "-maxmempool=5",
+ ]] * self.num_nodes
+ self.supports_cli = False
+
+ def raise_network_minfee(self):
+ filler_wallet = MiniWallet(self.nodes[0])
+ fill_mempool(self, self.nodes[0], filler_wallet)
+
+ self.log.debug("Wait for the network to sync mempools")
+ self.sync_mempools()
+
+ self.log.debug("Check that all nodes' mempool minimum feerates are above min relay feerate")
+ for node in self.nodes:
+ assert_equal(node.getmempoolinfo()['minrelaytxfee'], FEERATE_1SAT_VB)
+ assert_greater_than(node.getmempoolinfo()['mempoolminfee'], FEERATE_1SAT_VB)
+
+ def create_basic_1p1c(self, wallet):
+ low_fee_parent = wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB, confirmed_only=True)
+ high_fee_child = wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=999*FEERATE_1SAT_VB)
+ package_hex_basic = [low_fee_parent["hex"], high_fee_child["hex"]]
+ return package_hex_basic, low_fee_parent["tx"], high_fee_child["tx"]
+
+ def create_package_2outs(self, wallet):
+ # First create a tester tx to see the vsize, and then adjust the fees
+ utxo_for_2outs = wallet.get_utxo(confirmed_only=True)
+
+ low_fee_parent_2outs_tester = wallet.create_self_transfer_multi(
+ utxos_to_spend=[utxo_for_2outs],
+ num_outputs=2,
+ )
+
+ # Target 1sat/vB so the number of satoshis is equal to the vsize.
+ # Round up. The goal is to be between min relay feerate and mempool min feerate.
+ fee_2outs = ceil(low_fee_parent_2outs_tester["tx"].get_vsize() / 2)
+
+ low_fee_parent_2outs = wallet.create_self_transfer_multi(
+ utxos_to_spend=[utxo_for_2outs],
+ num_outputs=2,
+ fee_per_output=fee_2outs,
+ )
+
+ # Now create the child
+ high_fee_child_2outs = wallet.create_self_transfer_multi(
+ utxos_to_spend=low_fee_parent_2outs["new_utxos"][::-1],
+ fee_per_output=fee_2outs*100,
+ )
+ return [low_fee_parent_2outs["hex"], high_fee_child_2outs["hex"]], low_fee_parent_2outs["tx"], high_fee_child_2outs["tx"]
+
+ def create_package_2p1c(self, wallet):
+ parent1 = wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB*10, confirmed_only=True)
+ parent2 = wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB*20, confirmed_only=True)
+ child = wallet.create_self_transfer_multi(
+ utxos_to_spend=[parent1["new_utxo"], parent2["new_utxo"]],
+ fee_per_output=999*parent1["tx"].get_vsize(),
+ )
+ return [parent1["hex"], parent2["hex"], child["hex"]], parent1["tx"], parent2["tx"], child["tx"]
+
+ def create_packages(self):
+ # 1: Basic 1-parent-1-child package, parent 1sat/vB, child 999sat/vB
+ package_hex_1, parent_1, child_1 = self.create_basic_1p1c(self.wallet)
+
+ # 2: same as 1, parent's txid is the same as its wtxid.
+ package_hex_2, parent_2, child_2 = self.create_basic_1p1c(self.wallet_nonsegwit)
+
+ # 3: 2-parent-1-child package. Both parents are above mempool min feerate. No package submission happens.
+ # We require packages to be child-with-unconfirmed-parents and only allow 1-parent-1-child packages.
+ package_hex_3, parent_31, parent_32, child_3 = self.create_package_2p1c(self.wallet)
+
+ # 4: parent + child package where the child spends 2 different outputs from the parent.
+ package_hex_4, parent_4, child_4 = self.create_package_2outs(self.wallet)
+
+ # Assemble return results
+ packages_to_submit = [package_hex_1, package_hex_2, package_hex_3, package_hex_4]
+ # node0: sender
+ # node1: pre-received the children (orphan)
+ # node3: pre-received the parents (too low fee)
+ # All nodes receive parent_31 ahead of time.
+ txns_to_send = [
+ [],
+ [child_1, child_2, parent_31, child_3, child_4],
+ [parent_31],
+ [parent_1, parent_2, parent_31, parent_4]
+ ]
+
+ return packages_to_submit, txns_to_send
+
+ def run_test(self):
+ self.wallet = MiniWallet(self.nodes[1])
+ self.wallet_nonsegwit = MiniWallet(self.nodes[2], mode=MiniWalletMode.RAW_P2PK)
+ self.generate(self.wallet_nonsegwit, 10)
+ self.generate(self.wallet, 120)
+
+ self.log.info("Fill mempools with large transactions to raise mempool minimum feerates")
+ self.raise_network_minfee()
+
+ # Create the transactions.
+ self.wallet.rescan_utxos(include_mempool=True)
+ packages_to_submit, transactions_to_presend = self.create_packages()
+
+ self.peers = [self.nodes[i].add_p2p_connection(P2PInterface()) for i in range(self.num_nodes)]
+
+ self.log.info("Pre-send some transactions to nodes")
+ for (i, peer) in enumerate(self.peers):
+ for tx in transactions_to_presend[i]:
+ peer.send_and_ping(msg_tx(tx))
+ # This disconnect removes any sent orphans from the orphanage (EraseForPeer) and times
+ # out the in-flight requests. It is currently required for the test to pass right now,
+ # because the node will not reconsider an orphan tx and will not (re)try requesting
+ # orphan parents from multiple peers if the first one didn't respond.
+ # TODO: remove this in the future if the node tries orphan resolution with multiple peers.
+ peer.peer_disconnect()
+
+ self.log.info("Submit full packages to node0")
+ for package_hex in packages_to_submit:
+ submitpackage_result = self.nodes[0].submitpackage(package_hex)
+ assert_equal(submitpackage_result["package_msg"], "success")
+
+ self.log.info("Wait for mempools to sync")
+ self.sync_mempools(timeout=20)
+
+
+if __name__ == '__main__':
+ PackageRelayTest().main()
diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py
index 0950579580..9e314db110 100755
--- a/test/functional/p2p_compactblocks.py
+++ b/test/functional/p2p_compactblocks.py
@@ -387,7 +387,7 @@ class CompactBlocksTest(BitcoinTestFramework):
if announce == "inv":
test_node.send_message(msg_inv([CInv(MSG_BLOCK, block.sha256)]))
- test_node.wait_until(lambda: "getheaders" in test_node.last_message, timeout=30)
+ test_node.wait_for_getheaders(timeout=30)
test_node.send_header_for_blocks([block])
else:
test_node.send_header_for_blocks([block])
diff --git a/test/functional/p2p_initial_headers_sync.py b/test/functional/p2p_initial_headers_sync.py
index e67c384da7..bc6e0fb355 100755
--- a/test/functional/p2p_initial_headers_sync.py
+++ b/test/functional/p2p_initial_headers_sync.py
@@ -38,9 +38,10 @@ class HeadersSyncTest(BitcoinTestFramework):
def run_test(self):
self.log.info("Adding a peer to node0")
peer1 = self.nodes[0].add_p2p_connection(P2PInterface())
+ best_block_hash = int(self.nodes[0].getbestblockhash(), 16)
# Wait for peer1 to receive a getheaders
- peer1.wait_for_getheaders()
+ peer1.wait_for_getheaders(block_hash=best_block_hash)
# An empty reply will clear the outstanding getheaders request,
# allowing additional getheaders requests to be sent to this peer in
# the future.
@@ -60,17 +61,12 @@ class HeadersSyncTest(BitcoinTestFramework):
assert "getheaders" not in peer2.last_message
assert "getheaders" not in peer3.last_message
- with p2p_lock:
- peer1.last_message.pop("getheaders", None)
-
self.log.info("Have all peers announce a new block")
self.announce_random_block(all_peers)
self.log.info("Check that peer1 receives a getheaders in response")
- peer1.wait_for_getheaders()
+ peer1.wait_for_getheaders(block_hash=best_block_hash)
peer1.send_message(msg_headers()) # Send empty response, see above
- with p2p_lock:
- peer1.last_message.pop("getheaders", None)
self.log.info("Check that exactly 1 of {peer2, peer3} received a getheaders in response")
count = 0
@@ -80,7 +76,6 @@ class HeadersSyncTest(BitcoinTestFramework):
if "getheaders" in p.last_message:
count += 1
peer_receiving_getheaders = p
- p.last_message.pop("getheaders", None)
p.send_message(msg_headers()) # Send empty response, see above
assert_equal(count, 1)
@@ -89,14 +84,14 @@ class HeadersSyncTest(BitcoinTestFramework):
self.announce_random_block(all_peers)
self.log.info("Check that peer1 receives a getheaders in response")
- peer1.wait_for_getheaders()
+ peer1.wait_for_getheaders(block_hash=best_block_hash)
self.log.info("Check that the remaining peer received a getheaders as well")
expected_peer = peer2
if peer2 == peer_receiving_getheaders:
expected_peer = peer3
- expected_peer.wait_for_getheaders()
+ expected_peer.wait_for_getheaders(block_hash=best_block_hash)
self.log.info("Success!")
diff --git a/test/functional/p2p_opportunistic_1p1c.py b/test/functional/p2p_opportunistic_1p1c.py
new file mode 100755
index 0000000000..e07acd5481
--- /dev/null
+++ b/test/functional/p2p_opportunistic_1p1c.py
@@ -0,0 +1,414 @@
+#!/usr/bin/env python3
+# Copyright (c) 2024-present The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""
+Test opportunistic 1p1c package submission logic.
+"""
+
+from decimal import Decimal
+import time
+from test_framework.messages import (
+ CInv,
+ CTxInWitness,
+ MAX_BIP125_RBF_SEQUENCE,
+ MSG_WTX,
+ msg_inv,
+ msg_tx,
+ tx_from_hex,
+)
+from test_framework.p2p import (
+ P2PInterface,
+)
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+ assert_greater_than,
+ fill_mempool,
+)
+from test_framework.wallet import (
+ MiniWallet,
+ MiniWalletMode,
+)
+
+# 1sat/vB feerate denominated in BTC/KvB
+FEERATE_1SAT_VB = Decimal("0.00001000")
+# Number of seconds to wait to ensure no getdata is received
+GETDATA_WAIT = 60
+
+def cleanup(func):
+ def wrapper(self, *args, **kwargs):
+ try:
+ func(self, *args, **kwargs)
+ finally:
+ self.nodes[0].disconnect_p2ps()
+ # Do not clear the node's mempool, as each test requires mempool min feerate > min
+ # relay feerate. However, do check that this is the case.
+ assert self.nodes[0].getmempoolinfo()["mempoolminfee"] > self.nodes[0].getnetworkinfo()["relayfee"]
+ # Ensure we do not try to spend the same UTXOs in subsequent tests, as they will look like RBF attempts.
+ self.wallet.rescan_utxos(include_mempool=True)
+
+ # Resets if mocktime was used
+ self.nodes[0].setmocktime(0)
+ return wrapper
+
+class PackageRelayTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+ self.extra_args = [[
+ "-datacarriersize=100000",
+ "-maxmempool=5",
+ ]]
+ self.supports_cli = False
+
+ def create_tx_below_mempoolminfee(self, wallet):
+ """Create a 1-input 1sat/vB transaction using a confirmed UTXO. Decrement and use
+ self.sequence so that subsequent calls to this function result in unique transactions."""
+
+ self.sequence -= 1
+ assert_greater_than(self.nodes[0].getmempoolinfo()["mempoolminfee"], FEERATE_1SAT_VB)
+
+ return wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB, sequence=self.sequence, confirmed_only=True)
+
+ @cleanup
+ def test_basic_child_then_parent(self):
+ node = self.nodes[0]
+ self.log.info("Check that opportunistic 1p1c logic works when child is received before parent")
+
+ low_fee_parent = self.create_tx_below_mempoolminfee(self.wallet)
+ high_fee_child = self.wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=20*FEERATE_1SAT_VB)
+
+ peer_sender = node.add_p2p_connection(P2PInterface())
+
+ # 1. Child is received first (perhaps the low feerate parent didn't meet feefilter or the requests were sent to different nodes). It is missing an input.
+ high_child_wtxid_int = int(high_fee_child["tx"].getwtxid(), 16)
+ peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=high_child_wtxid_int)]))
+ peer_sender.wait_for_getdata([high_child_wtxid_int])
+ peer_sender.send_and_ping(msg_tx(high_fee_child["tx"]))
+
+ # 2. Node requests the missing parent by txid.
+ parent_txid_int = int(low_fee_parent["txid"], 16)
+ peer_sender.wait_for_getdata([parent_txid_int])
+
+ # 3. Sender relays the parent. Parent+Child are evaluated as a package and accepted.
+ peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
+
+ # 4. Both transactions should now be in mempool.
+ node_mempool = node.getrawmempool()
+ assert low_fee_parent["txid"] in node_mempool
+ assert high_fee_child["txid"] in node_mempool
+
+ node.disconnect_p2ps()
+
+ @cleanup
+ def test_basic_parent_then_child(self, wallet):
+ node = self.nodes[0]
+ low_fee_parent = self.create_tx_below_mempoolminfee(wallet)
+ high_fee_child = wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=20*FEERATE_1SAT_VB)
+
+ peer_sender = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=1, connection_type="outbound-full-relay")
+ peer_ignored = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=2, connection_type="outbound-full-relay")
+
+ # 1. Parent is relayed first. It is too low feerate.
+ parent_wtxid_int = int(low_fee_parent["tx"].getwtxid(), 16)
+ peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=parent_wtxid_int)]))
+ peer_sender.wait_for_getdata([parent_wtxid_int])
+ peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
+ assert low_fee_parent["txid"] not in node.getrawmempool()
+
+ # Send again from peer_ignored, check that it is ignored
+ peer_ignored.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=parent_wtxid_int)]))
+ assert "getdata" not in peer_ignored.last_message
+
+ # 2. Child is relayed next. It is missing an input.
+ high_child_wtxid_int = int(high_fee_child["tx"].getwtxid(), 16)
+ peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=high_child_wtxid_int)]))
+ peer_sender.wait_for_getdata([high_child_wtxid_int])
+ peer_sender.send_and_ping(msg_tx(high_fee_child["tx"]))
+
+ # 3. Node requests the missing parent by txid.
+ # It should do so even if it has previously rejected that parent for being too low feerate.
+ parent_txid_int = int(low_fee_parent["txid"], 16)
+ peer_sender.wait_for_getdata([parent_txid_int])
+
+ # 4. Sender re-relays the parent. Parent+Child are evaluated as a package and accepted.
+ peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
+
+ # 5. Both transactions should now be in mempool.
+ node_mempool = node.getrawmempool()
+ assert low_fee_parent["txid"] in node_mempool
+ assert high_fee_child["txid"] in node_mempool
+
+ @cleanup
+ def test_low_and_high_child(self, wallet):
+ node = self.nodes[0]
+ low_fee_parent = self.create_tx_below_mempoolminfee(wallet)
+ # This feerate is above mempoolminfee, but not enough to also bump the low feerate parent.
+ feerate_just_above = node.getmempoolinfo()["mempoolminfee"]
+ med_fee_child = wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=feerate_just_above)
+ high_fee_child = wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=999*FEERATE_1SAT_VB)
+
+ peer_sender = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=1, connection_type="outbound-full-relay")
+ peer_ignored = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=2, connection_type="outbound-full-relay")
+
+ self.log.info("Check that tx caches low fee parent + low fee child package rejections")
+
+ # 1. Send parent, rejected for being low feerate.
+ parent_wtxid_int = int(low_fee_parent["tx"].getwtxid(), 16)
+ peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=parent_wtxid_int)]))
+ peer_sender.wait_for_getdata([parent_wtxid_int])
+ peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
+ assert low_fee_parent["txid"] not in node.getrawmempool()
+
+ # Send again from peer_ignored, check that it is ignored
+ peer_ignored.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=parent_wtxid_int)]))
+ assert "getdata" not in peer_ignored.last_message
+
+ # 2. Send an (orphan) child that has a higher feerate, but not enough to bump the parent.
+ med_child_wtxid_int = int(med_fee_child["tx"].getwtxid(), 16)
+ peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=med_child_wtxid_int)]))
+ peer_sender.wait_for_getdata([med_child_wtxid_int])
+ peer_sender.send_and_ping(msg_tx(med_fee_child["tx"]))
+
+ # 3. Node requests the orphan's missing parent.
+ parent_txid_int = int(low_fee_parent["txid"], 16)
+ peer_sender.wait_for_getdata([parent_txid_int])
+
+ # 4. The low parent + low child are submitted as a package. They are not accepted due to low package feerate.
+ peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
+
+ assert low_fee_parent["txid"] not in node.getrawmempool()
+ assert med_fee_child["txid"] not in node.getrawmempool()
+
+ # If peer_ignored announces the low feerate child, it should be ignored
+ peer_ignored.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=med_child_wtxid_int)]))
+ assert "getdata" not in peer_ignored.last_message
+ # If either peer sends the parent again, package evaluation should not be attempted
+ peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
+ peer_ignored.send_and_ping(msg_tx(low_fee_parent["tx"]))
+
+ assert low_fee_parent["txid"] not in node.getrawmempool()
+ assert med_fee_child["txid"] not in node.getrawmempool()
+
+ # 5. Send the high feerate (orphan) child
+ high_child_wtxid_int = int(high_fee_child["tx"].getwtxid(), 16)
+ peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=high_child_wtxid_int)]))
+ peer_sender.wait_for_getdata([high_child_wtxid_int])
+ peer_sender.send_and_ping(msg_tx(high_fee_child["tx"]))
+
+ # 6. Node requests the orphan's parent, even though it has already been rejected, both by
+ # itself and with a child. This is necessary, otherwise high_fee_child can be censored.
+ parent_txid_int = int(low_fee_parent["txid"], 16)
+ peer_sender.wait_for_getdata([parent_txid_int])
+
+ # 7. The low feerate parent + high feerate child are submitted as a package.
+ peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
+
+ # 8. Both transactions should now be in mempool
+ node_mempool = node.getrawmempool()
+ assert low_fee_parent["txid"] in node_mempool
+ assert high_fee_child["txid"] in node_mempool
+ assert med_fee_child["txid"] not in node_mempool
+
+ @cleanup
+ def test_orphan_consensus_failure(self):
+ self.log.info("Check opportunistic 1p1c logic with consensus-invalid orphan causes disconnect of the correct peer")
+ node = self.nodes[0]
+ low_fee_parent = self.create_tx_below_mempoolminfee(self.wallet)
+ coin = low_fee_parent["new_utxo"]
+ address = node.get_deterministic_priv_key().address
+ # Create raw transaction spending the parent, but with no signature (a consensus error).
+ hex_orphan_no_sig = node.createrawtransaction([{"txid": coin["txid"], "vout": coin["vout"]}], {address : coin["value"] - Decimal("0.0001")})
+ tx_orphan_bad_wit = tx_from_hex(hex_orphan_no_sig)
+ tx_orphan_bad_wit.wit.vtxinwit.append(CTxInWitness())
+ tx_orphan_bad_wit.wit.vtxinwit[0].scriptWitness.stack = [b'garbage']
+
+ bad_orphan_sender = node.add_p2p_connection(P2PInterface())
+ parent_sender = node.add_p2p_connection(P2PInterface())
+
+ # 1. Child is received first. It is missing an input.
+ child_wtxid_int = int(tx_orphan_bad_wit.getwtxid(), 16)
+ bad_orphan_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=child_wtxid_int)]))
+ bad_orphan_sender.wait_for_getdata([child_wtxid_int])
+ bad_orphan_sender.send_and_ping(msg_tx(tx_orphan_bad_wit))
+
+ # 2. Node requests the missing parent by txid.
+ parent_txid_int = int(low_fee_parent["txid"], 16)
+ bad_orphan_sender.wait_for_getdata([parent_txid_int])
+
+ # 3. A different peer relays the parent. Parent+Child are evaluated as a package and rejected.
+ parent_sender.send_message(msg_tx(low_fee_parent["tx"]))
+
+ # 4. Transactions should not be in mempool.
+ node_mempool = node.getrawmempool()
+ assert low_fee_parent["txid"] not in node_mempool
+ assert tx_orphan_bad_wit.rehash() not in node_mempool
+
+ # 5. Peer that sent a consensus-invalid transaction should be disconnected.
+ bad_orphan_sender.wait_for_disconnect()
+
+ # The peer that didn't provide the orphan should not be disconnected.
+ parent_sender.sync_with_ping()
+
+ @cleanup
+ def test_parent_consensus_failure(self):
+ self.log.info("Check opportunistic 1p1c logic with consensus-invalid parent causes disconnect of the correct peer")
+ node = self.nodes[0]
+ low_fee_parent = self.create_tx_below_mempoolminfee(self.wallet)
+ high_fee_child = self.wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=999*FEERATE_1SAT_VB)
+
+ # Create invalid version of parent with a bad signature.
+ tx_parent_bad_wit = tx_from_hex(low_fee_parent["hex"])
+ tx_parent_bad_wit.wit.vtxinwit.append(CTxInWitness())
+ tx_parent_bad_wit.wit.vtxinwit[0].scriptWitness.stack = [b'garbage']
+
+ package_sender = node.add_p2p_connection(P2PInterface())
+ fake_parent_sender = node.add_p2p_connection(P2PInterface())
+
+ # 1. Child is received first. It is missing an input.
+ child_wtxid_int = int(high_fee_child["tx"].getwtxid(), 16)
+ package_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=child_wtxid_int)]))
+ package_sender.wait_for_getdata([child_wtxid_int])
+ package_sender.send_and_ping(msg_tx(high_fee_child["tx"]))
+
+ # 2. Node requests the missing parent by txid.
+ parent_txid_int = int(tx_parent_bad_wit.rehash(), 16)
+ package_sender.wait_for_getdata([parent_txid_int])
+
+ # 3. A different node relays the parent. The parent is first evaluated by itself and
+ # rejected for being too low feerate. Then it is evaluated as a package and, after passing
+ # feerate checks, rejected for having a bad signature (consensus error).
+ fake_parent_sender.send_message(msg_tx(tx_parent_bad_wit))
+
+ # 4. Transactions should not be in mempool.
+ node_mempool = node.getrawmempool()
+ assert tx_parent_bad_wit.rehash() not in node_mempool
+ assert high_fee_child["txid"] not in node_mempool
+
+ # 5. Peer sent a consensus-invalid transaction.
+ fake_parent_sender.wait_for_disconnect()
+
+ self.log.info("Check that fake parent does not cause orphan to be deleted and real package can still be submitted")
+ # 6. Child-sending should not have been punished and the orphan should remain in orphanage.
+ # It can send the "real" parent transaction, and the package is accepted.
+ parent_wtxid_int = int(low_fee_parent["tx"].getwtxid(), 16)
+ package_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=parent_wtxid_int)]))
+ package_sender.wait_for_getdata([parent_wtxid_int])
+ package_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
+
+ node_mempool = node.getrawmempool()
+ assert low_fee_parent["txid"] in node_mempool
+ assert high_fee_child["txid"] in node_mempool
+
+ @cleanup
+ def test_multiple_parents(self):
+ self.log.info("Check that node does not request more than 1 previously-rejected low feerate parent")
+
+ node = self.nodes[0]
+ node.setmocktime(int(time.time()))
+
+ # 2-parent-1-child package where both parents are below mempool min feerate
+ parent_low_1 = self.create_tx_below_mempoolminfee(self.wallet_nonsegwit)
+ parent_low_2 = self.create_tx_below_mempoolminfee(self.wallet_nonsegwit)
+ child_bumping = self.wallet_nonsegwit.create_self_transfer_multi(
+ utxos_to_spend=[parent_low_1["new_utxo"], parent_low_2["new_utxo"]],
+ fee_per_output=999*parent_low_1["tx"].get_vsize(),
+ )
+
+ peer_sender = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=1, connection_type="outbound-full-relay")
+
+ # 1. Send both parents. Each should be rejected for being too low feerate.
+ # Send unsolicited so that we can later check that no "getdata" was ever received.
+ peer_sender.send_and_ping(msg_tx(parent_low_1["tx"]))
+ peer_sender.send_and_ping(msg_tx(parent_low_2["tx"]))
+
+ # parent_low_1 and parent_low_2 are rejected for being low feerate.
+ assert parent_low_1["txid"] not in node.getrawmempool()
+ assert parent_low_2["txid"] not in node.getrawmempool()
+
+ # 2. Send child.
+ peer_sender.send_and_ping(msg_tx(child_bumping["tx"]))
+
+ # 3. Node should not request any parents, as it should recognize that it will not accept
+ # multi-parent-1-child packages.
+ node.bumpmocktime(GETDATA_WAIT)
+ peer_sender.sync_with_ping()
+ assert "getdata" not in peer_sender.last_message
+
+ @cleanup
+ def test_other_parent_in_mempool(self):
+ self.log.info("Check opportunistic 1p1c fails if child already has another parent in mempool")
+ node = self.nodes[0]
+
+ # This parent needs CPFP
+ parent_low = self.create_tx_below_mempoolminfee(self.wallet)
+ # This parent does not need CPFP and can be submitted alone ahead of time
+ parent_high = self.wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB*10, confirmed_only=True)
+ child = self.wallet.create_self_transfer_multi(
+ utxos_to_spend=[parent_high["new_utxo"], parent_low["new_utxo"]],
+ fee_per_output=999*parent_low["tx"].get_vsize(),
+ )
+
+ peer_sender = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=1, connection_type="outbound-full-relay")
+
+ # 1. Send first parent which will be accepted.
+ peer_sender.send_and_ping(msg_tx(parent_high["tx"]))
+ assert parent_high["txid"] in node.getrawmempool()
+
+ # 2. Send child.
+ peer_sender.send_and_ping(msg_tx(child["tx"]))
+
+ # 3. Node requests parent_low. However, 1p1c fails because package-not-child-with-unconfirmed-parents
+ parent_low_txid_int = int(parent_low["txid"], 16)
+ peer_sender.wait_for_getdata([parent_low_txid_int])
+ peer_sender.send_and_ping(msg_tx(parent_low["tx"]))
+
+ node_mempool = node.getrawmempool()
+ assert parent_high["txid"] in node_mempool
+ assert parent_low["txid"] not in node_mempool
+ assert child["txid"] not in node_mempool
+
+ # Same error if submitted through submitpackage without parent_high
+ package_hex_missing_parent = [parent_low["hex"], child["hex"]]
+ result_missing_parent = node.submitpackage(package_hex_missing_parent)
+ assert_equal(result_missing_parent["package_msg"], "package-not-child-with-unconfirmed-parents")
+
+ def run_test(self):
+ node = self.nodes[0]
+ # To avoid creating transactions with the same txid (can happen if we set the same feerate
+ # and reuse the same input as a previous transaction that wasn't successfully submitted),
+ # we give each subtest a different nSequence for its transactions.
+ self.sequence = MAX_BIP125_RBF_SEQUENCE
+
+ self.wallet = MiniWallet(node)
+ self.wallet_nonsegwit = MiniWallet(node, mode=MiniWalletMode.RAW_P2PK)
+ self.generate(self.wallet_nonsegwit, 10)
+ self.generate(self.wallet, 20)
+
+ filler_wallet = MiniWallet(node)
+ fill_mempool(self, node, filler_wallet)
+
+ self.log.info("Check opportunistic 1p1c logic when parent (txid != wtxid) is received before child")
+ self.test_basic_parent_then_child(self.wallet)
+
+ self.log.info("Check opportunistic 1p1c logic when parent (txid == wtxid) is received before child")
+ self.test_basic_parent_then_child(self.wallet_nonsegwit)
+
+ self.log.info("Check opportunistic 1p1c logic when child is received before parent")
+ self.test_basic_child_then_parent()
+
+ self.log.info("Check opportunistic 1p1c logic when 2 candidate children exist (parent txid != wtxid)")
+ self.test_low_and_high_child(self.wallet)
+
+ self.log.info("Check opportunistic 1p1c logic when 2 candidate children exist (parent txid == wtxid)")
+ self.test_low_and_high_child(self.wallet_nonsegwit)
+
+ self.test_orphan_consensus_failure()
+ self.test_parent_consensus_failure()
+ self.test_multiple_parents()
+ self.test_other_parent_in_mempool()
+
+
+if __name__ == '__main__':
+ PackageRelayTest().main()
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index af47c6d9f0..213c748eda 100755
--- a/test/functional/p2p_segwit.py
+++ b/test/functional/p2p_segwit.py
@@ -191,14 +191,13 @@ class TestP2PConn(P2PInterface):
def announce_block_and_wait_for_getdata(self, block, use_header, timeout=60):
with p2p_lock:
self.last_message.pop("getdata", None)
- self.last_message.pop("getheaders", None)
msg = msg_headers()
msg.headers = [CBlockHeader(block)]
if use_header:
self.send_message(msg)
else:
self.send_message(msg_inv(inv=[CInv(MSG_BLOCK, block.sha256)]))
- self.wait_for_getheaders(timeout=timeout)
+ self.wait_for_getheaders(block_hash=block.hashPrevBlock, timeout=timeout)
self.send_message(msg)
self.wait_for_getdata([block.sha256], timeout=timeout)
diff --git a/test/functional/p2p_sendheaders.py b/test/functional/p2p_sendheaders.py
index 508d6fe403..27a3aa8fb9 100755
--- a/test/functional/p2p_sendheaders.py
+++ b/test/functional/p2p_sendheaders.py
@@ -311,6 +311,7 @@ class SendHeadersTest(BitcoinTestFramework):
# Now that we've synced headers, headers announcements should work
tip = self.mine_blocks(1)
+ expected_hash = tip
inv_node.check_last_inv_announcement(inv=[tip])
test_node.check_last_headers_announcement(headers=[tip])
@@ -334,7 +335,10 @@ class SendHeadersTest(BitcoinTestFramework):
if j == 0:
# Announce via inv
test_node.send_block_inv(tip)
- test_node.wait_for_getheaders()
+ if i == 0:
+ test_node.wait_for_getheaders(block_hash=expected_hash)
+ else:
+ assert "getheaders" not in test_node.last_message
# Should have received a getheaders now
test_node.send_header_for_blocks(blocks)
# Test that duplicate inv's won't result in duplicate
@@ -521,6 +525,7 @@ class SendHeadersTest(BitcoinTestFramework):
self.log.info("Part 5: Testing handling of unconnecting headers")
# First we test that receipt of an unconnecting header doesn't prevent
# chain sync.
+ expected_hash = tip
for i in range(10):
self.log.debug("Part 5.{}: starting...".format(i))
test_node.last_message.pop("getdata", None)
@@ -533,15 +538,14 @@ class SendHeadersTest(BitcoinTestFramework):
block_time += 1
height += 1
# Send the header of the second block -> this won't connect.
- with p2p_lock:
- test_node.last_message.pop("getheaders", None)
test_node.send_header_for_blocks([blocks[1]])
- test_node.wait_for_getheaders()
+ test_node.wait_for_getheaders(block_hash=expected_hash)
test_node.send_header_for_blocks(blocks)
test_node.wait_for_getdata([x.sha256 for x in blocks])
[test_node.send_message(msg_block(x)) for x in blocks]
test_node.sync_with_ping()
assert_equal(int(self.nodes[0].getbestblockhash(), 16), blocks[1].sha256)
+ expected_hash = blocks[1].sha256
blocks = []
# Now we test that if we repeatedly don't send connecting headers, we
@@ -556,13 +560,12 @@ class SendHeadersTest(BitcoinTestFramework):
for i in range(1, MAX_NUM_UNCONNECTING_HEADERS_MSGS):
# Send a header that doesn't connect, check that we get a getheaders.
- with p2p_lock:
- test_node.last_message.pop("getheaders", None)
test_node.send_header_for_blocks([blocks[i]])
- test_node.wait_for_getheaders()
+ test_node.wait_for_getheaders(block_hash=expected_hash)
# Next header will connect, should re-set our count:
test_node.send_header_for_blocks([blocks[0]])
+ expected_hash = blocks[0].sha256
# Remove the first two entries (blocks[1] would connect):
blocks = blocks[2:]
@@ -571,10 +574,8 @@ class SendHeadersTest(BitcoinTestFramework):
# before we get disconnected. Should be 5*MAX_NUM_UNCONNECTING_HEADERS_MSGS
for i in range(5 * MAX_NUM_UNCONNECTING_HEADERS_MSGS - 1):
# Send a header that doesn't connect, check that we get a getheaders.
- with p2p_lock:
- test_node.last_message.pop("getheaders", None)
test_node.send_header_for_blocks([blocks[i % len(blocks)]])
- test_node.wait_for_getheaders()
+ test_node.wait_for_getheaders(block_hash=expected_hash)
# Eventually this stops working.
test_node.send_header_for_blocks([blocks[-1]])
diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py
index ce76008c46..00bd1e4017 100755
--- a/test/functional/test_framework/p2p.py
+++ b/test/functional/test_framework/p2p.py
@@ -644,15 +644,17 @@ class P2PInterface(P2PConnection):
self.wait_until(test_function, timeout=timeout)
- def wait_for_getheaders(self, *, timeout=60):
- """Waits for a getheaders message.
+ def wait_for_getheaders(self, block_hash=None, *, timeout=60):
+ """Waits for a getheaders message containing a specific block hash.
- Receiving any getheaders message will satisfy the predicate. the last_message["getheaders"]
- value must be explicitly cleared before calling this method, or this will return
- immediately with success. TODO: change this method to take a hash value and only
- return true if the correct block header has been requested."""
+ If no block hash is provided, checks whether any getheaders message has been received by the node."""
def test_function():
- return self.last_message.get("getheaders")
+ last_getheaders = self.last_message.pop("getheaders", None)
+ if block_hash is None:
+ return last_getheaders
+ if last_getheaders is None:
+ return False
+ return block_hash == last_getheaders.locator.vHave[0]
self.wait_until(test_function, timeout=timeout)
diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py
index 3275517888..7b19d31e17 100644
--- a/test/functional/test_framework/script.py
+++ b/test/functional/test_framework/script.py
@@ -483,7 +483,7 @@ class CScript(bytes):
i = 0
while i < len(self):
sop_idx = i
- opcode = self[i]
+ opcode = CScriptOp(self[i])
i += 1
if opcode > OP_PUSHDATA4:
@@ -590,7 +590,7 @@ class CScript(bytes):
n += 1
elif opcode in (OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY):
if fAccurate and (OP_1 <= lastOpcode <= OP_16):
- n += opcode.decode_op_n()
+ n += lastOpcode.decode_op_n()
else:
n += 20
lastOpcode = opcode
@@ -782,6 +782,20 @@ class TestFrameworkScript(unittest.TestCase):
for value in values:
self.assertEqual(CScriptNum.decode(CScriptNum.encode(CScriptNum(value))), value)
+ def test_legacy_sigopcount(self):
+ # test repeated single sig ops
+ for n_ops in range(1, 100, 10):
+ for singlesig_op in (OP_CHECKSIG, OP_CHECKSIGVERIFY):
+ singlesigs_script = CScript([singlesig_op]*n_ops)
+ self.assertEqual(singlesigs_script.GetSigOpCount(fAccurate=False), n_ops)
+ self.assertEqual(singlesigs_script.GetSigOpCount(fAccurate=True), n_ops)
+ # test multisig op (including accurate counting, i.e. BIP16)
+ for n in range(1, 16+1):
+ for multisig_op in (OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY):
+ multisig_script = CScript([CScriptOp.encode_op_n(n), multisig_op])
+ self.assertEqual(multisig_script.GetSigOpCount(fAccurate=False), 20)
+ self.assertEqual(multisig_script.GetSigOpCount(fAccurate=True), n)
+
def BIP341_sha_prevouts(txTo):
return sha256(b"".join(i.prevout.serialize() for i in txTo.vin))
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 32b55813a8..2b0b24ec05 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -26,7 +26,6 @@ import sys
import tempfile
import re
import logging
-import unittest
os.environ["REQUIRE_WALLET_TYPE_SET"] = "1"
@@ -70,23 +69,7 @@ if platform.system() != 'Windows' or sys.getwindowsversion() >= (10, 0, 14393):
TEST_EXIT_PASSED = 0
TEST_EXIT_SKIPPED = 77
-# List of framework modules containing unit tests. Should be kept in sync with
-# the output of `git grep unittest.TestCase ./test/functional/test_framework`
-TEST_FRAMEWORK_MODULES = [
- "address",
- "crypto.bip324_cipher",
- "blocktools",
- "crypto.chacha20",
- "crypto.ellswift",
- "key",
- "messages",
- "crypto.muhash",
- "crypto.poly1305",
- "crypto.ripemd160",
- "script",
- "segwit_addr",
- "wallet_util",
-]
+TEST_FRAMEWORK_UNIT_TESTS = 'feature_framework_unit_tests.py'
EXTENDED_SCRIPTS = [
# These tests are not run by default.
@@ -200,6 +183,8 @@ BASE_SCRIPTS = [
'wallet_txn_clone.py --segwit',
'rpc_getchaintips.py',
'rpc_misc.py',
+ 'p2p_1p1c_network.py',
+ 'p2p_opportunistic_1p1c.py',
'interface_rest.py',
'mempool_spend_coinbase.py',
'wallet_avoid_mixing_output_types.py --descriptors',
@@ -255,6 +240,7 @@ BASE_SCRIPTS = [
'wallet_keypool.py --descriptors',
'wallet_descriptor.py --descriptors',
'p2p_nobloomfilter_messages.py',
+ TEST_FRAMEWORK_UNIT_TESTS,
'p2p_filter.py',
'rpc_setban.py --v1transport',
'rpc_setban.py --v2transport',
@@ -440,7 +426,6 @@ def main():
parser.add_argument('--tmpdirprefix', '-t', default=tempfile.gettempdir(), help="Root directory for datadirs")
parser.add_argument('--failfast', '-F', action='store_true', help='stop execution after the first test failure')
parser.add_argument('--filter', help='filter scripts to run by regular expression')
- parser.add_argument('--skipunit', '-u', action='store_true', help='skip unit tests for the test framework')
args, unknown_args = parser.parse_known_args()
@@ -552,10 +537,9 @@ def main():
combined_logs_len=args.combinedlogslen,
failfast=args.failfast,
use_term_control=args.ansi,
- skipunit=args.skipunit,
)
-def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control, skipunit=False):
+def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control):
args = args or []
# Warn if bitcoind is already running
@@ -578,15 +562,6 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=
# a hard link or a copy on any platform. See https://github.com/bitcoin/bitcoin/pull/27561.
sys.path.append(tests_dir)
- if not skipunit:
- print("Running Unit Tests for Test Framework Modules")
- test_framework_tests = unittest.TestSuite()
- for module in TEST_FRAMEWORK_MODULES:
- test_framework_tests.addTest(unittest.TestLoader().loadTestsFromName("test_framework.{}".format(module)))
- result = unittest.TextTestRunner(verbosity=1, failfast=True).run(test_framework_tests)
- if not result.wasSuccessful():
- sys.exit("Early exiting after failure in TestFramework unit tests")
-
flags = ['--cachedir={}'.format(cache_dir)] + args
if enable_coverage:
diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py
index 558d63e85c..a635175e7c 100755
--- a/test/fuzz/test_runner.py
+++ b/test/fuzz/test_runner.py
@@ -11,6 +11,7 @@ import argparse
import configparser
import logging
import os
+import platform
import random
import subprocess
import sys
@@ -18,7 +19,7 @@ import sys
def get_fuzz_env(*, target, source_dir):
symbolizer = os.environ.get('LLVM_SYMBOLIZER_PATH', "/usr/bin/llvm-symbolizer")
- return {
+ fuzz_env = {
'FUZZ': target,
'UBSAN_OPTIONS':
f'suppressions={source_dir}/test/sanitizer_suppressions/ubsan:print_stacktrace=1:halt_on_error=1:report_error_type=1',
@@ -27,6 +28,10 @@ def get_fuzz_env(*, target, source_dir):
'ASAN_SYMBOLIZER_PATH':symbolizer,
'MSAN_SYMBOLIZER_PATH':symbolizer,
}
+ if platform.system() == "Windows":
+ # On Windows, `env` option must include valid `SystemRoot`.
+ fuzz_env = {**fuzz_env, 'SystemRoot': os.environ.get('SystemRoot')}
+ return fuzz_env
def main():
diff --git a/test/lint/commit-script-check.sh b/test/lint/commit-script-check.sh
index 55c9528dea..fe845ed19e 100755
--- a/test/lint/commit-script-check.sh
+++ b/test/lint/commit-script-check.sh
@@ -22,6 +22,11 @@ if ! sed --help 2>&1 | grep -q 'GNU'; then
exit 1;
fi
+if ! grep --help 2>&1 | grep -q 'GNU'; then
+ echo "Error: the installed grep package is not compatible. Please make sure you have GNU grep installed in your system.";
+ exit 1;
+fi
+
RET=0
PREV_BRANCH=$(git name-rev --name-only HEAD)
PREV_HEAD=$(git rev-parse HEAD)