aboutsummaryrefslogtreecommitdiff
path: root/test/functional
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional')
-rwxr-xr-xtest/functional/feature_asmap.py9
-rwxr-xr-xtest/functional/feature_assumeutxo.py4
-rwxr-xr-xtest/functional/feature_filelock.py6
-rwxr-xr-xtest/functional/feature_segwit.py23
-rwxr-xr-xtest/functional/interface_rest.py2
-rwxr-xr-xtest/functional/mempool_dust.py3
-rwxr-xr-xtest/functional/mempool_limit.py16
-rwxr-xr-xtest/functional/mempool_sigoplimit.py6
-rwxr-xr-xtest/functional/p2p_filter.py17
-rwxr-xr-xtest/functional/p2p_node_network_limited.py12
-rwxr-xr-xtest/functional/p2p_timeouts.py15
-rwxr-xr-xtest/functional/p2p_v2_transport.py16
-rwxr-xr-xtest/functional/rpc_blockchain.py34
-rwxr-xr-xtest/functional/rpc_net.py32
-rwxr-xr-xtest/functional/rpc_packages.py20
-rwxr-xr-xtest/functional/rpc_rawtransaction.py13
-rw-r--r--test/functional/test_framework/blockfilter.py2
-rw-r--r--test/functional/test_framework/script.py5
-rwxr-xr-xtest/functional/test_framework/test_framework.py8
-rwxr-xr-xtest/functional/test_framework/test_node.py42
-rw-r--r--test/functional/test_framework/util.py5
-rw-r--r--test/functional/test_framework/wallet.py3
-rwxr-xr-xtest/functional/test_runner.py8
-rwxr-xr-xtest/functional/wallet_avoidreuse.py2
-rwxr-xr-xtest/functional/wallet_basic.py2
-rwxr-xr-xtest/functional/wallet_fast_rescan.py4
-rwxr-xr-xtest/functional/wallet_listtransactions.py29
-rwxr-xr-xtest/functional/wallet_reindex.py108
28 files changed, 349 insertions, 97 deletions
diff --git a/test/functional/feature_asmap.py b/test/functional/feature_asmap.py
index 9cff8042a8..ae483fe449 100755
--- a/test/functional/feature_asmap.py
+++ b/test/functional/feature_asmap.py
@@ -111,6 +111,14 @@ class AsmapTest(BitcoinTestFramework):
self.node.assert_start_raises_init_error(extra_args=['-asmap'], expected_msg=msg)
os.remove(self.default_asmap)
+ def test_asmap_health_check(self):
+ self.log.info('Test bitcoind -asmap logs ASMap Health Check with basic stats')
+ shutil.copyfile(self.asmap_raw, self.default_asmap)
+ msg = "ASMap Health Check: 2 clearnet peers are mapped to 1 ASNs with 0 peers being unmapped"
+ with self.node.assert_debug_log(expected_msgs=[msg]):
+ self.start_node(0, extra_args=['-asmap'])
+ os.remove(self.default_asmap)
+
def run_test(self):
self.node = self.nodes[0]
self.datadir = self.node.chain_path
@@ -124,6 +132,7 @@ class AsmapTest(BitcoinTestFramework):
self.test_asmap_interaction_with_addrman_containing_entries()
self.test_default_asmap_with_missing_file()
self.test_empty_asmap()
+ self.test_asmap_health_check()
if __name__ == '__main__':
diff --git a/test/functional/feature_assumeutxo.py b/test/functional/feature_assumeutxo.py
index d1232c5133..2e3589b020 100755
--- a/test/functional/feature_assumeutxo.py
+++ b/test/functional/feature_assumeutxo.py
@@ -75,7 +75,7 @@ class AssumeutxoTest(BitcoinTestFramework):
with self.nodes[1].assert_debug_log([log_msg]):
assert_raises_rpc_error(-32603, f"Unable to load UTXO snapshot{rpc_details}", self.nodes[1].loadtxoutset, bad_snapshot_path)
- self.log.info(" - snapshot file refering to a block that is not in the assumeutxo parameters")
+ self.log.info(" - snapshot file referring to a block that is not in the assumeutxo parameters")
prev_block_hash = self.nodes[0].getblockhash(SNAPSHOT_BASE_HEIGHT - 1)
bogus_block_hash = "0" * 64 # Represents any unknown block hash
for bad_block_hash in [bogus_block_hash, prev_block_hash]:
@@ -112,7 +112,7 @@ class AssumeutxoTest(BitcoinTestFramework):
def test_invalid_chainstate_scenarios(self):
self.log.info("Test different scenarios of invalid snapshot chainstate in datadir")
- self.log.info(" - snapshot chainstate refering to a block that is not in the assumeutxo parameters")
+ self.log.info(" - snapshot chainstate referring to a block that is not in the assumeutxo parameters")
self.stop_node(0)
chainstate_snapshot_path = self.nodes[0].chain_path / "chainstate_snapshot"
chainstate_snapshot_path.mkdir()
diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py
index 24a68a04bf..567207915e 100755
--- a/test/functional/feature_filelock.py
+++ b/test/functional/feature_filelock.py
@@ -30,6 +30,12 @@ class FilelockTest(BitcoinTestFramework):
expected_msg = f"Error: Cannot obtain a lock on data directory {datadir}. {self.config['environment']['PACKAGE_NAME']} is probably already running."
self.nodes[1].assert_start_raises_init_error(extra_args=[f'-datadir={self.nodes[0].datadir_path}', '-noserver'], expected_msg=expected_msg)
+ self.log.info("Check that cookie and PID file are not deleted when attempting to start a second bitcoind using the same datadir")
+ cookie_file = datadir / ".cookie"
+ assert cookie_file.exists() # should not be deleted during the second bitcoind instance shutdown
+ pid_file = datadir / "bitcoind.pid"
+ assert pid_file.exists()
+
if self.is_wallet_compiled():
def check_wallet_filelock(descriptors):
wallet_name = ''.join([random.choice(string.ascii_lowercase) for _ in range(6)])
diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py
index 6c467fa613..4dc19222c4 100755
--- a/test/functional/feature_segwit.py
+++ b/test/functional/feature_segwit.py
@@ -88,14 +88,11 @@ class SegWitTest(BitcoinTestFramework):
self.extra_args = [
[
"-acceptnonstdtxn=1",
- "-rpcserialversion=0",
- "-deprecatedrpc=serialversion",
"-testactivationheight=segwit@165",
"-addresstype=legacy",
],
[
"-acceptnonstdtxn=1",
- "-rpcserialversion=1",
"-testactivationheight=segwit@165",
"-addresstype=legacy",
],
@@ -224,18 +221,6 @@ class SegWitTest(BitcoinTestFramework):
self.fail_accept(self.nodes[0], "mandatory-script-verify-flag-failed (Witness program hash mismatch)", p2sh_ids[NODE_0][P2WPKH][0], sign=False, redeem_script=witness_script(False, self.pubkey[0]))
self.fail_accept(self.nodes[0], "mandatory-script-verify-flag-failed (Witness program was passed an empty witness)", p2sh_ids[NODE_0][P2WSH][0], sign=False, redeem_script=witness_script(True, self.pubkey[0]))
- self.log.info("Verify block and transaction serialization rpcs return differing serializations depending on rpc serialization flag")
- assert self.nodes[2].getblock(blockhash, False) != self.nodes[0].getblock(blockhash, False)
- assert self.nodes[1].getblock(blockhash, False) == self.nodes[2].getblock(blockhash, False)
-
- for tx_id in segwit_tx_list:
- tx = tx_from_hex(self.nodes[2].gettransaction(tx_id)["hex"])
- assert self.nodes[2].getrawtransaction(tx_id, False, blockhash) != self.nodes[0].getrawtransaction(tx_id, False, blockhash)
- assert self.nodes[1].getrawtransaction(tx_id, False, blockhash) == self.nodes[2].getrawtransaction(tx_id, False, blockhash)
- assert self.nodes[0].getrawtransaction(tx_id, False, blockhash) != self.nodes[2].gettransaction(tx_id)["hex"]
- assert self.nodes[1].getrawtransaction(tx_id, False, blockhash) == self.nodes[2].gettransaction(tx_id)["hex"]
- assert self.nodes[0].getrawtransaction(tx_id, False, blockhash) == tx.serialize_without_witness().hex()
-
# Coinbase contains the witness commitment nonce, check that RPC shows us
coinbase_txid = self.nodes[2].getblock(blockhash)['tx'][0]
coinbase_tx = self.nodes[2].gettransaction(txid=coinbase_txid, verbose=True)
@@ -276,9 +261,6 @@ class SegWitTest(BitcoinTestFramework):
# tx3 (non-segwit input, paying to a non-segwit output).
# tx1 is allowed to appear in the block, but no others.
txid1 = send_to_witness(1, self.nodes[0], find_spendable_utxo(self.nodes[0], 50), self.pubkey[0], False, Decimal("49.996"))
- hex_tx = self.nodes[0].gettransaction(txid)['hex']
- tx = tx_from_hex(hex_tx)
- assert tx.wit.is_null() # This should not be a segwit input
assert txid1 in self.nodes[0].getrawmempool()
tx1_hex = self.nodes[0].gettransaction(txid1)['hex']
@@ -613,11 +595,6 @@ class SegWitTest(BitcoinTestFramework):
assert_equal(self.nodes[1].gettransaction(txid, True)["txid"], txid)
assert_equal(self.nodes[1].listtransactions("*", 1, 0, True)[0]["txid"], txid)
- self.log.info('Test negative and unknown rpcserialversion throw an init error')
- self.stop_node(0)
- self.nodes[0].assert_start_raises_init_error(["-rpcserialversion=-1"], "Error: rpcserialversion must be non-negative.")
- self.nodes[0].assert_start_raises_init_error(["-rpcserialversion=100"], "Error: Unknown rpcserialversion requested.")
-
def mine_and_test_listunspent(self, script_list, ismine):
utxo = find_spendable_utxo(self.nodes[0], 50)
tx = CTransaction()
diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py
index c0679c5ba9..b81eae2506 100755
--- a/test/functional/interface_rest.py
+++ b/test/functional/interface_rest.py
@@ -65,7 +65,7 @@ class RESTTest (BitcoinTestFramework):
body: str = '',
status: int = 200,
ret_type: RetType = RetType.JSON,
- query_params: Optional[typing.Dict[str, typing.Any]] = None,
+ query_params: Optional[dict[str, typing.Any]] = None,
) -> typing.Union[http.client.HTTPResponse, bytes, str, None]:
rest_uri = '/rest' + uri
if req_type in ReqType:
diff --git a/test/functional/mempool_dust.py b/test/functional/mempool_dust.py
index f4e385a112..e0c026207a 100755
--- a/test/functional/mempool_dust.py
+++ b/test/functional/mempool_dust.py
@@ -40,6 +40,7 @@ DUST_RELAY_TX_FEE = 3000 # default setting [sat/kvB]
class DustRelayFeeTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
+ self.extra_args = [['-permitbaremultisig']]
def test_dust_output(self, node: TestNode, dust_relay_fee: Decimal,
output_script: CScript, type_desc: str) -> None:
@@ -101,7 +102,7 @@ class DustRelayFeeTest(BitcoinTestFramework):
else:
dust_parameter = f"-dustrelayfee={dustfee_btc_kvb:.8f}"
self.log.info(f"Test dust limit setting {dust_parameter} ({dustfee_sat_kvb} sat/kvB)...")
- self.restart_node(0, extra_args=[dust_parameter])
+ self.restart_node(0, extra_args=[dust_parameter, "-permitbaremultisig"])
for output_script, description in output_scripts:
self.test_dust_output(self.nodes[0], dustfee_btc_kvb, output_script, description)
diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py
index a1147f70f3..6215610c31 100755
--- a/test/functional/mempool_limit.py
+++ b/test/functional/mempool_limit.py
@@ -125,8 +125,9 @@ class MempoolLimitTest(BitcoinTestFramework):
utxo_to_spend=tx_B["new_utxo"],
confirmed_only=True
)
-
- assert_raises_rpc_error(-26, "too-long-mempool-chain", node.submitpackage, [tx_B["hex"], tx_C["hex"]])
+ res = node.submitpackage([tx_B["hex"], tx_C["hex"]])
+ assert_equal(res["package_msg"], "transaction failed")
+ assert "too-long-mempool-chain" in res["tx-results"][tx_C["wtxid"]]["error"]
def test_mid_package_eviction(self):
node = self.nodes[0]
@@ -205,7 +206,7 @@ class MempoolLimitTest(BitcoinTestFramework):
# Package should be submitted, temporarily exceeding maxmempool, and then evicted.
with node.assert_debug_log(expected_msgs=["rolling minimum fee bumped"]):
- assert_raises_rpc_error(-26, "mempool full", node.submitpackage, package_hex)
+ assert_equal(node.submitpackage(package_hex)["package_msg"], "transaction failed")
# Maximum size must never be exceeded.
assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["bytes"])
@@ -273,7 +274,9 @@ class MempoolLimitTest(BitcoinTestFramework):
package_hex = [cpfp_parent["hex"], replacement_tx["hex"], child["hex"]]
# Package should be submitted, temporarily exceeding maxmempool, and then evicted.
- assert_raises_rpc_error(-26, "bad-txns-inputs-missingorspent", node.submitpackage, package_hex)
+ res = node.submitpackage(package_hex)
+ assert_equal(res["package_msg"], "transaction failed")
+ assert len([tx_res for _, tx_res in res["tx-results"].items() if "error" in tx_res and tx_res["error"] == "bad-txns-inputs-missingorspent"])
# Maximum size must never be exceeded.
assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["bytes"])
@@ -321,6 +324,7 @@ class MempoolLimitTest(BitcoinTestFramework):
package_txns.append(tx_child)
submitpackage_result = node.submitpackage([tx["hex"] for tx in package_txns])
+ assert_equal(submitpackage_result["package_msg"], "success")
rich_parent_result = submitpackage_result["tx-results"][tx_rich["wtxid"]]
poor_parent_result = submitpackage_result["tx-results"][tx_poor["wtxid"]]
@@ -366,7 +370,9 @@ class MempoolLimitTest(BitcoinTestFramework):
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"]])
+ res = node.submitpackage([tx_parent_just_below["hex"], tx_child_just_above["hex"]])
+ for wtxid in [tx_parent_just_below["wtxid"], tx_child_just_above["wtxid"]]:
+ assert_equal(res["tx-results"][wtxid]["error"], "mempool full")
self.log.info('Test passing a value below the minimum (5 MB) to -maxmempool throws an error')
self.stop_node(0)
diff --git a/test/functional/mempool_sigoplimit.py b/test/functional/mempool_sigoplimit.py
index fbec6d0dc8..2e7850fb40 100755
--- a/test/functional/mempool_sigoplimit.py
+++ b/test/functional/mempool_sigoplimit.py
@@ -34,7 +34,6 @@ from test_framework.util import (
assert_equal,
assert_greater_than,
assert_greater_than_or_equal,
- assert_raises_rpc_error,
)
from test_framework.wallet import MiniWallet
from test_framework.wallet_util import generate_keypair
@@ -140,7 +139,7 @@ class BytesPerSigOpTest(BitcoinTestFramework):
self.log.info("Test a overly-large sigops-vbyte hits package limits")
# Make a 2-transaction package which fails vbyte checks even though
# separately they would work.
- self.restart_node(0, extra_args=["-bytespersigop=5000"] + self.extra_args[0])
+ self.restart_node(0, extra_args=["-bytespersigop=5000","-permitbaremultisig=1"] + self.extra_args[0])
def create_bare_multisig_tx(utxo_to_spend=None):
_, pubkey = generate_keypair()
@@ -169,7 +168,8 @@ class BytesPerSigOpTest(BitcoinTestFramework):
assert_equal([x["package-error"] for x in packet_test], ["package-mempool-limits", "package-mempool-limits"])
# When we actually try to submit, the parent makes it into the mempool, but the child would exceed ancestor vsize limits
- assert_raises_rpc_error(-26, "too-long-mempool-chain", self.nodes[0].submitpackage, [tx_parent.serialize().hex(), tx_child.serialize().hex()])
+ res = self.nodes[0].submitpackage([tx_parent.serialize().hex(), tx_child.serialize().hex()])
+ assert "too-long-mempool-chain" in res["tx-results"][tx_child.getwtxid()]["error"]
assert tx_parent.rehash() in self.nodes[0].getrawmempool()
# Transactions are tiny in weight
diff --git a/test/functional/p2p_filter.py b/test/functional/p2p_filter.py
index 665f57365f..62d55cc101 100755
--- a/test/functional/p2p_filter.py
+++ b/test/functional/p2p_filter.py
@@ -11,6 +11,7 @@ from test_framework.messages import (
COIN,
MAX_BLOOM_FILTER_SIZE,
MAX_BLOOM_HASH_FUNCS,
+ MSG_WTX,
MSG_BLOCK,
MSG_FILTERED_BLOCK,
msg_filteradd,
@@ -135,14 +136,22 @@ class FilterTest(BitcoinTestFramework):
self.log.info("Check that a node with bloom filters enabled services p2p mempool messages")
filter_peer = P2PBloomFilter()
- self.log.debug("Create a tx relevant to the peer before connecting")
- txid = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=filter_peer.watch_script_pubkey, amount=9 * COIN)["txid"]
+ self.log.info("Create two tx before connecting, one relevant to the node another that is not")
+ rel_txid = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=filter_peer.watch_script_pubkey, amount=1 * COIN)["txid"]
+ irr_result = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=2 * COIN)
+ irr_txid = irr_result["txid"]
+ irr_wtxid = irr_result["wtxid"]
- self.log.debug("Send a mempool msg after connecting and check that the tx is received")
+ self.log.info("Send a mempool msg after connecting and check that the relevant tx is announced")
self.nodes[0].add_p2p_connection(filter_peer)
filter_peer.send_and_ping(filter_peer.watch_filter_init)
filter_peer.send_message(msg_mempool())
- filter_peer.wait_for_tx(txid)
+ filter_peer.wait_for_tx(rel_txid)
+
+ self.log.info("Request the irrelevant transaction even though it was not announced")
+ filter_peer.send_message(msg_getdata([CInv(t=MSG_WTX, h=int(irr_wtxid, 16))]))
+ self.log.info("We should get it anyway because it was in the mempool on connection to peer")
+ filter_peer.wait_for_tx(irr_txid)
def test_frelay_false(self, filter_peer):
self.log.info("Check that a node with fRelay set to false does not receive invs until the filter is set")
diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py
index a56afbcf7b..89c35e943b 100755
--- a/test/functional/p2p_node_network_limited.py
+++ b/test/functional/p2p_node_network_limited.py
@@ -8,7 +8,15 @@ Tests that a node configured with -prune=550 signals NODE_NETWORK_LIMITED correc
and that it responds to getdata requests for blocks correctly:
- send a block within 288 + 2 of the tip
- disconnect peers who request blocks older than that."""
-from test_framework.messages import CInv, MSG_BLOCK, msg_getdata, msg_verack, NODE_NETWORK_LIMITED, NODE_WITNESS
+from test_framework.messages import (
+ CInv,
+ MSG_BLOCK,
+ NODE_NETWORK_LIMITED,
+ NODE_P2P_V2,
+ NODE_WITNESS,
+ msg_getdata,
+ msg_verack,
+)
from test_framework.p2p import P2PInterface
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
@@ -50,6 +58,8 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
node = self.nodes[0].add_p2p_connection(P2PIgnoreInv())
expected_services = NODE_WITNESS | NODE_NETWORK_LIMITED
+ if self.options.v2transport:
+ expected_services |= NODE_P2P_V2
self.log.info("Check that node has signalled expected services.")
assert_equal(node.nServices, expected_services)
diff --git a/test/functional/p2p_timeouts.py b/test/functional/p2p_timeouts.py
index a308577c02..b4fa5099d8 100755
--- a/test/functional/p2p_timeouts.py
+++ b/test/functional/p2p_timeouts.py
@@ -68,11 +68,14 @@ class TimeoutsTest(BitcoinTestFramework):
with self.nodes[0].assert_debug_log(['Unsupported message "ping" prior to verack from peer=0']):
no_verack_node.send_message(msg_ping())
- with self.nodes[0].assert_debug_log(['non-version message before version handshake. Message "ping" from peer=1']):
- no_version_node.send_message(msg_ping())
- self.mock_forward(1)
+ # With v2, non-version messages before the handshake would be interpreted as part of the key exchange.
+ # Therefore, don't execute this part of the test if v2transport is chosen.
+ if not self.options.v2transport:
+ with self.nodes[0].assert_debug_log(['non-version message before version handshake. Message "ping" from peer=1']):
+ no_version_node.send_message(msg_ping())
+ self.mock_forward(1)
assert "version" in no_verack_node.last_message
assert no_verack_node.is_connected
@@ -80,11 +83,12 @@ class TimeoutsTest(BitcoinTestFramework):
assert no_send_node.is_connected
no_verack_node.send_message(msg_ping())
- no_version_node.send_message(msg_ping())
+ if not self.options.v2transport:
+ no_version_node.send_message(msg_ping())
expected_timeout_logs = [
"version handshake timeout peer=0",
- "socket no message in first 3 seconds, 1 0 peer=1",
+ f"socket no message in first 3 seconds, {'0' if self.options.v2transport else '1'} 0 peer=1",
"socket no message in first 3 seconds, 0 0 peer=2",
]
@@ -100,5 +104,6 @@ class TimeoutsTest(BitcoinTestFramework):
extra_args=['-peertimeout=0'],
)
+
if __name__ == '__main__':
TimeoutsTest().main()
diff --git a/test/functional/p2p_v2_transport.py b/test/functional/p2p_v2_transport.py
index 1a3b4a6d0a..72d22cb77f 100755
--- a/test/functional/p2p_v2_transport.py
+++ b/test/functional/p2p_v2_transport.py
@@ -133,9 +133,8 @@ class V2TransportTest(BitcoinTestFramework):
V1_PREFIX = MAGIC_BYTES["regtest"] + b"version\x00\x00\x00\x00\x00"
assert_equal(len(V1_PREFIX), 16)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
- num_peers = len(self.nodes[0].getpeerinfo())
- s.connect(("127.0.0.1", p2p_port(0)))
- self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == num_peers + 1)
+ with self.nodes[0].wait_for_new_peer():
+ s.connect(("127.0.0.1", p2p_port(0)))
s.sendall(V1_PREFIX[:-1])
assert_equal(self.nodes[0].getpeerinfo()[-1]["transport_protocol_type"], "detecting")
s.sendall(bytes([V1_PREFIX[-1]])) # send out last prefix byte
@@ -144,22 +143,23 @@ class V2TransportTest(BitcoinTestFramework):
# Check wrong network prefix detection (hits if the next 12 bytes correspond to a v1 version message)
wrong_network_magic_prefix = MAGIC_BYTES["signet"] + V1_PREFIX[4:]
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
- s.connect(("127.0.0.1", p2p_port(0)))
+ with self.nodes[0].wait_for_new_peer():
+ s.connect(("127.0.0.1", p2p_port(0)))
with self.nodes[0].assert_debug_log(["V2 transport error: V1 peer with wrong MessageStart"]):
s.sendall(wrong_network_magic_prefix + b"somepayload")
# Check detection of missing garbage terminator (hits after fixed amount of data if terminator never matches garbage)
MAX_KEY_GARB_AND_GARBTERM_LEN = 64 + 4095 + 16
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
- num_peers = len(self.nodes[0].getpeerinfo())
- s.connect(("127.0.0.1", p2p_port(0)))
- self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == num_peers + 1)
+ with self.nodes[0].wait_for_new_peer():
+ s.connect(("127.0.0.1", p2p_port(0)))
s.sendall(b'\x00' * (MAX_KEY_GARB_AND_GARBTERM_LEN - 1))
self.wait_until(lambda: self.nodes[0].getpeerinfo()[-1]["bytesrecv"] == MAX_KEY_GARB_AND_GARBTERM_LEN - 1)
with self.nodes[0].assert_debug_log(["V2 transport error: missing garbage terminator"]):
+ peer_id = self.nodes[0].getpeerinfo()[-1]["id"]
s.sendall(b'\x00') # send out last byte
# should disconnect immediately
- self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == num_peers)
+ self.wait_until(lambda: not peer_id in [p["id"] for p in self.nodes[0].getpeerinfo()])
if __name__ == '__main__':
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index 8eb9f3aeb1..9b7743cafa 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -437,7 +437,6 @@ class BlockchainTest(BitcoinTestFramework):
def _test_getnetworkhashps(self):
self.log.info("Test getnetworkhashps")
- hashes_per_second = self.nodes[0].getnetworkhashps()
assert_raises_rpc_error(
-3,
textwrap.dedent("""
@@ -449,17 +448,46 @@ class BlockchainTest(BitcoinTestFramework):
""").strip(),
lambda: self.nodes[0].getnetworkhashps("a", []),
)
+ assert_raises_rpc_error(
+ -8,
+ "Block does not exist at specified height",
+ lambda: self.nodes[0].getnetworkhashps(100, self.nodes[0].getblockcount() + 1),
+ )
+ assert_raises_rpc_error(
+ -8,
+ "Block does not exist at specified height",
+ lambda: self.nodes[0].getnetworkhashps(100, -10),
+ )
+ assert_raises_rpc_error(
+ -8,
+ "Invalid nblocks. Must be a positive number or -1.",
+ lambda: self.nodes[0].getnetworkhashps(-100),
+ )
+ assert_raises_rpc_error(
+ -8,
+ "Invalid nblocks. Must be a positive number or -1.",
+ lambda: self.nodes[0].getnetworkhashps(0),
+ )
+
+ # Genesis block height estimate should return 0
+ hashes_per_second = self.nodes[0].getnetworkhashps(100, 0)
+ assert_equal(hashes_per_second, 0)
+
# This should be 2 hashes every 10 minutes or 1/300
+ hashes_per_second = self.nodes[0].getnetworkhashps()
assert abs(hashes_per_second * 300 - 1) < 0.0001
- # Test setting the first param of getnetworkhashps to negative value returns the average network
+ # Test setting the first param of getnetworkhashps to -1 returns the average network
# hashes per second from the last difficulty change.
current_block_height = self.nodes[0].getmininginfo()['blocks']
blocks_since_last_diff_change = current_block_height % DIFFICULTY_ADJUSTMENT_INTERVAL + 1
expected_hashes_per_second_since_diff_change = self.nodes[0].getnetworkhashps(blocks_since_last_diff_change)
assert_equal(self.nodes[0].getnetworkhashps(-1), expected_hashes_per_second_since_diff_change)
- assert_equal(self.nodes[0].getnetworkhashps(-2), expected_hashes_per_second_since_diff_change)
+
+ # Ensure long lookups get truncated to chain length
+ hashes_per_second = self.nodes[0].getnetworkhashps(self.nodes[0].getblockcount() + 1000)
+ assert hashes_per_second > 0.003
def _test_stopatheight(self):
self.log.info("Test stopping at height")
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index 773ab3b50e..e1820b0f55 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -9,6 +9,7 @@ Tests correspond to code in rpc/net.cpp.
from decimal import Decimal
from itertools import product
+import platform
import time
import test_framework.messages
@@ -110,7 +111,7 @@ class NetTest(BitcoinTestFramework):
no_version_peer_id = 2
no_version_peer_conntime = int(time.time())
self.nodes[0].setmocktime(no_version_peer_conntime)
- with self.nodes[0].assert_debug_log([f"Added connection peer={no_version_peer_id}"]):
+ with self.nodes[0].wait_for_new_peer():
no_version_peer = self.nodes[0].add_p2p_connection(P2PInterface(), send_version=False, wait_for_verack=False)
self.nodes[0].setmocktime(0)
peer_info = self.nodes[0].getpeerinfo()[no_version_peer_id]
@@ -150,7 +151,7 @@ class NetTest(BitcoinTestFramework):
"synced_blocks": -1,
"synced_headers": -1,
"timeoffset": 0,
- "transport_protocol_type": "v1",
+ "transport_protocol_type": "v1" if not self.options.v2transport else "detecting",
"version": 0,
},
)
@@ -160,19 +161,23 @@ class NetTest(BitcoinTestFramework):
def test_getnettotals(self):
self.log.info("Test getnettotals")
# Test getnettotals and getpeerinfo by doing a ping. The bytes
- # sent/received should increase by at least the size of one ping (32
- # bytes) and one pong (32 bytes).
+ # sent/received should increase by at least the size of one ping
+ # and one pong. Both have a payload size of 8 bytes, but the total
+ # size depends on the used p2p version:
+ # - p2p v1: 24 bytes (header) + 8 bytes (payload) = 32 bytes
+ # - p2p v2: 21 bytes (header/tag with short-id) + 8 bytes (payload) = 29 bytes
+ ping_size = 32 if not self.options.v2transport else 29
net_totals_before = self.nodes[0].getnettotals()
peer_info_before = self.nodes[0].getpeerinfo()
self.nodes[0].ping()
- self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytessent'] >= net_totals_before['totalbytessent'] + 32 * 2), timeout=1)
- self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytesrecv'] >= net_totals_before['totalbytesrecv'] + 32 * 2), timeout=1)
+ self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytessent'] >= net_totals_before['totalbytessent'] + ping_size * 2), timeout=1)
+ self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytesrecv'] >= net_totals_before['totalbytesrecv'] + ping_size * 2), timeout=1)
for peer_before in peer_info_before:
peer_after = lambda: next(p for p in self.nodes[0].getpeerinfo() if p['id'] == peer_before['id'])
- self.wait_until(lambda: peer_after()['bytesrecv_per_msg'].get('pong', 0) >= peer_before['bytesrecv_per_msg'].get('pong', 0) + 32, timeout=1)
- self.wait_until(lambda: peer_after()['bytessent_per_msg'].get('ping', 0) >= peer_before['bytessent_per_msg'].get('ping', 0) + 32, timeout=1)
+ self.wait_until(lambda: peer_after()['bytesrecv_per_msg'].get('pong', 0) >= peer_before['bytesrecv_per_msg'].get('pong', 0) + ping_size, timeout=1)
+ self.wait_until(lambda: peer_after()['bytessent_per_msg'].get('ping', 0) >= peer_before['bytessent_per_msg'].get('ping', 0) + ping_size, timeout=1)
def test_getnetworkinfo(self):
self.log.info("Test getnetworkinfo")
@@ -216,8 +221,10 @@ class NetTest(BitcoinTestFramework):
ip_port = "127.0.0.1:{}".format(p2p_port(2))
self.nodes[0].addnode(node=ip_port, command='add')
# try to add an equivalent ip
- ip_port2 = "127.1:{}".format(p2p_port(2))
- assert_raises_rpc_error(-23, "Node already added", self.nodes[0].addnode, node=ip_port2, command='add')
+ # (note that OpenBSD doesn't support the IPv4 shorthand notation with omitted zero-bytes)
+ if platform.system() != "OpenBSD":
+ ip_port2 = "127.1:{}".format(p2p_port(2))
+ assert_raises_rpc_error(-23, "Node already added", self.nodes[0].addnode, node=ip_port2, command='add')
# check that the node has indeed been added
added_nodes = self.nodes[0].getaddednodeinfo()
assert_equal(len(added_nodes), 1)
@@ -345,7 +352,10 @@ class NetTest(BitcoinTestFramework):
node = self.nodes[0]
self.restart_node(0)
- self.connect_nodes(0, 1)
+ # we want to use a p2p v1 connection here in order to ensure
+ # a peer id of zero (a downgrade from v2 to v1 would lead
+ # to an increase of the peer id)
+ self.connect_nodes(0, 1, peer_advertises_v2=False)
self.log.info("Test sendmsgtopeer")
self.log.debug("Send a valid message")
diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py
index 5644a9f5a8..664f2df3f1 100755
--- a/test/functional/rpc_packages.py
+++ b/test/functional/rpc_packages.py
@@ -304,6 +304,7 @@ class RPCPackagesTest(BitcoinTestFramework):
submitpackage_result = node.submitpackage(package=[tx["hex"] for tx in package_txns])
# Check that each result is present, with the correct size and fees
+ assert_equal(submitpackage_result["package_msg"], "success")
for package_txn in package_txns:
tx = package_txn["tx"]
assert tx.getwtxid() in submitpackage_result["tx-results"]
@@ -334,9 +335,26 @@ class RPCPackagesTest(BitcoinTestFramework):
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)]
+ legacy_pool = node.getrawmempool()
+ chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=3)]
assert_raises_rpc_error(-25, "package topology disallowed", node.submitpackage, chain_hex)
+ assert_equal(legacy_pool, node.getrawmempool())
+ # Create a transaction chain such as only the parent gets accepted (by making the child's
+ # version non-standard). Make sure the parent does get broadcast.
+ self.log.info("If a package is partially submitted, transactions included in mempool get broadcast")
+ peer = node.add_p2p_connection(P2PTxInvStore())
+ txs = self.wallet.create_self_transfer_chain(chain_length=2)
+ bad_child = tx_from_hex(txs[1]["hex"])
+ bad_child.nVersion = -1
+ hex_partial_acceptance = [txs[0]["hex"], bad_child.serialize().hex()]
+ res = node.submitpackage(hex_partial_acceptance)
+ assert_equal(res["package_msg"], "transaction failed")
+ first_wtxid = txs[0]["tx"].getwtxid()
+ assert "error" not in res["tx-results"][first_wtxid]
+ sec_wtxid = bad_child.getwtxid()
+ assert_equal(res["tx-results"][sec_wtxid]["error"], "version")
+ peer.wait_for_broadcast([first_wtxid])
if __name__ == "__main__":
RPCPackagesTest().main()
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index 2395935620..c12865b5e3 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -32,6 +32,7 @@ from test_framework.script import (
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
+ assert_greater_than,
assert_raises_rpc_error,
)
from test_framework.wallet import (
@@ -70,7 +71,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.extra_args = [
["-txindex"],
["-txindex"],
- [],
+ ["-fastprune", "-prune=1"],
]
# whitelist all peers to speed up tx relay / mempool sync
for args in self.extra_args:
@@ -85,7 +86,6 @@ class RawTransactionsTest(BitcoinTestFramework):
self.wallet = MiniWallet(self.nodes[0])
self.getrawtransaction_tests()
- self.getrawtransaction_verbosity_tests()
self.createrawtransaction_tests()
self.sendrawtransaction_tests()
self.sendrawtransaction_testmempoolaccept_tests()
@@ -94,6 +94,8 @@ class RawTransactionsTest(BitcoinTestFramework):
if self.is_specified_wallet_compiled() and not self.options.descriptors:
self.import_deterministic_coinbase_privkeys()
self.raw_multisig_transaction_legacy_tests()
+ self.getrawtransaction_verbosity_tests()
+
def getrawtransaction_tests(self):
tx = self.wallet.send_self_transfer(from_node=self.nodes[0])
@@ -243,6 +245,13 @@ class RawTransactionsTest(BitcoinTestFramework):
coin_base = self.nodes[1].getblock(block1)['tx'][0]
gottx = self.nodes[1].getrawtransaction(txid=coin_base, verbosity=2, blockhash=block1)
assert 'fee' not in gottx
+ # check that verbosity 2 for a mempool tx will fallback to verbosity 1
+ # Do this with a pruned chain, as a regression test for https://github.com/bitcoin/bitcoin/pull/29003
+ self.generate(self.nodes[2], 400)
+ assert_greater_than(self.nodes[2].pruneblockchain(250), 0)
+ mempool_tx = self.wallet.send_self_transfer(from_node=self.nodes[2])['txid']
+ gottx = self.nodes[2].getrawtransaction(txid=mempool_tx, verbosity=2)
+ assert 'fee' not in gottx
def createrawtransaction_tests(self):
self.log.info("Test createrawtransaction")
diff --git a/test/functional/test_framework/blockfilter.py b/test/functional/test_framework/blockfilter.py
index 3d6b38a23d..a16aa3d34f 100644
--- a/test/functional/test_framework/blockfilter.py
+++ b/test/functional/test_framework/blockfilter.py
@@ -29,7 +29,7 @@ def bip158_basic_element_hash(script_pub_key, N, block_hash):
def bip158_relevant_scriptpubkeys(node, block_hash):
- """ Determines the basic filter relvant scriptPubKeys as defined in BIP158:
+ """ Determines the basic filter relevant scriptPubKeys as defined in BIP158:
'A basic filter MUST contain exactly the following items for each transaction in a block:
- The previous output script (the script being spent) for each input, except for
diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py
index f4628bf4af..78d8580794 100644
--- a/test/functional/test_framework/script.py
+++ b/test/functional/test_framework/script.py
@@ -10,7 +10,6 @@ This file is modified from python-bitcoinlib.
from collections import namedtuple
import struct
import unittest
-from typing import List, Dict
from .key import TaggedHash, tweak_add_pubkey, compute_xonly_pubkey
@@ -110,8 +109,8 @@ class CScriptOp(int):
_opcode_instances.append(super().__new__(cls, n))
return _opcode_instances[n]
-OPCODE_NAMES: Dict[CScriptOp, str] = {}
-_opcode_instances: List[CScriptOp] = []
+OPCODE_NAMES: dict[CScriptOp, str] = {}
+_opcode_instances: list[CScriptOp] = []
# Populate opcode instance table
for n in range(0xff + 1):
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 70b3943478..0ee332b75b 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -19,7 +19,6 @@ import sys
import tempfile
import time
-from typing import List
from .address import create_deterministic_address_bcrt1_p2tr_op_true
from .authproxy import JSONRPCException
from . import coverage
@@ -97,7 +96,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
"""Sets test framework defaults. Do not override this method. Instead, override the set_test_params() method"""
self.chain: str = 'regtest'
self.setup_clean_chain: bool = False
- self.nodes: List[TestNode] = []
+ self.nodes: list[TestNode] = []
self.extra_args = None
self.network_thread = None
self.rpc_timeout = 60 # Wait for up to 60 seconds for the RPC server to respond
@@ -508,8 +507,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
assert_equal(len(binary_cli), num_nodes)
for i in range(num_nodes):
args = list(extra_args[i])
- if self.options.v2transport and ("-v2transport=0" not in args):
- args.append("-v2transport=1")
test_node_i = TestNode(
i,
get_datadir_path(self.options.tmpdir, i),
@@ -528,6 +525,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
start_perf=self.options.perf,
use_valgrind=self.options.valgrind,
descriptors=self.options.descriptors,
+ v2transport=self.options.v2transport,
)
self.nodes.append(test_node_i)
if not test_node_i.version_is_at_least(170000):
@@ -602,7 +600,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
ip_port = "127.0.0.1:" + str(p2p_port(b))
if peer_advertises_v2 is None:
- peer_advertises_v2 = self.options.v2transport
+ peer_advertises_v2 = from_connection.use_v2transport
if peer_advertises_v2:
from_connection.addnode(node=ip_port, command="onetry", v2transport=True)
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 281469f9ed..b3f777b9df 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -67,7 +67,7 @@ class TestNode():
To make things easier for the test writer, any unrecognised messages will
be dispatched to the RPC connection."""
- def __init__(self, i, datadir_path, *, chain, rpchost, timewait, timeout_factor, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False):
+ def __init__(self, i, datadir_path, *, chain, rpchost, timewait, timeout_factor, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False, v2transport=False):
"""
Kwargs:
start_perf (bool): If True, begin profiling the node with `perf` as soon as
@@ -126,6 +126,12 @@ class TestNode():
if self.version_is_at_least(239000):
self.args.append("-loglevel=trace")
+ # Default behavior from global -v2transport flag is added to args to persist it over restarts.
+ # May be overwritten in individual tests, using extra_args.
+ self.default_to_v2 = v2transport
+ if self.default_to_v2:
+ self.args.append("-v2transport=1")
+
self.cli = TestNodeCLI(bitcoin_cli, self.datadir_path)
self.use_cli = use_cli
self.start_perf = start_perf
@@ -198,6 +204,8 @@ class TestNode():
if extra_args is None:
extra_args = self.extra_args
+ self.use_v2transport = "-v2transport=1" in extra_args or (self.default_to_v2 and "-v2transport=0" not in extra_args)
+
# Add a new stdout and stderr file each time bitcoind is started
if stderr is None:
stderr = tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False)
@@ -252,7 +260,7 @@ class TestNode():
if self.version_is_at_least(190000):
# getmempoolinfo.loaded is available since commit
# bb8ae2c (version 0.19.0)
- wait_until_helper_internal(lambda: rpc.getmempoolinfo()['loaded'], timeout_factor=self.timeout_factor)
+ self.wait_until(lambda: rpc.getmempoolinfo()['loaded'])
# Wait for the node to finish reindex, block import, and
# loading the mempool. Usually importing happens fast or
# even "immediate" when the node is started. However, there
@@ -406,7 +414,7 @@ class TestNode():
def wait_until_stopped(self, *, timeout=BITCOIND_PROC_WAIT_TIMEOUT, expect_error=False, **kwargs):
expected_ret_code = 1 if expect_error else 0 # Whether node shutdown return EXIT_FAILURE or EXIT_SUCCESS
- wait_until_helper_internal(lambda: self.is_node_stopped(expected_ret_code=expected_ret_code, **kwargs), timeout=timeout, timeout_factor=self.timeout_factor)
+ self.wait_until(lambda: self.is_node_stopped(expected_ret_code=expected_ret_code, **kwargs), timeout=timeout)
def replace_in_config(self, replacements):
"""
@@ -512,6 +520,23 @@ class TestNode():
str(expected_msgs), print_log))
@contextlib.contextmanager
+ def wait_for_new_peer(self, timeout=5):
+ """
+ Wait until the node is connected to at least one new peer. We detect this
+ by watching for an increased highest peer id, using the `getpeerinfo` RPC call.
+ Note that the simpler approach of only accounting for the number of peers
+ suffers from race conditions, as disconnects from unrelated previous peers
+ could happen anytime in-between.
+ """
+ def get_highest_peer_id():
+ peer_info = self.getpeerinfo()
+ return peer_info[-1]["id"] if peer_info else -1
+
+ initial_peer_id = get_highest_peer_id()
+ yield
+ self.wait_until(lambda: get_highest_peer_id() > initial_peer_id, timeout=timeout)
+
+ @contextlib.contextmanager
def profile_with_perf(self, profile_name: str):
"""
Context manager that allows easy profiling of node activity using `perf`.
@@ -721,7 +746,7 @@ class TestNode():
p.peer_disconnect()
del self.p2ps[:]
- wait_until_helper_internal(lambda: self.num_test_p2p_connections() == 0, timeout_factor=self.timeout_factor)
+ self.wait_until(lambda: self.num_test_p2p_connections() == 0)
def bumpmocktime(self, seconds):
"""Fast forward using setmocktime to self.mocktime + seconds. Requires setmocktime to have
@@ -730,6 +755,9 @@ class TestNode():
self.mocktime += seconds
self.setmocktime(self.mocktime)
+ def wait_until(self, test_function, timeout=60):
+ return wait_until_helper_internal(test_function, timeout=timeout, timeout_factor=self.timeout_factor)
+
class TestNodeCLIAttr:
def __init__(self, cli, command):
@@ -782,15 +810,15 @@ class TestNodeCLI():
results.append(dict(error=e))
return results
- def send_cli(self, command=None, *args, **kwargs):
+ def send_cli(self, clicommand=None, *args, **kwargs):
"""Run bitcoin-cli command. Deserializes returned string as python object."""
pos_args = [arg_to_cli(arg) for arg in args]
named_args = [str(key) + "=" + arg_to_cli(value) for (key, value) in kwargs.items()]
p_args = [self.binary, f"-datadir={self.datadir}"] + self.options
if named_args:
p_args += ["-named"]
- if command is not None:
- p_args += [command]
+ if clicommand is not None:
+ p_args += [clicommand]
p_args += pos_args + named_args
self.log.debug("Running bitcoin-cli {}".format(p_args[2:]))
process = subprocess.Popen(p_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index 104f158d92..c65e3e38e6 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -19,7 +19,8 @@ import time
from . import coverage
from .authproxy import AuthServiceProxy, JSONRPCException
-from typing import Callable, Optional, Tuple
+from collections.abc import Callable
+from typing import Optional
logger = logging.getLogger("TestFramework.utils")
@@ -409,7 +410,7 @@ def get_datadir_path(dirname, n):
return pathlib.Path(dirname) / f"node{n}"
-def get_temp_default_datadir(temp_dir: pathlib.Path) -> Tuple[dict, pathlib.Path]:
+def get_temp_default_datadir(temp_dir: pathlib.Path) -> tuple[dict, pathlib.Path]:
"""Return os-specific environment variables that can be set to make the
GetDefaultDataDir() function return a datadir path under the provided
temp_dir, as well as the complete path it would return."""
diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py
index 035a482f4c..53c8e1b0cc 100644
--- a/test/functional/test_framework/wallet.py
+++ b/test/functional/test_framework/wallet.py
@@ -9,7 +9,6 @@ from decimal import Decimal
from enum import Enum
from typing import (
Any,
- List,
Optional,
)
from test_framework.address import (
@@ -284,7 +283,7 @@ class MiniWallet:
def create_self_transfer_multi(
self,
*,
- utxos_to_spend: Optional[List[dict]] = None,
+ utxos_to_spend: Optional[list[dict]] = None,
num_outputs=1,
amount_per_output=0,
locktime=0,
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 35c662b0d9..2460b2e3e6 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -156,6 +156,7 @@ BASE_SCRIPTS = [
'p2p_invalid_messages.py',
'rpc_createmultisig.py',
'p2p_timeouts.py',
+ 'p2p_timeouts.py --v2transport',
'wallet_dump.py --legacy-wallet',
'rpc_signer.py',
'wallet_signer.py --descriptors',
@@ -206,6 +207,8 @@ BASE_SCRIPTS = [
'wallet_createwallet.py --descriptors',
'wallet_watchonly.py --legacy-wallet',
'wallet_watchonly.py --usecli --legacy-wallet',
+ 'wallet_reindex.py --legacy-wallet',
+ 'wallet_reindex.py --descriptors',
'wallet_reorgsrestore.py',
'wallet_conflicts.py --legacy-wallet',
'wallet_conflicts.py --descriptors',
@@ -243,6 +246,7 @@ BASE_SCRIPTS = [
'p2p_getdata.py',
'p2p_addrfetch.py',
'rpc_net.py',
+ 'rpc_net.py --v2transport',
'wallet_keypool.py --legacy-wallet',
'wallet_keypool.py --descriptors',
'wallet_descriptor.py --descriptors',
@@ -368,6 +372,7 @@ BASE_SCRIPTS = [
'wallet_orphanedreward.py',
'wallet_timelock.py',
'p2p_node_network_limited.py',
+ 'p2p_node_network_limited.py --v2transport',
'p2p_permissions.py',
'feature_blocksdir.py',
'wallet_startup.py',
@@ -562,8 +567,7 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=
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():
- logging.debug("Early exiting after failure in TestFramework unit tests")
- sys.exit(False)
+ sys.exit("Early exiting after failure in TestFramework unit tests")
flags = ['--cachedir={}'.format(cache_dir)] + args
diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py
index c257bda452..9d3c55d6b6 100755
--- a/test/functional/wallet_avoidreuse.py
+++ b/test/functional/wallet_avoidreuse.py
@@ -257,7 +257,7 @@ class AvoidReuseTest(BitcoinTestFramework):
if not self.options.descriptors:
# For the second send, we transmute it to a related single-key address
- # to make sure it's also detected as re-use
+ # to make sure it's also detected as reuse
fund_spk = address_to_scriptpubkey(fundaddr).hex()
fund_decoded = self.nodes[0].decodescript(fund_spk)
if second_addr_type == "p2sh-segwit":
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 78bfa97212..f798eee365 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -639,7 +639,7 @@ class WalletTest(BitcoinTestFramework):
node0_balance = self.nodes[0].getbalance()
# With walletrejectlongchains we will not create the tx and store it in our wallet.
- assert_raises_rpc_error(-6, "Transaction has too long of a mempool chain", self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01'))
+ assert_raises_rpc_error(-6, f"too many unconfirmed ancestors [limit: {chainlimit * 2}]", self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01'))
# Verify nothing new in wallet
assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999)))
diff --git a/test/functional/wallet_fast_rescan.py b/test/functional/wallet_fast_rescan.py
index 2f9c924e71..1315bccafd 100755
--- a/test/functional/wallet_fast_rescan.py
+++ b/test/functional/wallet_fast_rescan.py
@@ -4,8 +4,6 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test that fast rescan using block filters for descriptor wallets detects
top-ups correctly and finds the same transactions than the slow variant."""
-from typing import List
-
from test_framework.address import address_to_scriptpubkey
from test_framework.descriptors import descsum_create
from test_framework.test_framework import BitcoinTestFramework
@@ -32,7 +30,7 @@ class WalletFastRescanTest(BitcoinTestFramework):
self.skip_if_no_wallet()
self.skip_if_no_sqlite()
- def get_wallet_txids(self, node: TestNode, wallet_name: str) -> List[str]:
+ def get_wallet_txids(self, node: TestNode, wallet_name: str) -> list[str]:
w = node.get_wallet_rpc(wallet_name)
txs = w.listtransactions('*', 1000000)
return [tx['txid'] for tx in txs]
diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py
index 18bb8a0cd8..064ce12108 100755
--- a/test/functional/wallet_listtransactions.py
+++ b/test/functional/wallet_listtransactions.py
@@ -111,6 +111,7 @@ class ListTransactionsTest(BitcoinTestFramework):
self.run_rbf_opt_in_test()
self.run_externally_generated_address_test()
+ self.run_coinjoin_test()
self.run_invalid_parameters_test()
self.test_op_return()
@@ -281,6 +282,34 @@ class ListTransactionsTest(BitcoinTestFramework):
assert_equal(['pizza2'], self.nodes[0].getaddressinfo(addr2)['labels'])
assert_equal(['pizza3'], self.nodes[0].getaddressinfo(addr3)['labels'])
+ def run_coinjoin_test(self):
+ self.log.info('Check "coin-join" transaction')
+ input_0 = next(i for i in self.nodes[0].listunspent(query_options={"minimumAmount": 0.2}, include_unsafe=False))
+ input_1 = next(i for i in self.nodes[1].listunspent(query_options={"minimumAmount": 0.2}, include_unsafe=False))
+ raw_hex = self.nodes[0].createrawtransaction(
+ inputs=[
+ {
+ "txid": input_0["txid"],
+ "vout": input_0["vout"],
+ },
+ {
+ "txid": input_1["txid"],
+ "vout": input_1["vout"],
+ },
+ ],
+ outputs={
+ self.nodes[0].getnewaddress(): 0.123,
+ self.nodes[1].getnewaddress(): 0.123,
+ },
+ )
+ raw_hex = self.nodes[0].signrawtransactionwithwallet(raw_hex)["hex"]
+ raw_hex = self.nodes[1].signrawtransactionwithwallet(raw_hex)["hex"]
+ txid_join = self.nodes[0].sendrawtransaction(hexstring=raw_hex, maxfeerate=0)
+ fee_join = self.nodes[0].getmempoolentry(txid_join)["fees"]["base"]
+ # Fee should be correct: assert_equal(fee_join, self.nodes[0].gettransaction(txid_join)['fee'])
+ # But it is not, see for example https://github.com/bitcoin/bitcoin/issues/14136:
+ assert fee_join != self.nodes[0].gettransaction(txid_join)["fee"]
+
def run_invalid_parameters_test(self):
self.log.info("Test listtransactions RPC parameter validity")
assert_raises_rpc_error(-8, 'Label argument must be a valid label name or "*".', self.nodes[0].listtransactions, label="")
diff --git a/test/functional/wallet_reindex.py b/test/functional/wallet_reindex.py
new file mode 100755
index 0000000000..5388de4b71
--- /dev/null
+++ b/test/functional/wallet_reindex.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+# Copyright (c) 2023-present The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or https://www.opensource.org/licenses/mit-license.php.
+
+"""Test wallet-reindex interaction"""
+
+import time
+
+from test_framework.blocktools import COINBASE_MATURITY
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_equal,
+)
+BLOCK_TIME = 60 * 10
+
+class WalletReindexTest(BitcoinTestFramework):
+ def add_options(self, parser):
+ self.add_wallet_options(parser)
+
+ def set_test_params(self):
+ self.num_nodes = 1
+ self.setup_clean_chain = True
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def advance_time(self, node, secs):
+ self.node_time += secs
+ node.setmocktime(self.node_time)
+
+ # Verify the wallet updates the birth time accordingly when it detects a transaction
+ # with a time older than the oldest descriptor timestamp.
+ # This could happen when the user blindly imports a descriptor with 'timestamp=now'.
+ def birthtime_test(self, node, miner_wallet):
+ self.log.info("Test birth time update during tx scanning")
+ # Fund address to test
+ wallet_addr = miner_wallet.getnewaddress()
+ tx_id = miner_wallet.sendtoaddress(wallet_addr, 2)
+
+ # Generate 50 blocks, one every 10 min to surpass the 2 hours rescan window the wallet has
+ for _ in range(50):
+ self.generate(node, 1)
+ self.advance_time(node, BLOCK_TIME)
+
+ # Now create a new wallet, and import the descriptor
+ node.createwallet(wallet_name='watch_only', disable_private_keys=True, load_on_startup=True)
+ wallet_watch_only = node.get_wallet_rpc('watch_only')
+ # Blank wallets don't have a birth time
+ assert 'birthtime' not in wallet_watch_only.getwalletinfo()
+
+ # For a descriptors wallet: Import address with timestamp=now.
+ # For legacy wallet: There is no way of importing a script/address with a custom time. The wallet always imports it with birthtime=1.
+ # In both cases, disable rescan to not detect the transaction.
+ wallet_watch_only.importaddress(wallet_addr, rescan=False)
+ assert_equal(len(wallet_watch_only.listtransactions()), 0)
+
+ # Depending on the wallet type, the birth time changes.
+ wallet_birthtime = wallet_watch_only.getwalletinfo()['birthtime']
+ if self.options.descriptors:
+ # As blocks were generated every 10 min, the chain MTP timestamp is node_time - 60 min.
+ assert_equal(self.node_time - BLOCK_TIME * 6, wallet_birthtime)
+ else:
+ # No way of importing scripts/addresses with a custom time on a legacy wallet.
+ # It's always set to the beginning of time.
+ assert_equal(wallet_birthtime, 1)
+
+ # Rescan the wallet to detect the missing transaction
+ wallet_watch_only.rescanblockchain()
+ assert_equal(wallet_watch_only.gettransaction(tx_id)['confirmations'], 50)
+ assert_equal(wallet_watch_only.getbalances()['mine' if self.options.descriptors else 'watchonly']['trusted'], 2)
+
+ # Reindex and wait for it to finish
+ with node.assert_debug_log(expected_msgs=["initload thread exit"]):
+ self.restart_node(0, extra_args=['-reindex=1', f'-mocktime={self.node_time}'])
+ node.syncwithvalidationinterfacequeue()
+
+ # Verify the transaction is still 'confirmed' after reindex
+ wallet_watch_only = node.get_wallet_rpc('watch_only')
+ tx_info = wallet_watch_only.gettransaction(tx_id)
+ assert_equal(tx_info['confirmations'], 50)
+
+ # Depending on the wallet type, the birth time changes.
+ if self.options.descriptors:
+ # For descriptors, verify the wallet updated the birth time to the transaction time
+ assert_equal(tx_info['time'], wallet_watch_only.getwalletinfo()['birthtime'])
+ else:
+ # For legacy, as the birth time was set to the beginning of time, verify it did not change
+ assert_equal(wallet_birthtime, 1)
+
+ wallet_watch_only.unloadwallet()
+
+ def run_test(self):
+ node = self.nodes[0]
+ self.node_time = int(time.time())
+ node.setmocktime(self.node_time)
+
+ # Fund miner
+ node.createwallet(wallet_name='miner', load_on_startup=True)
+ miner_wallet = node.get_wallet_rpc('miner')
+ self.generatetoaddress(node, COINBASE_MATURITY + 10, miner_wallet.getnewaddress())
+
+ # Tests
+ self.birthtime_test(node, miner_wallet)
+
+
+if __name__ == '__main__':
+ WalletReindexTest().main()