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