/ src / test / private_broadcast_tests.cpp
private_broadcast_tests.cpp
  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 <util/time.h>
  9  
 10  #include <algorithm>
 11  #include <boost/test/unit_test.hpp>
 12  
 13  BOOST_FIXTURE_TEST_SUITE(private_broadcast_tests, BasicTestingSetup)
 14  
 15  static CTransactionRef MakeDummyTx(uint32_t id, size_t num_witness)
 16  {
 17      CMutableTransaction mtx;
 18      mtx.vin.resize(1);
 19      mtx.vin[0].nSequence = id;
 20      if (num_witness > 0) {
 21          mtx.vin[0].scriptWitness = CScriptWitness{};
 22          mtx.vin[0].scriptWitness.stack.resize(num_witness);
 23      }
 24      return MakeTransactionRef(mtx);
 25  }
 26  
 27  BOOST_AUTO_TEST_CASE(basic)
 28  {
 29      SetMockTime(Now<NodeSeconds>());
 30  
 31      PrivateBroadcast pb;
 32      const NodeId recipient1{1};
 33      in_addr ipv4Addr;
 34      ipv4Addr.s_addr = 0xa0b0c001;
 35      const CService addr1{ipv4Addr, 1111};
 36  
 37      // No transactions initially.
 38      BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/recipient1, /*will_send_to_address=*/addr1).has_value());
 39      BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
 40      BOOST_CHECK(!pb.HavePendingTransactions());
 41      BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), 0);
 42  
 43      // Make a transaction and add it.
 44      const auto tx1{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};
 45  
 46      BOOST_CHECK(pb.Add(tx1));
 47      BOOST_CHECK(!pb.Add(tx1));
 48  
 49      // Make another transaction with same txid, different wtxid and add it.
 50      const auto tx2{MakeDummyTx(/*id=*/1, /*num_witness=*/1)};
 51      BOOST_REQUIRE(tx1->GetHash() == tx2->GetHash());
 52      BOOST_REQUIRE(tx1->GetWitnessHash() != tx2->GetWitnessHash());
 53  
 54      BOOST_CHECK(pb.Add(tx2));
 55      const auto find_tx_info{[](auto& infos, const CTransactionRef& tx) -> const PrivateBroadcast::TxBroadcastInfo& {
 56          const auto it{std::ranges::find(infos, tx->GetWitnessHash(), [](const auto& info) { return info.tx->GetWitnessHash(); })};
 57          BOOST_REQUIRE(it != infos.end());
 58          return *it;
 59      }};
 60      const auto check_peer_counts{[&](size_t tx1_peer_count, size_t tx2_peer_count) {
 61          const auto infos{pb.GetBroadcastInfo()};
 62          BOOST_CHECK_EQUAL(infos.size(), 2);
 63          BOOST_CHECK_EQUAL(find_tx_info(infos, tx1).peers.size(), tx1_peer_count);
 64          BOOST_CHECK_EQUAL(find_tx_info(infos, tx2).peers.size(), tx2_peer_count);
 65      }};
 66  
 67      check_peer_counts(/*tx1_peer_count=*/0, /*tx2_peer_count=*/0);
 68  
 69      const auto tx_for_recipient1{pb.PickTxForSend(/*will_send_to_nodeid=*/recipient1, /*will_send_to_address=*/addr1).value()};
 70      BOOST_CHECK(tx_for_recipient1 == tx1 || tx_for_recipient1 == tx2);
 71  
 72      // A second pick must return the other transaction.
 73      const NodeId recipient2{2};
 74      const CService addr2{ipv4Addr, 2222};
 75      const auto tx_for_recipient2{pb.PickTxForSend(/*will_send_to_nodeid=*/recipient2, /*will_send_to_address=*/addr2).value()};
 76      BOOST_CHECK(tx_for_recipient2 == tx1 || tx_for_recipient2 == tx2);
 77      BOOST_CHECK_NE(tx_for_recipient1, tx_for_recipient2);
 78  
 79      check_peer_counts(/*tx1_peer_count=*/1, /*tx2_peer_count=*/1);
 80  
 81      const NodeId nonexistent_recipient{0};
 82  
 83      // Confirm transactions <-> recipients mapping is correct.
 84      BOOST_CHECK(!pb.GetTxForNode(nonexistent_recipient).has_value());
 85      BOOST_CHECK_EQUAL(pb.GetTxForNode(recipient1).value(), tx_for_recipient1);
 86      BOOST_CHECK_EQUAL(pb.GetTxForNode(recipient2).value(), tx_for_recipient2);
 87  
 88      // Confirm none of the transactions' reception have been confirmed.
 89      BOOST_CHECK(!pb.DidNodeConfirmReception(recipient1));
 90      BOOST_CHECK(!pb.DidNodeConfirmReception(recipient2));
 91      BOOST_CHECK(!pb.DidNodeConfirmReception(nonexistent_recipient));
 92  
 93      // 1. Freshly added transactions should NOT be stale yet.
 94      BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
 95  
 96      // 2. Fast-forward the mock clock past the INITIAL_STALE_DURATION.
 97      SetMockTime(Now<NodeSeconds>() + PrivateBroadcast::INITIAL_STALE_DURATION + 1min);
 98  
 99      // 3. Now that the initial duration has passed, both unconfirmed transactions should be stale.
100      BOOST_CHECK_EQUAL(pb.GetStale().size(), 2);
101  
102      // Confirm reception by recipient1.
103      pb.NodeConfirmedReception(nonexistent_recipient); // Dummy call.
104      pb.NodeConfirmedReception(recipient1);
105  
106      BOOST_CHECK(pb.DidNodeConfirmReception(recipient1));
107      BOOST_CHECK(!pb.DidNodeConfirmReception(recipient2));
108  
109      const auto infos{pb.GetBroadcastInfo()};
110      BOOST_CHECK_EQUAL(infos.size(), 2);
111      {
112          const auto& peers{find_tx_info(infos, tx_for_recipient1).peers};
113          BOOST_CHECK_EQUAL(peers.size(), 1);
114          BOOST_CHECK_EQUAL(peers[0].address.ToStringAddrPort(), addr1.ToStringAddrPort());
115          BOOST_CHECK(peers[0].received.has_value());
116      }
117      {
118          const auto& peers{find_tx_info(infos, tx_for_recipient2).peers};
119          BOOST_CHECK_EQUAL(peers.size(), 1);
120          BOOST_CHECK_EQUAL(peers[0].address.ToStringAddrPort(), addr2.ToStringAddrPort());
121          BOOST_CHECK(!peers[0].received.has_value());
122      }
123  
124      const auto stale_state{pb.GetStale()};
125      BOOST_CHECK_EQUAL(stale_state.size(), 1);
126      BOOST_CHECK_EQUAL(stale_state[0], tx_for_recipient2);
127  
128      SetMockTime(Now<NodeSeconds>() + 10h);
129  
130      BOOST_CHECK_EQUAL(pb.GetStale().size(), 2);
131  
132      BOOST_CHECK_EQUAL(pb.Remove(tx_for_recipient1).value(), 1);
133      BOOST_CHECK(!pb.Remove(tx_for_recipient1).has_value());
134      BOOST_CHECK_EQUAL(pb.Remove(tx_for_recipient2).value(), 0);
135      BOOST_CHECK(!pb.Remove(tx_for_recipient2).has_value());
136  
137      BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), 0);
138      const CService addr_nonexistent{ipv4Addr, 3333};
139      BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/nonexistent_recipient, /*will_send_to_address=*/addr_nonexistent).has_value());
140  }
141  
142  BOOST_AUTO_TEST_CASE(stale_unpicked_tx)
143  {
144      SetMockTime(Now<NodeSeconds>());
145  
146      PrivateBroadcast pb;
147      const auto tx{MakeDummyTx(/*id=*/42, /*num_witness=*/0)};
148      BOOST_REQUIRE(pb.Add(tx));
149  
150      // Unpicked transactions use the longer INITIAL_STALE_DURATION.
151      BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
152      SetMockTime(Now<NodeSeconds>() + PrivateBroadcast::INITIAL_STALE_DURATION - 1min);
153      BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
154      SetMockTime(Now<NodeSeconds>() + 2min);
155      const auto stale_state{pb.GetStale()};
156      BOOST_REQUIRE_EQUAL(stale_state.size(), 1);
157      BOOST_CHECK_EQUAL(stale_state[0], tx);
158  }
159  
160  BOOST_AUTO_TEST_SUITE_END()