Skip to content

Commit c4287b9

Browse files
committed
txgraph: Add ability to configure maximum cluster size/weight (feature)
This is integrated with the oversized property: the graph is oversized when any connected component within it contains more than the cluster count limit many transactions, or when their combined size/weight exceeds the cluster size limit. It becomes disallowed to call AddTransaction with a size larger than this limit, though this limit will be lifted in the next commit. In addition, SetTransactionFeeRate becomes SetTransactionFee, so that we do not need to deal with the case that a call to this function might affect the oversizedness.
1 parent a92e8b1 commit c4287b9

File tree

3 files changed

+68
-25
lines changed

3 files changed

+68
-25
lines changed

src/test/fuzz/txgraph.cpp

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,12 @@ struct SimTxGraph
5656
/** Which transactions have been modified in the graph since creation, either directly or by
5757
* being in a cluster which includes modifications. Only relevant for the staging graph. */
5858
SetType modified;
59+
/** The configured maximum total size of transactions per cluster. */
60+
uint64_t max_cluster_size;
5961

60-
/** Construct a new SimTxGraph with the specified maximum cluster count. */
61-
explicit SimTxGraph(DepGraphIndex max_cluster) : max_cluster_count(max_cluster) {}
62+
/** Construct a new SimTxGraph with the specified maximum cluster count and size. */
63+
explicit SimTxGraph(DepGraphIndex cluster_count, uint64_t cluster_size) :
64+
max_cluster_count(cluster_count), max_cluster_size(cluster_size) {}
6265

6366
// Permit copying and moving.
6467
SimTxGraph(const SimTxGraph&) noexcept = default;
@@ -78,6 +81,9 @@ struct SimTxGraph
7881
while (todo.Any()) {
7982
auto component = graph.FindConnectedComponent(todo);
8083
if (component.Count() > max_cluster_count) oversized = true;
84+
uint64_t component_size{0};
85+
for (auto i : component) component_size += graph.FeeRate(i).size;
86+
if (component_size > max_cluster_size) oversized = true;
8187
todo -= component;
8288
}
8389
}
@@ -260,14 +266,21 @@ FUZZ_TARGET(txgraph)
260266
/** Variable used whenever an empty TxGraph::Ref is needed. */
261267
TxGraph::Ref empty_ref;
262268

263-
// Decide the maximum number of transactions per cluster we will use in this simulation.
264-
auto max_count = provider.ConsumeIntegralInRange<DepGraphIndex>(1, MAX_CLUSTER_COUNT_LIMIT);
269+
/** The maximum number of transactions per (non-oversized) cluster we will use in this
270+
* simulation. */
271+
auto max_cluster_count = provider.ConsumeIntegralInRange<DepGraphIndex>(1, MAX_CLUSTER_COUNT_LIMIT);
272+
/** The maximum total size of transactions in a cluster, which also makes it an upper bound
273+
* on the individual size of a transaction (but this restriction will be lifted in a future
274+
* commit). */
275+
auto max_cluster_size = provider.ConsumeIntegralInRange<uint64_t>(1, 0x3fffff * MAX_CLUSTER_COUNT_LIMIT);
276+
/** The maximum individual transaction size used in this test (not a TxGraph parameter). */
277+
auto max_tx_size = std::min<uint64_t>(0x3fffff, max_cluster_size);
265278

266279
// Construct a real graph, and a vector of simulated graphs (main, and possibly staging).
267-
auto real = MakeTxGraph(max_count);
280+
auto real = MakeTxGraph(max_cluster_count, max_cluster_size);
268281
std::vector<SimTxGraph> sims;
269282
sims.reserve(2);
270-
sims.emplace_back(max_count);
283+
sims.emplace_back(max_cluster_count, max_cluster_size);
271284

272285
/** Struct encapsulating information about a BlockBuilder that's currently live. */
273286
struct BlockBuilderData
@@ -391,12 +404,12 @@ FUZZ_TARGET(txgraph)
391404
if (alt) {
392405
// If alt is true, pick fee and size from the entire range.
393406
fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
394-
size = provider.ConsumeIntegralInRange<int32_t>(1, 0x3fffff);
407+
size = provider.ConsumeIntegralInRange<int32_t>(1, max_tx_size);
395408
} else {
396409
// Otherwise, use smaller range which consume fewer fuzz input bytes, as just
397410
// these are likely sufficient to trigger all interesting code paths already.
398411
fee = provider.ConsumeIntegral<uint8_t>();
399-
size = provider.ConsumeIntegral<uint8_t>() + 1;
412+
size = provider.ConsumeIntegralInRange<uint32_t>(1, std::min<uint32_t>(0xff, max_tx_size));
400413
}
401414
FeePerWeight feerate{fee, size};
402415
// Create a real TxGraph::Ref.
@@ -534,7 +547,7 @@ FUZZ_TARGET(txgraph)
534547
auto ref = pick_fn();
535548
auto result = alt ? real->GetDescendants(*ref, use_main)
536549
: real->GetAncestors(*ref, use_main);
537-
assert(result.size() <= max_count);
550+
assert(result.size() <= max_cluster_count);
538551
auto result_set = sel_sim.MakeSet(result);
539552
assert(result.size() == result_set.Count());
540553
auto expect_set = sel_sim.GetAncDesc(ref, alt);
@@ -567,16 +580,20 @@ FUZZ_TARGET(txgraph)
567580
auto ref = pick_fn();
568581
auto result = real->GetCluster(*ref, use_main);
569582
// Check cluster count limit.
570-
assert(result.size() <= max_count);
583+
assert(result.size() <= max_cluster_count);
571584
// Require the result to be topologically valid and not contain duplicates.
572585
auto left = sel_sim.graph.Positions();
586+
uint64_t total_size{0};
573587
for (auto refptr : result) {
574588
auto simpos = sel_sim.Find(refptr);
589+
total_size += sel_sim.graph.FeeRate(simpos).size;
575590
assert(simpos != SimTxGraph::MISSING);
576591
assert(left[simpos]);
577592
left.Reset(simpos);
578593
assert(!sel_sim.graph.Ancestors(simpos).Overlaps(left));
579594
}
595+
// Check cluster size limit.
596+
assert(total_size <= max_cluster_size);
580597
// Require the set to be connected.
581598
auto result_set = sel_sim.MakeSet(result);
582599
assert(sel_sim.graph.IsConnected(result_set));
@@ -941,28 +958,32 @@ FUZZ_TARGET(txgraph)
941958
// Check its ancestors against simulation.
942959
auto expect_anc = sim.graph.Ancestors(i);
943960
auto anc = sim.MakeSet(real->GetAncestors(*sim.GetRef(i), main_only));
944-
assert(anc.Count() <= max_count);
961+
assert(anc.Count() <= max_cluster_count);
945962
assert(anc == expect_anc);
946963
// Check its descendants against simulation.
947964
auto expect_desc = sim.graph.Descendants(i);
948965
auto desc = sim.MakeSet(real->GetDescendants(*sim.GetRef(i), main_only));
949-
assert(desc.Count() <= max_count);
966+
assert(desc.Count() <= max_cluster_count);
950967
assert(desc == expect_desc);
951968
// Check the cluster the transaction is part of.
952969
auto cluster = real->GetCluster(*sim.GetRef(i), main_only);
953-
assert(cluster.size() <= max_count);
970+
assert(cluster.size() <= max_cluster_count);
954971
assert(sim.MakeSet(cluster) == component);
955972
// Check that the cluster is reported in a valid topological order (its
956973
// linearization).
957974
std::vector<DepGraphIndex> simlin;
958975
SimTxGraph::SetType done;
976+
uint64_t total_size{0};
959977
for (TxGraph::Ref* ptr : cluster) {
960978
auto simpos = sim.Find(ptr);
961979
assert(sim.graph.Descendants(simpos).IsSubsetOf(component - done));
962980
done.Set(simpos);
963981
assert(sim.graph.Ancestors(simpos).IsSubsetOf(done));
964982
simlin.push_back(simpos);
983+
total_size += sim.graph.FeeRate(simpos).size;
965984
}
985+
// Check cluster size.
986+
assert(total_size <= max_cluster_size);
966987
// Construct a chunking object for the simulated graph, using the reported cluster
967988
// linearization as ordering, and compare it against the reported chunk feerates.
968989
if (sims.size() == 1 || main_only) {

src/txgraph.cpp

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ class Cluster
109109
}
110110
/** Get the number of transactions in this Cluster. */
111111
LinearizationIndex GetTxCount() const noexcept { return m_linearization.size(); }
112+
/** Get the total size of the transactions in this Cluster. */
113+
uint64_t GetTotalTxSize() const noexcept;
112114
/** Given a DepGraphIndex into this Cluster, find the corresponding GraphIndex. */
113115
GraphIndex GetClusterEntry(DepGraphIndex index) const noexcept { return m_mapping[index]; }
114116
/** Only called by Graph::SwapIndexes. */
@@ -199,6 +201,8 @@ class TxGraphImpl final : public TxGraph
199201
FastRandomContext m_rng;
200202
/** This TxGraphImpl's maximum cluster count limit. */
201203
const DepGraphIndex m_max_cluster_count;
204+
/** This TxGraphImpl's maximum cluster size limit. */
205+
const uint64_t m_max_cluster_size;
202206

203207
/** Information about one group of Clusters to be merged. */
204208
struct GroupEntry
@@ -401,9 +405,10 @@ class TxGraphImpl final : public TxGraph
401405
std::vector<GraphIndex> m_unlinked;
402406

403407
public:
404-
/** Construct a new TxGraphImpl with the specified maximum cluster count. */
405-
explicit TxGraphImpl(DepGraphIndex max_cluster_count) noexcept :
408+
/** Construct a new TxGraphImpl with the specified limits. */
409+
explicit TxGraphImpl(DepGraphIndex max_cluster_count, uint64_t max_cluster_size) noexcept :
406410
m_max_cluster_count(max_cluster_count),
411+
m_max_cluster_size(max_cluster_size),
407412
m_main_chunkindex(ChunkOrder(this))
408413
{
409414
Assume(max_cluster_count >= 1);
@@ -635,6 +640,15 @@ void TxGraphImpl::CreateChunkData(GraphIndex idx, LinearizationIndex chunk_count
635640
}
636641
}
637642

643+
uint64_t Cluster::GetTotalTxSize() const noexcept
644+
{
645+
uint64_t ret{0};
646+
for (auto i : m_linearization) {
647+
ret += m_depgraph.FeeRate(i).size;
648+
}
649+
return ret;
650+
}
651+
638652
void TxGraphImpl::ClearLocator(int level, GraphIndex idx) noexcept
639653
{
640654
auto& entry = m_entries[idx];
@@ -1439,10 +1453,12 @@ void TxGraphImpl::GroupClusters(int level) noexcept
14391453
new_entry.m_deps_offset = clusterset.m_deps_to_add.size();
14401454
new_entry.m_deps_count = 0;
14411455
uint32_t total_count{0};
1456+
uint64_t total_size{0};
14421457
// Add all its clusters to it (copying those from an_clusters to m_group_clusters).
14431458
while (an_clusters_it != an_clusters.end() && an_clusters_it->second == rep) {
14441459
clusterset.m_group_data->m_group_clusters.push_back(an_clusters_it->first);
14451460
total_count += an_clusters_it->first->GetTxCount();
1461+
total_size += an_clusters_it->first->GetTotalTxSize();
14461462
++an_clusters_it;
14471463
++new_entry.m_cluster_count;
14481464
}
@@ -1453,7 +1469,7 @@ void TxGraphImpl::GroupClusters(int level) noexcept
14531469
++new_entry.m_deps_count;
14541470
}
14551471
// Detect oversizedness.
1456-
if (total_count > m_max_cluster_count) {
1472+
if (total_count > m_max_cluster_count || total_size > m_max_cluster_size) {
14571473
clusterset.m_group_data->m_group_oversized = true;
14581474
}
14591475
}
@@ -1587,6 +1603,7 @@ Cluster::Cluster(uint64_t sequence, TxGraphImpl& graph, const FeePerWeight& feer
15871603
TxGraph::Ref TxGraphImpl::AddTransaction(const FeePerWeight& feerate) noexcept
15881604
{
15891605
Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0);
1606+
Assume(feerate.size > 0 && uint64_t(feerate.size) <= m_max_cluster_size);
15901607
// Construct a new Ref.
15911608
Ref ret;
15921609
// Construct a new Entry, and link it with the Ref.
@@ -2124,6 +2141,10 @@ void Cluster::SanityCheck(const TxGraphImpl& graph, int level) const
21242141
assert(m_linearization.size() <= graph.m_max_cluster_count);
21252142
// The level must match the level the Cluster occurs in.
21262143
assert(m_level == level);
2144+
// The sum of their sizes cannot exceed m_max_cluster_size. Note that groups of to-be-merged
2145+
// clusters which would exceed this limit are marked oversized, which means they are never
2146+
// applied.
2147+
assert(GetTotalTxSize() <= graph.m_max_cluster_size);
21272148
// m_quality and m_setindex are checked in TxGraphImpl::SanityCheck.
21282149

21292150
// Compute the chunking of m_linearization.
@@ -2500,7 +2521,7 @@ TxGraph::Ref::Ref(Ref&& other) noexcept
25002521
std::swap(m_index, other.m_index);
25012522
}
25022523

2503-
std::unique_ptr<TxGraph> MakeTxGraph(unsigned max_cluster_count) noexcept
2524+
std::unique_ptr<TxGraph> MakeTxGraph(unsigned max_cluster_count, uint64_t max_cluster_size) noexcept
25042525
{
2505-
return std::make_unique<TxGraphImpl>(max_cluster_count);
2526+
return std::make_unique<TxGraphImpl>(max_cluster_count, max_cluster_size);
25062527
}

src/txgraph.h

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ class TxGraph
6363
/** Virtual destructor, so inheriting is safe. */
6464
virtual ~TxGraph() = default;
6565
/** Construct a new transaction with the specified feerate, and return a Ref to it.
66-
* If a staging graph exists, the new transaction is only created there. In all
67-
* further calls, only Refs created by AddTransaction() are allowed to be passed to this
68-
* TxGraph object (or empty Ref objects). Ref objects may outlive the TxGraph they were
69-
* created for. */
66+
* If a staging graph exists, the new transaction is only created there. feerate.size must be
67+
* strictly positive, and cannot exceed the graph's max cluster size. In all further calls,
68+
* only Refs created by AddTransaction() are allowed to be passed to this TxGraph object (or
69+
* empty Ref objects). Ref objects may outlive the TxGraph they were created for. */
7070
[[nodiscard]] virtual Ref AddTransaction(const FeePerWeight& feerate) noexcept = 0;
7171
/** Remove the specified transaction. If a staging graph exists, the removal only happens
7272
* there. This is a no-op if the transaction was already removed.
@@ -240,8 +240,9 @@ class TxGraph
240240
};
241241
};
242242

243-
/** Construct a new TxGraph with the specified limit on transactions within a cluster. That
244-
* number cannot exceed MAX_CLUSTER_COUNT_LIMIT. */
245-
std::unique_ptr<TxGraph> MakeTxGraph(unsigned max_cluster_count) noexcept;
243+
/** Construct a new TxGraph with the specified limit on the number of transactions within a cluster,
244+
* and on the sum of transaction sizes within a cluster. max_cluster_count cannot exceed
245+
* MAX_CLUSTER_COUNT_LIMIT. */
246+
std::unique_ptr<TxGraph> MakeTxGraph(unsigned max_cluster_count, uint64_t max_cluster_size) noexcept;
246247

247248
#endif // BITCOIN_TXGRAPH_H

0 commit comments

Comments
 (0)