private_broadcast_tests.cpp raw

   1  // Copyright (c) 2025-present The Bitcoin Core developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <primitives/transaction.h>
   6  #include <private_broadcast.h>
   7  #include <test/util/setup_common.h>
   8  #include <test/util/time.h>
   9  #include <util/time.h>
  10  
  11  #include <algorithm>
  12  #include <ostream>
  13  #include <boost/test/unit_test.hpp>
  14  
  15  std::ostream& operator<<(std::ostream& os, PrivateBroadcast::AddResult r)
  16  {
  17      switch (r) {
  18      case PrivateBroadcast::AddResult::Added: return os << "Added";
  19      case PrivateBroadcast::AddResult::AlreadyPresent: return os << "AlreadyPresent";
  20      case PrivateBroadcast::AddResult::QueueFull: return os << "QueueFull";
  21      } // no default case, so the compiler can warn about missing cases
  22      assert(false);
  23  }
  24  
  25  BOOST_FIXTURE_TEST_SUITE(private_broadcast_tests, BasicTestingSetup)
  26  
  27  static CTransactionRef MakeDummyTx(uint32_t id, size_t num_witness)
  28  {
  29      CMutableTransaction mtx;
  30      mtx.vin.resize(1);
  31      mtx.vin[0].nSequence = id;
  32      if (num_witness > 0) {
  33          mtx.vin[0].scriptWitness = CScriptWitness{};
  34          mtx.vin[0].scriptWitness.stack.resize(num_witness);
  35      }
  36      return MakeTransactionRef(mtx);
  37  }
  38  
  39  BOOST_AUTO_TEST_CASE(basic)
  40  {
  41      FakeNodeClock clock{};
  42  
  43      PrivateBroadcast pb;
  44      const NodeId recipient1{1};
  45      in_addr ipv4Addr;
  46      ipv4Addr.s_addr = 0xa0b0c001;
  47      const CService addr1{ipv4Addr, 1111};
  48  
  49      // No transactions initially.
  50      BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/recipient1, /*will_send_to_address=*/addr1).has_value());
  51      BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
  52      BOOST_CHECK(!pb.HavePendingTransactions());
  53      BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), 0);
  54  
  55      // Make a transaction and add it.
  56      const auto tx1{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};
  57  
  58      BOOST_CHECK_EQUAL(pb.Add(tx1), PrivateBroadcast::AddResult::Added);
  59      BOOST_CHECK_EQUAL(pb.Add(tx1), PrivateBroadcast::AddResult::AlreadyPresent);
  60  
  61      // Make another transaction with same txid, different wtxid and add it.
  62      const auto tx2{MakeDummyTx(/*id=*/1, /*num_witness=*/1)};
  63      BOOST_REQUIRE(tx1->GetHash() == tx2->GetHash());
  64      BOOST_REQUIRE(tx1->GetWitnessHash() != tx2->GetWitnessHash());
  65  
  66      BOOST_CHECK_EQUAL(pb.Add(tx2), PrivateBroadcast::AddResult::Added);
  67      const auto find_tx_info{[](auto& infos, const CTransactionRef& tx) -> const PrivateBroadcast::TxBroadcastInfo& {
  68          const auto it{std::ranges::find(infos, tx->GetWitnessHash(), [](const auto& info) { return info.tx->GetWitnessHash(); })};
  69          BOOST_REQUIRE(it != infos.end());
  70          return *it;
  71      }};
  72      const auto check_peer_counts{[&](size_t tx1_peer_count, size_t tx2_peer_count) {
  73          const auto infos{pb.GetBroadcastInfo()};
  74          BOOST_CHECK_EQUAL(infos.size(), 2);
  75          BOOST_CHECK_EQUAL(find_tx_info(infos, tx1).peers.size(), tx1_peer_count);
  76          BOOST_CHECK_EQUAL(find_tx_info(infos, tx2).peers.size(), tx2_peer_count);
  77      }};
  78  
  79      check_peer_counts(/*tx1_peer_count=*/0, /*tx2_peer_count=*/0);
  80  
  81      const auto tx_for_recipient1{pb.PickTxForSend(/*will_send_to_nodeid=*/recipient1, /*will_send_to_address=*/addr1).value()};
  82      BOOST_CHECK(tx_for_recipient1 == tx1 || tx_for_recipient1 == tx2);
  83  
  84      // A second pick must return the other transaction.
  85      const NodeId recipient2{2};
  86      const CService addr2{ipv4Addr, 2222};
  87      const auto tx_for_recipient2{pb.PickTxForSend(/*will_send_to_nodeid=*/recipient2, /*will_send_to_address=*/addr2).value()};
  88      BOOST_CHECK(tx_for_recipient2 == tx1 || tx_for_recipient2 == tx2);
  89      BOOST_CHECK_NE(tx_for_recipient1, tx_for_recipient2);
  90  
  91      check_peer_counts(/*tx1_peer_count=*/1, /*tx2_peer_count=*/1);
  92  
  93      const NodeId nonexistent_recipient{0};
  94  
  95      // Confirm transactions <-> recipients mapping is correct.
  96      BOOST_CHECK(!pb.GetTxForNode(nonexistent_recipient).has_value());
  97      BOOST_CHECK_EQUAL(pb.GetTxForNode(recipient1).value(), tx_for_recipient1);
  98      BOOST_CHECK_EQUAL(pb.GetTxForNode(recipient2).value(), tx_for_recipient2);
  99  
 100      // Confirm none of the transactions' reception have been confirmed.
 101      BOOST_CHECK(!pb.DidNodeConfirmReception(recipient1));
 102      BOOST_CHECK(!pb.DidNodeConfirmReception(recipient2));
 103      BOOST_CHECK(!pb.DidNodeConfirmReception(nonexistent_recipient));
 104  
 105      // 1. Freshly added transactions should NOT be stale yet.
 106      BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
 107  
 108      // 2. Fast-forward the mock clock past the INITIAL_STALE_DURATION.
 109      clock += PrivateBroadcast::INITIAL_STALE_DURATION + 1min;
 110  
 111      // 3. Now that the initial duration has passed, both unconfirmed transactions should be stale.
 112      BOOST_CHECK_EQUAL(pb.GetStale().size(), 2);
 113  
 114      // Confirm reception by recipient1.
 115      pb.NodeConfirmedReception(nonexistent_recipient); // Dummy call.
 116      pb.NodeConfirmedReception(recipient1);
 117  
 118      BOOST_CHECK(pb.DidNodeConfirmReception(recipient1));
 119      BOOST_CHECK(!pb.DidNodeConfirmReception(recipient2));
 120  
 121      const auto infos{pb.GetBroadcastInfo()};
 122      BOOST_CHECK_EQUAL(infos.size(), 2);
 123      {
 124          const auto& peers{find_tx_info(infos, tx_for_recipient1).peers};
 125          BOOST_CHECK_EQUAL(peers.size(), 1);
 126          BOOST_CHECK_EQUAL(peers[0].address.ToStringAddrPort(), addr1.ToStringAddrPort());
 127          BOOST_CHECK(peers[0].received.has_value());
 128      }
 129      {
 130          const auto& peers{find_tx_info(infos, tx_for_recipient2).peers};
 131          BOOST_CHECK_EQUAL(peers.size(), 1);
 132          BOOST_CHECK_EQUAL(peers[0].address.ToStringAddrPort(), addr2.ToStringAddrPort());
 133          BOOST_CHECK(!peers[0].received.has_value());
 134      }
 135  
 136      const auto stale_state{pb.GetStale()};
 137      BOOST_CHECK_EQUAL(stale_state.size(), 1);
 138      BOOST_CHECK_EQUAL(stale_state[0], tx_for_recipient2);
 139  
 140      clock += 10h;
 141  
 142      BOOST_CHECK_EQUAL(pb.GetStale().size(), 2);
 143  
 144      BOOST_CHECK_EQUAL(pb.Remove(tx_for_recipient1).value(), 1);
 145      BOOST_CHECK(!pb.Remove(tx_for_recipient1).has_value());
 146      BOOST_CHECK_EQUAL(pb.Remove(tx_for_recipient2).value(), 0);
 147      BOOST_CHECK(!pb.Remove(tx_for_recipient2).has_value());
 148  
 149      BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), 0);
 150      const CService addr_nonexistent{ipv4Addr, 3333};
 151      BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/nonexistent_recipient, /*will_send_to_address=*/addr_nonexistent).has_value());
 152  }
 153  
 154  BOOST_AUTO_TEST_CASE(stale_unpicked_tx)
 155  {
 156      FakeNodeClock clock{};
 157  
 158      PrivateBroadcast pb;
 159      const auto tx{MakeDummyTx(/*id=*/42, /*num_witness=*/0)};
 160      BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);
 161  
 162      // Unpicked transactions use the longer INITIAL_STALE_DURATION.
 163      BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
 164      clock += PrivateBroadcast::INITIAL_STALE_DURATION - 1min;
 165      BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
 166      clock += 2min;
 167      const auto stale_state{pb.GetStale()};
 168      BOOST_REQUIRE_EQUAL(stale_state.size(), 1);
 169      BOOST_CHECK_EQUAL(stale_state[0], tx);
 170  }
 171  
 172  BOOST_AUTO_TEST_CASE(rejection_at_cap)
 173  {
 174      PrivateBroadcast pb;
 175      constexpr size_t num_cap{PrivateBroadcast::MAX_TRANSACTIONS};
 176      constexpr size_t num_over{5};
 177  
 178      // Fill the queue exactly to the cap; every distinct Add() succeeds.
 179      std::vector<CTransactionRef> txs;
 180      txs.reserve(num_cap);
 181      for (size_t i{0}; i < num_cap; ++i) {
 182          auto tx{MakeDummyTx(/*id=*/static_cast<uint32_t>(i), /*num_witness=*/0)};
 183          BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);
 184          txs.push_back(std::move(tx));
 185      }
 186      BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
 187  
 188      // Further distinct transactions are rejected, and the queue is unchanged.
 189      for (size_t i{0}; i < num_over; ++i) {
 190          const auto tx{MakeDummyTx(/*id=*/static_cast<uint32_t>(num_cap + i), /*num_witness=*/0)};
 191          BOOST_CHECK_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::QueueFull);
 192      }
 193      BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
 194  
 195      // Nothing was evicted: all originally-added transactions are still present.
 196      const auto infos{pb.GetBroadcastInfo()};
 197      std::set<uint256> present_wtxids;
 198      for (const auto& info : infos) {
 199          present_wtxids.insert(info.tx->GetWitnessHash().ToUint256());
 200      }
 201      BOOST_CHECK_EQUAL(present_wtxids.size(), infos.size());
 202      for (size_t i{0}; i < num_cap; ++i) {
 203          BOOST_CHECK_MESSAGE(present_wtxids.contains(txs[i]->GetWitnessHash().ToUint256()),
 204                              "tx index " << i << " should still be present");
 205      }
 206  
 207      // Re-adding an already-present tx is AlreadyPresent even at the cap (not QueueFull).
 208      BOOST_CHECK_EQUAL(pb.Add(txs[0]), PrivateBroadcast::AddResult::AlreadyPresent);
 209      BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
 210  
 211      // Removing one frees exactly one slot for a new transaction.
 212      BOOST_REQUIRE(pb.Remove(txs[0]).has_value());
 213      BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap - 1);
 214      const auto fresh{MakeDummyTx(/*id=*/0xffffffff, /*num_witness=*/0)};
 215      BOOST_CHECK_EQUAL(pb.Add(fresh), PrivateBroadcast::AddResult::Added);
 216      BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
 217  
 218      // A previously-removed tx can be added again as a brand-new entry.
 219      BOOST_REQUIRE(pb.Remove(fresh).has_value());
 220      BOOST_CHECK_EQUAL(pb.Add(fresh), PrivateBroadcast::AddResult::Added);
 221      BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
 222  }
 223  
 224  BOOST_AUTO_TEST_SUITE_END()
 225