|
12 | 12 |
|
13 | 13 | #include <compare>
|
14 | 14 | #include <memory>
|
| 15 | +#include <set> |
15 | 16 | #include <span>
|
16 | 17 | #include <utility>
|
17 | 18 |
|
@@ -94,6 +95,8 @@ class Cluster
|
94 | 95 | }
|
95 | 96 | /** Get the number of transactions in this Cluster. */
|
96 | 97 | LinearizationIndex GetTxCount() const noexcept { return m_linearization.size(); }
|
| 98 | + /** Given a DepGraphIndex into this Cluster, find the corresponding GraphIndex. */ |
| 99 | + GraphIndex GetClusterEntry(DepGraphIndex index) const noexcept { return m_mapping[index]; } |
97 | 100 | /** Only called by Graph::SwapIndexes. */
|
98 | 101 | void UpdateMapping(DepGraphIndex cluster_idx, GraphIndex graph_idx) noexcept { m_mapping[cluster_idx] = graph_idx; }
|
99 | 102 | /** Push changes to Cluster and its linearization to the TxGraphImpl Entry objects. */
|
@@ -126,6 +129,10 @@ class Cluster
|
126 | 129 | FeePerWeight GetIndividualFeerate(DepGraphIndex idx) noexcept;
|
127 | 130 | /** Modify the fee of a Cluster element. */
|
128 | 131 | void SetFee(TxGraphImpl& graph, DepGraphIndex idx, int64_t fee) noexcept;
|
| 132 | + |
| 133 | + // Debugging functions. |
| 134 | + |
| 135 | + void SanityCheck(const TxGraphImpl& graph) const; |
129 | 136 | };
|
130 | 137 |
|
131 | 138 | /** The transaction graph.
|
@@ -189,6 +196,8 @@ class TxGraphImpl final : public TxGraph
|
189 | 196 | void SetMissing() noexcept { cluster = nullptr; index = 0; }
|
190 | 197 | /** Mark this Locator as present, in the specified Cluster. */
|
191 | 198 | void SetPresent(Cluster* c, DepGraphIndex i) noexcept { cluster = c; index = i; }
|
| 199 | + /** Check if this Locator is missing. */ |
| 200 | + bool IsMissing() const noexcept { return cluster == nullptr && index == 0; } |
192 | 201 | /** Check if this Locator is present (in some Cluster). */
|
193 | 202 | bool IsPresent() const noexcept { return cluster != nullptr; }
|
194 | 203 | };
|
@@ -285,6 +294,8 @@ class TxGraphImpl final : public TxGraph
|
285 | 294 | std::vector<Ref*> GetAncestors(const Ref& arg) noexcept final;
|
286 | 295 | std::vector<Ref*> GetDescendants(const Ref& arg) noexcept final;
|
287 | 296 | GraphIndex GetTransactionCount() noexcept final;
|
| 297 | + |
| 298 | + void SanityCheck() const final; |
288 | 299 | };
|
289 | 300 |
|
290 | 301 | void Cluster::Updated(TxGraphImpl& graph) noexcept
|
@@ -1098,6 +1109,118 @@ void TxGraphImpl::SetTransactionFee(const Ref& ref, int64_t fee) noexcept
|
1098 | 1109 | }
|
1099 | 1110 | }
|
1100 | 1111 |
|
| 1112 | +void Cluster::SanityCheck(const TxGraphImpl& graph) const |
| 1113 | +{ |
| 1114 | + // There must be an m_mapping for each m_depgraph position (including holes). |
| 1115 | + assert(m_depgraph.PositionRange() == m_mapping.size()); |
| 1116 | + // The linearization for this Cluster must contain every transaction once. |
| 1117 | + assert(m_depgraph.TxCount() == m_linearization.size()); |
| 1118 | + // m_quality and m_setindex are checked in TxGraphImpl::SanityCheck. |
| 1119 | + |
| 1120 | + // Compute the chunking of m_linearization. |
| 1121 | + LinearizationChunking linchunking(m_depgraph, m_linearization); |
| 1122 | + |
| 1123 | + // Verify m_linearization. |
| 1124 | + SetType m_done; |
| 1125 | + assert(m_depgraph.IsAcyclic()); |
| 1126 | + for (auto lin_pos : m_linearization) { |
| 1127 | + assert(lin_pos < m_mapping.size()); |
| 1128 | + const auto& entry = graph.m_entries[m_mapping[lin_pos]]; |
| 1129 | + // Check that the linearization is topological. |
| 1130 | + m_done.Set(lin_pos); |
| 1131 | + assert(m_done.IsSupersetOf(m_depgraph.Ancestors(lin_pos))); |
| 1132 | + // Check that the Entry has a locator pointing back to this Cluster & position within it. |
| 1133 | + assert(entry.m_locator.cluster == this); |
| 1134 | + assert(entry.m_locator.index == lin_pos); |
| 1135 | + // Check linearization position. |
| 1136 | + if (!linchunking.GetChunk(0).transactions[lin_pos]) { |
| 1137 | + linchunking.MarkDone(linchunking.GetChunk(0).transactions); |
| 1138 | + } |
| 1139 | + // If this Cluster has an acceptable quality level, its chunks must be connected. |
| 1140 | + if (IsAcceptable()) { |
| 1141 | + assert(m_depgraph.IsConnected(linchunking.GetChunk(0).transactions)); |
| 1142 | + } |
| 1143 | + } |
| 1144 | + // Verify that each element of m_depgraph occured in m_linearization. |
| 1145 | + assert(m_done == m_depgraph.Positions()); |
| 1146 | +} |
| 1147 | + |
| 1148 | +void TxGraphImpl::SanityCheck() const |
| 1149 | +{ |
| 1150 | + /** Which GraphIndexes ought to occur in m_unlinked, based on m_entries. */ |
| 1151 | + std::set<GraphIndex> expected_unlinked; |
| 1152 | + /** Which Clusters ought to occur in m_clusters, based on m_entries. */ |
| 1153 | + std::set<const Cluster*> expected_clusters; |
| 1154 | + |
| 1155 | + // Go over all Entry objects in m_entries. |
| 1156 | + for (GraphIndex idx = 0; idx < m_entries.size(); ++idx) { |
| 1157 | + const auto& entry = m_entries[idx]; |
| 1158 | + if (entry.m_ref == nullptr) { |
| 1159 | + // Unlinked Entry must have indexes appear in m_unlinked. |
| 1160 | + expected_unlinked.insert(idx); |
| 1161 | + } else { |
| 1162 | + // Every non-unlinked Entry must have a Ref that points back to it. |
| 1163 | + assert(GetRefGraph(*entry.m_ref) == this); |
| 1164 | + assert(GetRefIndex(*entry.m_ref) == idx); |
| 1165 | + } |
| 1166 | + const auto& locator = entry.m_locator; |
| 1167 | + // Every Locator must be in exactly one of these 2 states. |
| 1168 | + assert(locator.IsMissing() + locator.IsPresent() == 1); |
| 1169 | + if (locator.IsPresent()) { |
| 1170 | + // Verify that the Cluster agrees with where the Locator claims the transaction is. |
| 1171 | + assert(locator.cluster->GetClusterEntry(locator.index) == idx); |
| 1172 | + // Remember that we expect said Cluster to appear in the m_clusters. |
| 1173 | + expected_clusters.insert(locator.cluster); |
| 1174 | + } |
| 1175 | + |
| 1176 | + } |
| 1177 | + |
| 1178 | + std::set<const Cluster*> actual_clusters; |
| 1179 | + // For all quality levels... |
| 1180 | + for (int qual = 0; qual < int(QualityLevel::NONE); ++qual) { |
| 1181 | + QualityLevel quality{qual}; |
| 1182 | + const auto& quality_clusters = m_clusters[qual]; |
| 1183 | + // ... for all clusters in them ... |
| 1184 | + for (ClusterSetIndex setindex = 0; setindex < quality_clusters.size(); ++setindex) { |
| 1185 | + const auto& cluster = *quality_clusters[setindex]; |
| 1186 | + // Remember we saw this Cluster (only if it is non-empty; empty Clusters aren't |
| 1187 | + // expected to be referenced by the Entry vector). |
| 1188 | + if (cluster.GetTxCount() != 0) { |
| 1189 | + actual_clusters.insert(&cluster); |
| 1190 | + } |
| 1191 | + // Sanity check the cluster, according to the Cluster's internal rules. |
| 1192 | + cluster.SanityCheck(*this); |
| 1193 | + // Check that the cluster's quality and setindex matches its position in the quality list. |
| 1194 | + assert(cluster.m_quality == quality); |
| 1195 | + assert(cluster.m_setindex == setindex); |
| 1196 | + } |
| 1197 | + } |
| 1198 | + |
| 1199 | + // Verify that all to-be-removed transactions have valid identifiers, and aren't removed yet. |
| 1200 | + for (GraphIndex idx : m_to_remove) { |
| 1201 | + assert(idx < m_entries.size()); |
| 1202 | + assert(m_entries[idx].m_locator.IsPresent()); |
| 1203 | + } |
| 1204 | + |
| 1205 | + // Verify that all to-be-added dependencies have valid identifiers. |
| 1206 | + for (auto [par_idx, chl_idx] : m_deps_to_add) { |
| 1207 | + assert(par_idx != chl_idx); |
| 1208 | + assert(par_idx < m_entries.size()); |
| 1209 | + assert(chl_idx < m_entries.size()); |
| 1210 | + } |
| 1211 | + |
| 1212 | + // Verify that the actually encountered clusters match the ones occurring in Entry vector. |
| 1213 | + assert(actual_clusters == expected_clusters); |
| 1214 | + |
| 1215 | + // Verify that the contents of m_unlinked matches what was expected based on the Entry vector. |
| 1216 | + std::set<GraphIndex> actual_unlinked(m_unlinked.begin(), m_unlinked.end()); |
| 1217 | + assert(actual_unlinked == expected_unlinked); |
| 1218 | + |
| 1219 | + // If no to-be-removed transactions, or to-be-added dependencies remain, m_unlinked must be |
| 1220 | + // empty (to prevent memory leaks due to an ever-growing m_entries vector). |
| 1221 | + if (m_to_remove.empty() && m_deps_to_add.empty()) assert(actual_unlinked.empty()); |
| 1222 | +} |
| 1223 | + |
1101 | 1224 | } // namespace
|
1102 | 1225 |
|
1103 | 1226 | TxGraph::Ref::~Ref()
|
|
0 commit comments