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

Code before:
        self.destinations[1]["node"].wait_for_inv([inv])

        self.log.info("Checking getprivatebroadcastinfo reports final_state after it is received back")
        pbinfo = tx_originator.getprivatebroadcastinfo()
        info = [t for t in pbinfo["transactions"] if t["wtxid"] == txs[0]["wtxid"]]
        assert_equal(len(info), 1)
        assert_equal(info[0]["final_state"]["received_from"], self.destinations[0]["requested_to"])
        assert "aborted" not in info[0]["final_state"]

        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_time_before = int(time.time())
        abort_res = tx_originator.abortprivatebroadcast(tx_abort["txid"])
        abort_time_after = int(time.time())
        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())
        # After abort, the transaction should remain stored with an aborted final_state.
        pbinfo = tx_originator.getprivatebroadcastinfo()
        info = [t for t in pbinfo["transactions"] if t["wtxid"] == tx_abort["wtxid"]]
        assert_equal(len(info), 1)
        assert_equal(info[0]["final_state"]["aborted"], "Manually aborted via abortprivatebroadcast RPC")
        # The abort time is recorded on the node; allow a little slack around the RPC call.
        assert abort_time_before - 5 <= info[0]["final_state"]["time"] <= abort_time_after + 5
        assert "received_from" not in info[0]["final_state"]

        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")

Review comment (bug):
```suggestion
        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:
        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)

        # 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']}"