mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-12-14 06:32:33 +01:00
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.
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
#ifndef BITCOIN_CLUSTER_LINEARIZE_H
|
#ifndef BITCOIN_CLUSTER_LINEARIZE_H
|
||||||
#define BITCOIN_CLUSTER_LINEARIZE_H
|
#define BITCOIN_CLUSTER_LINEARIZE_H
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <utility>
|
#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
|
} // namespace cluster_linearize
|
||||||
|
|
||||||
#endif // BITCOIN_CLUSTER_LINEARIZE_H
|
#endif // BITCOIN_CLUSTER_LINEARIZE_H
|
||||||
|
|||||||
@@ -15,6 +15,30 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <utility>
|
#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)
|
FUZZ_TARGET(clusterlin_add_dependency)
|
||||||
{
|
{
|
||||||
// Verify that computing a DepGraph from a cluster, or building it step by step using AddDependency
|
// 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.
|
// Verify the graph is a DAG.
|
||||||
assert(IsAcyclic(depgraph));
|
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());
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user