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