aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPieter Wuille <pieter@wuille.net>2024-05-08 18:56:59 -0400
committerPieter Wuille <pieter@wuille.net>2024-07-25 10:16:37 -0400
commit4828079db327bf2aeaed744843a415d1654e8796 (patch)
tree2767f2171c5181e8356ae129f42f9f5dd3ca7603
parent58f7e01db4bad6d958d44f2bcdfd9df9e22931a4 (diff)
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.
-rw-r--r--src/cluster_linearize.h117
-rw-r--r--src/test/fuzz/cluster_linearize.cpp72
2 files changed, 189 insertions, 0 deletions
diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h
index 2e230bcd63..03ee894ae3 100644
--- a/src/cluster_linearize.h
+++ b/src/cluster_linearize.h
@@ -5,6 +5,7 @@
#ifndef BITCOIN_CLUSTER_LINEARIZE_H
#define BITCOIN_CLUSTER_LINEARIZE_H
+#include <optional>
#include <stdint.h>
#include <vector>
#include <utility>
@@ -166,6 +167,122 @@ public:
}
};
+/** A set of transactions together with their aggregate feerate. */
+template<typename SetType>
+struct SetInfo
+{
+ /** The transactions in the set. */
+ SetType transactions;
+ /** Their combined fee and size. */
+ FeeFrac feerate;
+
+ /** Construct a SetInfo for a specified set and feerate. */
+ SetInfo(const SetType& txn, const FeeFrac& fr) noexcept : transactions(txn), feerate(fr) {}
+
+ /** Construct a SetInfo for a set of transactions in a depgraph. */
+ explicit SetInfo(const DepGraph<SetType>& depgraph, const SetType& txn) noexcept :
+ transactions(txn), feerate(depgraph.FeeRate(txn)) {}
+
+ /** Permit equality testing. */
+ friend bool operator==(const SetInfo&, const SetInfo&) noexcept = default;
+};
+
+/** Class encapsulating the state needed to find the best remaining ancestor set.
+ *
+ * It is initialized for an entire DepGraph, and parts of the graph can be dropped by calling
+ * MarkDone.
+ *
+ * As long as any part of the graph remains, FindCandidateSet() can be called which will return a
+ * SetInfo with the highest-feerate ancestor set that remains (an ancestor set is a single
+ * transaction together with all its remaining ancestors).
+ */
+template<typename SetType>
+class AncestorCandidateFinder
+{
+ /** Internal dependency graph. */
+ const DepGraph<SetType>& m_depgraph;
+ /** Which transaction are left to include. */
+ SetType m_todo;
+ /** Precomputed ancestor-set feerates (only kept up-to-date for indices in m_todo). */
+ std::vector<FeeFrac> m_ancestor_set_feerates;
+
+public:
+ /** Construct an AncestorCandidateFinder for a given cluster.
+ *
+ * Complexity: O(N^2) where N=depgraph.TxCount().
+ */
+ AncestorCandidateFinder(const DepGraph<SetType>& depgraph LIFETIMEBOUND) noexcept :
+ m_depgraph(depgraph),
+ m_todo{SetType::Fill(depgraph.TxCount())},
+ m_ancestor_set_feerates(depgraph.TxCount())
+ {
+ // Precompute ancestor-set feerates.
+ for (ClusterIndex i = 0; i < depgraph.TxCount(); ++i) {
+ /** The remaining ancestors for transaction i. */
+ SetType anc_to_add = m_depgraph.Ancestors(i);
+ FeeFrac anc_feerate;
+ // Reuse accumulated feerate from first ancestor, if usable.
+ Assume(anc_to_add.Any());
+ ClusterIndex first = anc_to_add.First();
+ if (first < i) {
+ anc_feerate = m_ancestor_set_feerates[first];
+ Assume(!anc_feerate.IsEmpty());
+ anc_to_add -= m_depgraph.Ancestors(first);
+ }
+ // Add in other ancestors (which necessarily include i itself).
+ Assume(anc_to_add[i]);
+ anc_feerate += m_depgraph.FeeRate(anc_to_add);
+ // Store the result.
+ m_ancestor_set_feerates[i] = anc_feerate;
+ }
+ }
+
+ /** Remove a set of transactions from the set of to-be-linearized ones.
+ *
+ * The same transaction may not be MarkDone()'d twice.
+ *
+ * Complexity: O(N*M) where N=depgraph.TxCount(), M=select.Count().
+ */
+ void MarkDone(SetType select) noexcept
+ {
+ Assume(select.Any());
+ Assume(select.IsSubsetOf(m_todo));
+ m_todo -= select;
+ for (auto i : select) {
+ auto feerate = m_depgraph.FeeRate(i);
+ for (auto j : m_depgraph.Descendants(i) & m_todo) {
+ m_ancestor_set_feerates[j] -= feerate;
+ }
+ }
+ }
+
+ /** Check whether any unlinearized transactions remain. */
+ bool AllDone() const noexcept
+ {
+ return m_todo.None();
+ }
+
+ /** Find the best (highest-feerate, smallest among those in case of a tie) ancestor set
+ * among the remaining transactions. Requires !AllDone().
+ *
+ * Complexity: O(N) where N=depgraph.TxCount();
+ */
+ SetInfo<SetType> FindCandidateSet() const noexcept
+ {
+ Assume(!AllDone());
+ std::optional<ClusterIndex> best;
+ for (auto i : m_todo) {
+ if (best.has_value()) {
+ Assume(!m_ancestor_set_feerates[i].IsEmpty());
+ if (!(m_ancestor_set_feerates[i] > m_ancestor_set_feerates[*best])) continue;
+ }
+ best = i;
+ }
+ Assume(best.has_value());
+ return {m_depgraph.Ancestors(*best) & m_todo, m_ancestor_set_feerates[*best]};
+ }
+};
+
} // namespace cluster_linearize
#endif // BITCOIN_CLUSTER_LINEARIZE_H
diff --git a/src/test/fuzz/cluster_linearize.cpp b/src/test/fuzz/cluster_linearize.cpp
index fd75fd5b08..880fcb79aa 100644
--- a/src/test/fuzz/cluster_linearize.cpp
+++ b/src/test/fuzz/cluster_linearize.cpp
@@ -15,6 +15,30 @@
#include <vector>
#include <utility>
+using namespace cluster_linearize;
+
+namespace {
+
+/** Given a dependency graph, and a todo set, read a topological subset of todo from reader. */
+template<typename SetType>
+SetType ReadTopologicalSet(const DepGraph<SetType>& depgraph, const SetType& todo, SpanReader& reader)
+{
+ uint64_t mask{0};
+ try {
+ reader >> VARINT(mask);
+ } catch(const std::ios_base::failure&) {}
+ SetType ret;
+ for (auto i : todo) {
+ if (!ret[i]) {
+ if (mask & 1) ret |= depgraph.Ancestors(i);
+ mask >>= 1;
+ }
+ }
+ return ret & todo;
+}
+
+} // namespace
+
FUZZ_TARGET(clusterlin_add_dependency)
{
// 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)
// Verify the graph is a DAG.
assert(IsAcyclic(depgraph));
}
+
+FUZZ_TARGET(clusterlin_ancestor_finder)
+{
+ // Verify that AncestorCandidateFinder works as expected.
+
+ // Retrieve a depgraph from the fuzz input.
+ SpanReader reader(buffer);
+ DepGraph<TestBitSet> depgraph;
+ try {
+ reader >> Using<DepGraphFormatter>(depgraph);
+ } catch (const std::ios_base::failure&) {}
+
+ AncestorCandidateFinder anc_finder(depgraph);
+ auto todo = TestBitSet::Fill(depgraph.TxCount());
+ while (todo.Any()) {
+ // Call the ancestor finder's FindCandidateSet for what remains of the graph.
+ assert(!anc_finder.AllDone());
+ auto best_anc = anc_finder.FindCandidateSet();
+ // Sanity check the result.
+ assert(best_anc.transactions.Any());
+ assert(best_anc.transactions.IsSubsetOf(todo));
+ assert(depgraph.FeeRate(best_anc.transactions) == best_anc.feerate);
+ // Check that it is topologically valid.
+ for (auto i : best_anc.transactions) {
+ assert((depgraph.Ancestors(i) & todo).IsSubsetOf(best_anc.transactions));
+ }
+
+ // Compute all remaining ancestor sets.
+ std::optional<SetInfo<TestBitSet>> real_best_anc;
+ for (auto i : todo) {
+ SetInfo info(depgraph, todo & depgraph.Ancestors(i));
+ if (!real_best_anc.has_value() || info.feerate > real_best_anc->feerate) {
+ real_best_anc = info;
+ }
+ }
+ // The set returned by anc_finder must equal the real best ancestor sets.
+ assert(real_best_anc.has_value());
+ assert(*real_best_anc == best_anc);
+
+ // Find a topologically valid subset of transactions to remove from the graph.
+ auto del_set = ReadTopologicalSet(depgraph, todo, reader);
+ // If we did not find anything, use best_anc itself, because we should remove something.
+ if (del_set.None()) del_set = best_anc.transactions;
+ todo -= del_set;
+ anc_finder.MarkDone(del_set);
+ }
+ assert(anc_finder.AllDone());
+}