Skip to content

Commit 64f69ec

Browse files
committed
txgraph: Make max cluster count configurable and "oversize" state (feature)
Instead of leaving the responsibility on higher layers to guarantee that no connected component within TxGraph (a barely exposed concept, except through GetCluster()) exceeds the cluster count limit, move this responsibility to TxGraph itself: * TxGraph retains a cluster count limit, but it becomes configurable at construction time (this primarily helps with testing that it is properly enforced). * It is always allowed to perform mutators on TxGraph, even if they would cause the cluster count limit to be exceeded. Instead, TxGraph exposes an IsOversized() function, which queries whether it is in a special "oversize" state. * During oversize state, many inspectors are unavailable, but mutators remain valid, so the higher layer can "fix" the oversize state before continuing.
1 parent 1d27b74 commit 64f69ec

File tree

3 files changed

+166
-85
lines changed

3 files changed

+166
-85
lines changed

src/test/fuzz/txgraph.cpp

Lines changed: 101 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ struct SimTxGraph
2727
/** Maximum number of transactions to support simultaneously. Set this higher than txgraph's
2828
* cluster count, so we can exercise situations with more transactions than fit in one
2929
* cluster. */
30-
static constexpr unsigned MAX_TRANSACTIONS = CLUSTER_COUNT_LIMIT * 2;
30+
static constexpr unsigned MAX_TRANSACTIONS = MAX_CLUSTER_COUNT_LIMIT * 2;
3131
/** Set type to use in the simulation. */
3232
using SetType = BitSet<MAX_TRANSACTIONS>;
3333
/** Data type for representing positions within SimTxGraph::graph. */
@@ -44,6 +44,31 @@ struct SimTxGraph
4444
std::map<const TxGraph::Ref*, Pos> simrevmap;
4545
/** The set of TxGraph::Ref entries that have been removed, but not yet destroyed. */
4646
std::vector<std::unique_ptr<TxGraph::Ref>> removed;
47+
/** Whether the graph is oversized (true = yes, false = no, std::nullopt = unknown). */
48+
std::optional<bool> oversized;
49+
/** The configured maximum number of transactions per cluster. */
50+
DepGraphIndex max_cluster_count;
51+
52+
/** Construct a new SimData with the specified maximum cluster count. */
53+
explicit SimTxGraph(DepGraphIndex max_cluster) : max_cluster_count(max_cluster) {}
54+
55+
/** Check whether this graph is oversized (contains a connected component whose number of
56+
* transactions exceeds max_cluster_count. */
57+
bool IsOversized()
58+
{
59+
if (!oversized.has_value()) {
60+
// Only recompute when oversized isn't already known.
61+
oversized = false;
62+
auto todo = graph.Positions();
63+
// Iterate over all connected components of the graph.
64+
while (todo.Any()) {
65+
auto component = graph.FindConnectedComponent(todo);
66+
if (component.Count() > max_cluster_count) oversized = true;
67+
todo -= component;
68+
}
69+
}
70+
return *oversized;
71+
}
4772

4873
/** Determine the number of (non-removed) transactions in the graph. */
4974
DepGraphIndex GetTransactionCount() const { return graph.TxCount(); }
@@ -84,6 +109,8 @@ struct SimTxGraph
84109
auto chl_pos = Find(child);
85110
if (chl_pos == MISSING) return;
86111
graph.AddDependencies(SetType::Singleton(par_pos), chl_pos);
112+
// This may invalidate our cached oversized value.
113+
if (oversized.has_value() && !*oversized) oversized = std::nullopt;
87114
}
88115

89116
/** Modify the transaction fee of a ref, if it exists. */
@@ -105,6 +132,8 @@ struct SimTxGraph
105132
// invoked until the simulation explicitly decided to do so.
106133
removed.push_back(std::move(simmap[pos]));
107134
simmap[pos].reset();
135+
// This may invalidate our cached oversized value.
136+
if (oversized.has_value() && *oversized) oversized = std::nullopt;
108137
}
109138

110139
/** Construct the set with all positions in this graph corresponding to the specified
@@ -170,9 +199,12 @@ FUZZ_TARGET(txgraph)
170199
/** Variable used whenever an empty TxGraph::Ref is needed. */
171200
TxGraph::Ref empty_ref;
172201

202+
// Decide the maximum number of transactions per cluster we will use in this simulation.
203+
auto max_count = provider.ConsumeIntegralInRange<DepGraphIndex>(1, MAX_CLUSTER_COUNT_LIMIT);
204+
173205
// Construct a real and a simulated graph.
174-
auto real = MakeTxGraph();
175-
SimTxGraph sim;
206+
auto real = MakeTxGraph(max_count);
207+
SimTxGraph sim(max_count);
176208

177209
/** Function to pick any Ref (from sim.simmap or sim.removed, or the empty Ref). */
178210
auto pick_fn = [&]() noexcept -> TxGraph::Ref* {
@@ -245,17 +277,6 @@ FUZZ_TARGET(txgraph)
245277
// Determine if adding this would introduce a cycle (not allowed by TxGraph),
246278
// and if so, skip.
247279
if (sim.graph.Ancestors(pos_par)[pos_chl]) break;
248-
// Determine if adding this would violate CLUSTER_COUNT_LIMIT, and if so, skip.
249-
auto temp_depgraph = sim.graph;
250-
temp_depgraph.AddDependencies(SimTxGraph::SetType::Singleton(pos_par), pos_chl);
251-
auto todo = temp_depgraph.Positions();
252-
bool oversize{false};
253-
while (todo.Any()) {
254-
auto component = temp_depgraph.FindConnectedComponent(todo);
255-
if (component.Count() > CLUSTER_COUNT_LIMIT) oversize = true;
256-
todo -= component;
257-
}
258-
if (oversize) break;
259280
}
260281
sim.AddDependency(par, chl);
261282
real->AddDependency(*par, *chl);
@@ -310,6 +331,10 @@ FUZZ_TARGET(txgraph)
310331
bool should_exist = sim.Find(ref) != SimTxGraph::MISSING;
311332
assert(exists == should_exist);
312333
break;
334+
} else if (command-- == 0) {
335+
// IsOversized.
336+
assert(sim.IsOversized() == real->IsOversized());
337+
break;
313338
} else if (command-- == 0) {
314339
// GetIndividualFeerate.
315340
auto ref = pick_fn();
@@ -321,7 +346,7 @@ FUZZ_TARGET(txgraph)
321346
assert(feerate == sim.graph.FeeRate(simpos));
322347
}
323348
break;
324-
} else if (command-- == 0) {
349+
} else if (!sim.IsOversized() && command-- == 0) {
325350
// GetChunkFeerate.
326351
auto ref = pick_fn();
327352
auto feerate = real->GetChunkFeerate(*ref);
@@ -334,20 +359,22 @@ FUZZ_TARGET(txgraph)
334359
assert(feerate.size >= sim.graph.FeeRate(simpos).size);
335360
}
336361
break;
337-
} else if (command-- == 0) {
362+
} else if (!sim.IsOversized() && command-- == 0) {
338363
// GetAncestors/GetDescendants.
339364
auto ref = pick_fn();
340-
auto result_set = sim.MakeSet(alt ? real->GetDescendants(*ref) :
341-
real->GetAncestors(*ref));
365+
auto result = alt ? real->GetDescendants(*ref) : real->GetAncestors(*ref);
366+
assert(result.size() <= max_count);
367+
auto result_set = sim.MakeSet(result);
368+
assert(result.size() == result_set.Count());
342369
auto expect_set = sim.GetAncDesc(ref, alt);
343370
assert(result_set == expect_set);
344371
break;
345-
} else if (command-- == 0) {
372+
} else if (!sim.IsOversized() && command-- == 0) {
346373
// GetCluster.
347374
auto ref = pick_fn();
348375
auto result = real->GetCluster(*ref);
349376
// Check cluster count limit.
350-
assert(result.size() <= CLUSTER_COUNT_LIMIT);
377+
assert(result.size() <= max_count);
351378
// Require the result to be topologically valid and not contain duplicates.
352379
auto left = sim.graph.Positions();
353380
for (auto refptr : result) {
@@ -382,56 +409,62 @@ FUZZ_TARGET(txgraph)
382409
real->SanityCheck();
383410

384411
// Compare simple properties of the graph with the simulation.
412+
assert(real->IsOversized() == sim.IsOversized());
385413
assert(real->GetTransactionCount() == sim.GetTransactionCount());
386414

387-
// Perform a full comparison.
388-
auto todo = sim.graph.Positions();
389-
// Iterate over all connected components of the resulting (simulated) graph, each of which
390-
// should correspond to a cluster in the real one.
391-
while (todo.Any()) {
392-
auto component = sim.graph.FindConnectedComponent(todo);
393-
todo -= component;
394-
// Iterate over the transactions in that component.
395-
for (auto i : component) {
396-
// Check its individual feerate against simulation.
397-
assert(sim.graph.FeeRate(i) == real->GetIndividualFeerate(*sim.GetRef(i)));
398-
// Check its ancestors against simulation.
399-
auto expect_anc = sim.graph.Ancestors(i);
400-
auto anc = sim.MakeSet(real->GetAncestors(*sim.GetRef(i)));
401-
assert(anc == expect_anc);
402-
// Check its descendants against simulation.
403-
auto expect_desc = sim.graph.Descendants(i);
404-
auto desc = sim.MakeSet(real->GetDescendants(*sim.GetRef(i)));
405-
assert(desc == expect_desc);
406-
// Check the cluster the transaction is part of.
407-
auto cluster = real->GetCluster(*sim.GetRef(i));
408-
assert(sim.MakeSet(cluster) == component);
409-
// Check that the cluster is reported in a valid topological order (its
410-
// linearization).
411-
std::vector<DepGraphIndex> simlin;
412-
SimTxGraph::SetType done;
413-
for (TxGraph::Ref* ptr : cluster) {
414-
auto simpos = sim.Find(ptr);
415-
assert(sim.graph.Descendants(simpos).IsSubsetOf(component - done));
416-
done.Set(simpos);
417-
assert(sim.graph.Ancestors(simpos).IsSubsetOf(done));
418-
simlin.push_back(simpos);
419-
}
420-
// Construct a chunking object for the simulated graph, using the reported cluster
421-
// linearization as ordering, and compare it against the reported chunk feerates.
422-
cluster_linearize::LinearizationChunking simlinchunk(sim.graph, simlin);
423-
DepGraphIndex idx{0};
424-
for (unsigned chunknum = 0; chunknum < simlinchunk.NumChunksLeft(); ++chunknum) {
425-
auto chunk = simlinchunk.GetChunk(chunknum);
426-
// Require that the chunks of cluster linearizations are connected (this must
427-
// be the case as all linearizations inside are PostLinearized).
428-
assert(sim.graph.IsConnected(chunk.transactions));
429-
// Check the chunk feerates of all transactions in the cluster.
430-
while (chunk.transactions.Any()) {
431-
assert(chunk.transactions[simlin[idx]]);
432-
chunk.transactions.Reset(simlin[idx]);
433-
assert(chunk.feerate == real->GetChunkFeerate(*cluster[idx]));
434-
++idx;
415+
// If the graph (and the simulation) are not oversized, perform a full comparison.
416+
if (!sim.IsOversized()) {
417+
auto todo = sim.graph.Positions();
418+
// Iterate over all connected components of the resulting (simulated) graph, each of which
419+
// should correspond to a cluster in the real one.
420+
while (todo.Any()) {
421+
auto component = sim.graph.FindConnectedComponent(todo);
422+
todo -= component;
423+
// Iterate over the transactions in that component.
424+
for (auto i : component) {
425+
// Check its individual feerate against simulation.
426+
assert(sim.graph.FeeRate(i) == real->GetIndividualFeerate(*sim.GetRef(i)));
427+
// Check its ancestors against simulation.
428+
auto expect_anc = sim.graph.Ancestors(i);
429+
auto anc = sim.MakeSet(real->GetAncestors(*sim.GetRef(i)));
430+
assert(anc.Count() <= max_count);
431+
assert(anc == expect_anc);
432+
// Check its descendants against simulation.
433+
auto expect_desc = sim.graph.Descendants(i);
434+
auto desc = sim.MakeSet(real->GetDescendants(*sim.GetRef(i)));
435+
assert(desc.Count() <= max_count);
436+
assert(desc == expect_desc);
437+
// Check the cluster the transaction is part of.
438+
auto cluster = real->GetCluster(*sim.GetRef(i));
439+
assert(cluster.size() <= max_count);
440+
assert(sim.MakeSet(cluster) == component);
441+
// Check that the cluster is reported in a valid topological order (its
442+
// linearization).
443+
std::vector<DepGraphIndex> simlin;
444+
SimTxGraph::SetType done;
445+
for (TxGraph::Ref* ptr : cluster) {
446+
auto simpos = sim.Find(ptr);
447+
assert(sim.graph.Descendants(simpos).IsSubsetOf(component - done));
448+
done.Set(simpos);
449+
assert(sim.graph.Ancestors(simpos).IsSubsetOf(done));
450+
simlin.push_back(simpos);
451+
}
452+
// Construct a chunking object for the simulated graph, using the reported cluster
453+
// linearization as ordering, and compare it against the reported chunk feerates.
454+
cluster_linearize::LinearizationChunking simlinchunk(sim.graph, simlin);
455+
DepGraphIndex idx{0};
456+
for (unsigned chunknum = 0; chunknum < simlinchunk.NumChunksLeft(); ++chunknum) {
457+
auto chunk = simlinchunk.GetChunk(chunknum);
458+
// Require that the chunks of cluster linearizations are connected (this must
459+
// be the case as all linearizations inside are PostLinearized).
460+
assert(sim.graph.IsConnected(chunk.transactions));
461+
// Check the chunk feerates of all transactions in the cluster.
462+
while (chunk.transactions.Any()) {
463+
assert(chunk.transactions[simlin[idx]]);
464+
chunk.transactions.Reset(simlin[idx]);
465+
assert(chunk.feerate == real->GetChunkFeerate(*cluster[idx]));
466+
++idx;
467+
}
435468
}
436469
}
437470
}

0 commit comments

Comments
 (0)