clusterlin: sort tx in chunk by feerate and size (feature)

This changes the order of transactions within a chunk to be:
1. Topology (parents before children)
2. Individual transaction feerate (high to low)
3. Individual transaction weight (small to large)
4. Random tiebreak (will be changed in a future commit)

To do so, use a heap of topology-ready transactions within
GetLinearization(), sorted by (2), (3), and (4).

This is analogous to the order of chunks within a cluster, which is
unchanged:
1. Topology (chunks after chunks they depend on)
2. Chunk feerate (high to low)
3. Chunk weight (small to large)
4. Random tiebreak (will be changed in a future commit)
This commit is contained in:
Pieter Wuille
2026-01-07 10:36:03 -05:00
parent 6c1bcb2c7c
commit e0bc73ba92
2 changed files with 99 additions and 23 deletions

View File

@@ -1082,6 +1082,57 @@ FUZZ_TARGET(clusterlin_linearize)
auto read_chunking = ChunkLinearization(depgraph, read);
auto cmp_read = CompareChunks(chunking, read_chunking);
assert(cmp_read >= 0);
// Verify that within every chunk, the transactions are in a valid order. For any pair of
// transactions, it should not be possible to swap them; either due to a missing
// dependency, or because the order would be inconsistent with decreasing feerate and
// increasing size.
auto chunking_info = ChunkLinearizationInfo(depgraph, linearization);
/** The set of all transactions (strictly) before tx1 (see below), or (strictly) before
* chunk1 (see even further below). */
TestBitSet done;
unsigned pos{0};
for (const auto& chunk : chunking_info) {
auto chunk_start = pos;
auto chunk_end = pos + chunk.transactions.Count() - 1;
// Go over all pairs of transactions. done is the set of transactions seen before pos1.
for (unsigned pos1 = chunk_start; pos1 <= chunk_end; ++pos1) {
auto tx1 = linearization[pos1];
for (unsigned pos2 = pos1 + 1; pos2 <= chunk_end; ++pos2) {
auto tx2 = linearization[pos2];
// Check whether tx2 only depends on transactions that precede tx1.
if ((depgraph.Ancestors(tx2) - done).Count() == 1) {
// tx2 could take position pos1.
// Verify that individual transaction feerate is decreasing (note that >=
// tie-breaks by size).
assert(depgraph.FeeRate(tx1) >= depgraph.FeeRate(tx2));
}
}
done.Set(tx1);
}
pos += chunk.transactions.Count();
}
// Verify that chunks themselves are in a valid order. For any pair of chunks, it should
// not be possible to swap them; either due to a missing dependency, or because the order
// would be inconsistent with decreasing chunk feerate and increasing chunk size.
done = {};
// Go over all pairs of chunks. done is the set of transactions seen before chunk_num1.
for (unsigned chunk_num1 = 0; chunk_num1 < chunking_info.size(); ++chunk_num1) {
const auto& chunk1 = chunking_info[chunk_num1];
for (unsigned chunk_num2 = chunk_num1 + 1; chunk_num2 < chunking_info.size(); ++chunk_num2) {
const auto& chunk2 = chunking_info[chunk_num2];
TestBitSet chunk2_ancestors;
for (auto tx : chunk2.transactions) chunk2_ancestors |= depgraph.Ancestors(tx);
// Check whether chunk2 only depends on transactions that precede chunk1.
if ((chunk2_ancestors - done).IsSubsetOf(chunk2.transactions)) {
// chunk2 could take position chunk_num1.
// Verify that chunk feerate is decreasing (note that >= tie-breaks by size).
assert(chunk1.feerate >= chunk2.feerate);
}
}
done |= chunk1.transactions;
}
}
}