/root/bitcoin/src/test/fuzz/txorphan.cpp
Line | Count | Source |
1 | | // Copyright (c) 2022-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 <consensus/amount.h> |
6 | | #include <consensus/validation.h> |
7 | | #include <net_processing.h> |
8 | | #include <node/eviction.h> |
9 | | #include <node/txorphanage.h> |
10 | | #include <policy/policy.h> |
11 | | #include <primitives/transaction.h> |
12 | | #include <script/script.h> |
13 | | #include <sync.h> |
14 | | #include <test/fuzz/FuzzedDataProvider.h> |
15 | | #include <test/fuzz/fuzz.h> |
16 | | #include <test/fuzz/util.h> |
17 | | #include <test/util/setup_common.h> |
18 | | #include <uint256.h> |
19 | | #include <util/check.h> |
20 | | #include <util/feefrac.h> |
21 | | #include <util/time.h> |
22 | | |
23 | | #include <algorithm> |
24 | | #include <bitset> |
25 | | #include <cmath> |
26 | | #include <cstdint> |
27 | | #include <iostream> |
28 | | #include <memory> |
29 | | #include <set> |
30 | | #include <utility> |
31 | | #include <vector> |
32 | | |
33 | | void initialize_orphanage() |
34 | 0 | { |
35 | 0 | static const auto testing_setup = MakeNoLogFileContext(); |
36 | 0 | } |
37 | | |
38 | | FUZZ_TARGET(txorphan, .init = initialize_orphanage) |
39 | 0 | { |
40 | 0 | SeedRandomStateForTest(SeedRand::ZEROS); |
41 | 0 | FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
42 | 0 | FastRandomContext orphanage_rng{ConsumeUInt256(fuzzed_data_provider)}; |
43 | 0 | SetMockTime(ConsumeTime(fuzzed_data_provider)); |
44 | |
|
45 | 0 | auto orphanage = node::MakeTxOrphanage(); |
46 | 0 | std::vector<COutPoint> outpoints; // Duplicates are tolerated |
47 | 0 | outpoints.reserve(200'000); |
48 | | |
49 | | // initial outpoints used to construct transactions later |
50 | 0 | for (uint8_t i = 0; i < 4; i++) { |
51 | 0 | outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0); |
52 | 0 | } |
53 | |
|
54 | 0 | CTransactionRef ptx_potential_parent = nullptr; |
55 | |
|
56 | 0 | std::vector<CTransactionRef> tx_history; |
57 | |
|
58 | 0 | LIMITED_WHILE(outpoints.size() < 200'000 && fuzzed_data_provider.ConsumeBool(), 1000) |
59 | 0 | { |
60 | | // construct transaction |
61 | 0 | const CTransactionRef tx = [&] { |
62 | 0 | CMutableTransaction tx_mut; |
63 | 0 | const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); |
64 | 0 | const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 256); |
65 | | // pick outpoints from outpoints as input. We allow input duplicates on purpose, given we are not |
66 | | // running any transaction validation logic before adding transactions to the orphanage |
67 | 0 | tx_mut.vin.reserve(num_in); |
68 | 0 | for (uint32_t i = 0; i < num_in; i++) { |
69 | 0 | auto& prevout = PickValue(fuzzed_data_provider, outpoints); |
70 | | // try making transactions unique by setting a random nSequence, but allow duplicate transactions if they happen |
71 | 0 | tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL)); |
72 | 0 | } |
73 | | // output amount will not affect txorphanage |
74 | 0 | tx_mut.vout.reserve(num_out); |
75 | 0 | for (uint32_t i = 0; i < num_out; i++) { |
76 | 0 | tx_mut.vout.emplace_back(CAmount{0}, CScript{}); |
77 | 0 | } |
78 | 0 | auto new_tx = MakeTransactionRef(tx_mut); |
79 | | // add newly constructed outpoints to the coin pool |
80 | 0 | for (uint32_t i = 0; i < num_out; i++) { |
81 | 0 | outpoints.emplace_back(new_tx->GetHash(), i); |
82 | 0 | } |
83 | 0 | return new_tx; |
84 | 0 | }(); |
85 | |
|
86 | 0 | tx_history.push_back(tx); |
87 | |
|
88 | 0 | const auto wtxid{tx->GetWitnessHash()}; |
89 | | |
90 | | // Trigger orphanage functions that are called using parents. ptx_potential_parent is a tx we constructed in a |
91 | | // previous loop and potentially the parent of this tx. |
92 | 0 | if (ptx_potential_parent) { |
93 | | // Set up future GetTxToReconsider call. |
94 | 0 | orphanage->AddChildrenToWorkSet(*ptx_potential_parent, orphanage_rng); |
95 | | |
96 | | // Check that all txns returned from GetChildrenFrom* are indeed a direct child of this tx. |
97 | 0 | NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>(); |
98 | 0 | for (const auto& child : orphanage->GetChildrenFromSamePeer(ptx_potential_parent, peer_id)) { |
99 | 0 | assert(std::any_of(child->vin.cbegin(), child->vin.cend(), [&](const auto& input) { |
100 | 0 | return input.prevout.hash == ptx_potential_parent->GetHash(); |
101 | 0 | })); |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | // trigger orphanage functions |
106 | 0 | LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 1000) |
107 | 0 | { |
108 | 0 | NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>(); |
109 | 0 | const auto total_bytes_start{orphanage->TotalOrphanUsage()}; |
110 | 0 | const auto total_peer_bytes_start{orphanage->UsageByPeer(peer_id)}; |
111 | 0 | const auto tx_weight{GetTransactionWeight(*tx)}; |
112 | |
|
113 | 0 | CallOneOf( |
114 | 0 | fuzzed_data_provider, |
115 | 0 | [&] { |
116 | 0 | { |
117 | 0 | CTransactionRef ref = orphanage->GetTxToReconsider(peer_id); |
118 | 0 | if (ref) { |
119 | 0 | Assert(orphanage->HaveTx(ref->GetWitnessHash())); |
120 | 0 | } |
121 | 0 | } |
122 | 0 | }, |
123 | 0 | [&] { |
124 | 0 | bool have_tx = orphanage->HaveTx(tx->GetWitnessHash()); |
125 | 0 | bool have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id); |
126 | | // AddTx should return false if tx is too big or already have it |
127 | | // tx weight is unknown, we only check when tx is already in orphanage |
128 | 0 | { |
129 | 0 | bool add_tx = orphanage->AddTx(tx, peer_id); |
130 | | // have_tx == true -> add_tx == false |
131 | 0 | Assert(!have_tx || !add_tx); |
132 | | // have_tx_and_peer == true -> add_tx == false |
133 | 0 | Assert(!have_tx_and_peer || !add_tx); |
134 | | // After AddTx, the orphanage may trim itself, so the peer's usage may have gone up or down. |
135 | |
|
136 | 0 | if (add_tx) { |
137 | 0 | Assert(tx_weight <= MAX_STANDARD_TX_WEIGHT); |
138 | 0 | } else { |
139 | | // Peer may have been added as an announcer. |
140 | 0 | if (orphanage->UsageByPeer(peer_id) > total_peer_bytes_start) { |
141 | 0 | Assert(orphanage->HaveTxFromPeer(wtxid, peer_id)); |
142 | 0 | } |
143 | | |
144 | | // If announcement was added, total bytes does not increase. |
145 | | // However, if eviction was triggered, the value may decrease. |
146 | 0 | Assert(orphanage->TotalOrphanUsage() <= total_bytes_start); |
147 | 0 | } |
148 | 0 | } |
149 | | // We are not guaranteed to have_tx after AddTx. There are a few possible reasons: |
150 | | // - tx itself exceeds the per-peer memory usage limit, so LimitOrphans had to remove it immediately |
151 | | // - tx itself exceeds the per-peer latency score limit, so LimitOrphans had to remove it immediately |
152 | | // - the orphanage needed trim and all other announcements from this peer are reconsiderable |
153 | 0 | }, |
154 | 0 | [&] { |
155 | 0 | bool have_tx = orphanage->HaveTx(tx->GetWitnessHash()); |
156 | 0 | bool have_tx_and_peer = orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id); |
157 | | // AddAnnouncer should return false if tx doesn't exist or we already HaveTxFromPeer. |
158 | 0 | { |
159 | 0 | bool added_announcer = orphanage->AddAnnouncer(tx->GetWitnessHash(), peer_id); |
160 | | // have_tx == false -> added_announcer == false |
161 | 0 | Assert(have_tx || !added_announcer); |
162 | | // have_tx_and_peer == true -> added_announcer == false |
163 | 0 | Assert(!have_tx_and_peer || !added_announcer); |
164 | | |
165 | | // If announcement was added, total bytes does not increase. |
166 | | // However, if eviction was triggered, the value may decrease. |
167 | 0 | Assert(orphanage->TotalOrphanUsage() <= total_bytes_start); |
168 | 0 | } |
169 | 0 | }, |
170 | 0 | [&] { |
171 | 0 | bool have_tx = orphanage->HaveTx(tx->GetWitnessHash()); |
172 | 0 | bool have_tx_and_peer{orphanage->HaveTxFromPeer(wtxid, peer_id)}; |
173 | | // EraseTx should return 0 if m_orphans doesn't have the tx |
174 | 0 | { |
175 | 0 | auto bytes_from_peer_before{orphanage->UsageByPeer(peer_id)}; |
176 | 0 | Assert(have_tx == orphanage->EraseTx(tx->GetWitnessHash())); |
177 | | // After EraseTx, the orphanage may trim itself, so all peers' usage may have gone up or down. |
178 | 0 | if (have_tx) { |
179 | 0 | if (!have_tx_and_peer) { |
180 | 0 | Assert(orphanage->UsageByPeer(peer_id) == bytes_from_peer_before); |
181 | 0 | } |
182 | 0 | } |
183 | 0 | } |
184 | 0 | have_tx = orphanage->HaveTx(tx->GetWitnessHash()); |
185 | 0 | have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id); |
186 | | // have_tx should be false and EraseTx should fail |
187 | 0 | { |
188 | 0 | Assert(!have_tx && !have_tx_and_peer && !orphanage->EraseTx(wtxid)); |
189 | 0 | } |
190 | 0 | }, |
191 | 0 | [&] { |
192 | 0 | orphanage->EraseForPeer(peer_id); |
193 | 0 | Assert(!orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id)); |
194 | 0 | Assert(orphanage->UsageByPeer(peer_id) == 0); |
195 | 0 | }, |
196 | 0 | [&] { |
197 | | // Make a block out of txs and then EraseForBlock |
198 | 0 | CBlock block; |
199 | 0 | int num_txs = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 1000); |
200 | 0 | for (int i{0}; i < num_txs; ++i) { |
201 | 0 | auto& tx_to_remove = PickValue(fuzzed_data_provider, tx_history); |
202 | 0 | block.vtx.push_back(tx_to_remove); |
203 | 0 | } |
204 | 0 | orphanage->EraseForBlock(block); |
205 | 0 | for (const auto& tx_removed : block.vtx) { |
206 | 0 | Assert(!orphanage->HaveTx(tx_removed->GetWitnessHash())); |
207 | 0 | Assert(!orphanage->HaveTxFromPeer(tx_removed->GetWitnessHash(), peer_id)); |
208 | 0 | } |
209 | 0 | } |
210 | 0 | ); |
211 | 0 | } |
212 | | |
213 | | // Set tx as potential parent to be used for future GetChildren() calls. |
214 | 0 | if (!ptx_potential_parent || fuzzed_data_provider.ConsumeBool()) { |
215 | 0 | ptx_potential_parent = tx; |
216 | 0 | } |
217 | |
|
218 | 0 | const bool have_tx{orphanage->HaveTx(tx->GetWitnessHash())}; |
219 | 0 | const bool get_tx_nonnull{orphanage->GetTx(tx->GetWitnessHash()) != nullptr}; |
220 | 0 | Assert(have_tx == get_tx_nonnull); |
221 | 0 | } |
222 | 0 | orphanage->SanityCheck(); |
223 | 0 | } |
224 | | |
225 | | FUZZ_TARGET(txorphan_protected, .init = initialize_orphanage) |
226 | 0 | { |
227 | 0 | SeedRandomStateForTest(SeedRand::ZEROS); |
228 | 0 | FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
229 | 0 | FastRandomContext orphanage_rng{ConsumeUInt256(fuzzed_data_provider)}; |
230 | 0 | SetMockTime(ConsumeTime(fuzzed_data_provider)); |
231 | | |
232 | | // We have num_peers peers. Some subset of them will never exceed their reserved weight or announcement count, and |
233 | | // should therefore never have any orphans evicted. |
234 | 0 | const unsigned int MAX_PEERS = 125; |
235 | 0 | const unsigned int num_peers = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, MAX_PEERS); |
236 | | // Generate a vector of bools for whether each peer is protected from eviction |
237 | 0 | std::bitset<MAX_PEERS> protected_peers; |
238 | 0 | for (unsigned int i = 0; i < num_peers; i++) { |
239 | 0 | protected_peers.set(i, fuzzed_data_provider.ConsumeBool()); |
240 | 0 | } |
241 | | |
242 | | // Params for orphanage. |
243 | 0 | const unsigned int global_latency_score_limit = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(num_peers, 6'000); |
244 | 0 | const int64_t per_peer_weight_reservation = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(1, 4'040'000); |
245 | 0 | auto orphanage = node::MakeTxOrphanage(global_latency_score_limit, per_peer_weight_reservation); |
246 | | |
247 | | // The actual limit, MaxPeerLatencyScore(), may be higher, since TxOrphanage only counts peers |
248 | | // that have announced an orphan. The honest peer will not experience evictions if it never |
249 | | // exceeds this. |
250 | 0 | const unsigned int honest_latency_limit = global_latency_score_limit / num_peers; |
251 | | // Honest peer will not experience evictions if it never exceeds this. |
252 | 0 | const int64_t honest_mem_limit = per_peer_weight_reservation; |
253 | |
|
254 | 0 | std::vector<COutPoint> outpoints; // Duplicates are tolerated |
255 | 0 | outpoints.reserve(400); |
256 | | |
257 | | // initial outpoints used to construct transactions later |
258 | 0 | for (uint8_t i = 0; i < 4; i++) { |
259 | 0 | outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0); |
260 | 0 | } |
261 | | |
262 | | // These are honest peer's live announcements. We expect them to be protected from eviction. |
263 | 0 | std::set<Wtxid> protected_wtxids; |
264 | |
|
265 | 0 | LIMITED_WHILE(outpoints.size() < 400 && fuzzed_data_provider.ConsumeBool(), 1000) |
266 | 0 | { |
267 | | // construct transaction |
268 | 0 | const CTransactionRef tx = [&] { |
269 | 0 | CMutableTransaction tx_mut; |
270 | 0 | const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); |
271 | 0 | const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 256); |
272 | | // pick outpoints from outpoints as input. We allow input duplicates on purpose, given we are not |
273 | | // running any transaction validation logic before adding transactions to the orphanage |
274 | 0 | tx_mut.vin.reserve(num_in); |
275 | 0 | for (uint32_t i = 0; i < num_in; i++) { |
276 | 0 | auto& prevout = PickValue(fuzzed_data_provider, outpoints); |
277 | | // try making transactions unique by setting a random nSequence, but allow duplicate transactions if they happen |
278 | 0 | tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL)); |
279 | 0 | } |
280 | | // output amount or spendability will not affect txorphanage |
281 | 0 | tx_mut.vout.reserve(num_out); |
282 | 0 | for (uint32_t i = 0; i < num_out; i++) { |
283 | 0 | const auto payload_size = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 100000); |
284 | 0 | if (payload_size) { |
285 | 0 | tx_mut.vout.emplace_back(0, CScript() << OP_RETURN << std::vector<unsigned char>(payload_size)); |
286 | 0 | } else { |
287 | 0 | tx_mut.vout.emplace_back(0, CScript{}); |
288 | 0 | } |
289 | 0 | } |
290 | 0 | auto new_tx = MakeTransactionRef(tx_mut); |
291 | | // add newly constructed outpoints to the coin pool |
292 | 0 | for (uint32_t i = 0; i < num_out; i++) { |
293 | 0 | outpoints.emplace_back(new_tx->GetHash(), i); |
294 | 0 | } |
295 | 0 | return new_tx; |
296 | 0 | }(); |
297 | |
|
298 | 0 | const auto wtxid{tx->GetWitnessHash()}; |
299 | | |
300 | | // orphanage functions |
301 | 0 | LIMITED_WHILE(fuzzed_data_provider.remaining_bytes(), 10 * global_latency_score_limit) |
302 | 0 | { |
303 | 0 | NodeId peer_id = fuzzed_data_provider.ConsumeIntegralInRange<NodeId>(0, num_peers - 1); |
304 | 0 | const auto tx_weight{GetTransactionWeight(*tx)}; |
305 | | |
306 | | // This protected peer will never send orphans that would |
307 | | // exceed their own personal allotment, so is never evicted. |
308 | 0 | const bool peer_is_protected{protected_peers[peer_id]}; |
309 | |
|
310 | 0 | CallOneOf( |
311 | 0 | fuzzed_data_provider, |
312 | 0 | [&] { // AddTx |
313 | 0 | bool have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id); |
314 | 0 | if (peer_is_protected && !have_tx_and_peer && |
315 | 0 | (orphanage->UsageByPeer(peer_id) + tx_weight > honest_mem_limit || |
316 | 0 | orphanage->LatencyScoreFromPeer(peer_id) + (tx->vin.size() / 10) + 1 > honest_latency_limit)) { |
317 | | // We never want our protected peer oversized or over-announced |
318 | 0 | } else { |
319 | 0 | orphanage->AddTx(tx, peer_id); |
320 | 0 | if (peer_is_protected && orphanage->HaveTxFromPeer(wtxid, peer_id)) { |
321 | 0 | protected_wtxids.insert(wtxid); |
322 | 0 | } |
323 | 0 | } |
324 | 0 | }, |
325 | 0 | [&] { // AddAnnouncer |
326 | 0 | bool have_tx_and_peer = orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id); |
327 | | // AddAnnouncer should return false if tx doesn't exist or we already HaveTxFromPeer. |
328 | 0 | { |
329 | 0 | if (peer_is_protected && !have_tx_and_peer && |
330 | 0 | (orphanage->UsageByPeer(peer_id) + tx_weight > honest_mem_limit || |
331 | 0 | orphanage->LatencyScoreFromPeer(peer_id) + (tx->vin.size() / 10) + 1 > honest_latency_limit)) { |
332 | | // We never want our protected peer oversized |
333 | 0 | } else { |
334 | 0 | orphanage->AddAnnouncer(tx->GetWitnessHash(), peer_id); |
335 | 0 | if (peer_is_protected && orphanage->HaveTxFromPeer(wtxid, peer_id)) { |
336 | 0 | protected_wtxids.insert(wtxid); |
337 | 0 | } |
338 | 0 | } |
339 | 0 | } |
340 | 0 | }, |
341 | 0 | [&] { // EraseTx |
342 | 0 | if (protected_wtxids.count(tx->GetWitnessHash())) { |
343 | 0 | protected_wtxids.erase(wtxid); |
344 | 0 | } |
345 | 0 | orphanage->EraseTx(wtxid); |
346 | 0 | Assert(!orphanage->HaveTx(wtxid)); |
347 | 0 | }, |
348 | 0 | [&] { // EraseForPeer |
349 | 0 | if (!protected_peers[peer_id]) { |
350 | 0 | orphanage->EraseForPeer(peer_id); |
351 | 0 | Assert(orphanage->UsageByPeer(peer_id) == 0); |
352 | 0 | Assert(orphanage->LatencyScoreFromPeer(peer_id) == 0); |
353 | 0 | Assert(orphanage->AnnouncementsFromPeer(peer_id) == 0); |
354 | 0 | } |
355 | 0 | } |
356 | 0 | ); |
357 | 0 | } |
358 | 0 | } |
359 | |
|
360 | 0 | orphanage->SanityCheck(); |
361 | | // All of the honest peer's announcements are still present. |
362 | 0 | for (const auto& wtxid : protected_wtxids) { |
363 | 0 | Assert(orphanage->HaveTx(wtxid)); |
364 | 0 | } |
365 | 0 | } |
366 | | |
367 | | FUZZ_TARGET(txorphanage_sim) |
368 | 0 | { |
369 | 0 | SeedRandomStateForTest(SeedRand::ZEROS); |
370 | | // This is a comprehensive simulation fuzz test, which runs through a scenario involving up to |
371 | | // 16 transactions (which may have simple or complex topology, and may have duplicate txids |
372 | | // with distinct wtxids, and up to 16 peers. The scenario is performed both on a real |
373 | | // TxOrphanage object and the behavior is compared with a naive reimplementation (just a vector |
374 | | // of announcements) where possible, and tested for desired properties where not possible. |
375 | | |
376 | | // |
377 | | // 1. Setup. |
378 | | // |
379 | | |
380 | | /** The total number of transactions this simulation uses (not all of which will necessarily |
381 | | * be present in the orphanage at once). */ |
382 | 0 | static constexpr unsigned NUM_TX = 16; |
383 | | /** The number of peers this simulation uses (not all of which will necessarily be present in |
384 | | * the orphanage at once). */ |
385 | 0 | static constexpr unsigned NUM_PEERS = 16; |
386 | | /** The maximum number of announcements this simulation uses (which may be higher than the |
387 | | * number permitted inside the orphanage). */ |
388 | 0 | static constexpr unsigned MAX_ANN = 64; |
389 | |
|
390 | 0 | FuzzedDataProvider provider(buffer.data(), buffer.size()); |
391 | | /** Local RNG. Only used for topology/sizes of the transaction set, the order of transactions |
392 | | * in EraseForBlock, and for the randomized passed to AddChildrenToWorkSet. */ |
393 | 0 | InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>()); |
394 | | |
395 | | // |
396 | | // 2. Construct an interesting set of 16 transactions. |
397 | | // |
398 | | |
399 | | // - Pick a topological order among the transactions. |
400 | 0 | std::vector<unsigned> txorder(NUM_TX); |
401 | 0 | std::iota(txorder.begin(), txorder.end(), unsigned{0}); |
402 | 0 | std::shuffle(txorder.begin(), txorder.end(), rng); |
403 | | // - Pick a set of dependencies (pair<child_index, parent_index>). |
404 | 0 | std::vector<std::pair<unsigned, unsigned>> deps; |
405 | 0 | deps.reserve((NUM_TX * (NUM_TX - 1)) / 2); |
406 | 0 | for (unsigned p = 0; p < NUM_TX - 1; ++p) { |
407 | 0 | for (unsigned c = p + 1; c < NUM_TX; ++c) { |
408 | 0 | deps.emplace_back(c, p); |
409 | 0 | } |
410 | 0 | } |
411 | 0 | std::shuffle(deps.begin(), deps.end(), rng); |
412 | 0 | deps.resize(provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX * 4 - 1)); |
413 | | // - Construct the actual transactions. |
414 | 0 | std::set<Wtxid> wtxids; |
415 | 0 | std::vector<CTransactionRef> txn(NUM_TX); |
416 | 0 | node::TxOrphanage::Usage total_usage{0}; |
417 | 0 | for (unsigned t = 0; t < NUM_TX; ++t) { |
418 | 0 | CMutableTransaction tx; |
419 | 0 | if (t > 0 && rng.randrange(4) == 0) { |
420 | | // Occasionally duplicate the previous transaction, so that repetitions of the same |
421 | | // txid are possible (with different wtxid). |
422 | 0 | tx = CMutableTransaction(*txn[txorder[t - 1]]); |
423 | 0 | } else { |
424 | 0 | tx.version = 1; |
425 | 0 | tx.nLockTime = 0xffffffff; |
426 | | // Construct 1 to 16 outputs. |
427 | 0 | auto num_outputs = rng.randrange<unsigned>(1 << rng.randrange<unsigned>(5)) + 1; |
428 | 0 | for (unsigned output = 0; output < num_outputs; ++output) { |
429 | 0 | CScript scriptpubkey; |
430 | 0 | scriptpubkey.resize(provider.ConsumeIntegralInRange<unsigned>(20, 34)); |
431 | 0 | tx.vout.emplace_back(CAmount{0}, std::move(scriptpubkey)); |
432 | 0 | } |
433 | | // Construct inputs (one for each dependency). |
434 | 0 | for (auto& [child, parent] : deps) { |
435 | 0 | if (child == t) { |
436 | 0 | auto& partx = txn[txorder[parent]]; |
437 | 0 | assert(partx->version == 1); |
438 | 0 | COutPoint outpoint(partx->GetHash(), rng.randrange<size_t>(partx->vout.size())); |
439 | 0 | tx.vin.emplace_back(outpoint); |
440 | 0 | tx.vin.back().scriptSig.resize(provider.ConsumeIntegralInRange<unsigned>(16, 200)); |
441 | 0 | } |
442 | 0 | } |
443 | | // Construct fallback input in case there are no dependencies. |
444 | 0 | if (tx.vin.empty()) { |
445 | 0 | COutPoint outpoint(Txid::FromUint256(rng.rand256()), rng.randrange<size_t>(16)); |
446 | 0 | tx.vin.emplace_back(outpoint); |
447 | 0 | tx.vin.back().scriptSig.resize(provider.ConsumeIntegralInRange<unsigned>(16, 200)); |
448 | 0 | } |
449 | 0 | } |
450 | | // Optionally modify the witness (allowing wtxid != txid), and certainly when the wtxid |
451 | | // already exists. |
452 | 0 | while (wtxids.contains(CTransaction(tx).GetWitnessHash()) || rng.randrange(4) == 0) { |
453 | 0 | auto& input = tx.vin[rng.randrange(tx.vin.size())]; |
454 | 0 | if (rng.randbool()) { |
455 | 0 | input.scriptWitness.stack.resize(1); |
456 | 0 | input.scriptWitness.stack[0].resize(rng.randrange(100)); |
457 | 0 | } else { |
458 | 0 | input.scriptWitness.stack.resize(0); |
459 | 0 | } |
460 | 0 | } |
461 | | // Convert to CTransactionRef. |
462 | 0 | txn[txorder[t]] = MakeTransactionRef(std::move(tx)); |
463 | 0 | wtxids.insert(txn[txorder[t]]->GetWitnessHash()); |
464 | 0 | auto weight = GetTransactionWeight(*txn[txorder[t]]); |
465 | 0 | assert(weight < MAX_STANDARD_TX_WEIGHT); |
466 | 0 | total_usage += GetTransactionWeight(*txn[txorder[t]]); |
467 | 0 | } |
468 | | |
469 | | // |
470 | | // 3. Initialize real orphanage |
471 | | // |
472 | | |
473 | 0 | auto max_global_latency_score = provider.ConsumeIntegralInRange<node::TxOrphanage::Count>(NUM_PEERS, MAX_ANN); |
474 | 0 | auto reserved_peer_usage = provider.ConsumeIntegralInRange<node::TxOrphanage::Usage>(1, total_usage); |
475 | 0 | auto real = node::MakeTxOrphanage(max_global_latency_score, reserved_peer_usage); |
476 | | |
477 | | // |
478 | | // 4. Functions and data structures for the simulation. |
479 | | // |
480 | | |
481 | | /** Data structure representing one announcement (pair of (tx, peer), plus whether it's |
482 | | * reconsiderable or not. */ |
483 | 0 | struct SimAnnouncement |
484 | 0 | { |
485 | 0 | unsigned tx; |
486 | 0 | NodeId announcer; |
487 | 0 | bool reconsider{false}; |
488 | 0 | SimAnnouncement(unsigned tx_in, NodeId announcer_in, bool reconsider_in) noexcept : |
489 | 0 | tx(tx_in), announcer(announcer_in), reconsider(reconsider_in) {} |
490 | 0 | }; |
491 | | /** The entire simulated orphanage is represented by this list of announcements, in |
492 | | * announcement order (unlike TxOrphanageImpl which uses a sequence number to represent |
493 | | * announcement order). New announcements are added to the back. */ |
494 | 0 | std::vector<SimAnnouncement> sim_announcements; |
495 | | |
496 | | /** Consume a transaction (index into txn) from provider. */ |
497 | 0 | auto read_tx_fn = [&]() -> unsigned { return provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX - 1); }; |
498 | | /** Consume a NodeId from provider. */ |
499 | 0 | auto read_peer_fn = [&]() -> NodeId { return provider.ConsumeIntegralInRange<unsigned>(0, NUM_PEERS - 1); }; |
500 | | /** Consume both a transaction (index into txn) and a NodeId from provider. */ |
501 | 0 | auto read_tx_peer_fn = [&]() -> std::pair<unsigned, NodeId> { |
502 | 0 | auto code = provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX * NUM_PEERS - 1); |
503 | 0 | return {code % NUM_TX, code / NUM_TX}; |
504 | 0 | }; |
505 | | /** Determine if we have any announcements of the given transaction in the simulation. */ |
506 | 0 | auto have_tx_fn = [&](unsigned tx) -> bool { |
507 | 0 | for (auto& ann : sim_announcements) { |
508 | 0 | if (ann.tx == tx) return true; |
509 | 0 | } |
510 | 0 | return false; |
511 | 0 | }; |
512 | | /** Count the number of peers in the simulation. */ |
513 | 0 | auto count_peers_fn = [&]() -> unsigned { |
514 | 0 | std::bitset<NUM_PEERS> mask; |
515 | 0 | for (auto& ann : sim_announcements) { |
516 | 0 | mask.set(ann.announcer); |
517 | 0 | } |
518 | 0 | return mask.count(); |
519 | 0 | }; |
520 | | /** Determine if we have any reconsiderable announcements of a given transaction. */ |
521 | 0 | auto have_reconsiderable_fn = [&](unsigned tx) -> bool { |
522 | 0 | for (auto& ann : sim_announcements) { |
523 | 0 | if (ann.reconsider && ann.tx == tx) return true; |
524 | 0 | } |
525 | 0 | return false; |
526 | 0 | }; |
527 | | /** Determine if a peer has any transactions to reconsider. */ |
528 | 0 | auto have_reconsider_fn = [&](NodeId peer) -> bool { |
529 | 0 | for (auto& ann : sim_announcements) { |
530 | 0 | if (ann.reconsider && ann.announcer == peer) return true; |
531 | 0 | } |
532 | 0 | return false; |
533 | 0 | }; |
534 | | /** Get an iterator to an existing (wtxid, peer) pair in the simulation. */ |
535 | 0 | auto find_announce_wtxid_fn = [&](const Wtxid& wtxid, NodeId peer) -> std::vector<SimAnnouncement>::iterator { |
536 | 0 | for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) { |
537 | 0 | if (txn[it->tx]->GetWitnessHash() == wtxid && it->announcer == peer) return it; |
538 | 0 | } |
539 | 0 | return sim_announcements.end(); |
540 | 0 | }; |
541 | | /** Get an iterator to an existing (tx, peer) pair in the simulation. */ |
542 | 0 | auto find_announce_fn = [&](unsigned tx, NodeId peer) { |
543 | 0 | for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) { |
544 | 0 | if (it->tx == tx && it->announcer == peer) return it; |
545 | 0 | } |
546 | 0 | return sim_announcements.end(); |
547 | 0 | }; |
548 | | /** Compute a peer's DoS score according to simulation data. */ |
549 | 0 | auto dos_score_fn = [&](NodeId peer, int32_t max_count, int32_t max_usage) -> FeeFrac { |
550 | 0 | int64_t count{0}; |
551 | 0 | int64_t usage{0}; |
552 | 0 | for (auto& ann : sim_announcements) { |
553 | 0 | if (ann.announcer != peer) continue; |
554 | 0 | count += 1 + (txn[ann.tx]->vin.size() / 10); |
555 | 0 | usage += GetTransactionWeight(*txn[ann.tx]); |
556 | 0 | } |
557 | 0 | return std::max(FeeFrac{count, max_count}, FeeFrac{usage, max_usage}); |
558 | 0 | }; |
559 | | |
560 | | // |
561 | | // 5. Run through a scenario of mutators on both real and simulated orphanage. |
562 | | // |
563 | |
|
564 | 0 | LIMITED_WHILE(provider.remaining_bytes() > 0, 200) { |
565 | 0 | int command = provider.ConsumeIntegralInRange<uint8_t>(0, 15); |
566 | 0 | while (true) { |
567 | 0 | if (sim_announcements.size() < MAX_ANN && command-- == 0) { |
568 | | // AddTx |
569 | 0 | auto [tx, peer] = read_tx_peer_fn(); |
570 | 0 | bool added = real->AddTx(txn[tx], peer); |
571 | 0 | bool sim_have_tx = have_tx_fn(tx); |
572 | 0 | assert(added == !sim_have_tx); |
573 | 0 | if (find_announce_fn(tx, peer) == sim_announcements.end()) { |
574 | 0 | sim_announcements.emplace_back(tx, peer, false); |
575 | 0 | } |
576 | 0 | break; |
577 | 0 | } else if (sim_announcements.size() < MAX_ANN && command-- == 0) { |
578 | | // AddAnnouncer |
579 | 0 | auto [tx, peer] = read_tx_peer_fn(); |
580 | 0 | bool added = real->AddAnnouncer(txn[tx]->GetWitnessHash(), peer); |
581 | 0 | bool sim_have_tx = have_tx_fn(tx); |
582 | 0 | auto sim_it = find_announce_fn(tx, peer); |
583 | 0 | assert(added == (sim_it == sim_announcements.end() && sim_have_tx)); |
584 | 0 | if (added) { |
585 | 0 | sim_announcements.emplace_back(tx, peer, false); |
586 | 0 | } |
587 | 0 | break; |
588 | 0 | } else if (command-- == 0) { |
589 | | // EraseTx |
590 | 0 | auto tx = read_tx_fn(); |
591 | 0 | bool erased = real->EraseTx(txn[tx]->GetWitnessHash()); |
592 | 0 | bool sim_have = have_tx_fn(tx); |
593 | 0 | assert(erased == sim_have); |
594 | 0 | std::erase_if(sim_announcements, [&](auto& ann) { return ann.tx == tx; }); |
595 | 0 | break; |
596 | 0 | } else if (command-- == 0) { |
597 | | // EraseForPeer |
598 | 0 | auto peer = read_peer_fn(); |
599 | 0 | real->EraseForPeer(peer); |
600 | 0 | std::erase_if(sim_announcements, [&](auto& ann) { return ann.announcer == peer; }); |
601 | 0 | break; |
602 | 0 | } else if (command-- == 0) { |
603 | | // EraseForBlock |
604 | 0 | auto pattern = provider.ConsumeIntegralInRange<uint64_t>(0, (uint64_t{1} << NUM_TX) - 1); |
605 | 0 | CBlock block; |
606 | 0 | std::set<COutPoint> spent; |
607 | 0 | for (unsigned tx = 0; tx < NUM_TX; ++tx) { |
608 | 0 | if ((pattern >> tx) & 1) { |
609 | 0 | block.vtx.emplace_back(txn[tx]); |
610 | 0 | for (auto& txin : block.vtx.back()->vin) { |
611 | 0 | spent.insert(txin.prevout); |
612 | 0 | } |
613 | 0 | } |
614 | 0 | } |
615 | 0 | std::shuffle(block.vtx.begin(), block.vtx.end(), rng); |
616 | 0 | real->EraseForBlock(block); |
617 | 0 | std::erase_if(sim_announcements, [&](auto& ann) { |
618 | 0 | for (auto& txin : txn[ann.tx]->vin) { |
619 | 0 | if (spent.count(txin.prevout)) return true; |
620 | 0 | } |
621 | 0 | return false; |
622 | 0 | }); |
623 | 0 | break; |
624 | 0 | } else if (command-- == 0) { |
625 | | // AddChildrenToWorkSet |
626 | 0 | auto tx = read_tx_fn(); |
627 | 0 | FastRandomContext rand_ctx(rng.rand256()); |
628 | 0 | auto added = real->AddChildrenToWorkSet(*txn[tx], rand_ctx); |
629 | | /** Set of not-already-reconsiderable child wtxids. */ |
630 | 0 | std::set<Wtxid> child_wtxids; |
631 | 0 | for (unsigned child_tx = 0; child_tx < NUM_TX; ++child_tx) { |
632 | 0 | if (!have_tx_fn(child_tx)) continue; |
633 | 0 | if (have_reconsiderable_fn(child_tx)) continue; |
634 | 0 | bool child_of = false; |
635 | 0 | for (auto& txin : txn[child_tx]->vin) { |
636 | 0 | if (txin.prevout.hash == txn[tx]->GetHash()) { |
637 | 0 | child_of = true; |
638 | 0 | break; |
639 | 0 | } |
640 | 0 | } |
641 | 0 | if (child_of) { |
642 | 0 | child_wtxids.insert(txn[child_tx]->GetWitnessHash()); |
643 | 0 | } |
644 | 0 | } |
645 | 0 | for (auto& [wtxid, peer] : added) { |
646 | | // Wtxid must be a child of tx that is not yet reconsiderable. |
647 | 0 | auto child_wtxid_it = child_wtxids.find(wtxid); |
648 | 0 | assert(child_wtxid_it != child_wtxids.end()); |
649 | | // Announcement must exist. |
650 | 0 | auto sim_ann_it = find_announce_wtxid_fn(wtxid, peer); |
651 | 0 | assert(sim_ann_it != sim_announcements.end()); |
652 | | // Announcement must not yet be reconsiderable. |
653 | 0 | assert(sim_ann_it->reconsider == false); |
654 | | // Make reconsiderable. |
655 | 0 | sim_ann_it->reconsider = true; |
656 | | // Remove from child_wtxids map, to disallow it being reported a second time in added. |
657 | 0 | child_wtxids.erase(wtxid); |
658 | 0 | } |
659 | | // Verify that AddChildrenToWorkSet does not select announcements that were already reconsiderable: |
660 | | // Check all child wtxids which did not occur at least once in the result were already reconsiderable |
661 | | // due to a previous AddChildrenToWorkSet. |
662 | 0 | assert(child_wtxids.empty()); |
663 | 0 | break; |
664 | 0 | } else if (command-- == 0) { |
665 | | // GetTxToReconsider. |
666 | 0 | auto peer = read_peer_fn(); |
667 | 0 | auto result = real->GetTxToReconsider(peer); |
668 | 0 | if (result) { |
669 | | // A transaction was found. It must have a corresponding reconsiderable |
670 | | // announcement from peer. |
671 | 0 | auto sim_ann_it = find_announce_wtxid_fn(result->GetWitnessHash(), peer); |
672 | 0 | assert(sim_ann_it != sim_announcements.end()); |
673 | 0 | assert(sim_ann_it->announcer == peer); |
674 | 0 | assert(sim_ann_it->reconsider); |
675 | | // Make it non-reconsiderable. |
676 | 0 | sim_ann_it->reconsider = false; |
677 | 0 | } else { |
678 | | // No reconsiderable transaction was found from peer. Verify that it does not |
679 | | // have any. |
680 | 0 | assert(!have_reconsider_fn(peer)); |
681 | 0 | } |
682 | 0 | break; |
683 | 0 | } |
684 | 0 | } |
685 | | // Always trim after each command if needed. |
686 | 0 | const auto max_ann = max_global_latency_score / std::max<unsigned>(1, count_peers_fn()); |
687 | 0 | const auto max_mem = reserved_peer_usage; |
688 | 0 | while (true) { |
689 | | // Count global usage and number of peers. |
690 | 0 | node::TxOrphanage::Usage total_usage{0}; |
691 | 0 | node::TxOrphanage::Count total_latency_score = sim_announcements.size(); |
692 | 0 | for (unsigned tx = 0; tx < NUM_TX; ++tx) { |
693 | 0 | if (have_tx_fn(tx)) { |
694 | 0 | total_usage += GetTransactionWeight(*txn[tx]); |
695 | 0 | total_latency_score += txn[tx]->vin.size() / 10; |
696 | 0 | } |
697 | 0 | } |
698 | 0 | auto num_peers = count_peers_fn(); |
699 | 0 | bool oversized = (total_usage > reserved_peer_usage * num_peers) || |
700 | 0 | (total_latency_score > real->MaxGlobalLatencyScore()); |
701 | 0 | if (!oversized) break; |
702 | | // Find worst peer. |
703 | 0 | FeeFrac worst_dos_score{0, 1}; |
704 | 0 | unsigned worst_peer = unsigned(-1); |
705 | 0 | for (unsigned peer = 0; peer < NUM_PEERS; ++peer) { |
706 | 0 | auto dos_score = dos_score_fn(peer, max_ann, max_mem); |
707 | | // Use >= so that the more recent peer (higher NodeId) wins in case of |
708 | | // ties. |
709 | 0 | if (dos_score >= worst_dos_score) { |
710 | 0 | worst_dos_score = dos_score; |
711 | 0 | worst_peer = peer; |
712 | 0 | } |
713 | 0 | } |
714 | 0 | assert(worst_peer != unsigned(-1)); |
715 | 0 | assert(worst_dos_score >> FeeFrac(1, 1)); |
716 | | // Find oldest announcement from worst_peer, preferring non-reconsiderable ones. |
717 | 0 | bool done{false}; |
718 | 0 | for (int reconsider = 0; reconsider < 2; ++reconsider) { |
719 | 0 | for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) { |
720 | 0 | if (it->announcer != worst_peer || it->reconsider != reconsider) continue; |
721 | 0 | sim_announcements.erase(it); |
722 | 0 | done = true; |
723 | 0 | break; |
724 | 0 | } |
725 | 0 | if (done) break; |
726 | 0 | } |
727 | 0 | assert(done); |
728 | 0 | } |
729 | | // We must now be within limits, otherwise LimitOrphans should have continued further. |
730 | | // We don't check the contents of the orphanage until the end to make fuzz runs faster. |
731 | 0 | assert(real->TotalLatencyScore() <= real->MaxGlobalLatencyScore()); |
732 | 0 | assert(real->TotalOrphanUsage() <= real->MaxGlobalUsage()); |
733 | 0 | } |
734 | | |
735 | | // |
736 | | // 6. Perform a full comparison between the real orphanage's inspectors and the simulation. |
737 | | // |
738 | | |
739 | 0 | real->SanityCheck(); |
740 | | |
741 | |
|
742 | 0 | auto all_orphans = real->GetOrphanTransactions(); |
743 | 0 | node::TxOrphanage::Usage orphan_usage{0}; |
744 | 0 | std::vector<node::TxOrphanage::Usage> usage_by_peer(NUM_PEERS); |
745 | 0 | node::TxOrphanage::Count unique_orphans{0}; |
746 | 0 | std::vector<node::TxOrphanage::Count> count_by_peer(NUM_PEERS); |
747 | 0 | node::TxOrphanage::Count total_latency_score = sim_announcements.size(); |
748 | 0 | for (unsigned tx = 0; tx < NUM_TX; ++tx) { |
749 | 0 | bool sim_have_tx = have_tx_fn(tx); |
750 | 0 | if (sim_have_tx) { |
751 | 0 | orphan_usage += GetTransactionWeight(*txn[tx]); |
752 | 0 | total_latency_score += txn[tx]->vin.size() / 10; |
753 | 0 | } |
754 | 0 | unique_orphans += sim_have_tx; |
755 | 0 | auto orphans_it = std::find_if(all_orphans.begin(), all_orphans.end(), [&](auto& orph) { return orph.tx->GetWitnessHash() == txn[tx]->GetWitnessHash(); }); |
756 | | // GetOrphanTransactions (OrphanBase existence) |
757 | 0 | assert((orphans_it != all_orphans.end()) == sim_have_tx); |
758 | | // HaveTx |
759 | 0 | bool have_tx = real->HaveTx(txn[tx]->GetWitnessHash()); |
760 | 0 | assert(have_tx == sim_have_tx); |
761 | | // GetTx |
762 | 0 | auto txref = real->GetTx(txn[tx]->GetWitnessHash()); |
763 | 0 | assert(!!txref == sim_have_tx); |
764 | 0 | if (sim_have_tx) assert(txref->GetWitnessHash() == txn[tx]->GetWitnessHash()); |
765 | | |
766 | 0 | for (NodeId peer = 0; peer < NUM_PEERS; ++peer) { |
767 | 0 | auto it_sim_ann = find_announce_fn(tx, peer); |
768 | 0 | bool sim_have_ann = it_sim_ann != sim_announcements.end(); |
769 | 0 | if (sim_have_ann) usage_by_peer[peer] += GetTransactionWeight(*txn[tx]); |
770 | 0 | count_by_peer[peer] += sim_have_ann; |
771 | | // GetOrphanTransactions (announcers presence) |
772 | 0 | if (sim_have_ann) assert(sim_have_tx); |
773 | 0 | if (sim_have_tx) assert(orphans_it->announcers.count(peer) == sim_have_ann); |
774 | | // HaveTxFromPeer |
775 | 0 | bool have_ann = real->HaveTxFromPeer(txn[tx]->GetWitnessHash(), peer); |
776 | 0 | assert(sim_have_ann == have_ann); |
777 | | // GetChildrenFromSamePeer |
778 | 0 | auto children_from_peer = real->GetChildrenFromSamePeer(txn[tx], peer); |
779 | 0 | auto it = children_from_peer.rbegin(); |
780 | 0 | for (int phase = 0; phase < 2; ++phase) { |
781 | | // First expect all children which have reconsiderable announcement from peer, then the others. |
782 | 0 | for (auto& ann : sim_announcements) { |
783 | 0 | if (ann.announcer != peer) continue; |
784 | 0 | if (ann.reconsider != (phase == 1)) continue; |
785 | 0 | bool matching_parent{false}; |
786 | 0 | for (const auto& vin : txn[ann.tx]->vin) { |
787 | 0 | if (vin.prevout.hash == txn[tx]->GetHash()) matching_parent = true; |
788 | 0 | } |
789 | 0 | if (!matching_parent) continue; |
790 | | // Found an announcement from peer which is a child of txn[tx]. |
791 | 0 | assert(it != children_from_peer.rend()); |
792 | 0 | assert((*it)->GetWitnessHash() == txn[ann.tx]->GetWitnessHash()); |
793 | 0 | ++it; |
794 | 0 | } |
795 | 0 | } |
796 | 0 | assert(it == children_from_peer.rend()); |
797 | 0 | } |
798 | 0 | } |
799 | | // TotalOrphanUsage |
800 | 0 | assert(orphan_usage == real->TotalOrphanUsage()); |
801 | 0 | for (NodeId peer = 0; peer < NUM_PEERS; ++peer) { |
802 | 0 | bool sim_have_reconsider = have_reconsider_fn(peer); |
803 | | // HaveTxToReconsider |
804 | 0 | bool have_reconsider = real->HaveTxToReconsider(peer); |
805 | 0 | assert(have_reconsider == sim_have_reconsider); |
806 | | // UsageByPeer |
807 | 0 | assert(usage_by_peer[peer] == real->UsageByPeer(peer)); |
808 | | // AnnouncementsFromPeer |
809 | 0 | assert(count_by_peer[peer] == real->AnnouncementsFromPeer(peer)); |
810 | 0 | } |
811 | | // CountAnnouncements |
812 | 0 | assert(sim_announcements.size() == real->CountAnnouncements()); |
813 | | // CountUniqueOrphans |
814 | 0 | assert(unique_orphans == real->CountUniqueOrphans()); |
815 | | // MaxGlobalLatencyScore |
816 | 0 | assert(max_global_latency_score == real->MaxGlobalLatencyScore()); |
817 | | // ReservedPeerUsage |
818 | 0 | assert(reserved_peer_usage == real->ReservedPeerUsage()); |
819 | | // MaxPeerLatencyScore |
820 | 0 | auto present_peers = count_peers_fn(); |
821 | 0 | assert(max_global_latency_score / std::max<unsigned>(1, present_peers) == real->MaxPeerLatencyScore()); |
822 | | // MaxGlobalUsage |
823 | 0 | assert(reserved_peer_usage * std::max<unsigned>(1, present_peers) == real->MaxGlobalUsage()); |
824 | | // TotalLatencyScore. |
825 | 0 | assert(real->TotalLatencyScore() == total_latency_score); |
826 | 0 | } |