From 576dd97cb91ecff7d95898a0dc79b9b1a8a8f4f2 Mon Sep 17 00:00:00 2001 From: Martin Zumsande Date: Fri, 1 Aug 2025 17:20:26 -0400 Subject: [PATCH 1/3] test: increase timeout in p2p_leak_tx.py With a low but not negligible probability in the order of 10^-6 the exponential timer NextInvToInBounds can lead to an interval >60s, making the test fail. Also uses mocktime to speed up the test and fixes a non-matching on_inv override. Co-authored-by: Vasil Dimov --- test/functional/p2p_leak_tx.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/functional/p2p_leak_tx.py b/test/functional/p2p_leak_tx.py index 42e586fca3d..29b00397c09 100755 --- a/test/functional/p2p_leak_tx.py +++ b/test/functional/p2p_leak_tx.py @@ -15,7 +15,7 @@ from test_framework.wallet import MiniWallet import time class P2PNode(P2PDataStore): - def on_inv(self, msg): + def on_inv(self, message): pass @@ -26,6 +26,7 @@ class P2PLeakTxTest(BitcoinTestFramework): def run_test(self): self.gen_node = self.nodes[0] # The block and tx generating node self.miniwallet = MiniWallet(self.gen_node) + self.mocktime = int(time.time()) self.test_tx_in_block() self.test_notfound_on_replaced_tx() @@ -33,20 +34,20 @@ class P2PLeakTxTest(BitcoinTestFramework): def test_tx_in_block(self): self.log.info("Check that a transaction in the last block is uploaded (beneficial for compact block relay)") + self.gen_node.setmocktime(self.mocktime) inbound_peer = self.gen_node.add_p2p_connection(P2PNode()) self.log.debug("Generate transaction and block") inbound_peer.last_message.pop("inv", None) - self.gen_node.setmocktime(int(time.time())) # pause time based activities wtxid = self.miniwallet.send_self_transfer(from_node=self.gen_node)["wtxid"] rawmp = self.gen_node.getrawmempool(False, True) pi = self.gen_node.getpeerinfo()[0] assert_equal(rawmp["mempool_sequence"], 2) # our tx cause mempool activity assert_equal(pi["last_inv_sequence"], 1) # that is after the last inv assert_equal(pi["inv_to_send"], 1) # and our tx has been queued - self.gen_node.setmocktime(0) - + self.mocktime += 120 + self.gen_node.setmocktime(self.mocktime) inbound_peer.wait_until(lambda: "inv" in inbound_peer.last_message and inbound_peer.last_message.get("inv").inv[0].hash == int(wtxid, 16)) rawmp = self.gen_node.getrawmempool(False, True) @@ -65,15 +66,20 @@ class P2PLeakTxTest(BitcoinTestFramework): def test_notfound_on_replaced_tx(self): self.gen_node.disconnect_p2ps() + self.gen_node.setmocktime(self.mocktime) inbound_peer = self.gen_node.add_p2p_connection(P2PTxInvStore()) self.log.info("Transaction tx_a is broadcast") tx_a = self.miniwallet.send_self_transfer(from_node=self.gen_node) + self.mocktime += 120 + self.gen_node.setmocktime(self.mocktime) inbound_peer.wait_for_broadcast(txns=[tx_a["wtxid"]]) tx_b = tx_a["tx"] tx_b.vout[0].nValue -= 9000 self.gen_node.sendrawtransaction(tx_b.serialize().hex()) + self.mocktime += 120 + self.gen_node.setmocktime(self.mocktime) inbound_peer.wait_until(lambda: "tx" in inbound_peer.last_message and inbound_peer.last_message.get("tx").tx.wtxid_hex == tx_b.wtxid_hex) self.log.info("Re-request of tx_a after replacement is answered with notfound") From 99bc552980d9a10da03e4b90c390bcd6cae686be Mon Sep 17 00:00:00 2001 From: Martin Zumsande Date: Fri, 1 Aug 2025 17:04:44 -0400 Subject: [PATCH 2/3] test: fix (w)txid confusion in p2p_leak_tx.py Before, we'd send a MSG_TX with a wtxid in it, which would always result in a notfound answer --- test/functional/p2p_leak_tx.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/functional/p2p_leak_tx.py b/test/functional/p2p_leak_tx.py index 29b00397c09..e09420ba5ee 100755 --- a/test/functional/p2p_leak_tx.py +++ b/test/functional/p2p_leak_tx.py @@ -106,24 +106,23 @@ class P2PLeakTxTest(BitcoinTestFramework): self.log.info("Running test up to {} times.".format(MAX_REPEATS)) for i in range(MAX_REPEATS): self.log.info('Run repeat {}'.format(i + 1)) - txid = self.miniwallet.send_self_transfer(from_node=self.gen_node)["wtxid"] + wtxid = self.miniwallet.send_self_transfer(from_node=self.gen_node)["wtxid"] want_tx = msg_getdata() - want_tx.inv.append(CInv(t=MSG_TX, h=int(txid, 16))) + want_tx.inv.append(CInv(t=MSG_WTX, h=int(wtxid, 16))) with p2p_lock: inbound_peer.last_message.pop('notfound', None) inbound_peer.send_and_ping(want_tx) - if inbound_peer.last_message.get('notfound'): - self.log.debug('tx {} was not yet announced to us.'.format(txid)) + self.log.debug('tx {} was not yet announced to us.'.format(wtxid)) self.log.debug("node has responded with a notfound message. End test.") - assert_equal(inbound_peer.last_message['notfound'].vec[0].hash, int(txid, 16)) + assert_equal(inbound_peer.last_message['notfound'].vec[0].hash, int(wtxid, 16)) with p2p_lock: inbound_peer.last_message.pop('notfound') break else: - self.log.debug('tx {} was already announced to us. Try test again.'.format(txid)) - assert int(txid, 16) in [inv.hash for inv in inbound_peer.last_message['inv'].inv] + self.log.debug('tx {} was already announced to us. Try test again.'.format(wtxid)) + assert int(wtxid, 16) in [inv.hash for inv in inbound_peer.last_message['inv'].inv] if __name__ == '__main__': From 14ae71f323dd011c6d51470ea15cf00750970f65 Mon Sep 17 00:00:00 2001 From: David Gumberg Date: Wed, 27 Aug 2025 15:31:32 -0700 Subject: [PATCH 3/3] test: make notfound_on_unannounced more reliable By using mocktime, we will always hit both the notfound branch and the tx sent branch. The previous version didn't achieve that due to timing issues. Co-authored-by: Martin Zumsande --- test/functional/p2p_leak_tx.py | 44 ++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/test/functional/p2p_leak_tx.py b/test/functional/p2p_leak_tx.py index e09420ba5ee..c347f59772d 100755 --- a/test/functional/p2p_leak_tx.py +++ b/test/functional/p2p_leak_tx.py @@ -102,27 +102,31 @@ class P2PLeakTxTest(BitcoinTestFramework): self.gen_node.disconnect_p2ps() inbound_peer = self.gen_node.add_p2p_connection(P2PNode()) # An "attacking" inbound peer - MAX_REPEATS = 100 - self.log.info("Running test up to {} times.".format(MAX_REPEATS)) - for i in range(MAX_REPEATS): - self.log.info('Run repeat {}'.format(i + 1)) - wtxid = self.miniwallet.send_self_transfer(from_node=self.gen_node)["wtxid"] + # Set a mock time so that time does not pass, and gen_node never announces the transaction + self.gen_node.setmocktime(self.mocktime) + wtxid = int(self.miniwallet.send_self_transfer(from_node=self.gen_node)["wtxid"], 16) - want_tx = msg_getdata() - want_tx.inv.append(CInv(t=MSG_WTX, h=int(wtxid, 16))) - with p2p_lock: - inbound_peer.last_message.pop('notfound', None) - inbound_peer.send_and_ping(want_tx) - if inbound_peer.last_message.get('notfound'): - self.log.debug('tx {} was not yet announced to us.'.format(wtxid)) - self.log.debug("node has responded with a notfound message. End test.") - assert_equal(inbound_peer.last_message['notfound'].vec[0].hash, int(wtxid, 16)) - with p2p_lock: - inbound_peer.last_message.pop('notfound') - break - else: - self.log.debug('tx {} was already announced to us. Try test again.'.format(wtxid)) - assert int(wtxid, 16) in [inv.hash for inv in inbound_peer.last_message['inv'].inv] + want_tx = msg_getdata() + want_tx.inv.append(CInv(t=MSG_WTX, h=wtxid)) + with p2p_lock: + inbound_peer.last_message.pop('notfound', None) + inbound_peer.send_and_ping(want_tx) + inbound_peer.wait_until(lambda: "notfound" in inbound_peer.last_message) + with p2p_lock: + assert_equal(inbound_peer.last_message.get("notfound").vec[0].hash, wtxid) + inbound_peer.last_message.pop('notfound') + + # Move mocktime forward and wait for the announcement. + inbound_peer.last_message.pop('inv', None) + self.mocktime += 120 + self.gen_node.setmocktime(self.mocktime) + inbound_peer.wait_for_inv([CInv(t=MSG_WTX, h=wtxid)], timeout=120) + + # Send the getdata again, this time the node should send us a TX message. + inbound_peer.last_message.pop('tx', None) + inbound_peer.send_and_ping(want_tx) + self.wait_until(lambda: "tx" in inbound_peer.last_message) + assert_equal(wtxid, int(inbound_peer.last_message["tx"].tx.wtxid_hex, 16)) if __name__ == '__main__':