Bitcoin Core 32.1.0
P2P Digital Currency
Loading...
Searching...
No Matches
private_broadcast_tests.cpp
Go to the documentation of this file.
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
6#include <private_broadcast.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
15std::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
25BOOST_FIXTURE_TEST_SUITE(private_broadcast_tests, BasicTestingSetup)
26
27static CTransactionRef MakeDummyTx(uint32_t id, size_t num_witness)
28{
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
40{
41 FakeNodeClock clock{};
42
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);
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
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
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 for (const auto& info : pb.GetBroadcastInfo()) {
102 BOOST_CHECK(std::ranges::none_of(info.peers, [](const auto& peer) { return peer.received.has_value(); }));
103 }
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.
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 const auto infos{pb.GetBroadcastInfo()};
119 BOOST_CHECK_EQUAL(infos.size(), 2);
120 {
121 const auto& peers{find_tx_info(infos, tx_for_recipient1).peers};
122 BOOST_CHECK_EQUAL(peers.size(), 1);
123 BOOST_CHECK_EQUAL(peers[0].address.ToStringAddrPort(), addr1.ToStringAddrPort());
124 BOOST_CHECK(peers[0].received.has_value());
125 }
126 {
127 const auto& peers{find_tx_info(infos, tx_for_recipient2).peers};
128 BOOST_CHECK_EQUAL(peers.size(), 1);
129 BOOST_CHECK_EQUAL(peers[0].address.ToStringAddrPort(), addr2.ToStringAddrPort());
130 BOOST_CHECK(!peers[0].received.has_value());
131 }
132
133 const auto stale_state{pb.GetStale()};
134 BOOST_CHECK_EQUAL(stale_state.size(), 1);
135 BOOST_CHECK_EQUAL(stale_state[0], tx_for_recipient2);
136
137 clock += 10h;
138
139 BOOST_CHECK_EQUAL(pb.GetStale().size(), 2);
140
141 BOOST_CHECK_EQUAL(pb.Remove(tx_for_recipient1).value(), 2);
142 BOOST_CHECK(!pb.Remove(tx_for_recipient1).has_value());
143 BOOST_CHECK_EQUAL(pb.Remove(tx_for_recipient2).value(), 2);
144 BOOST_CHECK(!pb.Remove(tx_for_recipient2).has_value());
145
146 BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), 0);
147 const CService addr_nonexistent{ipv4Addr, 3333};
148 BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/nonexistent_recipient, /*will_send_to_address=*/addr_nonexistent).has_value());
149}
150
151BOOST_AUTO_TEST_CASE(complete_all_initial_sends)
152{
154 const auto tx{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};
155 BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);
156
157 in_addr ipv4_addr;
158 ipv4_addr.s_addr = 0xa0b0c001;
159 const CService address{ipv4_addr, 1111};
160
161 // Pick the first send, then receive the transaction back from the network.
162 BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/1, address).value(), tx);
164 BOOST_REQUIRE(pb.MarkResolved(tx));
166 BOOST_REQUIRE_EQUAL(pb.GetTxForNode(1).value(), tx);
167 BOOST_CHECK(pb.GetBroadcastInfo().empty());
168
169 // Receipt must not prevent either of the two remaining initial sends.
170 BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/2, address).value(), tx);
171 BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/3, address).value(), tx);
172
174 pb.NodeDisconnected(/*nodeid=*/1);
175 BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/4, address).has_value());
176}
177
178BOOST_AUTO_TEST_CASE(stale_unpicked_tx)
179{
180 FakeNodeClock clock{};
181
183 const auto tx{MakeDummyTx(/*id=*/42, /*num_witness=*/0)};
184 BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);
185
186 // Unpicked transactions use the longer INITIAL_STALE_DURATION.
187 BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
189 BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
190 clock += 2min;
191 const auto stale_state{pb.GetStale()};
192 BOOST_REQUIRE_EQUAL(stale_state.size(), 1);
193 BOOST_CHECK_EQUAL(stale_state[0], tx);
194}
195
196BOOST_AUTO_TEST_CASE(send_attempt_limit)
197{
198 FakeNodeClock clock{};
199
200 constexpr size_t max_attempts{5};
202 const auto tx{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};
203 BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);
204
205 in_addr ipv4_addr;
206 ipv4_addr.s_addr = 0xa0b0c001;
207 const CService address{ipv4_addr, 1111};
208
209 NodeId node_id{0};
210 for (size_t attempt{0}; attempt < max_attempts; ++attempt) {
212 BOOST_REQUIRE(pb.TryGrantRetry(tx));
213 }
215 BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/node_id++, address).value(), tx);
216 }
217
218 // The transaction and its complete send history remain available, but no
219 // further connections should be opened for it.
221 BOOST_CHECK(!pb.TryGrantRetry(tx));
222 BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/node_id++, address).has_value());
223 const auto info{pb.GetBroadcastInfo()};
224 BOOST_REQUIRE_EQUAL(info.size(), 1);
225 BOOST_CHECK_EQUAL(info[0].peers.size(), max_attempts);
226 BOOST_CHECK_EQUAL(info[0].attempts_remaining, 0);
227
229 BOOST_CHECK(pb.GetStale().empty());
230
231 // An exhausted transaction does not prevent another transaction from being sent.
232 const auto next_tx{MakeDummyTx(/*id=*/2, /*num_witness=*/0)};
233 BOOST_REQUIRE_EQUAL(pb.Add(next_tx), PrivateBroadcast::AddResult::Added);
235 BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/node_id++, address).value(), next_tx);
236
237 // Re-adding an exhausted transaction resets its state and starts a fresh
238 // initial stale period.
239 BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);
241 BOOST_CHECK(pb.GetStale().empty());
242 const auto reset_info{pb.GetBroadcastInfo()};
243 const auto reset_tx_info{std::ranges::find(reset_info, tx->GetWitnessHash(), [](const auto& entry) { return entry.tx->GetWitnessHash(); })};
244 BOOST_REQUIRE(reset_tx_info != reset_info.end());
245 BOOST_CHECK(reset_tx_info->peers.empty());
246 BOOST_CHECK_EQUAL(reset_tx_info->attempts_remaining, max_attempts);
247 BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/node_id++, address).value(), tx);
248}
249
250BOOST_AUTO_TEST_CASE(reset_with_equivalent_transaction_reference)
251{
252 PrivateBroadcast pb{PrivateBroadcast::MAX_TRANSACTIONS, /*max_send_attempts=*/1};
253 const auto tx{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};
254 const auto equivalent_tx{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};
255 BOOST_REQUIRE(tx != equivalent_tx);
256 BOOST_REQUIRE(tx->GetWitnessHash() == equivalent_tx->GetWitnessHash());
257
258 in_addr ipv4_addr;
259 ipv4_addr.s_addr = 0xa0b0c001;
260 const CService address{ipv4_addr, 1111};
261 BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);
262 BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/0, address).value(), tx);
263 pb.NodeConfirmedReception(/*nodeid=*/0);
264 const auto confirmed_info{pb.GetBroadcastInfo()};
265 BOOST_REQUIRE_EQUAL(confirmed_info.size(), 1);
266 BOOST_REQUIRE_EQUAL(confirmed_info[0].peers.size(), 1);
267 BOOST_CHECK(confirmed_info[0].peers[0].received.has_value());
269
270 // A distinct CTransactionRef with the same WTXID must reset the exhausted
271 // transaction, including its send and confirmation history.
272 BOOST_REQUIRE_EQUAL(pb.Add(equivalent_tx), PrivateBroadcast::AddResult::Added);
274 BOOST_CHECK(!pb.GetTxForNode(/*nodeid=*/0).has_value());
275 const auto info{pb.GetBroadcastInfo()};
276 BOOST_REQUIRE_EQUAL(info.size(), 1);
277 BOOST_CHECK(info[0].tx->GetWitnessHash() == tx->GetWitnessHash());
278 BOOST_CHECK(info[0].peers.empty());
279 BOOST_CHECK_EQUAL(pb.Remove(equivalent_tx).value(), 1);
280}
281
282BOOST_AUTO_TEST_CASE(rejection_at_cap)
283{
285 constexpr size_t num_cap{PrivateBroadcast::MAX_TRANSACTIONS};
286 constexpr size_t num_over{5};
287
288 // Fill the queue exactly to the cap; every distinct Add() succeeds.
289 std::vector<CTransactionRef> txs;
290 txs.reserve(num_cap);
291 for (size_t i{0}; i < num_cap; ++i) {
292 auto tx{MakeDummyTx(/*id=*/static_cast<uint32_t>(i), /*num_witness=*/0)};
293 BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);
294 txs.push_back(std::move(tx));
295 }
296 BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
297
298 // Further distinct transactions are rejected, and the queue is unchanged.
299 for (size_t i{0}; i < num_over; ++i) {
300 const auto tx{MakeDummyTx(/*id=*/static_cast<uint32_t>(num_cap + i), /*num_witness=*/0)};
302 }
303 BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
304
305 // Nothing was evicted: all originally-added transactions are still present.
306 const auto infos{pb.GetBroadcastInfo()};
307 std::set<uint256> present_wtxids;
308 for (const auto& info : infos) {
309 present_wtxids.insert(info.tx->GetWitnessHash().ToUint256());
310 }
311 BOOST_CHECK_EQUAL(present_wtxids.size(), infos.size());
312 for (size_t i{0}; i < num_cap; ++i) {
313 BOOST_CHECK_MESSAGE(present_wtxids.contains(txs[i]->GetWitnessHash().ToUint256()),
314 "tx index " << i << " should still be present");
315 }
316
317 // Re-adding an already-present tx is AlreadyPresent even at the cap (not QueueFull).
319 BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
320
321 // Removing one frees exactly one slot for a new transaction.
322 BOOST_REQUIRE(pb.Remove(txs[0]).has_value());
323 BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap - 1);
324 const auto fresh{MakeDummyTx(/*id=*/0xffffffff, /*num_witness=*/0)};
326 BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
327
328 // A previously-removed tx can be added again as a brand-new entry.
329 BOOST_REQUIRE(pb.Remove(fresh).has_value());
331 BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
332}
333
QDataStream & operator<<(QDataStream &out, const BitcoinUnit &unit)
A combination of a network address (CNetAddr) and a (TCP) port.
Definition netaddress.h:531
std::string ToStringAddrPort() const
Helper to initialize the global NodeClock, let a duration elapse, and reset it after use in a test.
Definition time.h:54
Store a list of transactions to be broadcast privately.
void NodeConfirmedReception(const NodeId &nodeid) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Mark that the node has confirmed reception of the transaction we sent it by responding with PONG to o...
AddResult Add(const CTransactionRef &tx) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Add a transaction to the storage, or reset an exhausted transaction so it can be broadcast again.
std::vector< TxBroadcastInfo > GetBroadcastInfo() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Get stats about all transactions currently being privately broadcast.
bool MarkResolved(const CTransactionRef &tx) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Mark a transaction as resolved because it was received back from the network or is no longer acceptab...
AddResult
Outcome of Add().
@ QueueFull
Rejected: the queue is already at MAX_TRANSACTIONS.
@ AlreadyPresent
The transaction was already present with send attempts remaining; no change.
@ Added
The transaction was newly added or reset after exhausting its send attempts.
bool HavePendingTransactions() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Check if there are transactions with send attempts remaining.
std::optional< size_t > Remove(const CTransactionRef &tx) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Forget a transaction.
void NodeDisconnected(NodeId nodeid) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Mark a connection finished.
std::optional< CTransactionRef > PickTxForSend(const NodeId &will_send_to_nodeid, const CService &will_send_to_address) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Pick the transaction with the fewest send attempts, and confirmations, and oldest send/confirm times.
bool TryGrantRetry(const CTransactionRef &tx) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Grant one additional send attempt if the transaction is unresolved, all planned sends have been picke...
static constexpr size_t MAX_TRANSACTIONS
Maximum number of transactions tracked simultaneously.
std::optional< CTransactionRef > GetTxForNode(const NodeId &nodeid) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Get the transaction that was picked for sending to a given node by PickTxForSend().
std::vector< CTransactionRef > GetStale() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Get the transactions that have not been broadcast recently and have send attempts remaining.
static constexpr size_t INITIAL_CONNECTION_COUNT
Number of connections to make for initial broadcast.
static constexpr auto INITIAL_STALE_DURATION
If a transaction is not sent to any peer for this duration, then we consider it stale / for rebroadca...
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(ipc_tests)
int64_t NodeId
Definition net.h:107
#define BOOST_CHECK_EQUAL(v1, v2)
Definition object.cpp:17
#define BOOST_CHECK(expr)
Definition object.cpp:16
std::shared_ptr< const CTransaction > CTransactionRef
static CTransactionRef MakeDummyTx(uint32_t id, size_t num_witness)
Basic testing setup.
A mutable version of CTransaction.
std::vector< CTxIn > vin
BOOST_CHECK_NE(OneL.ToString(), ArrayToString(ZeroArray, 32))
assert(!tx.IsCoinBase())