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