Repository: bitcoin/bitcoin
File: test/functional/p2p_leak_tx.py (Python)

Code before:

    def run_test(self):
        self.gen_node = self.nodes[0]  # The block and tx generating node
        self.miniwallet = MiniWallet(self.gen_node)

        self.test_tx_in_block()
        self.test_notfound_on_replaced_tx()
        self.test_notfound_on_unannounced_tx()

    def test_tx_in_block(self):
        self.log.info("Check that a transaction in the last block is uploaded (beneficial for compact block relay)")
        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)

        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)
        pi = self.gen_node.getpeerinfo()[0]
        assert_equal(rawmp["mempool_sequence"], 2) # no mempool update
        assert_equal(pi["last_inv_sequence"], 2) # announced the current mempool
        assert_equal(pi["inv_to_send"], 0) # nothing left in the queue

        want_tx = msg_getdata(inv=inbound_peer.last_message.get("inv").inv)
        self.generate(self.gen_node, 1)

        self.log.debug("Request transaction")
        inbound_peer.last_message.pop("tx", None)
        inbound_peer.send_and_ping(want_tx)
        assert_equal(inbound_peer.last_message.get("tx").tx.wtxid_hex, wtxid)

    def test_notfound_on_replaced_tx(self):
        self.gen_node.disconnect_p2ps()
        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)
        inbound_peer.wait_for_broadcast(txns=[tx_a["wtxid"]])

        tx_b = tx_a["tx"]
        tx_b.vout[0].nValue -= 9000

Review comment (bug):
In the first commit, it is possible to generate fake time passage without spending wall clock time of the test. That is, fix the problem and make the test faster:

```diff
     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(int(time.time()) - 120) # pause time based activities
         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)
 
-        inbound_peer.wait_until(lambda: "inv" in inbound_peer.last_message and inbound_peer.last_message.get("inv").inv[0].hash == int(wtxid, 16), timeout=120)
+        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)
```

Edit: to clarify, the first `setmocktime()` sets the time to 120s in the past, recording that in `m_next_inv_to_inbounds`, then when the time comes for `bitcoind` to wait for that delay, the second `setmocktime(0)` lets time flow normally and the current time is about 120s more than `m_next_inv_to_inbounds`, so there is no waiting.

Code after:

    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()
        self.test_notfound_on_unannounced_tx()

    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)

        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.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)
        pi = self.gen_node.getpeerinfo()[0]
        assert_equal(rawmp["mempool_sequence"], 2) # no mempool update
        assert_equal(pi["last_inv_sequence"], 2) # announced the current mempool
        assert_equal(pi["inv_to_send"], 0) # nothing left in the queue

        want_tx = msg_getdata(inv=inbound_peer.last_message.get("inv").inv)
        self.generate(self.gen_node, 1)

        self.log.debug("Request transaction")
        inbound_peer.last_message.pop("tx", None)
        inbound_peer.send_and_ping(want_tx)
        assert_equal(inbound_peer.last_message.get("tx").tx.wtxid_hex, wtxid)

    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)