Skip to content

Commit 24afee8

Browse files
glozowinstagibbs
andcommitted
[fuzz] TxOrphanage protects peers that don't go over limit
Co-authored-by: Greg Sanders <[email protected]>
1 parent a2878cf commit 24afee8

File tree

1 file changed

+169
-0
lines changed

1 file changed

+169
-0
lines changed

src/test/fuzz/txorphan.cpp

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include <util/check.h>
2020
#include <util/time.h>
2121

22+
#include <bitset>
23+
#include <cmath>
2224
#include <cstdint>
2325
#include <memory>
2426
#include <set>
@@ -234,3 +236,170 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
234236
}
235237
orphanage->SanityCheck();
236238
}
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

Comments
 (0)