Branch data Line data Source code
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 : 0 : std::ostream& operator<<(std::ostream& os, PrivateBroadcast::AddResult r)
16 : : {
17 [ # # # # ]: 0 : switch (r) {
18 : 0 : case PrivateBroadcast::AddResult::Added: return os << "Added";
19 : 0 : case PrivateBroadcast::AddResult::AlreadyPresent: return os << "AlreadyPresent";
20 : 0 : case PrivateBroadcast::AddResult::QueueFull: return os << "QueueFull";
21 : : } // no default case, so the compiler can warn about missing cases
22 : 0 : assert(false);
23 : : }
24 : :
25 : : BOOST_FIXTURE_TEST_SUITE(private_broadcast_tests, BasicTestingSetup)
26 : :
27 : 10013 : static CTransactionRef MakeDummyTx(uint32_t id, size_t num_witness)
28 : : {
29 : 10013 : CMutableTransaction mtx;
30 [ + - ]: 10013 : mtx.vin.resize(1);
31 [ + + ]: 10013 : mtx.vin[0].nSequence = id;
32 [ + + ]: 10013 : if (num_witness > 0) {
33 : 1 : mtx.vin[0].scriptWitness = CScriptWitness{};
34 [ + - ]: 1 : mtx.vin[0].scriptWitness.stack.resize(num_witness);
35 : : }
36 [ + - ]: 20026 : return MakeTransactionRef(mtx);
37 : 10013 : }
38 : :
39 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(basic)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
40 : : {
41 : 1 : FakeNodeClock clock{};
42 : :
43 [ + - ]: 1 : PrivateBroadcast pb;
44 : 1 : const NodeId recipient1{1};
45 : 1 : in_addr ipv4Addr;
46 : 1 : ipv4Addr.s_addr = 0xa0b0c001;
47 [ + - ]: 1 : const CService addr1{ipv4Addr, 1111};
48 : :
49 : : // No transactions initially.
50 [ + - + - : 2 : BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/recipient1, /*will_send_to_address=*/addr1).has_value());
+ - + - ]
51 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
+ - ]
52 [ + - + - : 2 : BOOST_CHECK(!pb.HavePendingTransactions());
+ - + - ]
53 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), 0);
+ - ]
54 : :
55 : : // Make a transaction and add it.
56 [ + - ]: 1 : const auto tx1{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};
57 : :
58 [ + - + - : 1 : BOOST_CHECK_EQUAL(pb.Add(tx1), PrivateBroadcast::AddResult::Added);
+ - ]
59 [ + - + - : 1 : BOOST_CHECK_EQUAL(pb.Add(tx1), PrivateBroadcast::AddResult::AlreadyPresent);
+ - ]
60 : :
61 : : // Make another transaction with same txid, different wtxid and add it.
62 [ + - ]: 1 : const auto tx2{MakeDummyTx(/*id=*/1, /*num_witness=*/1)};
63 [ + - - + : 2 : BOOST_REQUIRE(tx1->GetHash() == tx2->GetHash());
+ - + - ]
64 [ + - + - : 3 : BOOST_REQUIRE(tx1->GetWitnessHash() != tx2->GetWitnessHash());
+ - + - ]
65 : :
66 [ + - + - : 1 : BOOST_CHECK_EQUAL(pb.Add(tx2), PrivateBroadcast::AddResult::Added);
+ - ]
67 : 7 : const auto find_tx_info{[](auto& infos, const CTransactionRef& tx) -> const PrivateBroadcast::TxBroadcastInfo& {
68 [ + + ]: 15 : const auto it{std::ranges::find(infos, tx->GetWitnessHash(), [](const auto& info) { return info.tx->GetWitnessHash(); })};
69 [ + - ]: 12 : BOOST_REQUIRE(it != infos.end());
70 : 6 : return *it;
71 : : }};
72 : 3 : const auto check_peer_counts{[&](size_t tx1_peer_count, size_t tx2_peer_count) {
73 : 2 : const auto infos{pb.GetBroadcastInfo()};
74 [ + - - + : 2 : BOOST_CHECK_EQUAL(infos.size(), 2);
+ - ]
75 [ + - + - : 2 : BOOST_CHECK_EQUAL(find_tx_info(infos, tx1).peers.size(), tx1_peer_count);
- + + - ]
76 [ + - + - : 2 : BOOST_CHECK_EQUAL(find_tx_info(infos, tx2).peers.size(), tx2_peer_count);
- + + - ]
77 : 3 : }};
78 : :
79 [ + - ]: 1 : check_peer_counts(/*tx1_peer_count=*/0, /*tx2_peer_count=*/0);
80 : :
81 [ + - ]: 2 : const auto tx_for_recipient1{pb.PickTxForSend(/*will_send_to_nodeid=*/recipient1, /*will_send_to_address=*/addr1).value()};
82 [ + - + - : 3 : BOOST_CHECK(tx_for_recipient1 == tx1 || tx_for_recipient1 == tx2);
+ - + - +
- ]
83 : :
84 : : // A second pick must return the other transaction.
85 : 1 : const NodeId recipient2{2};
86 [ + - ]: 1 : const CService addr2{ipv4Addr, 2222};
87 [ + - ]: 2 : const auto tx_for_recipient2{pb.PickTxForSend(/*will_send_to_nodeid=*/recipient2, /*will_send_to_address=*/addr2).value()};
88 [ + - - + : 2 : BOOST_CHECK(tx_for_recipient2 == tx1 || tx_for_recipient2 == tx2);
- - + - +
- ]
89 [ + - + - ]: 1 : BOOST_CHECK_NE(tx_for_recipient1, tx_for_recipient2);
90 : :
91 [ + - ]: 1 : check_peer_counts(/*tx1_peer_count=*/1, /*tx2_peer_count=*/1);
92 : :
93 : 1 : const NodeId nonexistent_recipient{0};
94 : :
95 : : // Confirm transactions <-> recipients mapping is correct.
96 [ + - + - : 2 : BOOST_CHECK(!pb.GetTxForNode(nonexistent_recipient).has_value());
+ - + - ]
97 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetTxForNode(recipient1).value(), tx_for_recipient1);
+ - ]
98 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetTxForNode(recipient2).value(), tx_for_recipient2);
+ - ]
99 : :
100 : : // Confirm none of the transactions' reception have been confirmed.
101 [ + - + - : 2 : BOOST_CHECK(!pb.DidNodeConfirmReception(recipient1));
+ - + - ]
102 [ + - + - : 2 : BOOST_CHECK(!pb.DidNodeConfirmReception(recipient2));
+ - + - ]
103 [ + - + - : 2 : BOOST_CHECK(!pb.DidNodeConfirmReception(nonexistent_recipient));
+ - + - ]
104 : :
105 : : // 1. Freshly added transactions should NOT be stale yet.
106 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
+ - ]
107 : :
108 : : // 2. Fast-forward the mock clock past the INITIAL_STALE_DURATION.
109 [ + - ]: 1 : clock += PrivateBroadcast::INITIAL_STALE_DURATION + 1min;
110 : :
111 : : // 3. Now that the initial duration has passed, both unconfirmed transactions should be stale.
112 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetStale().size(), 2);
+ - ]
113 : :
114 : : // Confirm reception by recipient1.
115 [ + - ]: 1 : pb.NodeConfirmedReception(nonexistent_recipient); // Dummy call.
116 [ + - ]: 1 : pb.NodeConfirmedReception(recipient1);
117 : :
118 [ + - + - : 2 : BOOST_CHECK(pb.DidNodeConfirmReception(recipient1));
+ - + - ]
119 [ + - + - : 2 : BOOST_CHECK(!pb.DidNodeConfirmReception(recipient2));
+ - + - ]
120 : :
121 [ + - ]: 1 : const auto infos{pb.GetBroadcastInfo()};
122 [ + - - + : 1 : BOOST_CHECK_EQUAL(infos.size(), 2);
+ - ]
123 : 1 : {
124 [ + - ]: 1 : const auto& peers{find_tx_info(infos, tx_for_recipient1).peers};
125 [ + - - + : 1 : BOOST_CHECK_EQUAL(peers.size(), 1);
+ - ]
126 [ + - + - : 1 : BOOST_CHECK_EQUAL(peers[0].address.ToStringAddrPort(), addr1.ToStringAddrPort());
+ - + - ]
127 [ + - + - : 2 : BOOST_CHECK(peers[0].received.has_value());
+ - ]
128 : : }
129 : 1 : {
130 [ + - ]: 1 : const auto& peers{find_tx_info(infos, tx_for_recipient2).peers};
131 [ + - - + : 1 : BOOST_CHECK_EQUAL(peers.size(), 1);
+ - ]
132 [ + - + - : 1 : BOOST_CHECK_EQUAL(peers[0].address.ToStringAddrPort(), addr2.ToStringAddrPort());
+ - + - ]
133 [ + - + - : 2 : BOOST_CHECK(!peers[0].received.has_value());
+ - ]
134 : : }
135 : :
136 [ + - ]: 1 : const auto stale_state{pb.GetStale()};
137 [ + - - + : 1 : BOOST_CHECK_EQUAL(stale_state.size(), 1);
+ - ]
138 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(stale_state[0], tx_for_recipient2);
139 : :
140 [ + - ]: 1 : clock += 10h;
141 : :
142 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetStale().size(), 2);
+ - ]
143 : :
144 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.Remove(tx_for_recipient1).value(), 1);
+ - ]
145 [ + - + - : 2 : BOOST_CHECK(!pb.Remove(tx_for_recipient1).has_value());
+ - + - ]
146 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.Remove(tx_for_recipient2).value(), 0);
+ - ]
147 [ + - + - : 2 : BOOST_CHECK(!pb.Remove(tx_for_recipient2).has_value());
+ - + - ]
148 : :
149 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), 0);
+ - ]
150 [ + - ]: 1 : const CService addr_nonexistent{ipv4Addr, 3333};
151 [ + - + - : 2 : BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/nonexistent_recipient, /*will_send_to_address=*/addr_nonexistent).has_value());
+ - ]
152 [ + - + - : 5 : }
+ - + - ]
153 : :
154 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(stale_unpicked_tx)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
155 : : {
156 : 1 : FakeNodeClock clock{};
157 : :
158 [ + - ]: 1 : PrivateBroadcast pb;
159 [ + - ]: 1 : const auto tx{MakeDummyTx(/*id=*/42, /*num_witness=*/0)};
160 [ + - + - : 1 : BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);
+ - ]
161 : :
162 : : // Unpicked transactions use the longer INITIAL_STALE_DURATION.
163 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
+ - ]
164 [ + - ]: 1 : clock += PrivateBroadcast::INITIAL_STALE_DURATION - 1min;
165 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
+ - ]
166 [ + - ]: 1 : clock += 2min;
167 [ + - ]: 1 : const auto stale_state{pb.GetStale()};
168 [ + - - + : 1 : BOOST_REQUIRE_EQUAL(stale_state.size(), 1);
+ - ]
169 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(stale_state[0], tx);
170 [ + - ]: 2 : }
171 : :
172 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(send_attempt_limit)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
173 : : {
174 : 1 : FakeNodeClock clock{};
175 : :
176 : 1 : constexpr size_t max_attempts{5};
177 [ + - ]: 1 : PrivateBroadcast pb{PrivateBroadcast::MAX_TRANSACTIONS, max_attempts};
178 [ + - ]: 1 : const auto tx{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};
179 [ + - + - : 1 : BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);
+ - ]
180 : :
181 : 1 : in_addr ipv4_addr;
182 : 1 : ipv4_addr.s_addr = 0xa0b0c001;
183 [ + - ]: 1 : const CService address{ipv4_addr, 1111};
184 : :
185 : : NodeId node_id{0};
186 [ + + ]: 6 : for (size_t attempt{0}; attempt < max_attempts; ++attempt) {
187 [ + - + - : 10 : BOOST_CHECK(pb.HavePendingTransactions());
+ - + - ]
188 [ + - + - : 10 : BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/node_id++, address).value(), tx);
+ - ]
189 : : }
190 : :
191 : : // The transaction and its complete send history remain available, but no
192 : : // further connections should be opened for it.
193 [ + - + - : 2 : BOOST_CHECK(!pb.HavePendingTransactions());
+ - + - ]
194 [ + - + - : 2 : BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/node_id++, address).has_value());
+ - + - ]
195 [ + - ]: 1 : const auto info{pb.GetBroadcastInfo()};
196 [ + - - + : 1 : BOOST_REQUIRE_EQUAL(info.size(), 1);
+ - ]
197 [ + - - + : 1 : BOOST_CHECK_EQUAL(info[0].peers.size(), max_attempts);
+ - ]
198 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(info[0].attempts_remaining, 0);
199 : :
200 [ + - ]: 1 : clock += PrivateBroadcast::INITIAL_STALE_DURATION + 1min;
201 [ + - + - : 2 : BOOST_CHECK(pb.GetStale().empty());
+ - + - ]
202 : :
203 : : // An exhausted transaction does not prevent another transaction from being sent.
204 [ + - ]: 1 : const auto next_tx{MakeDummyTx(/*id=*/2, /*num_witness=*/0)};
205 [ + - + - : 1 : BOOST_REQUIRE_EQUAL(pb.Add(next_tx), PrivateBroadcast::AddResult::Added);
+ - ]
206 [ + - + - : 2 : BOOST_CHECK(pb.HavePendingTransactions());
+ - + - ]
207 [ + - + - : 2 : BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/node_id++, address).value(), next_tx);
+ - ]
208 : :
209 : : // Re-adding an exhausted transaction resets its state and starts a fresh
210 : : // initial stale period.
211 [ + - + - : 1 : BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);
+ - ]
212 [ + - + - : 2 : BOOST_CHECK(pb.HavePendingTransactions());
+ - + - ]
213 [ + - + - : 2 : BOOST_CHECK(pb.GetStale().empty());
+ - + - ]
214 [ + - ]: 1 : const auto reset_info{pb.GetBroadcastInfo()};
215 [ + + ]: 3 : const auto reset_tx_info{std::ranges::find(reset_info, tx->GetWitnessHash(), [](const auto& entry) { return entry.tx->GetWitnessHash(); })};
216 [ + - + - : 2 : BOOST_REQUIRE(reset_tx_info != reset_info.end());
+ - ]
217 [ + - + - : 2 : BOOST_CHECK(reset_tx_info->peers.empty());
+ - ]
218 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(reset_tx_info->attempts_remaining, max_attempts);
219 [ + - + - : 2 : BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/node_id++, address).value(), tx);
+ - ]
220 [ + - + - ]: 3 : }
221 : :
222 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(reset_with_equivalent_transaction_reference)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
223 : : {
224 [ + - ]: 1 : PrivateBroadcast pb{PrivateBroadcast::MAX_TRANSACTIONS, /*max_send_attempts=*/1};
225 [ + - ]: 1 : const auto tx{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};
226 [ + - ]: 1 : const auto equivalent_tx{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};
227 [ + - + - : 2 : BOOST_REQUIRE(tx != equivalent_tx);
+ - ]
228 [ + - - + : 2 : BOOST_REQUIRE(tx->GetWitnessHash() == equivalent_tx->GetWitnessHash());
+ - + - ]
229 : :
230 : 1 : in_addr ipv4_addr;
231 : 1 : ipv4_addr.s_addr = 0xa0b0c001;
232 [ + - ]: 1 : const CService address{ipv4_addr, 1111};
233 [ + - + - : 1 : BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);
+ - ]
234 [ + - + - : 2 : BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/0, address).value(), tx);
+ - ]
235 [ + - ]: 1 : pb.NodeConfirmedReception(/*nodeid=*/0);
236 [ + - + - : 2 : BOOST_CHECK(pb.DidNodeConfirmReception(/*nodeid=*/0));
+ - + - ]
237 [ + - + - : 2 : BOOST_CHECK(!pb.HavePendingTransactions());
+ - + - ]
238 : :
239 : : // A distinct CTransactionRef with the same WTXID must reset the exhausted
240 : : // transaction, including its send and confirmation history.
241 [ + - + - : 1 : BOOST_REQUIRE_EQUAL(pb.Add(equivalent_tx), PrivateBroadcast::AddResult::Added);
+ - ]
242 [ + - + - : 2 : BOOST_CHECK(pb.HavePendingTransactions());
+ - + - ]
243 [ + - + - : 2 : BOOST_CHECK(!pb.GetTxForNode(/*nodeid=*/0).has_value());
+ - + - ]
244 [ + - + - : 2 : BOOST_CHECK(!pb.DidNodeConfirmReception(/*nodeid=*/0));
+ - + - ]
245 [ + - ]: 1 : const auto info{pb.GetBroadcastInfo()};
246 [ + - - + : 1 : BOOST_REQUIRE_EQUAL(info.size(), 1);
+ - ]
247 [ + - - + : 2 : BOOST_CHECK(info[0].tx->GetWitnessHash() == tx->GetWitnessHash());
+ - + - ]
248 [ + - + - : 2 : BOOST_CHECK(info[0].peers.empty());
+ - ]
249 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.Remove(equivalent_tx).value(), 0);
+ - ]
250 [ + - + - ]: 3 : }
251 : :
252 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(rejection_at_cap)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
253 : : {
254 [ + - ]: 1 : PrivateBroadcast pb;
255 : 1 : constexpr size_t num_cap{PrivateBroadcast::MAX_TRANSACTIONS};
256 : 1 : constexpr size_t num_over{5};
257 : :
258 : : // Fill the queue exactly to the cap; every distinct Add() succeeds.
259 : 1 : std::vector<CTransactionRef> txs;
260 [ + - ]: 1 : txs.reserve(num_cap);
261 [ + + ]: 10001 : for (size_t i{0}; i < num_cap; ++i) {
262 [ + - ]: 10000 : auto tx{MakeDummyTx(/*id=*/static_cast<uint32_t>(i), /*num_witness=*/0)};
263 [ + - + - : 10000 : BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);
+ - ]
264 [ + - - + ]: 10000 : txs.push_back(std::move(tx));
265 : 10000 : }
266 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
+ - ]
267 : :
268 : : // Further distinct transactions are rejected, and the queue is unchanged.
269 [ + + ]: 6 : for (size_t i{0}; i < num_over; ++i) {
270 [ + - ]: 5 : const auto tx{MakeDummyTx(/*id=*/static_cast<uint32_t>(num_cap + i), /*num_witness=*/0)};
271 [ + - + - : 5 : BOOST_CHECK_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::QueueFull);
+ - + - ]
272 : 5 : }
273 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
+ - ]
274 : :
275 : : // Nothing was evicted: all originally-added transactions are still present.
276 [ + - ]: 1 : const auto infos{pb.GetBroadcastInfo()};
277 : 1 : std::set<uint256> present_wtxids;
278 [ + + ]: 10001 : for (const auto& info : infos) {
279 [ + - ]: 10000 : present_wtxids.insert(info.tx->GetWitnessHash().ToUint256());
280 : : }
281 [ + - - + : 1 : BOOST_CHECK_EQUAL(present_wtxids.size(), infos.size());
+ - ]
282 [ + + ]: 10001 : for (size_t i{0}; i < num_cap; ++i) {
283 [ + - + - ]: 20000 : BOOST_CHECK_MESSAGE(present_wtxids.contains(txs[i]->GetWitnessHash().ToUint256()),
284 : : "tx index " << i << " should still be present");
285 : : }
286 : :
287 : : // Re-adding an already-present tx is AlreadyPresent even at the cap (not QueueFull).
288 [ + - + - : 1 : BOOST_CHECK_EQUAL(pb.Add(txs[0]), PrivateBroadcast::AddResult::AlreadyPresent);
+ - ]
289 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
+ - ]
290 : :
291 : : // Removing one frees exactly one slot for a new transaction.
292 [ + - + - : 2 : BOOST_REQUIRE(pb.Remove(txs[0]).has_value());
+ - + - ]
293 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap - 1);
+ - ]
294 [ + - ]: 1 : const auto fresh{MakeDummyTx(/*id=*/0xffffffff, /*num_witness=*/0)};
295 [ + - + - : 1 : BOOST_CHECK_EQUAL(pb.Add(fresh), PrivateBroadcast::AddResult::Added);
+ - ]
296 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
+ - ]
297 : :
298 : : // A previously-removed tx can be added again as a brand-new entry.
299 [ + - + - : 2 : BOOST_REQUIRE(pb.Remove(fresh).has_value());
+ - + - ]
300 [ + - + - : 1 : BOOST_CHECK_EQUAL(pb.Add(fresh), PrivateBroadcast::AddResult::Added);
+ - ]
301 [ + - + - : 2 : BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), num_cap);
+ - + - ]
302 : 1 : }
303 : :
304 : : BOOST_AUTO_TEST_SUITE_END()
|