Skip to content

Commit ee0ddfe

Browse files
committed
clusterlin: add chunking algorithm
A fuzz test is added which verifies various of its expected properties, including correctness
1 parent 2a41f15 commit ee0ddfe

File tree

2 files changed

+112
-0
lines changed

2 files changed

+112
-0
lines changed

src/cluster_linearize.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,23 @@ struct SetInfo
184184
/** Construct a SetInfo for a specified set and feerate. */
185185
SetInfo(const SetType& txn, const FeeFrac& fr) noexcept : transactions(txn), feerate(fr) {}
186186

187+
/** Construct a SetInfo for a given transaction in a depgraph. */
188+
explicit SetInfo(const DepGraph<SetType>& depgraph, ClusterIndex pos) noexcept :
189+
transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {}
190+
187191
/** Construct a SetInfo for a set of transactions in a depgraph. */
188192
explicit SetInfo(const DepGraph<SetType>& depgraph, const SetType& txn) noexcept :
189193
transactions(txn), feerate(depgraph.FeeRate(txn)) {}
190194

195+
/** Add the transactions of other to this SetInfo (no overlap allowed). */
196+
SetInfo& operator|=(const SetInfo& other) noexcept
197+
{
198+
Assume(!transactions.Overlaps(other.transactions));
199+
transactions |= other.transactions;
200+
feerate += other.feerate;
201+
return *this;
202+
}
203+
191204
/** Construct a new SetInfo equal to this, with more transactions added (which may overlap
192205
* with the existing transactions in the SetInfo). */
193206
[[nodiscard]] SetInfo Add(const DepGraph<SetType>& depgraph, const SetType& txn) const noexcept
@@ -199,6 +212,25 @@ struct SetInfo
199212
friend bool operator==(const SetInfo&, const SetInfo&) noexcept = default;
200213
};
201214

215+
/** Compute the feerates of the chunks of linearization. */
216+
template<typename SetType>
217+
std::vector<FeeFrac> ChunkLinearization(const DepGraph<SetType>& depgraph, Span<const ClusterIndex> linearization) noexcept
218+
{
219+
std::vector<FeeFrac> ret;
220+
for (ClusterIndex i : linearization) {
221+
/** The new chunk to be added, initially a singleton. */
222+
auto new_chunk = depgraph.FeeRate(i);
223+
// As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
224+
while (!ret.empty() && new_chunk >> ret.back()) {
225+
new_chunk += ret.back();
226+
ret.pop_back();
227+
}
228+
// Actually move that new chunk into the chunking.
229+
ret.push_back(std::move(new_chunk));
230+
}
231+
return ret;
232+
}
233+
202234
/** Class encapsulating the state needed to find the best remaining ancestor set.
203235
*
204236
* It is initialized for an entire DepGraph, and parts of the graph can be dropped by calling

src/test/fuzz/cluster_linearize.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,44 @@ SetType ReadTopologicalSet(const DepGraph<SetType>& depgraph, const SetType& tod
158158
return ret & todo;
159159
}
160160

161+
/** Given a dependency graph, construct any valid linearization for it, reading from a SpanReader. */
162+
template<typename BS>
163+
std::vector<ClusterIndex> ReadLinearization(const DepGraph<BS>& depgraph, SpanReader& reader)
164+
{
165+
std::vector<ClusterIndex> linearization;
166+
TestBitSet todo = TestBitSet::Fill(depgraph.TxCount());
167+
// In every iteration one topologically-valid transaction is appended to linearization.
168+
while (todo.Any()) {
169+
// Compute the set of transactions with no not-yet-included ancestors.
170+
TestBitSet potential_next;
171+
for (auto j : todo) {
172+
if ((depgraph.Ancestors(j) & todo) == TestBitSet::Singleton(j)) {
173+
potential_next.Set(j);
174+
}
175+
}
176+
// There must always be one (otherwise there is a cycle in the graph).
177+
assert(potential_next.Any());
178+
// Read a number from reader, and interpret it as index into potential_next.
179+
uint64_t idx{0};
180+
try {
181+
reader >> VARINT(idx);
182+
} catch (const std::ios_base::failure&) {}
183+
idx %= potential_next.Count();
184+
// Find out which transaction that corresponds to.
185+
for (auto j : potential_next) {
186+
if (idx == 0) {
187+
// When found, add it to linearization and remove it from todo.
188+
linearization.push_back(j);
189+
assert(todo[j]);
190+
todo.Reset(j);
191+
break;
192+
}
193+
--idx;
194+
}
195+
}
196+
return linearization;
197+
}
198+
161199
} // namespace
162200

163201
FUZZ_TARGET(clusterlin_add_dependency)
@@ -231,6 +269,48 @@ FUZZ_TARGET(clusterlin_depgraph_serialization)
231269
assert(IsAcyclic(depgraph));
232270
}
233271

272+
FUZZ_TARGET(clusterlin_chunking)
273+
{
274+
// Verify the correctness of the ChunkLinearization function.
275+
276+
// Construct a graph by deserializing.
277+
SpanReader reader(buffer);
278+
DepGraph<TestBitSet> depgraph;
279+
try {
280+
reader >> Using<DepGraphFormatter>(depgraph);
281+
} catch (const std::ios_base::failure&) {}
282+
283+
// Read a valid linearization for depgraph.
284+
auto linearization = ReadLinearization(depgraph, reader);
285+
286+
// Invoke the chunking function.
287+
auto chunking = ChunkLinearization(depgraph, linearization);
288+
289+
// Verify that chunk feerates are monotonically non-increasing.
290+
for (size_t i = 1; i < chunking.size(); ++i) {
291+
assert(!(chunking[i] >> chunking[i - 1]));
292+
}
293+
294+
// Naively recompute the chunks (each is the highest-feerate prefix of what remains).
295+
auto todo = TestBitSet::Fill(depgraph.TxCount());
296+
for (const auto& chunk_feerate : chunking) {
297+
assert(todo.Any());
298+
SetInfo<TestBitSet> accumulator, best;
299+
for (ClusterIndex idx : linearization) {
300+
if (todo[idx]) {
301+
accumulator |= SetInfo(depgraph, idx);
302+
if (best.feerate.IsEmpty() || accumulator.feerate >> best.feerate) {
303+
best = accumulator;
304+
}
305+
}
306+
}
307+
assert(chunk_feerate == best.feerate);
308+
assert(best.transactions.IsSubsetOf(todo));
309+
todo -= best.transactions;
310+
}
311+
assert(todo.None());
312+
}
313+
234314
FUZZ_TARGET(clusterlin_ancestor_finder)
235315
{
236316
// Verify that AncestorCandidateFinder works as expected.

0 commit comments

Comments
 (0)