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

Code before:
        received_back_msg = f"Received our privately broadcast transaction (txid={txs[0]['txid']}) from the network"
        with tx_originator.assert_debug_log(expected_msgs=[received_back_msg]):
            tx_returner.send_without_ping(msg_inv([inv]))
            tx_returner.wait_until(lambda: "getdata" in tx_returner.last_message)
            self.wait_until(lambda: len(tx_originator.getrawmempool()) > 0)

        self.log.info("Waiting for normal broadcast to another peer")
        self.destinations[1]["node"].wait_for_inv([inv])

        self.log.info("Checking getprivatebroadcastinfo no longer reports the transaction after it is received back")
        pbinfo = tx_originator.getprivatebroadcastinfo()
        pending = [t for t in pbinfo["transactions"] if t["txid"] == txs[0]["txid"] and t["wtxid"] == txs[0]["wtxid"]]
        assert_equal(len(pending), 0)

        self.log.info("Checking abortprivatebroadcast removes a pending private-broadcast transaction")
        tx_abort = wallet.create_self_transfer()
        tx_originator.sendrawtransaction(hexstring=tx_abort["hex"], maxfeerate=0.1)
        assert any(t["wtxid"] == tx_abort["wtxid"] for t in tx_originator.getprivatebroadcastinfo()["transactions"])
        abort_res = tx_originator.abortprivatebroadcast(tx_abort["txid"])
        assert_equal(len(abort_res["removed_transactions"]), 1)
        assert_equal(abort_res["removed_transactions"][0]["txid"], tx_abort["txid"])
        assert_equal(abort_res["removed_transactions"][0]["wtxid"], tx_abort["wtxid"])
        assert_equal(abort_res["removed_transactions"][0]["hex"].lower(), tx_abort["hex"].lower())
        assert all(t["wtxid"] != tx_abort["wtxid"] for t in tx_originator.getprivatebroadcastinfo()["transactions"])

        self.log.info("Sending a transaction that is already in the mempool")
        skip_destinations = len(self.destinations)
        tx_originator.sendrawtransaction(hexstring=txs[0]["hex"], maxfeerate=0)
        self.check_broadcasts("Broadcast of mempool transaction", txs[0], NUM_PRIVATE_BROADCAST_PER_TX, skip_destinations)

        self.log.info("Sending a transaction with a dependency in the mempool")
        skip_destinations = len(self.destinations)
        tx_originator.sendrawtransaction(hexstring=txs[1]["hex"], maxfeerate=0.1)
        self.check_broadcasts("Dependency in mempool", txs[1], NUM_PRIVATE_BROADCAST_PER_TX, skip_destinations)

        self.log.info("Sending a transaction with a dependency not in the mempool (should be rejected)")
        assert_equal(len(tx_originator.getrawmempool()), 1)
        assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent",
                                tx_originator.sendrawtransaction, hexstring=txs[2]["hex"], maxfeerate=0.1)
        assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent",
                                tx_originator.sendrawtransaction, hexstring=txs[2]["hex"], maxfeerate=0)

        # Since txs[1] has not been received back by tx_originator,
        # it should be re-broadcast after a while. Advance tx_originator's clock
        # to trigger a re-broadcast. Should be more than the maximum returned by
        # NextTxBroadcast() in net_processing.cpp.
        self.log.info("Checking that rebroadcast works")
        delta = 20 * 60 # 20min
        skip_destinations = len(self.destinations)
        rebroadcast_msg = f"Reattempting broadcast of stale txid={txs[1]['txid']}"
        with tx_originator.busy_wait_for_debug_log(expected_msgs=[rebroadcast_msg.encode()]):

Review comment (bug):
Might make sense to verify that `abortprivatebroadcast` fails for missing ids:
```python
        self.log.info("Checking abortprivatebroadcast fails for non-existent transaction")
        assert_raises_rpc_error(
            -5,
            "Transaction not in private broadcast queue",
            tx_originator.abortprivatebroadcast,
            "0" * 64,
        )
```

Code after:
        assert "getdata" not in tx_returner.last_message
        received_back_msg = f"Received our privately broadcast transaction (txid={txs[0]['txid']}) from the network"
        with tx_originator.assert_debug_log(expected_msgs=[received_back_msg]):
            tx_returner.send_without_ping(msg_inv([inv]))
            tx_returner.wait_until(lambda: "getdata" in tx_returner.last_message)
            self.wait_until(lambda: len(tx_originator.getrawmempool()) > 0)

        self.log.info("Waiting for normal broadcast to another peer")
        self.destinations[1]["node"].wait_for_inv([inv])

        self.log.info("Checking getprivatebroadcastinfo no longer reports the transaction after it is received back")
        pbinfo = tx_originator.getprivatebroadcastinfo()
        pending = [t for t in pbinfo["transactions"] if t["txid"] == txs[0]["txid"] and t["wtxid"] == txs[0]["wtxid"]]
        assert_equal(len(pending), 0)

        self.log.info("Checking abortprivatebroadcast removes a pending private-broadcast transaction")
        tx_abort = wallet.create_self_transfer()
        tx_originator.sendrawtransaction(hexstring=tx_abort["hex"], maxfeerate=0.1)
        assert any(t["wtxid"] == tx_abort["wtxid"] for t in tx_originator.getprivatebroadcastinfo()["transactions"])
        abort_res = tx_originator.abortprivatebroadcast(tx_abort["txid"])
        assert_equal(len(abort_res["removed_transactions"]), 1)
        assert_equal(abort_res["removed_transactions"][0]["txid"], tx_abort["txid"])
        assert_equal(abort_res["removed_transactions"][0]["wtxid"], tx_abort["wtxid"])
        assert_equal(abort_res["removed_transactions"][0]["hex"].lower(), tx_abort["hex"].lower())
        assert all(t["wtxid"] != tx_abort["wtxid"] for t in tx_originator.getprivatebroadcastinfo()["transactions"])

        self.log.info("Checking abortprivatebroadcast fails for non-existent transaction")
        assert_raises_rpc_error(
            -5,
            "Transaction not in private broadcast queue",
            tx_originator.abortprivatebroadcast,
            "0" * 64,
        )

        self.log.info("Sending a transaction that is already in the mempool")
        skip_destinations = len(self.destinations)
        tx_originator.sendrawtransaction(hexstring=txs[0]["hex"], maxfeerate=0)
        self.check_broadcasts("Broadcast of mempool transaction", txs[0], NUM_PRIVATE_BROADCAST_PER_TX, skip_destinations)

        self.log.info("Sending a transaction with a dependency in the mempool")
        skip_destinations = len(self.destinations)
        tx_originator.sendrawtransaction(hexstring=txs[1]["hex"], maxfeerate=0.1)
        self.check_broadcasts("Dependency in mempool", txs[1], NUM_PRIVATE_BROADCAST_PER_TX, skip_destinations)

        self.log.info("Sending a transaction with a dependency not in the mempool (should be rejected)")
        assert_equal(len(tx_originator.getrawmempool()), 1)
        assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent",
                                tx_originator.sendrawtransaction, hexstring=txs[2]["hex"], maxfeerate=0.1)
        assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent",
                                tx_originator.sendrawtransaction, hexstring=txs[2]["hex"], maxfeerate=0)