Skip to content

Commit 0e2812d

Browse files
committed
clusterlin: add algorithms for connectedness/connected components
Add utility functions to DepGraph for finding connected components.
1 parent 0e52728 commit 0e2812d

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed

src/cluster_linearize.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,50 @@ class DepGraph
171171
return ret;
172172
}
173173

174+
/** Find some connected component within the subset "todo" of this graph.
175+
*
176+
* Specifically, this finds the connected component which contains the first transaction of
177+
* todo (if any).
178+
*
179+
* Two transactions are considered connected if they are both in `todo`, and one is an ancestor
180+
* of the other in the entire graph (so not just within `todo`), or transitively there is a
181+
* path of transactions connecting them. This does mean that if `todo` contains a transaction
182+
* and a grandparent, but misses the parent, they will still be part of the same component.
183+
*
184+
* Complexity: O(ret.Count()).
185+
*/
186+
SetType FindConnectedComponent(const SetType& todo) const noexcept
187+
{
188+
if (todo.None()) return todo;
189+
auto to_add = SetType::Singleton(todo.First());
190+
SetType ret;
191+
do {
192+
SetType old = ret;
193+
for (auto add : to_add) {
194+
ret |= Descendants(add);
195+
ret |= Ancestors(add);
196+
}
197+
ret &= todo;
198+
to_add = ret - old;
199+
} while (to_add.Any());
200+
return ret;
201+
}
202+
203+
/** Determine if a subset is connected.
204+
*
205+
* Complexity: O(subset.Count()).
206+
*/
207+
bool IsConnected(const SetType& subset) const noexcept
208+
{
209+
return FindConnectedComponent(subset) == subset;
210+
}
211+
212+
/** Determine if this entire graph is connected.
213+
*
214+
* Complexity: O(TxCount()).
215+
*/
216+
bool IsConnected() const noexcept { return IsConnected(SetType::Fill(TxCount())); }
217+
174218
/** Append the entries of select to list in a topologically valid order.
175219
*
176220
* Complexity: O(select.Count() * log(select.Count())).

src/test/fuzz/cluster_linearize.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,81 @@ FUZZ_TARGET(clusterlin_depgraph_serialization)
294294
assert(IsAcyclic(depgraph));
295295
}
296296

297+
FUZZ_TARGET(clusterlin_components)
298+
{
299+
// Verify the behavior of DepGraphs's FindConnectedComponent and IsConnected functions.
300+
301+
// Construct a depgraph.
302+
SpanReader reader(buffer);
303+
DepGraph<TestBitSet> depgraph;
304+
try {
305+
reader >> Using<DepGraphFormatter>(depgraph);
306+
} catch (const std::ios_base::failure&) {}
307+
308+
TestBitSet todo = TestBitSet::Fill(depgraph.TxCount());
309+
while (todo.Any()) {
310+
// Find a connected component inside todo.
311+
auto component = depgraph.FindConnectedComponent(todo);
312+
313+
// The component must be a subset of todo and non-empty.
314+
assert(component.IsSubsetOf(todo));
315+
assert(component.Any());
316+
317+
// If todo is the entire graph, and the entire graph is connected, then the component must
318+
// be the entire graph.
319+
if (todo == TestBitSet::Fill(depgraph.TxCount())) {
320+
assert((component == todo) == depgraph.IsConnected());
321+
}
322+
323+
// If subset is connected, then component must match subset.
324+
assert((component == todo) == depgraph.IsConnected(todo));
325+
326+
// The component cannot have any ancestors or descendants outside of component but in todo.
327+
for (auto i : component) {
328+
assert((depgraph.Ancestors(i) & todo).IsSubsetOf(component));
329+
assert((depgraph.Descendants(i) & todo).IsSubsetOf(component));
330+
}
331+
332+
// Starting from any component element, we must be able to reach every element.
333+
for (auto i : component) {
334+
// Start with just i as reachable.
335+
TestBitSet reachable = TestBitSet::Singleton(i);
336+
// Add in-todo descendants and ancestors to reachable until it does not change anymore.
337+
while (true) {
338+
TestBitSet new_reachable = reachable;
339+
for (auto j : new_reachable) {
340+
new_reachable |= depgraph.Ancestors(j) & todo;
341+
new_reachable |= depgraph.Descendants(j) & todo;
342+
}
343+
if (new_reachable == reachable) break;
344+
reachable = new_reachable;
345+
}
346+
// Verify that the result is the entire component.
347+
assert(component == reachable);
348+
}
349+
350+
// Construct an arbitrary subset of todo.
351+
uint64_t subset_bits{0};
352+
try {
353+
reader >> VARINT(subset_bits);
354+
} catch (const std::ios_base::failure&) {}
355+
TestBitSet subset;
356+
for (ClusterIndex i = 0; i < depgraph.TxCount(); ++i) {
357+
if (todo[i]) {
358+
if (subset_bits & 1) subset.Set(i);
359+
subset_bits >>= 1;
360+
}
361+
}
362+
// Which must be non-empty.
363+
if (subset.None()) subset = TestBitSet::Singleton(todo.First());
364+
// Remove it from todo.
365+
todo -= subset;
366+
}
367+
368+
// No components can be found in an empty subset.
369+
assert(depgraph.FindConnectedComponent(todo).None());
370+
}
371+
297372
FUZZ_TARGET(clusterlin_chunking)
298373
{
299374
// Verify the correctness of the ChunkLinearization function.
@@ -357,6 +432,7 @@ FUZZ_TARGET(clusterlin_ancestor_finder)
357432
assert(best_anc.transactions.Any());
358433
assert(best_anc.transactions.IsSubsetOf(todo));
359434
assert(depgraph.FeeRate(best_anc.transactions) == best_anc.feerate);
435+
assert(depgraph.IsConnected(best_anc.transactions));
360436
// Check that it is topologically valid.
361437
for (auto i : best_anc.transactions) {
362438
assert((depgraph.Ancestors(i) & todo).IsSubsetOf(best_anc.transactions));
@@ -443,6 +519,9 @@ FUZZ_TARGET(clusterlin_search_finder)
443519

444520
// Perform quality checks only if SearchCandidateFinder claims an optimal result.
445521
if (iterations_done < max_iterations) {
522+
// Optimal sets are always connected.
523+
assert(depgraph.IsConnected(found.transactions));
524+
446525
// Compare with SimpleCandidateFinder.
447526
auto [simple, simple_iters] = smp_finder.FindCandidateSet(MAX_SIMPLE_ITERATIONS);
448527
assert(found.feerate >= simple.feerate);

0 commit comments

Comments
 (0)