0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-03 09:56:38 -05:00

clusterlin: add chunking algorithm

A fuzz test is added which verifies various of its expected properties, including
correctness
This commit is contained in:
Pieter Wuille 2024-07-09 20:38:12 -04:00
parent 2a41f151af
commit ee0ddfe4f6
2 changed files with 112 additions and 0 deletions

View file

@ -184,10 +184,23 @@ struct SetInfo
/** 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 given transaction in a depgraph. */
explicit SetInfo(const DepGraph<SetType>& depgraph, ClusterIndex pos) noexcept :
transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {}
/** 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)) {}
/** Add the transactions of other to this SetInfo (no overlap allowed). */
SetInfo& operator|=(const SetInfo& other) noexcept
{
Assume(!transactions.Overlaps(other.transactions));
transactions |= other.transactions;
feerate += other.feerate;
return *this;
}
/** Construct a new SetInfo equal to this, with more transactions added (which may overlap
* with the existing transactions in the SetInfo). */
[[nodiscard]] SetInfo Add(const DepGraph<SetType>& depgraph, const SetType& txn) const noexcept
@ -199,6 +212,25 @@ struct SetInfo
friend bool operator==(const SetInfo&, const SetInfo&) noexcept = default;
};
/** Compute the feerates of the chunks of linearization. */
template<typename SetType>
std::vector<FeeFrac> ChunkLinearization(const DepGraph<SetType>& depgraph, Span<const ClusterIndex> linearization) noexcept
{
std::vector<FeeFrac> ret;
for (ClusterIndex i : linearization) {
/** The new chunk to be added, initially a singleton. */
auto new_chunk = depgraph.FeeRate(i);
// As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
while (!ret.empty() && new_chunk >> ret.back()) {
new_chunk += ret.back();
ret.pop_back();
}
// Actually move that new chunk into the chunking.
ret.push_back(std::move(new_chunk));
}
return ret;
}
/** 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

View file

@ -158,6 +158,44 @@ SetType ReadTopologicalSet(const DepGraph<SetType>& depgraph, const SetType& tod
return ret & todo;
}
/** Given a dependency graph, construct any valid linearization for it, reading from a SpanReader. */
template<typename BS>
std::vector<ClusterIndex> ReadLinearization(const DepGraph<BS>& depgraph, SpanReader& reader)
{
std::vector<ClusterIndex> linearization;
TestBitSet todo = TestBitSet::Fill(depgraph.TxCount());
// In every iteration one topologically-valid transaction is appended to linearization.
while (todo.Any()) {
// Compute the set of transactions with no not-yet-included ancestors.
TestBitSet potential_next;
for (auto j : todo) {
if ((depgraph.Ancestors(j) & todo) == TestBitSet::Singleton(j)) {
potential_next.Set(j);
}
}
// There must always be one (otherwise there is a cycle in the graph).
assert(potential_next.Any());
// Read a number from reader, and interpret it as index into potential_next.
uint64_t idx{0};
try {
reader >> VARINT(idx);
} catch (const std::ios_base::failure&) {}
idx %= potential_next.Count();
// Find out which transaction that corresponds to.
for (auto j : potential_next) {
if (idx == 0) {
// When found, add it to linearization and remove it from todo.
linearization.push_back(j);
assert(todo[j]);
todo.Reset(j);
break;
}
--idx;
}
}
return linearization;
}
} // namespace
FUZZ_TARGET(clusterlin_add_dependency)
@ -231,6 +269,48 @@ FUZZ_TARGET(clusterlin_depgraph_serialization)
assert(IsAcyclic(depgraph));
}
FUZZ_TARGET(clusterlin_chunking)
{
// Verify the correctness of the ChunkLinearization function.
// Construct a graph by deserializing.
SpanReader reader(buffer);
DepGraph<TestBitSet> depgraph;
try {
reader >> Using<DepGraphFormatter>(depgraph);
} catch (const std::ios_base::failure&) {}
// Read a valid linearization for depgraph.
auto linearization = ReadLinearization(depgraph, reader);
// Invoke the chunking function.
auto chunking = ChunkLinearization(depgraph, linearization);
// Verify that chunk feerates are monotonically non-increasing.
for (size_t i = 1; i < chunking.size(); ++i) {
assert(!(chunking[i] >> chunking[i - 1]));
}
// Naively recompute the chunks (each is the highest-feerate prefix of what remains).
auto todo = TestBitSet::Fill(depgraph.TxCount());
for (const auto& chunk_feerate : chunking) {
assert(todo.Any());
SetInfo<TestBitSet> accumulator, best;
for (ClusterIndex idx : linearization) {
if (todo[idx]) {
accumulator |= SetInfo(depgraph, idx);
if (best.feerate.IsEmpty() || accumulator.feerate >> best.feerate) {
best = accumulator;
}
}
}
assert(chunk_feerate == best.feerate);
assert(best.transactions.IsSubsetOf(todo));
todo -= best.transactions;
}
assert(todo.None());
}
FUZZ_TARGET(clusterlin_ancestor_finder)
{
// Verify that AncestorCandidateFinder works as expected.