@@ -35,6 +35,9 @@ using ClusterSetIndex = uint32_t;
35
35
/* * Quality levels for cached cluster linearizations. */
36
36
enum class QualityLevel
37
37
{
38
+ /* * This is a singleton cluster consisting of a transaction that individually exceeds the
39
+ * cluster size limit. It cannot be merged with anything. */
40
+ OVERSIZED_SINGLETON,
38
41
/* * This cluster may have multiple disconnected components, which are all NEEDS_RELINEARIZE. */
39
42
NEEDS_SPLIT,
40
43
/* * This cluster may have multiple disconnected components, which are all ACCEPTABLE. */
@@ -101,6 +104,10 @@ class Cluster
101
104
{
102
105
return m_quality == QualityLevel::OPTIMAL;
103
106
}
107
+ /* * Whether this cluster is oversized. Note that no changes that can cause oversizedness are
108
+ * ever applied, so the only way a materialized Cluster object can be oversized is by being
109
+ * an individually oversized transaction singleton. */
110
+ bool IsOversized () const noexcept { return m_quality == QualityLevel::OVERSIZED_SINGLETON; }
104
111
/* * Whether this cluster requires splitting. */
105
112
bool NeedsSplitting () const noexcept
106
113
{
@@ -225,7 +232,7 @@ class TxGraphImpl final : public TxGraph
225
232
/* * Which clusters are to be merged. GroupEntry::m_cluster_offset indexes into this. */
226
233
std::vector<Cluster*> m_group_clusters;
227
234
/* * Whether at least one of the groups cannot be applied because it would result in a
228
- * Cluster that violates the cluster count limit. */
235
+ * Cluster that violates the max cluster count or size limit. */
229
236
bool m_group_oversized;
230
237
};
231
238
@@ -248,8 +255,11 @@ class TxGraphImpl final : public TxGraph
248
255
/* * Total number of transactions in this graph (sum of all transaction counts in all
249
256
* Clusters, and for staging also those inherited from the main ClusterSet). */
250
257
GraphIndex m_txcount{0 };
258
+ /* * Total number of individually oversized transactions in the graph. */
259
+ GraphIndex m_txcount_oversized{0 };
251
260
/* * Whether this graph is oversized (if known). This roughly matches
252
- * m_group_data->m_group_oversized, but may be known even if m_group_data is not. */
261
+ * m_group_data->m_group_oversized || (m_txcount_oversized > 0), but may be known even if
262
+ * m_group_data is not. */
253
263
std::optional<bool > m_oversized{false };
254
264
255
265
ClusterSet () noexcept = default ;
@@ -446,8 +456,10 @@ class TxGraphImpl final : public TxGraph
446
456
/* * Get a reference to the ClusterSet at the specified level (which must exist). */
447
457
ClusterSet& GetClusterSet (int level) noexcept ;
448
458
const ClusterSet& GetClusterSet (int level) const noexcept ;
449
- /* * Make a transaction not exist at a specified level. It must currently exist there. */
450
- void ClearLocator (int level, GraphIndex index) noexcept ;
459
+ /* * Make a transaction not exist at a specified level. It must currently exist there.
460
+ * oversized_tx indicates whether the transaction is an individually-oversized one
461
+ * (OVERSIZED_SINGLETON). */
462
+ void ClearLocator (int level, GraphIndex index, bool oversized_tx) noexcept ;
451
463
/* * Find which Clusters in main conflict with ones in staging. */
452
464
std::vector<Cluster*> GetConflicts () const noexcept ;
453
465
/* * Clear an Entry's ChunkData. */
@@ -649,7 +661,7 @@ uint64_t Cluster::GetTotalTxSize() const noexcept
649
661
return ret;
650
662
}
651
663
652
- void TxGraphImpl::ClearLocator (int level, GraphIndex idx) noexcept
664
+ void TxGraphImpl::ClearLocator (int level, GraphIndex idx, bool oversized_tx ) noexcept
653
665
{
654
666
auto & entry = m_entries[idx];
655
667
auto & clusterset = GetClusterSet (level);
@@ -663,12 +675,14 @@ void TxGraphImpl::ClearLocator(int level, GraphIndex idx) noexcept
663
675
}
664
676
// Update the transaction count.
665
677
--clusterset.m_txcount ;
678
+ clusterset.m_txcount_oversized -= oversized_tx;
666
679
// If clearing main, adjust the status of Locators of this transaction in staging, if it exists.
667
680
if (level == 0 && GetTopLevel () == 1 ) {
668
681
if (entry.m_locator [1 ].IsRemoved ()) {
669
682
entry.m_locator [1 ].SetMissing ();
670
683
} else if (!entry.m_locator [1 ].IsPresent ()) {
671
684
--m_staging_clusterset->m_txcount ;
685
+ m_staging_clusterset->m_txcount_oversized -= oversized_tx;
672
686
}
673
687
}
674
688
if (level == 0 ) ClearChunkData (entry);
@@ -799,7 +813,7 @@ void Cluster::ApplyRemovals(TxGraphImpl& graph, std::span<GraphIndex>& to_remove
799
813
entry.m_main_lin_index = LinearizationIndex (-1 );
800
814
}
801
815
// - Mark it as missing/removed in the Entry's locator.
802
- graph.ClearLocator (m_level, idx);
816
+ graph.ClearLocator (m_level, idx, m_quality == QualityLevel::OVERSIZED_SINGLETON );
803
817
to_remove = to_remove.subspan (1 );
804
818
} while (!to_remove.empty ());
805
819
@@ -838,7 +852,7 @@ void Cluster::ApplyRemovals(TxGraphImpl& graph, std::span<GraphIndex>& to_remove
838
852
void Cluster::Clear (TxGraphImpl& graph) noexcept
839
853
{
840
854
for (auto i : m_linearization) {
841
- graph.ClearLocator (m_level, m_mapping[i]);
855
+ graph.ClearLocator (m_level, m_mapping[i], m_quality == QualityLevel::OVERSIZED_SINGLETON );
842
856
}
843
857
m_depgraph = {};
844
858
m_linearization.clear ();
@@ -1298,6 +1312,17 @@ void TxGraphImpl::GroupClusters(int level) noexcept
1298
1312
* to-be-merged group). */
1299
1313
std::vector<std::pair<std::pair<GraphIndex, GraphIndex>, uint64_t >> an_deps;
1300
1314
1315
+ // Construct an an_clusters entry for every oversized cluster, including ones from levels below,
1316
+ // as they may be inherited in this one.
1317
+ for (int level_iter = 0 ; level_iter <= level; ++level_iter) {
1318
+ for (auto & cluster : GetClusterSet (level_iter).m_clusters [int (QualityLevel::OVERSIZED_SINGLETON)]) {
1319
+ auto graph_idx = cluster->GetClusterEntry (0 );
1320
+ auto cur_cluster = FindCluster (graph_idx, level);
1321
+ if (cur_cluster == nullptr ) continue ;
1322
+ an_clusters.emplace_back (cur_cluster, cur_cluster->m_sequence );
1323
+ }
1324
+ }
1325
+
1301
1326
// Construct a an_clusters entry for every parent and child in the to-be-applied dependencies,
1302
1327
// and an an_deps entry for each dependency to be applied.
1303
1328
an_deps.reserve (clusterset.m_deps_to_add .size ());
@@ -1314,7 +1339,7 @@ void TxGraphImpl::GroupClusters(int level) noexcept
1314
1339
an_deps.emplace_back (std::pair{par, chl}, chl_cluster->m_sequence );
1315
1340
}
1316
1341
// Sort and deduplicate an_clusters, so we end up with a sorted list of all involved Clusters
1317
- // to which dependencies apply.
1342
+ // to which dependencies apply, or which are oversized .
1318
1343
std::sort (an_clusters.begin (), an_clusters.end (), [](auto & a, auto & b) noexcept { return a.second < b.second ; });
1319
1344
an_clusters.erase (std::unique (an_clusters.begin (), an_clusters.end ()), an_clusters.end ());
1320
1345
// Sort an_deps by applying the same order to the involved child cluster.
@@ -1517,7 +1542,7 @@ void TxGraphImpl::ApplyDependencies(int level) noexcept
1517
1542
// Nothing to do if there are no dependencies to be added.
1518
1543
if (clusterset.m_deps_to_add .empty ()) return ;
1519
1544
// Dependencies cannot be applied if it would result in oversized clusters.
1520
- if (clusterset.m_group_data -> m_group_oversized ) return ;
1545
+ if (clusterset.m_oversized == true ) return ;
1521
1546
1522
1547
// For each group of to-be-merged Clusters.
1523
1548
for (const auto & group_entry : clusterset.m_group_data ->m_groups ) {
@@ -1573,7 +1598,7 @@ void Cluster::Relinearize(TxGraphImpl& graph, uint64_t max_iters) noexcept
1573
1598
void TxGraphImpl::MakeAcceptable (Cluster& cluster) noexcept
1574
1599
{
1575
1600
// Relinearize the Cluster if needed.
1576
- if (!cluster.NeedsSplitting () && !cluster.IsAcceptable ()) {
1601
+ if (!cluster.NeedsSplitting () && !cluster.IsAcceptable () && !cluster. IsOversized () ) {
1577
1602
cluster.Relinearize (*this , 10000 );
1578
1603
}
1579
1604
}
@@ -1603,7 +1628,7 @@ Cluster::Cluster(uint64_t sequence, TxGraphImpl& graph, const FeePerWeight& feer
1603
1628
TxGraph::Ref TxGraphImpl::AddTransaction (const FeePerWeight& feerate) noexcept
1604
1629
{
1605
1630
Assume (m_main_chunkindex_observers == 0 || GetTopLevel () != 0 );
1606
- Assume (feerate.size > 0 && uint64_t (feerate. size ) <= m_max_cluster_size );
1631
+ Assume (feerate.size > 0 );
1607
1632
// Construct a new Ref.
1608
1633
Ref ret;
1609
1634
// Construct a new Entry, and link it with the Ref.
@@ -1615,13 +1640,20 @@ TxGraph::Ref TxGraphImpl::AddTransaction(const FeePerWeight& feerate) noexcept
1615
1640
GetRefGraph (ret) = this ;
1616
1641
GetRefIndex (ret) = idx;
1617
1642
// Construct a new singleton Cluster (which is necessarily optimally linearized).
1643
+ bool oversized = uint64_t (feerate.size ) > m_max_cluster_size;
1618
1644
auto cluster = std::make_unique<Cluster>(m_next_sequence_counter++, *this , feerate, idx);
1619
1645
auto cluster_ptr = cluster.get ();
1620
1646
int level = GetTopLevel ();
1621
1647
auto & clusterset = GetClusterSet (level);
1622
- InsertCluster (level, std::move (cluster), QualityLevel::OPTIMAL);
1648
+ InsertCluster (level, std::move (cluster), oversized ? QualityLevel::OVERSIZED_SINGLETON : QualityLevel::OPTIMAL);
1623
1649
cluster_ptr->Updated (*this );
1624
1650
++clusterset.m_txcount ;
1651
+ // Deal with individually oversized transactions.
1652
+ if (oversized) {
1653
+ ++clusterset.m_txcount_oversized ;
1654
+ clusterset.m_oversized = true ;
1655
+ clusterset.m_group_data = std::nullopt;
1656
+ }
1625
1657
// Return the Ref.
1626
1658
return ret;
1627
1659
}
@@ -1934,11 +1966,15 @@ bool TxGraphImpl::IsOversized(bool main_only) noexcept
1934
1966
// Return cached value if known.
1935
1967
return *clusterset.m_oversized ;
1936
1968
}
1937
- // Find which Clusters will need to be merged together, as that is where the oversize
1938
- // property is assessed.
1939
- GroupClusters (level);
1940
- Assume (clusterset.m_group_data .has_value ());
1941
- clusterset.m_oversized = clusterset.m_group_data ->m_group_oversized ;
1969
+ ApplyRemovals (level);
1970
+ if (clusterset.m_txcount_oversized > 0 ) {
1971
+ clusterset.m_oversized = true ;
1972
+ } else {
1973
+ // Find which Clusters will need to be merged together, as that is where the oversize
1974
+ // property is assessed.
1975
+ GroupClusters (level);
1976
+ }
1977
+ Assume (clusterset.m_oversized .has_value ());
1942
1978
return *clusterset.m_oversized ;
1943
1979
}
1944
1980
@@ -1958,6 +1994,7 @@ void TxGraphImpl::StartStaging() noexcept
1958
1994
// Copy statistics, precomputed data, and to-be-applied dependencies (only if oversized) to
1959
1995
// the new graph. To-be-applied removals will always be empty at this point.
1960
1996
m_staging_clusterset->m_txcount = m_main_clusterset.m_txcount ;
1997
+ m_staging_clusterset->m_txcount_oversized = m_main_clusterset.m_txcount_oversized ;
1961
1998
m_staging_clusterset->m_deps_to_add = m_main_clusterset.m_deps_to_add ;
1962
1999
m_staging_clusterset->m_group_data = m_main_clusterset.m_group_data ;
1963
2000
m_staging_clusterset->m_oversized = m_main_clusterset.m_oversized ;
@@ -1985,7 +2022,13 @@ void TxGraphImpl::AbortStaging() noexcept
1985
2022
if (!m_main_clusterset.m_group_data .has_value ()) {
1986
2023
// In case m_oversized in main was kept after a Ref destruction while staging exists, we
1987
2024
// need to re-evaluate m_oversized now.
1988
- m_main_clusterset.m_oversized = std::nullopt;
2025
+ if (m_main_clusterset.m_to_remove .empty () && m_main_clusterset.m_txcount_oversized > 0 ) {
2026
+ // It is possible that a Ref destruction caused a removal in main while staging existed.
2027
+ // In this case, m_txcount_oversized may be inaccurate.
2028
+ m_main_clusterset.m_oversized = true ;
2029
+ } else {
2030
+ m_main_clusterset.m_oversized = std::nullopt;
2031
+ }
1989
2032
}
1990
2033
}
1991
2034
@@ -2019,6 +2062,7 @@ void TxGraphImpl::CommitStaging() noexcept
2019
2062
m_main_clusterset.m_group_data = std::move (m_staging_clusterset->m_group_data );
2020
2063
m_main_clusterset.m_oversized = std::move (m_staging_clusterset->m_oversized );
2021
2064
m_main_clusterset.m_txcount = std::move (m_staging_clusterset->m_txcount );
2065
+ m_main_clusterset.m_txcount_oversized = std::move (m_staging_clusterset->m_txcount_oversized );
2022
2066
// Delete the old staging graph, after all its information was moved to main.
2023
2067
m_staging_clusterset.reset ();
2024
2068
Compact ();
@@ -2033,7 +2077,9 @@ void Cluster::SetFee(TxGraphImpl& graph, DepGraphIndex idx, int64_t fee) noexcep
2033
2077
// Update the fee, remember that relinearization will be necessary, and update the Entries
2034
2078
// in the same Cluster.
2035
2079
m_depgraph.FeeRate (idx).fee = fee;
2036
- if (!NeedsSplitting ()) {
2080
+ if (m_quality == QualityLevel::OVERSIZED_SINGLETON) {
2081
+ // Nothing to do.
2082
+ } else if (!NeedsSplitting ()) {
2037
2083
graph.SetClusterQuality (m_level, m_quality, m_setindex, QualityLevel::NEEDS_RELINEARIZE);
2038
2084
} else {
2039
2085
graph.SetClusterQuality (m_level, m_quality, m_setindex, QualityLevel::NEEDS_SPLIT);
@@ -2141,12 +2187,15 @@ void Cluster::SanityCheck(const TxGraphImpl& graph, int level) const
2141
2187
assert (m_linearization.size () <= graph.m_max_cluster_count );
2142
2188
// The level must match the level the Cluster occurs in.
2143
2189
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 );
2190
+ // The sum of their sizes cannot exceed m_max_cluster_size, unless it is an individually
2191
+ // oversized transaction singleton. Note that groups of to-be-merged clusters which would
2192
+ // exceed this limit are marked oversized, which means they are never applied.
2193
+ assert (m_quality == QualityLevel::OVERSIZED_SINGLETON || GetTotalTxSize () <= graph.m_max_cluster_size );
2148
2194
// m_quality and m_setindex are checked in TxGraphImpl::SanityCheck.
2149
2195
2196
+ // OVERSIZED clusters are singletons.
2197
+ assert (m_quality != QualityLevel::OVERSIZED_SINGLETON || m_linearization.size () == 1 );
2198
+
2150
2199
// Compute the chunking of m_linearization.
2151
2200
LinearizationChunking linchunking (m_depgraph, m_linearization);
2152
2201
@@ -2317,9 +2366,11 @@ void TxGraphImpl::SanityCheck() const
2317
2366
if (!clusterset.m_to_remove .empty ()) compact_possible = false ;
2318
2367
if (!clusterset.m_removed .empty ()) compact_possible = false ;
2319
2368
2320
- // If m_group_data exists, its m_group_oversized must match m_oversized.
2321
- if (clusterset.m_group_data .has_value ()) {
2322
- assert (clusterset.m_oversized == clusterset.m_group_data ->m_group_oversized );
2369
+ // If m_group_data exists, and no outstanding removals remain, m_group_oversized must match
2370
+ // m_group_oversized || (m_txcount_oversized > 0).
2371
+ if (clusterset.m_group_data .has_value () && clusterset.m_to_remove .empty ()) {
2372
+ assert (clusterset.m_oversized ==
2373
+ (clusterset.m_group_data ->m_group_oversized || (clusterset.m_txcount_oversized > 0 )));
2323
2374
}
2324
2375
2325
2376
// For non-top levels, m_oversized must be known (as it cannot change until the level
0 commit comments