Skip to content

Commit 4828079

Browse files
committed
clusterlin: add AncestorCandidateFinder class
This is a class that encapsulates precomputed ancestor set feerates, and presents an interface for getting the best remaining ancestor set.
1 parent 58f7e01 commit 4828079

File tree

2 files changed

+189
-0
lines changed

2 files changed

+189
-0
lines changed

src/cluster_linearize.h

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#ifndef BITCOIN_CLUSTER_LINEARIZE_H
66
#define BITCOIN_CLUSTER_LINEARIZE_H
77

8+
#include <optional>
89
#include <stdint.h>
910
#include <vector>
1011
#include <utility>
@@ -166,6 +167,122 @@ class DepGraph
166167
}
167168
};
168169

170+
/** A set of transactions together with their aggregate feerate. */
171+
template<typename SetType>
172+
struct SetInfo
173+
{
174+
/** The transactions in the set. */
175+
SetType transactions;
176+
/** Their combined fee and size. */
177+
FeeFrac feerate;
178+
179+
/** Construct a SetInfo for a specified set and feerate. */
180+
SetInfo(const SetType& txn, const FeeFrac& fr) noexcept : transactions(txn), feerate(fr) {}
181+
182+
/** Construct a SetInfo for a set of transactions in a depgraph. */
183+
explicit SetInfo(const DepGraph<SetType>& depgraph, const SetType& txn) noexcept :
184+
transactions(txn), feerate(depgraph.FeeRate(txn)) {}
185+
186+
/** Permit equality testing. */
187+
friend bool operator==(const SetInfo&, const SetInfo&) noexcept = default;
188+
};
189+
190+
/** Class encapsulating the state needed to find the best remaining ancestor set.
191+
*
192+
* It is initialized for an entire DepGraph, and parts of the graph can be dropped by calling
193+
* MarkDone.
194+
*
195+
* As long as any part of the graph remains, FindCandidateSet() can be called which will return a
196+
* SetInfo with the highest-feerate ancestor set that remains (an ancestor set is a single
197+
* transaction together with all its remaining ancestors).
198+
*/
199+
template<typename SetType>
200+
class AncestorCandidateFinder
201+
{
202+
/** Internal dependency graph. */
203+
const DepGraph<SetType>& m_depgraph;
204+
/** Which transaction are left to include. */
205+
SetType m_todo;
206+
/** Precomputed ancestor-set feerates (only kept up-to-date for indices in m_todo). */
207+
std::vector<FeeFrac> m_ancestor_set_feerates;
208+
209+
public:
210+
/** Construct an AncestorCandidateFinder for a given cluster.
211+
*
212+
* Complexity: O(N^2) where N=depgraph.TxCount().
213+
*/
214+
AncestorCandidateFinder(const DepGraph<SetType>& depgraph LIFETIMEBOUND) noexcept :
215+
m_depgraph(depgraph),
216+
m_todo{SetType::Fill(depgraph.TxCount())},
217+
m_ancestor_set_feerates(depgraph.TxCount())
218+
{
219+
// Precompute ancestor-set feerates.
220+
for (ClusterIndex i = 0; i < depgraph.TxCount(); ++i) {
221+
/** The remaining ancestors for transaction i. */
222+
SetType anc_to_add = m_depgraph.Ancestors(i);
223+
FeeFrac anc_feerate;
224+
// Reuse accumulated feerate from first ancestor, if usable.
225+
Assume(anc_to_add.Any());
226+
ClusterIndex first = anc_to_add.First();
227+
if (first < i) {
228+
anc_feerate = m_ancestor_set_feerates[first];
229+
Assume(!anc_feerate.IsEmpty());
230+
anc_to_add -= m_depgraph.Ancestors(first);
231+
}
232+
// Add in other ancestors (which necessarily include i itself).
233+
Assume(anc_to_add[i]);
234+
anc_feerate += m_depgraph.FeeRate(anc_to_add);
235+
// Store the result.
236+
m_ancestor_set_feerates[i] = anc_feerate;
237+
}
238+
}
239+
240+
/** Remove a set of transactions from the set of to-be-linearized ones.
241+
*
242+
* The same transaction may not be MarkDone()'d twice.
243+
*
244+
* Complexity: O(N*M) where N=depgraph.TxCount(), M=select.Count().
245+
*/
246+
void MarkDone(SetType select) noexcept
247+
{
248+
Assume(select.Any());
249+
Assume(select.IsSubsetOf(m_todo));
250+
m_todo -= select;
251+
for (auto i : select) {
252+
auto feerate = m_depgraph.FeeRate(i);
253+
for (auto j : m_depgraph.Descendants(i) & m_todo) {
254+
m_ancestor_set_feerates[j] -= feerate;
255+
}
256+
}
257+
}
258+
259+
/** Check whether any unlinearized transactions remain. */
260+
bool AllDone() const noexcept
261+
{
262+
return m_todo.None();
263+
}
264+
265+
/** Find the best (highest-feerate, smallest among those in case of a tie) ancestor set
266+
* among the remaining transactions. Requires !AllDone().
267+
*
268+
* Complexity: O(N) where N=depgraph.TxCount();
269+
*/
270+
SetInfo<SetType> FindCandidateSet() const noexcept
271+
{
272+
Assume(!AllDone());
273+
std::optional<ClusterIndex> best;
274+
for (auto i : m_todo) {
275+
if (best.has_value()) {
276+
Assume(!m_ancestor_set_feerates[i].IsEmpty());
277+
if (!(m_ancestor_set_feerates[i] > m_ancestor_set_feerates[*best])) continue;
278+
}
279+
best = i;
280+
}
281+
Assume(best.has_value());
282+
return {m_depgraph.Ancestors(*best) & m_todo, m_ancestor_set_feerates[*best]};
283+
}
284+
};
285+
169286
} // namespace cluster_linearize
170287

171288
#endif // BITCOIN_CLUSTER_LINEARIZE_H

src/test/fuzz/cluster_linearize.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,30 @@
1515
#include <vector>
1616
#include <utility>
1717

18+
using namespace cluster_linearize;
19+
20+
namespace {
21+
22+
/** Given a dependency graph, and a todo set, read a topological subset of todo from reader. */
23+
template<typename SetType>
24+
SetType ReadTopologicalSet(const DepGraph<SetType>& depgraph, const SetType& todo, SpanReader& reader)
25+
{
26+
uint64_t mask{0};
27+
try {
28+
reader >> VARINT(mask);
29+
} catch(const std::ios_base::failure&) {}
30+
SetType ret;
31+
for (auto i : todo) {
32+
if (!ret[i]) {
33+
if (mask & 1) ret |= depgraph.Ancestors(i);
34+
mask >>= 1;
35+
}
36+
}
37+
return ret & todo;
38+
}
39+
40+
} // namespace
41+
1842
FUZZ_TARGET(clusterlin_add_dependency)
1943
{
2044
// Verify that computing a DepGraph from a cluster, or building it step by step using AddDependency
@@ -85,3 +109,51 @@ FUZZ_TARGET(clusterlin_depgraph_serialization)
85109
// Verify the graph is a DAG.
86110
assert(IsAcyclic(depgraph));
87111
}
112+
113+
FUZZ_TARGET(clusterlin_ancestor_finder)
114+
{
115+
// Verify that AncestorCandidateFinder works as expected.
116+
117+
// Retrieve a depgraph from the fuzz input.
118+
SpanReader reader(buffer);
119+
DepGraph<TestBitSet> depgraph;
120+
try {
121+
reader >> Using<DepGraphFormatter>(depgraph);
122+
} catch (const std::ios_base::failure&) {}
123+
124+
AncestorCandidateFinder anc_finder(depgraph);
125+
auto todo = TestBitSet::Fill(depgraph.TxCount());
126+
while (todo.Any()) {
127+
// Call the ancestor finder's FindCandidateSet for what remains of the graph.
128+
assert(!anc_finder.AllDone());
129+
auto best_anc = anc_finder.FindCandidateSet();
130+
// Sanity check the result.
131+
assert(best_anc.transactions.Any());
132+
assert(best_anc.transactions.IsSubsetOf(todo));
133+
assert(depgraph.FeeRate(best_anc.transactions) == best_anc.feerate);
134+
// Check that it is topologically valid.
135+
for (auto i : best_anc.transactions) {
136+
assert((depgraph.Ancestors(i) & todo).IsSubsetOf(best_anc.transactions));
137+
}
138+
139+
// Compute all remaining ancestor sets.
140+
std::optional<SetInfo<TestBitSet>> real_best_anc;
141+
for (auto i : todo) {
142+
SetInfo info(depgraph, todo & depgraph.Ancestors(i));
143+
if (!real_best_anc.has_value() || info.feerate > real_best_anc->feerate) {
144+
real_best_anc = info;
145+
}
146+
}
147+
// The set returned by anc_finder must equal the real best ancestor sets.
148+
assert(real_best_anc.has_value());
149+
assert(*real_best_anc == best_anc);
150+
151+
// Find a topologically valid subset of transactions to remove from the graph.
152+
auto del_set = ReadTopologicalSet(depgraph, todo, reader);
153+
// If we did not find anything, use best_anc itself, because we should remove something.
154+
if (del_set.None()) del_set = best_anc.transactions;
155+
todo -= del_set;
156+
anc_finder.MarkDone(del_set);
157+
}
158+
assert(anc_finder.AllDone());
159+
}

0 commit comments

Comments
 (0)