|
19 | 19 | #include <util/check.h> |
20 | 20 | #include <util/time.h> |
21 | 21 |
|
| 22 | +#include <bitset> |
22 | 23 | #include <cstdint> |
23 | 24 | #include <memory> |
24 | 25 | #include <set> |
@@ -228,3 +229,168 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage) |
228 | 229 | } |
229 | 230 | orphanage->SanityCheck(); |
230 | 231 | } |
| 232 | + |
| 233 | +FUZZ_TARGET(txorphan_protected, .init = initialize_orphanage) |
| 234 | +{ |
| 235 | + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
| 236 | + FastRandomContext orphanage_rng{/*fDeterministic=*/true}; |
| 237 | + SetMockTime(ConsumeTime(fuzzed_data_provider)); |
| 238 | + |
| 239 | + // We have NUM_PEERS, of which Peer==0 is the "honest" one |
| 240 | + // who will never exceed their reserved weight or announcement |
| 241 | + // count, and should therefore never be evicted. |
| 242 | + const unsigned int MAX_PEERS = 125; |
| 243 | + const unsigned int NUM_PEERS = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, MAX_PEERS); |
| 244 | + // Generate a vector of bools for whether each peer is protected from eviction |
| 245 | + std::bitset<MAX_PEERS> protected_peers; |
| 246 | + for (unsigned int i = 0; i < NUM_PEERS; i++) { |
| 247 | + protected_peers.set(i, fuzzed_data_provider.ConsumeBool()); |
| 248 | + } |
| 249 | + |
| 250 | + // Params for orphanage. |
| 251 | + const unsigned int global_announcement_limit = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(NUM_PEERS, 6'000); |
| 252 | + const int64_t per_peer_weight_reservation = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(1, 4'040'000); |
| 253 | + auto orphanage = node::MakeTxOrphanage(global_announcement_limit, per_peer_weight_reservation); |
| 254 | + |
| 255 | + // The actual limit, MaxPeerAnnouncements(), may be higher, since TxOrphanage only counts peers |
| 256 | + // that have announced an orphan. The honest peer will not experience evictions if it never |
| 257 | + // exceeds this. |
| 258 | + const unsigned int honest_ann_limit = global_announcement_limit / NUM_PEERS; |
| 259 | + // Honest peer will not experience evictions if it never exceeds this. |
| 260 | + const int64_t honest_mem_limit = per_peer_weight_reservation; |
| 261 | + |
| 262 | + std::vector<COutPoint> outpoints; // Duplicates are tolerated |
| 263 | + outpoints.reserve(200'000); |
| 264 | + |
| 265 | + // initial outpoints used to construct transactions later |
| 266 | + for (uint8_t i = 0; i < 4; i++) { |
| 267 | + outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0); |
| 268 | + } |
| 269 | + |
| 270 | + // These are honest peer's live announcements. We expect them to be protected from eviction. |
| 271 | + std::set<Wtxid> protected_wtxids; |
| 272 | + |
| 273 | + LIMITED_WHILE(outpoints.size() < 200'000 && fuzzed_data_provider.ConsumeBool(), 10 * global_announcement_limit) |
| 274 | + { |
| 275 | + // construct transaction |
| 276 | + const CTransactionRef tx = [&] { |
| 277 | + CMutableTransaction tx_mut; |
| 278 | + const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); |
| 279 | + const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 256); |
| 280 | + // pick outpoints from outpoints as input. We allow input duplicates on purpose, given we are not |
| 281 | + // running any transaction validation logic before adding transactions to the orphanage |
| 282 | + tx_mut.vin.reserve(num_in); |
| 283 | + for (uint32_t i = 0; i < num_in; i++) { |
| 284 | + auto& prevout = PickValue(fuzzed_data_provider, outpoints); |
| 285 | + // try making transactions unique by setting a random nSequence, but allow duplicate transactions if they happen |
| 286 | + tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL)); |
| 287 | + } |
| 288 | + // output amount or spendability will not affect txorphanage |
| 289 | + tx_mut.vout.reserve(num_out); |
| 290 | + for (uint32_t i = 0; i < num_out; i++) { |
| 291 | + const auto payload_size = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 100000); |
| 292 | + if (payload_size) { |
| 293 | + tx_mut.vout.emplace_back(0, CScript() << OP_RETURN << std::vector<unsigned char>(payload_size)); |
| 294 | + } else { |
| 295 | + tx_mut.vout.emplace_back(0, CScript{}); |
| 296 | + } |
| 297 | + } |
| 298 | + auto new_tx = MakeTransactionRef(tx_mut); |
| 299 | + // add newly constructed outpoints to the coin pool |
| 300 | + for (uint32_t i = 0; i < num_out; i++) { |
| 301 | + outpoints.emplace_back(new_tx->GetHash(), i); |
| 302 | + } |
| 303 | + return new_tx; |
| 304 | + }(); |
| 305 | + |
| 306 | + const auto wtxid{tx->GetWitnessHash()}; |
| 307 | + |
| 308 | + // orphanage functions |
| 309 | + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10 * global_announcement_limit) |
| 310 | + { |
| 311 | + NodeId peer_id = fuzzed_data_provider.ConsumeIntegralInRange<NodeId>(0, NUM_PEERS - 1); |
| 312 | + const auto tx_weight{GetTransactionWeight(*tx)}; |
| 313 | + |
| 314 | + // This protected peer will never send orphans that would |
| 315 | + // exceed their own personal allotment, so is never evicted. |
| 316 | + const bool peer_is_protected{protected_peers[peer_id]}; |
| 317 | + |
| 318 | + CallOneOf( |
| 319 | + fuzzed_data_provider, |
| 320 | + [&] { // AddTx |
| 321 | + bool have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id); |
| 322 | + if (peer_is_protected && !have_tx_and_peer && |
| 323 | + (orphanage->UsageFromPeer(peer_id) + tx_weight > honest_mem_limit || |
| 324 | + orphanage->AnnouncementsFromPeer(peer_id) + 1 > honest_ann_limit)) { |
| 325 | + // We never want our protected peer oversized or over-announced |
| 326 | + } else { |
| 327 | + orphanage->AddTx(tx, peer_id); |
| 328 | + if (peer_is_protected && orphanage->HaveTxFromPeer(wtxid, peer_id)) { |
| 329 | + protected_wtxids.insert(wtxid); |
| 330 | + } |
| 331 | + } |
| 332 | + }, |
| 333 | + [&] { // AddAnnouncer |
| 334 | + bool have_tx_and_peer = orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id); |
| 335 | + // AddAnnouncer should return false if tx doesn't exist or we already HaveTxFromPeer. |
| 336 | + { |
| 337 | + if (peer_is_protected && !have_tx_and_peer && |
| 338 | + (orphanage->UsageFromPeer(peer_id) + tx_weight > honest_mem_limit || |
| 339 | + orphanage->AnnouncementsFromPeer(peer_id) + 1 > honest_ann_limit)) { |
| 340 | + // We never want our protected peer oversized |
| 341 | + } else { |
| 342 | + orphanage->AddAnnouncer(tx->GetWitnessHash(), peer_id); |
| 343 | + if (peer_is_protected && orphanage->HaveTxFromPeer(wtxid, peer_id)) { |
| 344 | + protected_wtxids.insert(wtxid); |
| 345 | + } |
| 346 | + } |
| 347 | + } |
| 348 | + }, |
| 349 | + [&] { // EraseTx |
| 350 | + if (protected_wtxids.count(tx->GetWitnessHash())) { |
| 351 | + protected_wtxids.erase(wtxid); |
| 352 | + } |
| 353 | + orphanage->EraseTx(wtxid); |
| 354 | + Assert(!orphanage->HaveTx(wtxid)); |
| 355 | + }, |
| 356 | + [&] { // EraseForPeer |
| 357 | + if (!protected_peers[peer_id]) { |
| 358 | + orphanage->EraseForPeer(peer_id); |
| 359 | + } |
| 360 | + }, |
| 361 | + [&] { // LimitOrphans |
| 362 | + // Assert that protected peers are never affected by LimitOrphans. |
| 363 | + unsigned int protected_count = 0; |
| 364 | + unsigned int protected_bytes = 0; |
| 365 | + for (unsigned int peer = 0; peer < NUM_PEERS; ++peer) { |
| 366 | + if (protected_peers[peer]) { |
| 367 | + protected_count += orphanage->AnnouncementsFromPeer(peer); |
| 368 | + protected_bytes += orphanage->UsageByPeer(peer); |
| 369 | + } |
| 370 | + } |
| 371 | + orphanage->LimitOrphans(); |
| 372 | + Assert(!orphanage->NeedsTrim()); |
| 373 | + Assert(orphanage->CountAnnouncements() <= global_announcement_limit); |
| 374 | + Assert(orphanage->TotalOrphanUsage() <= per_peer_weight_reservation * NUM_PEERS); |
| 375 | + |
| 376 | + // Number of announcements and usage should never differ before and after since |
| 377 | + // we've never exceeded the per-peer reservations. |
| 378 | + for (unsigned int peer = 0; peer < NUM_PEERS; ++peer) { |
| 379 | + if (protected_peers[peer]) { |
| 380 | + protected_count -= orphanage->AnnouncementsFromPeer(peer); |
| 381 | + protected_bytes -= orphanage->UsageByPeer(peer); |
| 382 | + } |
| 383 | + } |
| 384 | + Assert(protected_count == 0); |
| 385 | + Assert(protected_bytes == 0); |
| 386 | + }); |
| 387 | + |
| 388 | + } |
| 389 | + } |
| 390 | + |
| 391 | + orphanage->SanityCheck(); |
| 392 | + // All of the honest peer's announcements are still present. |
| 393 | + for (const auto& wtxid : protected_wtxids) { |
| 394 | + Assert(orphanage->HaveTx(wtxid)); |
| 395 | + } |
| 396 | +} |
0 commit comments